@powerhousedao/reactor-api 6.0.0-dev.156 → 6.0.0-dev.158

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -31,10 +31,10 @@ import { typeDefs } from "@powerhousedao/document-engineering/graphql";
31
31
  import { camelCase, kebabCase, pascalCase } from "change-case";
32
32
  import { GraphQLJSONObject } from "graphql-type-json";
33
33
  import { setName } from "@powerhousedao/shared/document-model";
34
- import { consolidateSyncOperations, driveIdFromUrl, envelopesToSyncOperations, parseDriveUrl, sortEnvelopesByFirstOperationTimestamp, trimMailboxFromAckOrdinal } from "@powerhousedao/reactor";
35
- import { createHandler } from "graphql-sse/lib/use/fetch";
34
+ import { PropagationMode as PropagationMode$1, consolidateSyncOperations, driveIdFromUrl, envelopesToSyncOperations, parseDriveUrl, sortEnvelopesByFirstOperationTimestamp, trimMailboxFromAckOrdinal } from "@powerhousedao/reactor";
36
35
  import * as z$1 from "zod";
37
36
  import { z } from "zod";
37
+ import { createHandler } from "graphql-sse/lib/use/fetch";
38
38
  import { PubSub, withFilter } from "graphql-subscriptions";
39
39
  import dotenv from "dotenv";
40
40
  import { getConfig } from "@powerhousedao/config/node";
@@ -1771,1676 +1771,177 @@ function generateNewApiSchema(documentName, specification, _stateSchemaTypes, pr
1771
1771
  ${moduleSchemas}`;
1772
1772
  }
1773
1773
  //#endregion
1774
- //#region src/graphql/reactor/adapters.ts
1775
- /**
1776
- * Converts a PagedResults from ReactorClient to the GraphQL DocumentModelResultPage format
1777
- */
1778
- function toDocumentModelResultPage(result) {
1779
- const models = result.results.map((module) => module.documentModel);
1780
- return {
1781
- cursor: result.nextCursor ?? null,
1782
- hasNextPage: !!result.nextCursor,
1783
- hasPreviousPage: !!result.options.cursor,
1784
- items: models.map(toGqlDocumentModelState),
1785
- totalCount: result.results.length
1786
- };
1774
+ //#region src/graphql/reactor/gen/graphql.ts
1775
+ let DocumentChangeType = /* @__PURE__ */ function(DocumentChangeType) {
1776
+ DocumentChangeType["ChildAdded"] = "CHILD_ADDED";
1777
+ DocumentChangeType["ChildRemoved"] = "CHILD_REMOVED";
1778
+ DocumentChangeType["Created"] = "CREATED";
1779
+ DocumentChangeType["Deleted"] = "DELETED";
1780
+ DocumentChangeType["ParentAdded"] = "PARENT_ADDED";
1781
+ DocumentChangeType["ParentRemoved"] = "PARENT_REMOVED";
1782
+ DocumentChangeType["Updated"] = "UPDATED";
1783
+ return DocumentChangeType;
1784
+ }({});
1785
+ let PropagationMode = /* @__PURE__ */ function(PropagationMode) {
1786
+ PropagationMode["Cascade"] = "CASCADE";
1787
+ PropagationMode["Orphan"] = "ORPHAN";
1788
+ return PropagationMode;
1789
+ }({});
1790
+ let SyncEnvelopeType = /* @__PURE__ */ function(SyncEnvelopeType) {
1791
+ SyncEnvelopeType["Ack"] = "ACK";
1792
+ SyncEnvelopeType["Operations"] = "OPERATIONS";
1793
+ return SyncEnvelopeType;
1794
+ }({});
1795
+ const isDefinedNonNullAny = (v) => v !== void 0 && v !== null;
1796
+ const definedNonNullAnySchema = z$1.any().refine((v) => isDefinedNonNullAny(v));
1797
+ const DocumentChangeTypeSchema = z$1.enum(DocumentChangeType);
1798
+ const PropagationModeSchema = z$1.enum(PropagationMode);
1799
+ const SyncEnvelopeTypeSchema = z$1.enum(SyncEnvelopeType);
1800
+ function ActionContextInputSchema() {
1801
+ return z$1.object({ signer: z$1.lazy(() => ReactorSignerInputSchema().nullish()) });
1787
1802
  }
1788
- /**
1789
- * Gets the namespace from a DocumentModelGlobalState
1790
- */
1791
- function getNamespace(model) {
1792
- return model.global.name.split("/")[0];
1803
+ function ActionInputSchema() {
1804
+ return z$1.object({
1805
+ attachments: z$1.array(z$1.lazy(() => AttachmentInputSchema())).nullish(),
1806
+ context: z$1.lazy(() => ActionContextInputSchema().nullish()),
1807
+ id: z$1.string(),
1808
+ input: z$1.custom((v) => v != null),
1809
+ scope: z$1.string(),
1810
+ timestampUtcMs: z$1.string(),
1811
+ type: z$1.string()
1812
+ });
1793
1813
  }
1794
- /**
1795
- * Converts a DocumentModelGlobalState from ReactorClient to GraphQL format
1796
- */
1797
- function toGqlDocumentModelState(model) {
1798
- const global = model.global;
1799
- const specification = global.specifications.length > 0 ? global.specifications[0] : {};
1800
- const namespace = getNamespace(model);
1801
- return {
1802
- id: global.id,
1803
- name: global.name,
1804
- namespace,
1805
- specification,
1806
- version: null
1807
- };
1814
+ function AttachmentInputSchema() {
1815
+ return z$1.object({
1816
+ data: z$1.string(),
1817
+ extension: z$1.string().nullish(),
1818
+ fileName: z$1.string().nullish(),
1819
+ hash: z$1.string(),
1820
+ mimeType: z$1.string()
1821
+ });
1808
1822
  }
1809
- /**
1810
- * Converts a PagedResults of PHDocument to GraphQL PhDocumentResultPage format
1811
- */
1812
- function toPhDocumentResultPage(result) {
1813
- return {
1814
- cursor: result.nextCursor ?? null,
1815
- hasNextPage: !!result.nextCursor,
1816
- hasPreviousPage: !!result.options.cursor,
1817
- items: result.results.map(toGqlPhDocument),
1818
- totalCount: result.totalCount ?? result.results.length
1819
- };
1823
+ function ChannelMetaInputSchema() {
1824
+ return z$1.object({ id: z$1.string() });
1820
1825
  }
1821
- /**
1822
- * Converts a PHDocument from ReactorClient to GraphQL PhDocument format
1823
- */
1824
- function toGqlPhDocument(doc) {
1825
- const revisionsList = Object.entries(doc.header.revision).map(([scope, revision]) => ({
1826
- scope,
1827
- revision
1828
- }));
1829
- return {
1830
- id: doc.header.id,
1831
- name: doc.header.name,
1832
- documentType: doc.header.documentType,
1833
- slug: doc.header.slug,
1834
- preferredEditor: doc.header.meta?.preferredEditor ?? null,
1835
- createdAtUtcIso: doc.header.createdAtUtcIso,
1836
- lastModifiedAtUtcIso: doc.header.lastModifiedAtUtcIso,
1837
- revisionsList,
1838
- state: doc.state
1839
- };
1826
+ function DocumentOperationsFilterInputSchema() {
1827
+ return z$1.object({
1828
+ actionTypes: z$1.array(z$1.string()).nullish(),
1829
+ branch: z$1.string().nullish(),
1830
+ scopes: z$1.array(z$1.string()).nullish(),
1831
+ sinceRevision: z$1.number().nullish(),
1832
+ timestampFrom: z$1.string().nullish(),
1833
+ timestampTo: z$1.string().nullish()
1834
+ });
1840
1835
  }
1841
- /**
1842
- * Converts JobInfo from ReactorClient to GraphQL format
1843
- */
1844
- function toGqlJobInfo(job) {
1845
- return {
1846
- id: job.id,
1847
- status: job.status,
1848
- createdAt: job.createdAtUtcIso,
1849
- completedAt: job.completedAtUtcIso ?? null,
1850
- error: job.error?.message ?? null,
1851
- result: job.result ?? null
1852
- };
1836
+ function OperationContextInputSchema() {
1837
+ return z$1.object({
1838
+ branch: z$1.string(),
1839
+ documentId: z$1.string(),
1840
+ documentType: z$1.string(),
1841
+ ordinal: z$1.number(),
1842
+ scope: z$1.string()
1843
+ });
1853
1844
  }
1854
- /**
1855
- * Handles nullable/undefined conversion for GraphQL InputMaybe types
1856
- */
1857
- function fromInputMaybe(value) {
1858
- return value === null ? void 0 : value;
1845
+ function OperationInputSchema() {
1846
+ return z$1.object({
1847
+ action: z$1.lazy(() => ActionInputSchema()),
1848
+ error: z$1.string().nullish(),
1849
+ hash: z$1.string(),
1850
+ id: z$1.string().nullish(),
1851
+ index: z$1.number(),
1852
+ skip: z$1.number(),
1853
+ timestampUtcMs: z$1.string()
1854
+ });
1859
1855
  }
1860
- /**
1861
- * Converts readonly arrays to mutable arrays for ReactorClient
1862
- */
1863
- function toMutableArray(arr) {
1864
- return arr ? [...arr] : void 0;
1856
+ function OperationWithContextInputSchema() {
1857
+ return z$1.object({
1858
+ context: z$1.lazy(() => OperationContextInputSchema()),
1859
+ operation: z$1.lazy(() => OperationInputSchema())
1860
+ });
1865
1861
  }
1866
- /**
1867
- * Validates that a JSONObject represents a valid Action structure
1868
- */
1869
- function validateActionStructure(obj) {
1870
- if (!obj || typeof obj !== "object") return false;
1871
- const action = obj;
1872
- if (typeof action.type !== "string" || !action.type) return false;
1873
- if (typeof action.scope !== "string" || !action.scope) return false;
1874
- if (!("input" in action)) return false;
1875
- return true;
1862
+ function OperationsFilterInputSchema() {
1863
+ return z$1.object({
1864
+ actionTypes: z$1.array(z$1.string()).nullish(),
1865
+ branch: z$1.string().nullish(),
1866
+ documentId: z$1.string(),
1867
+ scopes: z$1.array(z$1.string()).nullish(),
1868
+ sinceRevision: z$1.number().nullish(),
1869
+ timestampFrom: z$1.string().nullish(),
1870
+ timestampTo: z$1.string().nullish()
1871
+ });
1876
1872
  }
1877
- /**
1878
- * Converts a JSONObject to an Action, validating basic structure
1879
- */
1880
- function jsonObjectToAction(obj) {
1881
- if (!validateActionStructure(obj)) throw new GraphQLError("Invalid action structure. Actions must have: type (string), scope (string), and input (any)");
1882
- return obj;
1873
+ function PagingInputSchema() {
1874
+ return z$1.object({
1875
+ cursor: z$1.string().nullish(),
1876
+ limit: z$1.number().nullish(),
1877
+ offset: z$1.number().nullish()
1878
+ });
1883
1879
  }
1884
- /**
1885
- * Validates a list of actions by converting them from JSON and checking structure
1886
- */
1887
- function validateActions(actions) {
1888
- const convertedActions = [];
1889
- for (let i = 0; i < actions.length; i++) try {
1890
- convertedActions.push(jsonObjectToAction(actions[i]));
1891
- } catch (error) {
1892
- throw new GraphQLError(`Action at index ${i}: ${error instanceof Error ? error.message : "Invalid action structure"}`);
1893
- }
1894
- return convertedActions;
1880
+ function ReactorSignerAppInputSchema() {
1881
+ return z$1.object({
1882
+ key: z$1.string(),
1883
+ name: z$1.string()
1884
+ });
1895
1885
  }
1896
- /**
1897
- * Transforms an operation to serialize signatures from tuples to strings for GraphQL compatibility.
1898
- */
1899
- function serializeOperationForGraphQL(operation) {
1900
- const signer = operation.action.context?.signer;
1901
- if (!signer?.signatures) return operation;
1902
- return {
1903
- ...operation,
1904
- action: {
1905
- ...operation.action,
1906
- context: {
1907
- ...operation.action.context,
1908
- signer: {
1909
- ...signer,
1910
- signatures: signer.signatures.map((sig) => Array.isArray(sig) ? sig.join(", ") : sig)
1911
- }
1912
- }
1913
- }
1914
- };
1886
+ function ReactorSignerInputSchema() {
1887
+ return z$1.object({
1888
+ app: z$1.lazy(() => ReactorSignerAppInputSchema().nullish()),
1889
+ signatures: z$1.array(z$1.string()),
1890
+ user: z$1.lazy(() => ReactorSignerUserInputSchema().nullish())
1891
+ });
1915
1892
  }
1916
- /**
1917
- * Converts a PagedResults of Operation to GraphQL ReactorOperationResultPage format
1918
- */
1919
- function toOperationResultPage(result) {
1920
- return {
1921
- cursor: result.nextCursor ?? null,
1922
- hasNextPage: !!result.nextCursor,
1923
- hasPreviousPage: !!result.options.cursor && result.options.cursor !== "0",
1924
- items: result.results.map(serializeOperationForGraphQL),
1925
- totalCount: result.results.length
1926
- };
1893
+ function ReactorSignerUserInputSchema() {
1894
+ return z$1.object({
1895
+ address: z$1.string(),
1896
+ chainId: z$1.number(),
1897
+ networkId: z$1.string()
1898
+ });
1927
1899
  }
1928
- function toGqlDocumentChangeEvent(event) {
1929
- const mappedType = {
1930
- created: "CREATED",
1931
- deleted: "DELETED",
1932
- updated: "UPDATED",
1933
- parent_added: "PARENT_ADDED",
1934
- parent_removed: "PARENT_REMOVED",
1935
- child_added: "CHILD_ADDED",
1936
- child_removed: "CHILD_REMOVED"
1937
- }[event.type];
1938
- if (!mappedType) throw new GraphQLError(`Unknown document change type: ${event.type}`);
1939
- return {
1940
- type: mappedType,
1941
- documents: event.documents.map(toGqlPhDocument),
1942
- context: event.context ? {
1943
- parentId: event.context.parentId ?? null,
1944
- childId: event.context.childId ?? null
1945
- } : null
1946
- };
1947
- }
1948
- function matchesSearchFilter(event, search) {
1949
- if (search.type) {
1950
- if (!event.documents.some((doc) => doc.header.documentType === search.type)) return false;
1951
- }
1952
- if (search.parentId) {
1953
- if (!event.context?.parentId || event.context.parentId !== search.parentId) return false;
1954
- }
1955
- return true;
1956
- }
1957
- function matchesJobFilter(payload, args) {
1958
- return payload.jobId === args.jobId;
1959
- }
1960
- //#endregion
1961
- //#region src/graphql/reactor/resolvers.ts
1962
- const DRIVE_DOCUMENT_TYPE = "powerhouse/document-drive";
1963
- async function documentModels(reactorClient, args) {
1964
- const namespace = fromInputMaybe(args.namespace);
1965
- let paging;
1966
- if (args.paging) {
1967
- const cursor = fromInputMaybe(args.paging.cursor);
1968
- const limit = fromInputMaybe(args.paging.limit);
1969
- if (cursor || limit) paging = {
1970
- cursor: cursor || "",
1971
- limit: limit || 10
1972
- };
1973
- }
1974
- let result;
1975
- try {
1976
- result = await reactorClient.getDocumentModelModules(namespace, paging);
1977
- } catch (error) {
1978
- throw new GraphQLError(`Failed to fetch document models: ${error instanceof Error ? error.message : "Unknown error"}`);
1979
- }
1980
- try {
1981
- return toDocumentModelResultPage(result);
1982
- } catch (error) {
1983
- throw new GraphQLError(`Failed to convert document models to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
1984
- }
1900
+ function RemoteCursorInputSchema() {
1901
+ return z$1.object({
1902
+ cursorOrdinal: z$1.number(),
1903
+ lastSyncedAtUtcMs: z$1.string().nullish(),
1904
+ remoteName: z$1.string()
1905
+ });
1985
1906
  }
1986
- async function document(reactorClient, args) {
1987
- let view;
1988
- if (args.view) view = {
1989
- branch: fromInputMaybe(args.view.branch),
1990
- scopes: toMutableArray(fromInputMaybe(args.view.scopes))
1991
- };
1992
- let result;
1993
- try {
1994
- result = await reactorClient.get(args.identifier, view);
1995
- } catch (error) {
1996
- throw new GraphQLError(`Failed to fetch document: ${error instanceof Error ? error.message : "Unknown error"}`);
1997
- }
1998
- let children;
1999
- try {
2000
- children = await reactorClient.getChildren(args.identifier, view);
2001
- } catch (error) {
2002
- throw new GraphQLError(`Failed to fetch children: ${error instanceof Error ? error.message : "Unknown error"}`);
2003
- }
2004
- try {
2005
- return {
2006
- document: toGqlPhDocument(result),
2007
- childIds: children.results.map((child) => child.header.id)
2008
- };
2009
- } catch (error) {
2010
- throw new GraphQLError(`Failed to convert document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
2011
- }
1907
+ function RemoteFilterInputSchema() {
1908
+ return z$1.object({
1909
+ branch: z$1.string(),
1910
+ documentId: z$1.array(z$1.string()),
1911
+ scope: z$1.array(z$1.string())
1912
+ });
2012
1913
  }
2013
- async function documentChildren(reactorClient, args) {
2014
- let view;
2015
- if (args.view) view = {
2016
- branch: fromInputMaybe(args.view.branch),
2017
- scopes: toMutableArray(fromInputMaybe(args.view.scopes))
2018
- };
2019
- let paging;
2020
- if (args.paging) {
2021
- const cursor = fromInputMaybe(args.paging.cursor);
2022
- const limit = fromInputMaybe(args.paging.limit);
2023
- if (cursor || limit) paging = {
2024
- cursor: cursor || "",
2025
- limit: limit || 10
2026
- };
2027
- }
2028
- let result;
2029
- try {
2030
- result = await reactorClient.getChildren(args.parentIdentifier, view, paging);
2031
- } catch (error) {
2032
- throw new GraphQLError(`Failed to fetch document children: ${error instanceof Error ? error.message : "Unknown error"}`);
2033
- }
2034
- try {
2035
- return toPhDocumentResultPage(result);
2036
- } catch (error) {
2037
- throw new GraphQLError(`Failed to convert document children to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
2038
- }
1914
+ function SearchFilterInputSchema() {
1915
+ return z$1.object({
1916
+ identifiers: z$1.array(z$1.string()).nullish(),
1917
+ parentId: z$1.string().nullish(),
1918
+ type: z$1.string().nullish()
1919
+ });
2039
1920
  }
2040
- async function documentParents(reactorClient, args) {
2041
- let view;
2042
- if (args.view) view = {
2043
- branch: fromInputMaybe(args.view.branch),
2044
- scopes: toMutableArray(fromInputMaybe(args.view.scopes))
2045
- };
2046
- let paging;
2047
- if (args.paging) {
2048
- const cursor = fromInputMaybe(args.paging.cursor);
2049
- const limit = fromInputMaybe(args.paging.limit);
2050
- if (cursor || limit) paging = {
2051
- cursor: cursor || "",
2052
- limit: limit || 10
2053
- };
2054
- }
2055
- let result;
2056
- try {
2057
- result = await reactorClient.getParents(args.childIdentifier, view, paging);
2058
- } catch (error) {
2059
- throw new GraphQLError(`Failed to fetch document parents: ${error instanceof Error ? error.message : "Unknown error"}`);
2060
- }
2061
- try {
2062
- return toPhDocumentResultPage(result);
2063
- } catch (error) {
2064
- throw new GraphQLError(`Failed to convert document parents to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
2065
- }
1921
+ function SyncEnvelopeInputSchema() {
1922
+ return z$1.object({
1923
+ channelMeta: z$1.lazy(() => ChannelMetaInputSchema()),
1924
+ cursor: z$1.lazy(() => RemoteCursorInputSchema().nullish()),
1925
+ dependsOn: z$1.array(z$1.string()).nullish(),
1926
+ key: z$1.string().nullish(),
1927
+ operations: z$1.array(z$1.lazy(() => OperationWithContextInputSchema())).nullish(),
1928
+ type: SyncEnvelopeTypeSchema
1929
+ });
2066
1930
  }
2067
- async function findDocuments(reactorClient, args) {
2068
- let view;
2069
- if (args.view) view = {
2070
- branch: fromInputMaybe(args.view.branch),
2071
- scopes: toMutableArray(fromInputMaybe(args.view.scopes))
2072
- };
2073
- let paging;
2074
- if (args.paging) {
2075
- const cursor = fromInputMaybe(args.paging.cursor);
2076
- const limit = fromInputMaybe(args.paging.limit);
2077
- if (cursor || limit) paging = {
2078
- cursor: cursor || "",
2079
- limit: limit || 10
2080
- };
2081
- }
2082
- const search = {
2083
- type: fromInputMaybe(args.search.type),
2084
- parentId: fromInputMaybe(args.search.parentId)
2085
- };
2086
- let result;
2087
- try {
2088
- result = await reactorClient.find(search, view, paging);
2089
- } catch (error) {
2090
- throw new GraphQLError(`Failed to find documents: ${error instanceof Error ? error.message : "Unknown error"}`);
2091
- }
2092
- try {
2093
- return toPhDocumentResultPage(result);
2094
- } catch (error) {
2095
- throw new GraphQLError(`Failed to convert documents to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
2096
- }
1931
+ function TouchChannelInputSchema() {
1932
+ return z$1.object({
1933
+ collectionId: z$1.string(),
1934
+ filter: z$1.lazy(() => RemoteFilterInputSchema()),
1935
+ id: z$1.string(),
1936
+ name: z$1.string(),
1937
+ sinceTimestampUtcMs: z$1.string()
1938
+ });
2097
1939
  }
2098
- async function jobStatus(reactorClient, args) {
2099
- let result;
2100
- try {
2101
- result = await reactorClient.getJobStatus(args.jobId);
2102
- } catch (error) {
2103
- throw new GraphQLError(`Failed to fetch job status: ${error instanceof Error ? error.message : "Unknown error"}`);
2104
- }
2105
- try {
2106
- return toGqlJobInfo(result);
2107
- } catch (error) {
2108
- throw new GraphQLError(`Failed to convert job status to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
2109
- }
2110
- }
2111
- async function documentOperations(reactorClient, args) {
2112
- let view;
2113
- const branch = fromInputMaybe(args.filter.branch);
2114
- const scopes = toMutableArray(fromInputMaybe(args.filter.scopes));
2115
- if (branch || scopes) view = {
2116
- branch,
2117
- scopes
2118
- };
2119
- const actionTypes = toMutableArray(fromInputMaybe(args.filter.actionTypes));
2120
- const sinceRevision = fromInputMaybe(args.filter.sinceRevision);
2121
- const timestampFrom = fromInputMaybe(args.filter.timestampFrom);
2122
- const timestampTo = fromInputMaybe(args.filter.timestampTo);
2123
- let operationFilter;
2124
- if (actionTypes && actionTypes.length > 0 || sinceRevision !== void 0 || timestampFrom || timestampTo) operationFilter = {
2125
- actionTypes: actionTypes && actionTypes.length > 0 ? actionTypes : void 0,
2126
- sinceRevision,
2127
- timestampFrom: timestampFrom || void 0,
2128
- timestampTo: timestampTo || void 0
2129
- };
2130
- let paging;
2131
- if (args.paging) {
2132
- const cursor = fromInputMaybe(args.paging.cursor);
2133
- const limit = fromInputMaybe(args.paging.limit);
2134
- if (cursor || limit) paging = {
2135
- cursor: cursor || "",
2136
- limit: limit || 100
2137
- };
2138
- }
2139
- let result;
2140
- try {
2141
- result = await reactorClient.getOperations(args.filter.documentId, view, operationFilter, paging);
2142
- } catch (error) {
2143
- throw new GraphQLError(`Failed to fetch document operations: ${error instanceof Error ? error.message : "Unknown error"}`);
2144
- }
2145
- try {
2146
- return toOperationResultPage(result);
2147
- } catch (error) {
2148
- throw new GraphQLError(`Failed to convert operations to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
2149
- }
2150
- }
2151
- async function createDocument(reactorClient, args) {
2152
- if (!args.document || typeof args.document !== "object") throw new GraphQLError("Invalid document: must be an object");
2153
- const document = args.document;
2154
- if (!document.header || typeof document.header !== "object") throw new GraphQLError("Invalid document: missing or invalid header");
2155
- const parentIdentifier = fromInputMaybe(args.parentIdentifier);
2156
- let result;
2157
- try {
2158
- if (parentIdentifier) if ((await reactorClient.get(parentIdentifier)).header.documentType === DRIVE_DOCUMENT_TYPE) result = await reactorClient.createDocumentInDrive(parentIdentifier, document);
2159
- else result = await reactorClient.create(document, parentIdentifier);
2160
- else result = await reactorClient.create(document);
2161
- } catch (error) {
2162
- throw new GraphQLError(`Failed to create document: ${error instanceof Error ? error.message : "Unknown error"}`);
2163
- }
2164
- try {
2165
- return toGqlPhDocument(result);
2166
- } catch (error) {
2167
- throw new GraphQLError(`Failed to convert created document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
2168
- }
2169
- }
2170
- async function createEmptyDocument(reactorClient, args) {
2171
- const parentIdentifier = fromInputMaybe(args.parentIdentifier);
2172
- const name = fromInputMaybe(args.name);
2173
- let result;
2174
- try {
2175
- if (parentIdentifier) if ((await reactorClient.get(parentIdentifier)).header.documentType === DRIVE_DOCUMENT_TYPE) {
2176
- const document = (await reactorClient.getDocumentModelModule(args.documentType)).utils.createDocument();
2177
- if (name) document.header.name = name;
2178
- result = await reactorClient.createDocumentInDrive(parentIdentifier, document);
2179
- } else result = await reactorClient.createEmpty(args.documentType, { parentIdentifier });
2180
- else result = await reactorClient.createEmpty(args.documentType, {});
2181
- } catch (error) {
2182
- throw new GraphQLError(`Failed to create empty document: ${error instanceof Error ? error.message : "Unknown error"}`);
2183
- }
2184
- try {
2185
- return toGqlPhDocument(result);
2186
- } catch (error) {
2187
- throw new GraphQLError(`Failed to convert created document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
2188
- }
2189
- }
2190
- async function createDocumentWithInitialState(reactorClient, args) {
2191
- const parentIdentifier = fromInputMaybe(args.parentIdentifier);
2192
- const name = fromInputMaybe(args.name);
2193
- const slug = fromInputMaybe(args.slug);
2194
- const preferredEditor = fromInputMaybe(args.preferredEditor);
2195
- let module;
2196
- try {
2197
- module = await reactorClient.getDocumentModelModule(args.documentType);
2198
- } catch (error) {
2199
- throw new GraphQLError(`Document model not found for type ${args.documentType}: ${error instanceof Error ? error.message : "Unknown error"}`);
2200
- }
2201
- const document = module.utils.createDocument();
2202
- const allowedScopes = new Set(Object.keys(module.documentModel.global.specifications.at(-1)?.state ?? {}));
2203
- const state = document.state;
2204
- for (const [scope, scopeState] of Object.entries(args.initialState)) if (allowedScopes.has(scope) && scope in state) state[scope] = {
2205
- ...state[scope],
2206
- ...scopeState
2207
- };
2208
- if (name) document.header.name = name;
2209
- if (slug) document.header.slug = slug;
2210
- if (preferredEditor) document.header.meta = {
2211
- ...document.header.meta,
2212
- preferredEditor
2213
- };
2214
- let result;
2215
- if (parentIdentifier) {
2216
- let parent;
2217
- try {
2218
- parent = await reactorClient.get(parentIdentifier);
2219
- } catch (error) {
2220
- throw new GraphQLError(`Parent document not found: ${error instanceof Error ? error.message : "Unknown error"}`);
2221
- }
2222
- if (parent.header.documentType === DRIVE_DOCUMENT_TYPE) try {
2223
- result = await reactorClient.createDocumentInDrive(parentIdentifier, document);
2224
- } catch (error) {
2225
- throw new GraphQLError(`Failed to create document in drive: ${error instanceof Error ? error.message : "Unknown error"}`);
2226
- }
2227
- else try {
2228
- result = await reactorClient.create(document, parentIdentifier);
2229
- } catch (error) {
2230
- throw new GraphQLError(`Failed to create document with parent: ${error instanceof Error ? error.message : "Unknown error"}`);
2231
- }
2232
- } else try {
2233
- result = await reactorClient.create(document);
2234
- } catch (error) {
2235
- throw new GraphQLError(`Failed to create document: ${error instanceof Error ? error.message : "Unknown error"}`);
2236
- }
2237
- try {
2238
- return toGqlPhDocument(result);
2239
- } catch (error) {
2240
- throw new GraphQLError(`Failed to convert created document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
2241
- }
2242
- }
2243
- async function mutateDocument(reactorClient, args) {
2244
- let validatedActions;
2245
- try {
2246
- validatedActions = validateActions(args.actions);
2247
- } catch (error) {
2248
- if (error instanceof GraphQLError) throw error;
2249
- throw new GraphQLError(`Action validation failed: ${error instanceof Error ? error.message : "Unknown error"}`);
2250
- }
2251
- const branch = args.view?.branch ?? "main";
2252
- let result;
2253
- try {
2254
- result = await reactorClient.execute(args.documentIdentifier, branch, validatedActions);
2255
- } catch (error) {
2256
- throw new GraphQLError(`Failed to mutate document: ${error instanceof Error ? error.message : "Unknown error"}`);
2257
- }
2258
- try {
2259
- return toGqlPhDocument(result);
2260
- } catch (error) {
2261
- throw new GraphQLError(`Failed to convert mutated document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
2262
- }
2263
- }
2264
- async function mutateDocumentAsync(reactorClient, args) {
2265
- let validatedActions;
2266
- try {
2267
- validatedActions = validateActions(args.actions);
2268
- } catch (error) {
2269
- if (error instanceof GraphQLError) throw error;
2270
- throw new GraphQLError(`Action validation failed: ${error instanceof Error ? error.message : "Unknown error"}`);
2271
- }
2272
- const branch = args.view?.branch ?? "main";
2273
- let result;
2274
- try {
2275
- result = await reactorClient.executeAsync(args.documentIdentifier, branch, validatedActions);
2276
- } catch (error) {
2277
- throw new GraphQLError(`Failed to submit document mutation: ${error instanceof Error ? error.message : "Unknown error"}`);
2278
- }
2279
- return result.id;
2280
- }
2281
- async function renameDocument(reactorClient, args, signal) {
2282
- const branch = fromInputMaybe(args.branch);
2283
- let result;
2284
- try {
2285
- result = await reactorClient.rename(args.documentIdentifier, args.name, branch, signal);
2286
- } catch (error) {
2287
- throw new GraphQLError(`Failed to rename document: ${error instanceof Error ? error.message : "Unknown error"}`);
2288
- }
2289
- try {
2290
- return toGqlPhDocument(result);
2291
- } catch (error) {
2292
- throw new GraphQLError(`Failed to convert renamed document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
2293
- }
2294
- }
2295
- async function addChildren(reactorClient, args) {
2296
- const branch = fromInputMaybe(args.branch);
2297
- const documentIdentifiers = [...args.documentIdentifiers];
2298
- let result;
2299
- try {
2300
- result = await reactorClient.addChildren(args.parentIdentifier, documentIdentifiers, branch);
2301
- } catch (error) {
2302
- throw new GraphQLError(`Failed to add children: ${error instanceof Error ? error.message : "Unknown error"}`);
2303
- }
2304
- try {
2305
- return toGqlPhDocument(result);
2306
- } catch (error) {
2307
- throw new GraphQLError(`Failed to convert document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
2308
- }
2309
- }
2310
- async function removeChildren(reactorClient, args) {
2311
- const branch = fromInputMaybe(args.branch);
2312
- const documentIdentifiers = [...args.documentIdentifiers];
2313
- let result;
2314
- try {
2315
- result = await reactorClient.removeChildren(args.parentIdentifier, documentIdentifiers, branch);
2316
- } catch (error) {
2317
- throw new GraphQLError(`Failed to remove children: ${error instanceof Error ? error.message : "Unknown error"}`);
2318
- }
2319
- try {
2320
- return toGqlPhDocument(result);
2321
- } catch (error) {
2322
- throw new GraphQLError(`Failed to convert document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
2323
- }
2324
- }
2325
- async function moveChildren(reactorClient, args) {
2326
- const branch = fromInputMaybe(args.branch);
2327
- const documentIdentifiers = [...args.documentIdentifiers];
2328
- let result;
2329
- try {
2330
- result = await reactorClient.moveChildren(args.sourceParentIdentifier, args.targetParentIdentifier, documentIdentifiers, branch);
2331
- } catch (error) {
2332
- throw new GraphQLError(`Failed to move children: ${error instanceof Error ? error.message : "Unknown error"}`);
2333
- }
2334
- try {
2335
- return {
2336
- source: toGqlPhDocument(result.source),
2337
- target: toGqlPhDocument(result.target)
2338
- };
2339
- } catch (error) {
2340
- throw new GraphQLError(`Failed to convert documents to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
2341
- }
2342
- }
2343
- async function deleteDocument(reactorClient, args) {
2344
- const propagate = fromInputMaybe(args.propagate);
2345
- try {
2346
- await reactorClient.deleteDocument(args.identifier, propagate);
2347
- return true;
2348
- } catch (error) {
2349
- throw new GraphQLError(`Failed to delete document: ${error instanceof Error ? error.message : "Unknown error"}`);
2350
- }
2351
- }
2352
- async function deleteDocuments(reactorClient, args) {
2353
- const propagate = fromInputMaybe(args.propagate);
2354
- const identifiers = [...args.identifiers];
2355
- try {
2356
- await reactorClient.deleteDocuments(identifiers, propagate);
2357
- return true;
2358
- } catch (error) {
2359
- throw new GraphQLError(`Failed to delete documents: ${error instanceof Error ? error.message : "Unknown error"}`);
2360
- }
2361
- }
2362
- async function touchChannel(syncManager, args) {
2363
- try {
2364
- return {
2365
- success: true,
2366
- ackOrdinal: syncManager.getById(args.input.id).channel.inbox.ackOrdinal
2367
- };
2368
- } catch {}
2369
- const filter = {
2370
- documentId: [...args.input.filter.documentId],
2371
- scope: [...args.input.filter.scope],
2372
- branch: args.input.filter.branch
2373
- };
2374
- const options = { sinceTimestampUtcMs: args.input.sinceTimestampUtcMs };
2375
- try {
2376
- await syncManager.add(args.input.name, args.input.collectionId, {
2377
- type: "polling",
2378
- parameters: {}
2379
- }, filter, options, args.input.id);
2380
- } catch (error) {
2381
- throw new GraphQLError(`Failed to create channel: ${error instanceof Error ? error.message : "Unknown error"}`);
2382
- }
2383
- return {
2384
- success: true,
2385
- ackOrdinal: 0
2386
- };
2387
- }
2388
- /**
2389
- * Polls the switchboard for new sync envelopes and acknowledges previously
2390
- * received operations.
2391
- *
2392
- * Ordinal frames of reference:
2393
- * - `outboxAck` / `outboxLatest`: switchboard's ordinals (used to trim/filter
2394
- * the switchboard's outbox)
2395
- * - `ackOrdinal` in the response: the pushing client's ordinals (highest
2396
- * client ordinal the switchboard has successfully applied, so the client
2397
- * can trim its own outbox)
2398
- */
2399
- function pollSyncEnvelopes(syncManager, args) {
2400
- let remote;
2401
- try {
2402
- remote = syncManager.getById(args.channelId);
2403
- } catch (error) {
2404
- throw new GraphQLError(`Channel not found: ${error instanceof Error ? error.message : "Unknown error"}`);
2405
- }
2406
- const deadLetters = remote.channel.deadLetter.items.map((syncOp) => ({
2407
- documentId: syncOp.documentId,
2408
- error: syncOp.error?.message ?? "Unknown error",
2409
- jobId: syncOp.jobId,
2410
- branch: syncOp.branch,
2411
- scopes: syncOp.scopes,
2412
- operationCount: syncOp.operations.length
2413
- }));
2414
- if (args.outboxAck > 0) trimMailboxFromAckOrdinal(remote.channel.outbox, args.outboxAck);
2415
- let operations = remote.channel.outbox.items;
2416
- operations = operations.filter((syncOp) => {
2417
- let maxOrdinal = 0;
2418
- for (const op of syncOp.operations) maxOrdinal = Math.max(maxOrdinal, op.context.ordinal);
2419
- if (maxOrdinal > args.outboxLatest) return true;
2420
- return false;
2421
- });
2422
- if (operations.length === 0) return {
2423
- envelopes: [],
2424
- ackOrdinal: remote.channel.inbox.ackOrdinal,
2425
- deadLetters
2426
- };
2427
- let maxOrdinal = args.outboxLatest;
2428
- for (const syncOp of operations) for (const op of syncOp.operations) {
2429
- const opOrdinal = op.context.ordinal;
2430
- if (opOrdinal > maxOrdinal) maxOrdinal = opOrdinal;
2431
- }
2432
- return {
2433
- envelopes: sortEnvelopesByFirstOperationTimestamp(operations.map((syncOp) => ({
2434
- type: "OPERATIONS",
2435
- channelMeta: { id: args.channelId },
2436
- operations: syncOp.operations.map((op) => ({
2437
- operation: serializeOperationForGraphQL(op.operation),
2438
- context: op.context
2439
- })),
2440
- cursor: {
2441
- remoteName: remote.name,
2442
- cursorOrdinal: maxOrdinal,
2443
- lastSyncedAtUtcMs: Date.now().toString()
2444
- },
2445
- key: syncOp.jobId || void 0,
2446
- dependsOn: syncOp.jobDependencies.filter(Boolean).length > 0 ? syncOp.jobDependencies.filter(Boolean) : void 0
2447
- }))),
2448
- ackOrdinal: remote.channel.inbox.ackOrdinal,
2449
- deadLetters
2450
- };
2451
- }
2452
- /**
2453
- * Receives sync envelopes pushed by a client and adds them to the
2454
- * appropriate channel inboxes.
2455
- *
2456
- * The `ordinal` in each operation's context is the client's local ordinal.
2457
- * It must be preserved because the inbox mailbox tracks applied ordinals
2458
- * and returns the highest one as `ackOrdinal` in pollSyncEnvelopes.
2459
- */
2460
- function pushSyncEnvelopes(syncManager, args) {
2461
- const sortedEnvelopes = sortEnvelopesByFirstOperationTimestamp(args.envelopes);
2462
- const remoteSyncOps = /* @__PURE__ */ new Map();
2463
- for (const envelope of sortedEnvelopes) {
2464
- let remote;
2465
- try {
2466
- remote = syncManager.getById(envelope.channelMeta.id);
2467
- } catch (error) {
2468
- throw new GraphQLError(`Channel not found: ${error instanceof Error ? error.message : "Unknown error"}`);
2469
- }
2470
- if (!envelope.operations || envelope.operations.length === 0) continue;
2471
- const syncOps = envelopesToSyncOperations(envelope, remote.name);
2472
- if (!remoteSyncOps.has(remote)) remoteSyncOps.set(remote, []);
2473
- remoteSyncOps.get(remote).push(...syncOps);
2474
- }
2475
- for (const [remote, syncOps] of remoteSyncOps) {
2476
- const consolidated = consolidateSyncOperations(syncOps);
2477
- const validKeys = new Set(consolidated.map((op) => op.jobId).filter(Boolean));
2478
- for (const syncOp of consolidated) syncOp.jobDependencies = syncOp.jobDependencies.filter((dep) => validKeys.has(dep));
2479
- remote.channel.inbox.add(...consolidated);
2480
- }
2481
- return Promise.resolve(true);
2482
- }
2483
- //#endregion
2484
- //#region src/graphql/document-model-subgraph.ts
2485
- /**
2486
- * New document model subgraph that uses reactorClient instead of legacy reactor.
2487
- * This class auto-generates GraphQL queries and mutations for a document model.
2488
- */
2489
- var DocumentModelSubgraph = class extends BaseSubgraph {
2490
- documentModel;
2491
- constructor(documentModel, args) {
2492
- super(args);
2493
- this.documentModel = documentModel;
2494
- this.name = kebabCase(documentModel.documentModel.global.name);
2495
- this.typeDefs = generateDocumentModelSchema(this.documentModel.documentModel.global, { useNewApi: true });
2496
- this.resolvers = this.generateResolvers();
2497
- }
2498
- /** Returns the typed query resolvers for this document model. */
2499
- get queryResolvers() {
2500
- const documentName = getDocumentModelSchemaName(this.documentModel.documentModel.global);
2501
- return this.resolvers[`${documentName}Queries`];
2502
- }
2503
- /** Returns the typed mutation resolvers for this document model. */
2504
- get mutationResolvers() {
2505
- const documentName = getDocumentModelSchemaName(this.documentModel.documentModel.global);
2506
- return this.resolvers[`${documentName}Mutations`];
2507
- }
2508
- /**
2509
- * Generate __resolveType functions for union types found in the document model schema.
2510
- * Parses the state schema to find union definitions and their member types,
2511
- * then uses unique field presence to discriminate between member types at runtime.
2512
- */
2513
- generateUnionResolvers() {
2514
- const documentName = getDocumentModelSchemaName(this.documentModel.documentModel.global);
2515
- const specification = this.documentModel.documentModel.global.specifications.at(-1);
2516
- if (!specification) return {};
2517
- const fullSchema = `${specification.state.global.schema ?? ""}\n${specification.state.local.schema ?? ""}`;
2518
- if (!fullSchema.trim()) return {};
2519
- let ast;
2520
- try {
2521
- ast = parse(fullSchema);
2522
- } catch {
2523
- return {};
2524
- }
2525
- const objectFieldsMap = /* @__PURE__ */ new Map();
2526
- for (const def of ast.definitions) if (def.kind === Kind.OBJECT_TYPE_DEFINITION) objectFieldsMap.set(def.name.value, def.fields?.map((f) => f.name.value) ?? []);
2527
- const resolvers = {};
2528
- for (const def of ast.definitions) {
2529
- if (def.kind !== Kind.UNION_TYPE_DEFINITION) continue;
2530
- const unionName = def.name.value;
2531
- const memberTypes = def.types?.map((t) => t.name.value) ?? [];
2532
- if (memberTypes.length === 0) continue;
2533
- const uniqueFields = {};
2534
- for (const memberType of memberTypes) {
2535
- const ownFields = objectFieldsMap.get(memberType) ?? [];
2536
- const otherFields = new Set(memberTypes.filter((t) => t !== memberType).flatMap((t) => objectFieldsMap.get(t) ?? []));
2537
- uniqueFields[memberType] = ownFields.filter((f) => !otherFields.has(f));
2538
- }
2539
- const prefixedUnionName = `${documentName}_${unionName}`;
2540
- resolvers[prefixedUnionName] = { __resolveType: (obj) => {
2541
- for (const memberType of memberTypes) {
2542
- const fields = uniqueFields[memberType] ?? [];
2543
- if (fields.length > 0 && fields.some((f) => f in obj)) return `${documentName}_${memberType}`;
2544
- }
2545
- return `${documentName}_${memberTypes[0]}`;
2546
- } };
2547
- }
2548
- return resolvers;
2549
- }
2550
- /**
2551
- * Generate resolvers for this document model using reactorClient
2552
- * Uses flat queries (not nested) consistent with ReactorSubgraph patterns
2553
- */
2554
- generateResolvers() {
2555
- const documentType = this.documentModel.documentModel.global.id;
2556
- const documentName = getDocumentModelSchemaName(this.documentModel.documentModel.global);
2557
- const operations = this.documentModel.documentModel.global.specifications.at(-1)?.modules.flatMap((module) => module.operations.filter((op) => op.name)) ?? [];
2558
- return {
2559
- ...this.generateUnionResolvers(),
2560
- Query: { [documentName]: () => ({}) },
2561
- [`${documentName}Queries`]: {
2562
- document: async (_, args, ctx) => {
2563
- const { identifier, view } = args;
2564
- if (!identifier) throw new GraphQLError("Document identifier is required");
2565
- const result = await document(this.reactorClient, {
2566
- identifier,
2567
- view
2568
- });
2569
- if (result.document.documentType !== documentType) throw new GraphQLError(`Document with id ${identifier} is not of type ${documentType}`);
2570
- await this.assertCanRead(result.document.id, ctx);
2571
- return result;
2572
- },
2573
- documents: async (_, args, ctx) => {
2574
- const { paging } = args;
2575
- const result = await findDocuments(this.reactorClient, {
2576
- search: { type: documentType },
2577
- paging
2578
- });
2579
- if (!this.hasGlobalAdminAccess(ctx) && this.documentPermissionService) {
2580
- const filteredItems = [];
2581
- for (const item of result.items) if (await this.canReadDocument(item.id, ctx)) filteredItems.push(item);
2582
- return {
2583
- ...result,
2584
- items: filteredItems,
2585
- totalCount: filteredItems.length
2586
- };
2587
- }
2588
- return result;
2589
- },
2590
- findDocuments: async (_, args, ctx) => {
2591
- const { search, view, paging } = args;
2592
- const result = await findDocuments(this.reactorClient, {
2593
- search: {
2594
- type: documentType,
2595
- parentId: search?.parentId
2596
- },
2597
- view,
2598
- paging
2599
- });
2600
- if (!this.hasGlobalAdminAccess(ctx) && this.documentPermissionService) {
2601
- const filteredItems = [];
2602
- for (const item of result.items) if (await this.canReadDocument(item.id, ctx)) filteredItems.push(item);
2603
- return {
2604
- ...result,
2605
- items: filteredItems,
2606
- totalCount: filteredItems.length
2607
- };
2608
- }
2609
- return result;
2610
- },
2611
- documentChildren: async (_, args, ctx) => {
2612
- const { parentIdentifier, view, paging } = args;
2613
- await this.assertCanRead(parentIdentifier, ctx);
2614
- const result = await documentChildren(this.reactorClient, {
2615
- parentIdentifier,
2616
- view,
2617
- paging
2618
- });
2619
- const filteredItems = result.items.filter((item) => item.documentType === documentType);
2620
- return {
2621
- ...result,
2622
- items: filteredItems,
2623
- totalCount: filteredItems.length
2624
- };
2625
- },
2626
- documentParents: async (_, args, ctx) => {
2627
- const { childIdentifier, view, paging } = args;
2628
- await this.assertCanRead(childIdentifier, ctx);
2629
- return documentParents(this.reactorClient, {
2630
- childIdentifier,
2631
- view,
2632
- paging
2633
- });
2634
- }
2635
- },
2636
- Mutation: { [documentName]: () => ({}) },
2637
- [`${documentName}Mutations`]: {
2638
- createDocument: async (_, args, ctx) => {
2639
- const { parentIdentifier, name, slug, preferredEditor, initialState } = args;
2640
- if (parentIdentifier) await this.assertCanWrite(parentIdentifier, ctx);
2641
- else if (this.authorizationService) {
2642
- if (!ctx.user?.address) throw new GraphQLError("Forbidden: authentication required to create documents");
2643
- } else if (!this.hasGlobalAdminAccess(ctx)) throw new GraphQLError("Forbidden: insufficient permissions to create documents");
2644
- let createdDoc;
2645
- if (initialState || preferredEditor) createdDoc = await createDocumentWithInitialState(this.reactorClient, {
2646
- documentType,
2647
- parentIdentifier,
2648
- name,
2649
- slug,
2650
- preferredEditor,
2651
- initialState: initialState ?? {}
2652
- });
2653
- else createdDoc = await createEmptyDocument(this.reactorClient, {
2654
- documentType,
2655
- parentIdentifier,
2656
- name
2657
- });
2658
- if (this.authorizationService && ctx.user?.address && createdDoc?.id) await this.documentPermissionService?.initializeDocumentProtection(createdDoc.id, ctx.user.address, this.authorizationService.config.defaultProtection);
2659
- if (!initialState && !preferredEditor && name && createdDoc.name !== name) return toGqlPhDocument(await this.reactorClient.execute(createdDoc.id, "main", [setName(name)]));
2660
- return createdDoc;
2661
- },
2662
- createEmptyDocument: async (_, args, ctx) => {
2663
- const { parentIdentifier } = args;
2664
- if (parentIdentifier) await this.assertCanWrite(parentIdentifier, ctx);
2665
- else if (this.authorizationService) {
2666
- if (!ctx.user?.address) throw new GraphQLError("Forbidden: authentication required to create documents");
2667
- } else if (!this.hasGlobalAdminAccess(ctx)) throw new GraphQLError("Forbidden: insufficient permissions to create documents");
2668
- const result = await createEmptyDocument(this.reactorClient, {
2669
- documentType,
2670
- parentIdentifier
2671
- });
2672
- if (this.authorizationService && ctx.user?.address && result?.id) await this.documentPermissionService?.initializeDocumentProtection(result.id, ctx.user.address, this.authorizationService.config.defaultProtection);
2673
- return result;
2674
- },
2675
- ...operations.reduce((mutations, op) => {
2676
- mutations[camelCase(op.name)] = async (_, args, ctx) => {
2677
- const { docId, input } = args;
2678
- if (!this.authorizationService) await this.assertCanWrite(docId, ctx);
2679
- await this.assertCanExecuteOperation(docId, op.name, ctx);
2680
- if ((await this.reactorClient.get(docId)).header.documentType !== documentType) throw new GraphQLError(`Document with id ${docId} is not of type ${documentType}`);
2681
- const action = this.documentModel.actions[camelCase(op.name)];
2682
- if (!action) throw new GraphQLError(`Action ${op.name} not found`);
2683
- try {
2684
- return toGqlPhDocument(await this.reactorClient.execute(docId, "main", [action(input)]));
2685
- } catch (error) {
2686
- throw new GraphQLError(error instanceof Error ? error.message : `Failed to ${op.name}`);
2687
- }
2688
- };
2689
- mutations[`${camelCase(op.name)}Async`] = async (_, args, ctx) => {
2690
- const { docId, input } = args;
2691
- if (!this.authorizationService) await this.assertCanWrite(docId, ctx);
2692
- await this.assertCanExecuteOperation(docId, op.name, ctx);
2693
- if ((await this.reactorClient.get(docId)).header.documentType !== documentType) throw new GraphQLError(`Document with id ${docId} is not of type ${documentType}`);
2694
- const action = this.documentModel.actions[camelCase(op.name)];
2695
- if (!action) throw new GraphQLError(`Action ${op.name} not found`);
2696
- try {
2697
- return (await this.reactorClient.executeAsync(docId, "main", [action(input)])).id;
2698
- } catch (error) {
2699
- throw new GraphQLError(error instanceof Error ? error.message : `Failed to ${op.name}`);
2700
- }
2701
- };
2702
- return mutations;
2703
- }, {})
2704
- }
2705
- };
2706
- }
2707
- };
2708
- //#endregion
2709
- //#region src/graphql/sse.ts
2710
- /**
2711
- * Create a Fetch-API-compatible SSE handler for GraphQL subscriptions
2712
- * using the graphql-sse library (graphql-sse protocol).
2713
- *
2714
- * This runs alongside the existing WebSocket (graphql-ws) transport
2715
- * so clients can choose either protocol.
2716
- *
2717
- * The returned handler is a standard FetchHandler: (req: Request) => Promise<Response>.
2718
- * It can be mounted via httpAdapter.mount() like any other handler, and auth
2719
- * flows through the WeakMap pattern (authFetchMiddleware populates the context
2720
- * before this handler is called).
2721
- *
2722
- * Clients connect via "distinct connections mode" (POST with
2723
- * `Accept: text/event-stream`). Single-connection mode is disabled
2724
- * because it adds token-management complexity with no benefit here.
2725
- */
2726
- function createGraphQLSSEHandler(options) {
2727
- const { schema, contextFactory } = options;
2728
- return createHandler({
2729
- schema,
2730
- authenticate: () => null,
2731
- context: (req) => contextFactory(req.raw)
2732
- });
2733
- }
2734
- //#endregion
2735
- //#region src/graphql/graphql-manager.ts
2736
- const DOCUMENT_MODELS_TO_EXCLUDE = [];
2737
- /**
2738
- * Check if a document model has any operations with valid schemas.
2739
- * Document models without valid operation schemas cannot generate valid subgraph schemas.
2740
- */
2741
- function hasOperationSchemas(documentModel) {
2742
- const specification = documentModel.documentModel.global.specifications.at(-1);
2743
- if (!specification) return false;
2744
- const hasValidSchema = (schema) => schema && /\b(input|type|enum|union|interface)\s+\w+/.test(schema);
2745
- return specification.modules.some((module) => module.operations.some((op) => hasValidSchema(op.schema)));
2746
- }
2747
- /**
2748
- * Filter document models to keep only the latest version of each unique document model.
2749
- */
2750
- function filterLatestDocumentModelVersions(documentModels) {
2751
- const latestByName = /* @__PURE__ */ new Map();
2752
- for (const documentModel of documentModels) {
2753
- const name = documentModel.documentModel.global.name;
2754
- const existing = latestByName.get(name);
2755
- if (!existing) {
2756
- latestByName.set(name, documentModel);
2757
- continue;
2758
- }
2759
- if ((documentModel.documentModel.global.specifications.at(-1)?.version ?? 0) > (existing.documentModel.global.specifications.at(-1)?.version ?? 0)) latestByName.set(name, documentModel);
2760
- }
2761
- return Array.from(latestByName.values());
2762
- }
2763
- const DefaultFeatureFlags = { enableDocumentModelSubgraphs: true };
2764
- var GraphQLManager = class {
2765
- initialized = false;
2766
- coreSubgraphsMap = /* @__PURE__ */ new Map();
2767
- contextFields = {};
2768
- subgraphs = /* @__PURE__ */ new Map();
2769
- authService = null;
2770
- subgraphWsDisposers = /* @__PURE__ */ new Map();
2771
- #authMiddleware;
2772
- /** Cached document models for schema generation - updated on init and regenerate */
2773
- cachedDocumentModels = [];
2774
- subgraphHandlerCache = /* @__PURE__ */ new Map();
2775
- constructor(path, httpServer, wsServer, reactorClient, relationalDb, analyticsStore, syncManager, logger, httpAdapter, gatewayAdapter, authConfig, documentPermissionService, featureFlags = DefaultFeatureFlags, port = 4001, authorizationService) {
2776
- this.path = path;
2777
- this.httpServer = httpServer;
2778
- this.wsServer = wsServer;
2779
- this.reactorClient = reactorClient;
2780
- this.relationalDb = relationalDb;
2781
- this.analyticsStore = analyticsStore;
2782
- this.syncManager = syncManager;
2783
- this.logger = logger;
2784
- this.httpAdapter = httpAdapter;
2785
- this.gatewayAdapter = gatewayAdapter;
2786
- this.authConfig = authConfig;
2787
- this.documentPermissionService = documentPermissionService;
2788
- this.featureFlags = featureFlags;
2789
- this.port = port;
2790
- this.authorizationService = authorizationService;
2791
- if (this.authConfig) this.authService = new AuthService(this.authConfig);
2792
- }
2793
- async init(coreSubgraphs, authMiddleware) {
2794
- this.#authMiddleware = authMiddleware;
2795
- this.logger.debug(`Initializing Subgraph Manager...`);
2796
- const models = (await this.reactorClient.getDocumentModelModules()).results;
2797
- this.cachedDocumentModels = models;
2798
- if (!models.find((it) => it.documentModel.global.name === "DocumentDrive")) throw new Error("DocumentDrive model required");
2799
- await this.gatewayAdapter.start(this.httpServer);
2800
- this.httpAdapter.setupMiddleware({ bodyLimit: "50mb" });
2801
- const driveRoutePath = path.join(this.path, "d/:drive");
2802
- const driveMatcher = match(driveRoutePath);
2803
- this.httpAdapter.mount(driveRoutePath, async (request) => {
2804
- const url = new URL(request.url);
2805
- const matched = driveMatcher(url.pathname);
2806
- const driveIdOrSlug = matched ? matched.params.drive : void 0;
2807
- if (!driveIdOrSlug) return Response.json({ error: "Drive ID or slug is required" }, { status: 400 });
2808
- try {
2809
- const driveDoc = await this.reactorClient.get(driveIdOrSlug);
2810
- const graphqlEndpoint = `${(request.headers.get("x-forwarded-proto") ?? url.protocol.replace(":", "")) + ":"}//${request.headers.get("host") ?? ""}${this.path === "/" ? "" : this.path}/graphql/r`;
2811
- return Response.json({
2812
- id: driveDoc.header.id,
2813
- slug: driveDoc.header.slug,
2814
- meta: driveDoc.header.meta,
2815
- name: driveDoc.state.global.name,
2816
- icon: driveDoc.state.global.icon ?? void 0,
2817
- ...graphqlEndpoint && { graphqlEndpoint }
2818
- });
2819
- } catch (error) {
2820
- this.logger.debug(`Drive not found: ${driveIdOrSlug}`, error);
2821
- return Response.json({ error: "Drive not found" }, { status: 404 });
2822
- }
2823
- });
2824
- this.logger.info(`Registered REST endpoint: GET ${driveRoutePath}`);
2825
- await this.#setupCoreSubgraphs("graphql", coreSubgraphs);
2826
- if (this.featureFlags.enableDocumentModelSubgraphs) await this.#setupDocumentModelSubgraphs("graphql", models);
2827
- await this.#createSupergraphGateway();
2828
- return this.updateRouter();
2829
- }
2830
- /**
2831
- * Regenerate document model subgraphs when models are dynamically loaded.
2832
- * Fetches current modules from reactor client (source of truth).
2833
- */
2834
- async regenerateDocumentModelSubgraphs() {
2835
- if (!this.featureFlags.enableDocumentModelSubgraphs) return;
2836
- try {
2837
- const models = (await this.reactorClient.getDocumentModelModules()).results;
2838
- this.cachedDocumentModels = models;
2839
- await this.#setupDocumentModelSubgraphs("graphql", models);
2840
- await this.updateRouter();
2841
- this.logger.info("Regenerated document model subgraphs with @count models", models.length);
2842
- } catch (error) {
2843
- this.logger.error("Failed to regenerate document model subgraphs", error);
2844
- throw error;
2845
- }
2846
- }
2847
- async #setupCoreSubgraphs(supergraph, coreSubgraphs) {
2848
- for (const subgraph of coreSubgraphs) try {
2849
- await this.registerSubgraph(subgraph, supergraph, true);
2850
- } catch (error) {
2851
- this.logger.error(`Failed to setup core subgraph ${subgraph.name}`, error);
2852
- }
2853
- return this.#setupSubgraphs(this.coreSubgraphsMap);
2854
- }
2855
- async #setupDocumentModelSubgraphs(supergraph, documentModels) {
2856
- const latestDocumentModels = filterLatestDocumentModelVersions(documentModels);
2857
- for (const documentModel of latestDocumentModels) {
2858
- if (DOCUMENT_MODELS_TO_EXCLUDE.includes(documentModel.documentModel.global.id)) continue;
2859
- if (!hasOperationSchemas(documentModel)) continue;
2860
- try {
2861
- const subgraphInstance = new DocumentModelSubgraph(documentModel, {
2862
- relationalDb: this.relationalDb,
2863
- analyticsStore: this.analyticsStore,
2864
- reactorClient: this.reactorClient,
2865
- graphqlManager: this,
2866
- syncManager: this.syncManager,
2867
- path: this.path,
2868
- documentPermissionService: this.documentPermissionService,
2869
- authorizationService: this.authorizationService
2870
- });
2871
- await this.#addSubgraphInstance(subgraphInstance, supergraph, false);
2872
- } catch (error) {
2873
- this.logger.error(`Failed to setup document model subgraph for ${documentModel.documentModel.global.id}`, error instanceof Error ? error.message : error);
2874
- this.logger.debug("@error", error);
2875
- }
2876
- }
2877
- }
2878
- async #addSubgraphInstance(subgraphInstance, supergraph = "", core = false) {
2879
- const subgraphsMap = core ? this.coreSubgraphsMap : this.subgraphs;
2880
- const subgraphs = subgraphsMap.get(supergraph) ?? [];
2881
- const existingSubgraph = subgraphs.find((it) => it.name === subgraphInstance.name);
2882
- if (existingSubgraph) {
2883
- this.logger.debug(`Skipping duplicate subgraph: ${subgraphInstance.name}`);
2884
- return existingSubgraph;
2885
- }
2886
- await subgraphInstance.onSetup?.();
2887
- subgraphs.push(subgraphInstance);
2888
- subgraphsMap.set(supergraph, subgraphs);
2889
- if (supergraph !== "" && supergraph !== "graphql") subgraphsMap.get("graphql")?.push(subgraphInstance);
2890
- this.logger.info(`Registered ${this.path.endsWith("/") ? this.path : this.path + "/"}${supergraph ? supergraph + "/" : ""}${subgraphInstance.name} subgraph.`);
2891
- return subgraphInstance;
2892
- }
2893
- /**
2894
- * Register a pre-constructed subgraph instance.
2895
- * Use this when you need to pass custom dependencies to a subgraph.
2896
- */
2897
- async registerSubgraphInstance(subgraphInstance, supergraph = "", core = false) {
2898
- return this.#addSubgraphInstance(subgraphInstance, supergraph, core);
2899
- }
2900
- /**
2901
- * Get the base path used for subgraph registration.
2902
- */
2903
- getBasePath() {
2904
- return this.path;
2905
- }
2906
- async registerSubgraph(subgraph, supergraph = "", core = false) {
2907
- const subgraphInstance = new subgraph({
2908
- relationalDb: this.relationalDb,
2909
- analyticsStore: this.analyticsStore,
2910
- reactorClient: this.reactorClient,
2911
- graphqlManager: this,
2912
- syncManager: this.syncManager,
2913
- path: this.path,
2914
- documentPermissionService: this.documentPermissionService,
2915
- authorizationService: this.authorizationService
2916
- });
2917
- return this.#addSubgraphInstance(subgraphInstance, supergraph, core);
2918
- }
2919
- updateRouter = debounce(this._updateRouter.bind(this), 1e3);
2920
- async _updateRouter() {
2921
- this.logger.debug("Updating router");
2922
- await this.#setupSubgraphs(this.subgraphs);
2923
- try {
2924
- await this.gatewayAdapter.updateSupergraph();
2925
- this.logger.debug("Updated Apollo Gateway supergraph");
2926
- } catch (error) {
2927
- this.logger.error("Failed to update Apollo Gateway supergraph", error);
2928
- }
2929
- const superGraphPath = path.join(this.path, "graphql");
2930
- this.#setupSupergraphSSE(superGraphPath);
2931
- }
2932
- getAdditionalContextFields = () => {
2933
- return this.contextFields;
2934
- };
2935
- setAdditionalContextFields(fields) {
2936
- this.contextFields = {
2937
- ...this.contextFields,
2938
- ...fields
2939
- };
2940
- }
2941
- async #createWebSocketContext(connectionParams) {
2942
- let user = null;
2943
- if (this.authService) user = await this.authService.authenticateWebSocketConnection(connectionParams);
2944
- const context = {
2945
- headers: connectionParams,
2946
- db: this.relationalDb,
2947
- ...this.getAdditionalContextFields()
2948
- };
2949
- if (user) context.user = user;
2950
- return context;
2951
- }
2952
- #makeContextFactory() {
2953
- return (request) => {
2954
- const authCtx = getAuthContext(request);
2955
- const headers = {};
2956
- request.headers.forEach((v, k) => {
2957
- headers[k] = v;
2958
- });
2959
- return Promise.resolve({
2960
- headers,
2961
- db: this.relationalDb,
2962
- ...this.getAdditionalContextFields(),
2963
- user: authCtx?.user,
2964
- isAdmin: authCtx ? (addr) => !authCtx.auth_enabled ? true : authCtx.admins.includes(addr.toLowerCase()) : () => true
2965
- });
2966
- };
2967
- }
2968
- #makeWsContextFactory() {
2969
- return (connectionParams) => this.#createWebSocketContext(connectionParams);
2970
- }
2971
- setSupergraph(supergraph, subgraphs) {
2972
- this.subgraphs.set(supergraph, subgraphs);
2973
- const globalSubgraphs = this.subgraphs.get("graphql");
2974
- if (globalSubgraphs) this.subgraphs.set("graphql", [...globalSubgraphs, ...subgraphs]);
2975
- else this.subgraphs.set("graphql", subgraphs);
2976
- return this.updateRouter();
2977
- }
2978
- async shutdown() {
2979
- this.logger.info("Shutting down GraphQL Manager");
2980
- for (const disposer of this.subgraphWsDisposers.values()) await disposer.dispose();
2981
- this.subgraphWsDisposers.clear();
2982
- await this.gatewayAdapter.stop();
2983
- return new Promise((resolve) => {
2984
- this.wsServer.close(() => {
2985
- this.logger.info("WebSocket server closed");
2986
- resolve();
2987
- });
2988
- });
2989
- }
2990
- #getSubgraphPath(subgraph, supergraph) {
2991
- return path.join(subgraph.path ?? "", supergraph, subgraph.name);
2992
- }
2993
- async #setupSubgraphs(subgraphsMap) {
2994
- for (const [supergraph, subgraphs] of subgraphsMap.entries()) for (const subgraph of subgraphs) {
2995
- this.logger.debug(`Setting up subgraph ${subgraph.name}`);
2996
- const subgraphPath = this.#getSubgraphPath(subgraph, supergraph);
2997
- try {
2998
- if (this.subgraphHandlerCache.has(subgraphPath)) continue;
2999
- const schema = createSchema(this.cachedDocumentModels, subgraph.resolvers, subgraph.typeDefs);
3000
- const rawHandler = await this.gatewayAdapter.createHandler(schema, this.#makeContextFactory());
3001
- const fetchHandler = this.#authMiddleware ? this.#authMiddleware(rawHandler) : rawHandler;
3002
- this.subgraphHandlerCache.set(subgraphPath, fetchHandler);
3003
- this.httpAdapter.mount(subgraphPath, fetchHandler);
3004
- if (subgraph.hasSubscriptions) {
3005
- try {
3006
- const wsDisposer = this.gatewayAdapter.attachWebSocket(this.wsServer, schema, this.#makeWsContextFactory());
3007
- this.subgraphWsDisposers.set(subgraphPath, wsDisposer);
3008
- this.logger.debug(`WebSocket subscriptions enabled for ${subgraph.name}`);
3009
- } catch (error) {
3010
- this.logger.error("Failed to setup websocket for subgraph @name at path @path: @error", subgraph.name, subgraphPath, error);
3011
- }
3012
- try {
3013
- this.#setupSSEHandler(schema, subgraphPath);
3014
- this.logger.debug(`SSE subscriptions enabled for ${subgraph.name}`);
3015
- } catch (error) {
3016
- this.logger.error("Failed to setup SSE for subgraph @name at path @path: @error", subgraph.name, subgraphPath, error);
3017
- }
3018
- }
3019
- } catch (error) {
3020
- this.logger.error("Failed to setup subgraph @name at path @path: @error", subgraph.name, subgraphPath, error);
3021
- }
3022
- }
3023
- }
3024
- #getAllSubgraphs() {
3025
- const subgraphsMap = /* @__PURE__ */ new Map();
3026
- for (const [supergraph, subgraphs] of [...this.coreSubgraphsMap.entries(), ...this.subgraphs.entries()]) {
3027
- if (supergraph === "") continue;
3028
- for (const subgraph of subgraphs) {
3029
- const subgraphPath = this.#getSubgraphPath(subgraph, supergraph);
3030
- subgraphsMap.set(subgraphPath, subgraph);
3031
- }
3032
- }
3033
- return subgraphsMap;
3034
- }
3035
- #buildSubgraphSchemaModule(subgraph) {
3036
- return buildSubgraphSchemaModule(this.cachedDocumentModels, subgraph.resolvers, subgraph.typeDefs);
3037
- }
3038
- #getSubgraphDefinitions() {
3039
- const subgraphs = this.#getAllSubgraphs();
3040
- const herokuOrLocal = process.env.HEROKU_APP_DEFAULT_DOMAIN_NAME ? `https://${process.env.HEROKU_APP_DEFAULT_DOMAIN_NAME}` : `http://localhost:${this.port}`;
3041
- return Array.from(subgraphs.entries()).map(([subgraphPath, subgraph]) => ({
3042
- name: subgraphPath.replace("/", ":"),
3043
- typeDefs: this.#buildSubgraphSchemaModule(subgraph).typeDefs,
3044
- url: `${herokuOrLocal}${subgraphPath}`
3045
- }));
3046
- }
3047
- async #createSupergraphGateway() {
3048
- const superGraphPath = path.join(this.path, "graphql");
3049
- const rawHandler = await this.gatewayAdapter.createSupergraphHandler(() => this.#getSubgraphDefinitions(), this.httpServer, this.#makeContextFactory());
3050
- const fetchHandler = this.#authMiddleware ? this.#authMiddleware(rawHandler) : rawHandler;
3051
- this.httpAdapter.mount(superGraphPath, fetchHandler);
3052
- this.#setupSupergraphSSE(superGraphPath);
3053
- if (!this.initialized) {
3054
- this.logger.info(`Registered ${superGraphPath} supergraph `);
3055
- this.initialized = true;
3056
- }
3057
- }
3058
- /**
3059
- * Set up an SSE subscription endpoint at the supergraph level.
3060
- * Merges the schemas of all subscription-enabled subgraphs so that
3061
- * clients can subscribe at /graphql/stream without knowing individual
3062
- * subgraph paths.
3063
- */
3064
- #setupSupergraphSSE(superGraphPath) {
3065
- const allSubgraphs = this.#getAllSubgraphs();
3066
- const modules = Array.from(allSubgraphs.values()).filter((subgraph) => subgraph.hasSubscriptions).map((subgraph) => this.#buildSubgraphSchemaModule(subgraph));
3067
- if (modules.length === 0) return;
3068
- try {
3069
- const mergedSchema = createMergedSchema(modules);
3070
- this.#setupSSEHandler(mergedSchema, superGraphPath);
3071
- this.logger.debug(`SSE subscriptions enabled at supergraph level (merged from ${modules.length} subgraph(s))`);
3072
- } catch (error) {
3073
- this.logger.error("Failed to setup supergraph SSE: @error", error);
3074
- }
3075
- }
3076
- /**
3077
- * Set up a GraphQL-over-SSE handler at `<basePath>/stream`.
3078
- *
3079
- * Clients subscribe by sending a POST with `Accept: text/event-stream`
3080
- * to the `/stream` sub-path. Authentication is handled by the normal
3081
- * Express middleware (Authorization header), unlike WebSocket which
3082
- * needs its own connectionParams-based auth.
3083
- */
3084
- #setupSSEHandler(schema, basePath) {
3085
- const ssePath = basePath + "/stream";
3086
- const rawHandler = createGraphQLSSEHandler({
3087
- schema,
3088
- contextFactory: this.#makeContextFactory()
3089
- });
3090
- const handler = this.#authMiddleware ? this.#authMiddleware(rawHandler) : rawHandler;
3091
- this.httpAdapter.mount(ssePath, handler, { exact: true });
3092
- }
3093
- };
3094
- //#endregion
3095
- //#region src/graphql/packages/schema.graphql
3096
- var schema_default$1 = "# Packages Subgraph Schema\n# Provides operations for runtime package management\n\nscalar DateTime\n\n# Information about an installed package\ntype InstalledPackage {\n # Package name (e.g., \"@powerhousedao/vetra\")\n name: String!\n # Package version if known\n version: String\n # Registry URL where the package was installed from\n registryUrl: String!\n # Timestamp when the package was installed\n installedAt: DateTime!\n # Document type IDs provided by this package\n documentTypes: [String!]!\n}\n\n# Result returned after installing a package\ntype InstallPackageResult {\n # The installed package information\n package: InstalledPackage!\n # Number of document models loaded from the package\n documentModelsLoaded: Int!\n}\n\ntype Query {\n # Get all installed packages\n installedPackages: [InstalledPackage!]!\n\n # Get information about a specific installed package\n installedPackage(name: String!): InstalledPackage\n}\n\ntype Mutation {\n # Install a package from the registry (requires admin access)\n installPackage(\n # Package name (e.g., \"@powerhousedao/vetra\")\n name: String!\n # Registry URL (uses default if not provided)\n registryUrl: String\n ): InstallPackageResult!\n\n # Uninstall a package (requires admin access)\n uninstallPackage(\n # Package name to uninstall\n name: String!\n ): Boolean!\n}\n";
3097
- //#endregion
3098
- //#region src/graphql/packages/resolvers.ts
3099
- function requireAdmin(ctx) {
3100
- if (!(ctx.isAdmin?.(ctx.user?.address ?? "") ?? false)) throw new GraphQLError("Admin access required");
3101
- }
3102
- function formatPackageInfo(info) {
3103
- return {
3104
- name: info.name,
3105
- version: info.version ?? null,
3106
- registryUrl: info.registryUrl,
3107
- installedAt: info.installedAt.toISOString(),
3108
- documentTypes: info.documentTypes
3109
- };
3110
- }
3111
- async function installedPackages(service) {
3112
- return (await service.getInstalledPackages()).map(formatPackageInfo);
3113
- }
3114
- async function installedPackage(service, args) {
3115
- const pkg = await service.getInstalledPackage(args.name);
3116
- return pkg ? formatPackageInfo(pkg) : null;
3117
- }
3118
- async function installPackage(service, args, ctx) {
3119
- requireAdmin(ctx);
3120
- const result = await service.installPackage(args.name, args.registryUrl ?? void 0);
3121
- return {
3122
- package: formatPackageInfo(result.package),
3123
- documentModelsLoaded: result.documentModelsLoaded
3124
- };
3125
- }
3126
- async function uninstallPackage(service, args, ctx) {
3127
- requireAdmin(ctx);
3128
- return service.uninstallPackage(args.name);
3129
- }
3130
- //#endregion
3131
- //#region src/graphql/packages/subgraph.ts
3132
- var PackagesSubgraph = class extends BaseSubgraph {
3133
- logger = new ConsoleLogger(["PackagesSubgraph"]);
3134
- packageManagementService;
3135
- constructor(args) {
3136
- super(args);
3137
- this.packageManagementService = args.packageManagementService;
3138
- this.logger.verbose(`constructor()`);
3139
- }
3140
- name = "packages";
3141
- hasSubscriptions = false;
3142
- typeDefs = gql(schema_default$1);
3143
- resolvers = {
3144
- Query: {
3145
- installedPackages: async () => {
3146
- this.logger.debug("installedPackages");
3147
- try {
3148
- return await installedPackages(this.packageManagementService);
3149
- } catch (error) {
3150
- this.logger.error("Error in installedPackages:", error);
3151
- throw error;
3152
- }
3153
- },
3154
- installedPackage: async (_parent, args) => {
3155
- this.logger.debug("installedPackage", args);
3156
- try {
3157
- return await installedPackage(this.packageManagementService, args);
3158
- } catch (error) {
3159
- this.logger.error("Error in installedPackage:", error);
3160
- throw error;
3161
- }
3162
- }
3163
- },
3164
- Mutation: {
3165
- installPackage: async (_parent, args, ctx) => {
3166
- this.logger.debug("installPackage", args);
3167
- try {
3168
- return await installPackage(this.packageManagementService, args, ctx);
3169
- } catch (error) {
3170
- this.logger.error("Error in installPackage:", error);
3171
- throw error;
3172
- }
3173
- },
3174
- uninstallPackage: async (_parent, args, ctx) => {
3175
- this.logger.debug("uninstallPackage", args);
3176
- try {
3177
- return await uninstallPackage(this.packageManagementService, args, ctx);
3178
- } catch (error) {
3179
- this.logger.error("Error in uninstallPackage:", error);
3180
- throw error;
3181
- }
3182
- }
3183
- }
3184
- };
3185
- onSetup() {
3186
- this.logger.debug("Setting up PackagesSubgraph");
3187
- return Promise.resolve();
3188
- }
3189
- };
3190
- //#endregion
3191
- //#region src/graphql/playground.ts
3192
- /**
3193
- * Pinned CDN versions for GraphiQL playground dependencies.
3194
- * Using pinned versions avoids unpkg.com redirect issues that can
3195
- * trigger CORS errors in the browser.
3196
- */
3197
- const CDN_VERSIONS = {
3198
- react: "18.3.1",
3199
- reactDom: "18.3.1",
3200
- graphiql: "3.8.3",
3201
- pluginExplorer: "4.0.0"
3202
- };
3203
- function renderGraphqlPlayground(url, query, headers = {}) {
3204
- return `<!doctype html>
3205
- <html lang="en">
3206
- <head>
3207
- <title>GraphiQL</title>
3208
- <style>
3209
- body {
3210
- height: 100%;
3211
- margin: 0;
3212
- width: 100%;
3213
- overflow: hidden;
3214
- }
3215
-
3216
- #graphiql {
3217
- height: 100vh;
3218
- }
3219
- </style>
3220
- <script
3221
- src="https://unpkg.com/react@${CDN_VERSIONS.react}/umd/react.production.min.js"
3222
- ><\/script>
3223
- <script
3224
- src="https://unpkg.com/react-dom@${CDN_VERSIONS.reactDom}/umd/react-dom.production.min.js"
3225
- ><\/script>
3226
- <script
3227
- src="https://unpkg.com/graphiql@${CDN_VERSIONS.graphiql}/graphiql.min.js"
3228
- ><\/script>
3229
- <link rel="stylesheet" href="https://unpkg.com/graphiql@${CDN_VERSIONS.graphiql}/graphiql.min.css" />
3230
- <script
3231
- src="https://unpkg.com/@graphiql/plugin-explorer@${CDN_VERSIONS.pluginExplorer}/dist/index.umd.js"
3232
- ><\/script>
3233
- <link
3234
- rel="stylesheet"
3235
- href="https://unpkg.com/@graphiql/plugin-explorer@${CDN_VERSIONS.pluginExplorer}/dist/style.css"
3236
- />
3237
- </head>
3238
-
3239
- <body>
3240
- <div id="graphiql">Loading...</div>
3241
- <script>
3242
- var fetcher = GraphiQL.createFetcher({
3243
- url: '${url}',
3244
- headers: ${JSON.stringify(headers)}
3245
- });
3246
- var defaultQuery = ${query ? `\`${query}\`` : void 0};
3247
-
3248
- if (defaultQuery) {
3249
- var sessionQuery = localStorage.getItem("graphiql:query");
3250
- if (sessionQuery) {
3251
- localStorage.setItem("graphiql:query", defaultQuery);
3252
- }
3253
- }
3254
-
3255
- var explorerPlugin = GraphiQLPluginExplorer.explorerPlugin();
3256
-
3257
- function GraphiQLWithExplorer() {
3258
- return React.createElement(GraphiQL, {
3259
- fetcher: fetcher,
3260
- defaultEditorToolsVisibility: true,
3261
- plugins: [explorerPlugin],
3262
- defaultQuery
3263
- });
3264
- }
3265
-
3266
- const root = ReactDOM.createRoot(document.getElementById('graphiql'));
3267
- root.render(React.createElement(GraphiQLWithExplorer));
3268
- <\/script>
3269
- </body>
3270
- </html>`;
3271
- }
3272
- //#endregion
3273
- //#region src/graphql/reactor/gen/graphql.ts
3274
- let DocumentChangeType = /* @__PURE__ */ function(DocumentChangeType) {
3275
- DocumentChangeType["ChildAdded"] = "CHILD_ADDED";
3276
- DocumentChangeType["ChildRemoved"] = "CHILD_REMOVED";
3277
- DocumentChangeType["Created"] = "CREATED";
3278
- DocumentChangeType["Deleted"] = "DELETED";
3279
- DocumentChangeType["ParentAdded"] = "PARENT_ADDED";
3280
- DocumentChangeType["ParentRemoved"] = "PARENT_REMOVED";
3281
- DocumentChangeType["Updated"] = "UPDATED";
3282
- return DocumentChangeType;
3283
- }({});
3284
- let PropagationMode = /* @__PURE__ */ function(PropagationMode) {
3285
- PropagationMode["Cascade"] = "CASCADE";
3286
- PropagationMode["Orphan"] = "ORPHAN";
3287
- return PropagationMode;
3288
- }({});
3289
- let SyncEnvelopeType = /* @__PURE__ */ function(SyncEnvelopeType) {
3290
- SyncEnvelopeType["Ack"] = "ACK";
3291
- SyncEnvelopeType["Operations"] = "OPERATIONS";
3292
- return SyncEnvelopeType;
3293
- }({});
3294
- const isDefinedNonNullAny = (v) => v !== void 0 && v !== null;
3295
- const definedNonNullAnySchema = z$1.any().refine((v) => isDefinedNonNullAny(v));
3296
- const DocumentChangeTypeSchema = z$1.enum(DocumentChangeType);
3297
- const PropagationModeSchema = z$1.enum(PropagationMode);
3298
- const SyncEnvelopeTypeSchema = z$1.enum(SyncEnvelopeType);
3299
- function ActionContextInputSchema() {
3300
- return z$1.object({ signer: z$1.lazy(() => ReactorSignerInputSchema().nullish()) });
3301
- }
3302
- function ActionInputSchema() {
3303
- return z$1.object({
3304
- attachments: z$1.array(z$1.lazy(() => AttachmentInputSchema())).nullish(),
3305
- context: z$1.lazy(() => ActionContextInputSchema().nullish()),
3306
- id: z$1.string(),
3307
- input: z$1.custom((v) => v != null),
3308
- scope: z$1.string(),
3309
- timestampUtcMs: z$1.string(),
3310
- type: z$1.string()
3311
- });
3312
- }
3313
- function AttachmentInputSchema() {
3314
- return z$1.object({
3315
- data: z$1.string(),
3316
- extension: z$1.string().nullish(),
3317
- fileName: z$1.string().nullish(),
3318
- hash: z$1.string(),
3319
- mimeType: z$1.string()
3320
- });
3321
- }
3322
- function ChannelMetaInputSchema() {
3323
- return z$1.object({ id: z$1.string() });
3324
- }
3325
- function DocumentOperationsFilterInputSchema() {
3326
- return z$1.object({
3327
- actionTypes: z$1.array(z$1.string()).nullish(),
3328
- branch: z$1.string().nullish(),
3329
- scopes: z$1.array(z$1.string()).nullish(),
3330
- sinceRevision: z$1.number().nullish(),
3331
- timestampFrom: z$1.string().nullish(),
3332
- timestampTo: z$1.string().nullish()
3333
- });
3334
- }
3335
- function OperationContextInputSchema() {
3336
- return z$1.object({
3337
- branch: z$1.string(),
3338
- documentId: z$1.string(),
3339
- documentType: z$1.string(),
3340
- ordinal: z$1.number(),
3341
- scope: z$1.string()
3342
- });
3343
- }
3344
- function OperationInputSchema() {
3345
- return z$1.object({
3346
- action: z$1.lazy(() => ActionInputSchema()),
3347
- error: z$1.string().nullish(),
3348
- hash: z$1.string(),
3349
- id: z$1.string().nullish(),
3350
- index: z$1.number(),
3351
- skip: z$1.number(),
3352
- timestampUtcMs: z$1.string()
3353
- });
3354
- }
3355
- function OperationWithContextInputSchema() {
3356
- return z$1.object({
3357
- context: z$1.lazy(() => OperationContextInputSchema()),
3358
- operation: z$1.lazy(() => OperationInputSchema())
3359
- });
3360
- }
3361
- function OperationsFilterInputSchema() {
3362
- return z$1.object({
3363
- actionTypes: z$1.array(z$1.string()).nullish(),
3364
- branch: z$1.string().nullish(),
3365
- documentId: z$1.string(),
3366
- scopes: z$1.array(z$1.string()).nullish(),
3367
- sinceRevision: z$1.number().nullish(),
3368
- timestampFrom: z$1.string().nullish(),
3369
- timestampTo: z$1.string().nullish()
3370
- });
3371
- }
3372
- function PagingInputSchema() {
3373
- return z$1.object({
3374
- cursor: z$1.string().nullish(),
3375
- limit: z$1.number().nullish(),
3376
- offset: z$1.number().nullish()
3377
- });
3378
- }
3379
- function ReactorSignerAppInputSchema() {
3380
- return z$1.object({
3381
- key: z$1.string(),
3382
- name: z$1.string()
3383
- });
3384
- }
3385
- function ReactorSignerInputSchema() {
3386
- return z$1.object({
3387
- app: z$1.lazy(() => ReactorSignerAppInputSchema().nullish()),
3388
- signatures: z$1.array(z$1.string()),
3389
- user: z$1.lazy(() => ReactorSignerUserInputSchema().nullish())
3390
- });
3391
- }
3392
- function ReactorSignerUserInputSchema() {
3393
- return z$1.object({
3394
- address: z$1.string(),
3395
- chainId: z$1.number(),
3396
- networkId: z$1.string()
3397
- });
3398
- }
3399
- function RemoteCursorInputSchema() {
3400
- return z$1.object({
3401
- cursorOrdinal: z$1.number(),
3402
- lastSyncedAtUtcMs: z$1.string().nullish(),
3403
- remoteName: z$1.string()
3404
- });
3405
- }
3406
- function RemoteFilterInputSchema() {
3407
- return z$1.object({
3408
- branch: z$1.string(),
3409
- documentId: z$1.array(z$1.string()),
3410
- scope: z$1.array(z$1.string())
3411
- });
3412
- }
3413
- function SearchFilterInputSchema() {
3414
- return z$1.object({
3415
- identifiers: z$1.array(z$1.string()).nullish(),
3416
- parentId: z$1.string().nullish(),
3417
- type: z$1.string().nullish()
3418
- });
3419
- }
3420
- function SyncEnvelopeInputSchema() {
3421
- return z$1.object({
3422
- channelMeta: z$1.lazy(() => ChannelMetaInputSchema()),
3423
- cursor: z$1.lazy(() => RemoteCursorInputSchema().nullish()),
3424
- dependsOn: z$1.array(z$1.string()).nullish(),
3425
- key: z$1.string().nullish(),
3426
- operations: z$1.array(z$1.lazy(() => OperationWithContextInputSchema())).nullish(),
3427
- type: SyncEnvelopeTypeSchema
3428
- });
3429
- }
3430
- function TouchChannelInputSchema() {
3431
- return z$1.object({
3432
- collectionId: z$1.string(),
3433
- filter: z$1.lazy(() => RemoteFilterInputSchema()),
3434
- id: z$1.string(),
3435
- name: z$1.string(),
3436
- sinceTimestampUtcMs: z$1.string()
3437
- });
3438
- }
3439
- function ViewFilterInputSchema() {
3440
- return z$1.object({
3441
- branch: z$1.string().nullish(),
3442
- scopes: z$1.array(z$1.string()).nullish()
3443
- });
1940
+ function ViewFilterInputSchema() {
1941
+ return z$1.object({
1942
+ branch: z$1.string().nullish(),
1943
+ scopes: z$1.array(z$1.string()).nullish()
1944
+ });
3444
1945
  }
3445
1946
  const PhDocumentFieldsFragmentDoc = gql`
3446
1947
  fragment PHDocumentFields on PHDocument {
@@ -3920,76 +2421,1587 @@ const PushSyncEnvelopesDocument = gql`
3920
2421
  `;
3921
2422
  function getSdk(requester) {
3922
2423
  return {
3923
- GetDocumentModels(variables, options) {
3924
- return requester(GetDocumentModelsDocument, variables, options);
3925
- },
3926
- GetDocument(variables, options) {
3927
- return requester(GetDocumentDocument, variables, options);
3928
- },
3929
- GetDocumentWithOperations(variables, options) {
3930
- return requester(GetDocumentWithOperationsDocument, variables, options);
3931
- },
3932
- GetDocumentChildren(variables, options) {
3933
- return requester(GetDocumentChildrenDocument, variables, options);
3934
- },
3935
- GetDocumentParents(variables, options) {
3936
- return requester(GetDocumentParentsDocument, variables, options);
3937
- },
3938
- FindDocuments(variables, options) {
3939
- return requester(FindDocumentsDocument, variables, options);
3940
- },
3941
- GetDocumentOperations(variables, options) {
3942
- return requester(GetDocumentOperationsDocument, variables, options);
3943
- },
3944
- GetJobStatus(variables, options) {
3945
- return requester(GetJobStatusDocument, variables, options);
3946
- },
3947
- CreateDocument(variables, options) {
3948
- return requester(CreateDocumentDocument, variables, options);
3949
- },
3950
- CreateEmptyDocument(variables, options) {
3951
- return requester(CreateEmptyDocumentDocument, variables, options);
3952
- },
3953
- MutateDocument(variables, options) {
3954
- return requester(MutateDocumentDocument, variables, options);
3955
- },
3956
- MutateDocumentAsync(variables, options) {
3957
- return requester(MutateDocumentAsyncDocument, variables, options);
3958
- },
3959
- RenameDocument(variables, options) {
3960
- return requester(RenameDocumentDocument, variables, options);
3961
- },
3962
- AddChildren(variables, options) {
3963
- return requester(AddChildrenDocument, variables, options);
3964
- },
3965
- RemoveChildren(variables, options) {
3966
- return requester(RemoveChildrenDocument, variables, options);
3967
- },
3968
- MoveChildren(variables, options) {
3969
- return requester(MoveChildrenDocument, variables, options);
3970
- },
3971
- DeleteDocument(variables, options) {
3972
- return requester(DeleteDocumentDocument, variables, options);
3973
- },
3974
- DeleteDocuments(variables, options) {
3975
- return requester(DeleteDocumentsDocument, variables, options);
3976
- },
3977
- DocumentChanges(variables, options) {
3978
- return requester(DocumentChangesDocument, variables, options);
3979
- },
3980
- JobChanges(variables, options) {
3981
- return requester(JobChangesDocument, variables, options);
3982
- },
3983
- PollSyncEnvelopes(variables, options) {
3984
- return requester(PollSyncEnvelopesDocument, variables, options);
3985
- },
3986
- TouchChannel(variables, options) {
3987
- return requester(TouchChannelDocument, variables, options);
2424
+ GetDocumentModels(variables, options) {
2425
+ return requester(GetDocumentModelsDocument, variables, options);
2426
+ },
2427
+ GetDocument(variables, options) {
2428
+ return requester(GetDocumentDocument, variables, options);
2429
+ },
2430
+ GetDocumentWithOperations(variables, options) {
2431
+ return requester(GetDocumentWithOperationsDocument, variables, options);
2432
+ },
2433
+ GetDocumentChildren(variables, options) {
2434
+ return requester(GetDocumentChildrenDocument, variables, options);
2435
+ },
2436
+ GetDocumentParents(variables, options) {
2437
+ return requester(GetDocumentParentsDocument, variables, options);
2438
+ },
2439
+ FindDocuments(variables, options) {
2440
+ return requester(FindDocumentsDocument, variables, options);
2441
+ },
2442
+ GetDocumentOperations(variables, options) {
2443
+ return requester(GetDocumentOperationsDocument, variables, options);
2444
+ },
2445
+ GetJobStatus(variables, options) {
2446
+ return requester(GetJobStatusDocument, variables, options);
2447
+ },
2448
+ CreateDocument(variables, options) {
2449
+ return requester(CreateDocumentDocument, variables, options);
2450
+ },
2451
+ CreateEmptyDocument(variables, options) {
2452
+ return requester(CreateEmptyDocumentDocument, variables, options);
2453
+ },
2454
+ MutateDocument(variables, options) {
2455
+ return requester(MutateDocumentDocument, variables, options);
2456
+ },
2457
+ MutateDocumentAsync(variables, options) {
2458
+ return requester(MutateDocumentAsyncDocument, variables, options);
2459
+ },
2460
+ RenameDocument(variables, options) {
2461
+ return requester(RenameDocumentDocument, variables, options);
2462
+ },
2463
+ AddChildren(variables, options) {
2464
+ return requester(AddChildrenDocument, variables, options);
2465
+ },
2466
+ RemoveChildren(variables, options) {
2467
+ return requester(RemoveChildrenDocument, variables, options);
2468
+ },
2469
+ MoveChildren(variables, options) {
2470
+ return requester(MoveChildrenDocument, variables, options);
2471
+ },
2472
+ DeleteDocument(variables, options) {
2473
+ return requester(DeleteDocumentDocument, variables, options);
2474
+ },
2475
+ DeleteDocuments(variables, options) {
2476
+ return requester(DeleteDocumentsDocument, variables, options);
2477
+ },
2478
+ DocumentChanges(variables, options) {
2479
+ return requester(DocumentChangesDocument, variables, options);
2480
+ },
2481
+ JobChanges(variables, options) {
2482
+ return requester(JobChangesDocument, variables, options);
2483
+ },
2484
+ PollSyncEnvelopes(variables, options) {
2485
+ return requester(PollSyncEnvelopesDocument, variables, options);
2486
+ },
2487
+ TouchChannel(variables, options) {
2488
+ return requester(TouchChannelDocument, variables, options);
2489
+ },
2490
+ PushSyncEnvelopes(variables, options) {
2491
+ return requester(PushSyncEnvelopesDocument, variables, options);
2492
+ }
2493
+ };
2494
+ }
2495
+ //#endregion
2496
+ //#region src/graphql/reactor/adapters.ts
2497
+ /**
2498
+ * Converts a PagedResults from ReactorClient to the GraphQL DocumentModelResultPage format
2499
+ */
2500
+ function toDocumentModelResultPage(result) {
2501
+ const models = result.results.map((module) => module.documentModel);
2502
+ return {
2503
+ cursor: result.nextCursor ?? null,
2504
+ hasNextPage: !!result.nextCursor,
2505
+ hasPreviousPage: !!result.options.cursor,
2506
+ items: models.map(toGqlDocumentModelState),
2507
+ totalCount: result.results.length
2508
+ };
2509
+ }
2510
+ /**
2511
+ * Gets the namespace from a DocumentModelGlobalState
2512
+ */
2513
+ function getNamespace(model) {
2514
+ return model.global.name.split("/")[0];
2515
+ }
2516
+ /**
2517
+ * Converts a DocumentModelGlobalState from ReactorClient to GraphQL format
2518
+ */
2519
+ function toGqlDocumentModelState(model) {
2520
+ const global = model.global;
2521
+ const specification = global.specifications.length > 0 ? global.specifications[0] : {};
2522
+ const namespace = getNamespace(model);
2523
+ return {
2524
+ id: global.id,
2525
+ name: global.name,
2526
+ namespace,
2527
+ specification,
2528
+ version: null
2529
+ };
2530
+ }
2531
+ /**
2532
+ * Converts a PagedResults of PHDocument to GraphQL PhDocumentResultPage format
2533
+ */
2534
+ function toPhDocumentResultPage(result) {
2535
+ return {
2536
+ cursor: result.nextCursor ?? null,
2537
+ hasNextPage: !!result.nextCursor,
2538
+ hasPreviousPage: !!result.options.cursor,
2539
+ items: result.results.map(toGqlPhDocument),
2540
+ totalCount: result.totalCount ?? result.results.length
2541
+ };
2542
+ }
2543
+ /**
2544
+ * Converts a PHDocument from ReactorClient to GraphQL PhDocument format
2545
+ */
2546
+ function toGqlPhDocument(doc) {
2547
+ const revisionsList = Object.entries(doc.header.revision).map(([scope, revision]) => ({
2548
+ scope,
2549
+ revision
2550
+ }));
2551
+ return {
2552
+ id: doc.header.id,
2553
+ name: doc.header.name,
2554
+ documentType: doc.header.documentType,
2555
+ slug: doc.header.slug,
2556
+ preferredEditor: doc.header.meta?.preferredEditor ?? null,
2557
+ createdAtUtcIso: doc.header.createdAtUtcIso,
2558
+ lastModifiedAtUtcIso: doc.header.lastModifiedAtUtcIso,
2559
+ revisionsList,
2560
+ state: doc.state
2561
+ };
2562
+ }
2563
+ /**
2564
+ * Converts JobInfo from ReactorClient to GraphQL format
2565
+ */
2566
+ function toGqlJobInfo(job) {
2567
+ return {
2568
+ id: job.id,
2569
+ status: job.status,
2570
+ createdAt: job.createdAtUtcIso,
2571
+ completedAt: job.completedAtUtcIso ?? null,
2572
+ error: job.error?.message ?? null,
2573
+ result: job.result ?? null
2574
+ };
2575
+ }
2576
+ /**
2577
+ * Handles nullable/undefined conversion for GraphQL InputMaybe types
2578
+ */
2579
+ function fromInputMaybe(value) {
2580
+ return value === null ? void 0 : value;
2581
+ }
2582
+ /**
2583
+ * Maps GraphQL PropagationMode enum to reactor PropagationMode enum.
2584
+ * The GQL enum uses uppercase values ("CASCADE") while the reactor
2585
+ * enum uses lowercase values ("cascade").
2586
+ */
2587
+ function toReactorPropagationMode(gqlMode) {
2588
+ if (gqlMode == null) return;
2589
+ switch (gqlMode) {
2590
+ case PropagationMode.Cascade: return PropagationMode$1.Cascade;
2591
+ case PropagationMode.Orphan: return PropagationMode$1.None;
2592
+ }
2593
+ }
2594
+ /**
2595
+ * Converts readonly arrays to mutable arrays for ReactorClient
2596
+ */
2597
+ function toMutableArray(arr) {
2598
+ return arr ? [...arr] : void 0;
2599
+ }
2600
+ /**
2601
+ * Validates that a JSONObject represents a valid Action structure
2602
+ */
2603
+ function validateActionStructure(obj) {
2604
+ if (!obj || typeof obj !== "object") return false;
2605
+ const action = obj;
2606
+ if (typeof action.type !== "string" || !action.type) return false;
2607
+ if (typeof action.scope !== "string" || !action.scope) return false;
2608
+ if (!("input" in action)) return false;
2609
+ return true;
2610
+ }
2611
+ /**
2612
+ * Converts a JSONObject to an Action, validating basic structure
2613
+ */
2614
+ function jsonObjectToAction(obj) {
2615
+ if (!validateActionStructure(obj)) throw new GraphQLError("Invalid action structure. Actions must have: type (string), scope (string), and input (any)");
2616
+ return obj;
2617
+ }
2618
+ /**
2619
+ * Validates a list of actions by converting them from JSON and checking structure
2620
+ */
2621
+ function validateActions(actions) {
2622
+ const convertedActions = [];
2623
+ for (let i = 0; i < actions.length; i++) try {
2624
+ convertedActions.push(jsonObjectToAction(actions[i]));
2625
+ } catch (error) {
2626
+ throw new GraphQLError(`Action at index ${i}: ${error instanceof Error ? error.message : "Invalid action structure"}`);
2627
+ }
2628
+ return convertedActions;
2629
+ }
2630
+ /**
2631
+ * Transforms an operation to serialize signatures from tuples to strings for GraphQL compatibility.
2632
+ */
2633
+ function serializeOperationForGraphQL(operation) {
2634
+ const signer = operation.action.context?.signer;
2635
+ if (!signer?.signatures) return operation;
2636
+ return {
2637
+ ...operation,
2638
+ action: {
2639
+ ...operation.action,
2640
+ context: {
2641
+ ...operation.action.context,
2642
+ signer: {
2643
+ ...signer,
2644
+ signatures: signer.signatures.map((sig) => Array.isArray(sig) ? sig.join(", ") : sig)
2645
+ }
2646
+ }
2647
+ }
2648
+ };
2649
+ }
2650
+ /**
2651
+ * Converts a PagedResults of Operation to GraphQL ReactorOperationResultPage format
2652
+ */
2653
+ function toOperationResultPage(result) {
2654
+ return {
2655
+ cursor: result.nextCursor ?? null,
2656
+ hasNextPage: !!result.nextCursor,
2657
+ hasPreviousPage: !!result.options.cursor && result.options.cursor !== "0",
2658
+ items: result.results.map(serializeOperationForGraphQL),
2659
+ totalCount: result.results.length
2660
+ };
2661
+ }
2662
+ function toGqlDocumentChangeEvent(event) {
2663
+ const mappedType = {
2664
+ created: "CREATED",
2665
+ deleted: "DELETED",
2666
+ updated: "UPDATED",
2667
+ parent_added: "PARENT_ADDED",
2668
+ parent_removed: "PARENT_REMOVED",
2669
+ child_added: "CHILD_ADDED",
2670
+ child_removed: "CHILD_REMOVED"
2671
+ }[event.type];
2672
+ if (!mappedType) throw new GraphQLError(`Unknown document change type: ${event.type}`);
2673
+ return {
2674
+ type: mappedType,
2675
+ documents: event.documents.map(toGqlPhDocument),
2676
+ context: event.context ? {
2677
+ parentId: event.context.parentId ?? null,
2678
+ childId: event.context.childId ?? null
2679
+ } : null
2680
+ };
2681
+ }
2682
+ function matchesSearchFilter(event, search) {
2683
+ if (search.type) {
2684
+ if (!event.documents.some((doc) => doc.header.documentType === search.type)) return false;
2685
+ }
2686
+ if (search.parentId) {
2687
+ if (!event.context?.parentId || event.context.parentId !== search.parentId) return false;
2688
+ }
2689
+ return true;
2690
+ }
2691
+ function matchesJobFilter(payload, args) {
2692
+ return payload.jobId === args.jobId;
2693
+ }
2694
+ //#endregion
2695
+ //#region src/graphql/reactor/resolvers.ts
2696
+ const DRIVE_DOCUMENT_TYPE = "powerhouse/document-drive";
2697
+ async function documentModels(reactorClient, args) {
2698
+ const namespace = fromInputMaybe(args.namespace);
2699
+ let paging;
2700
+ if (args.paging) {
2701
+ const cursor = fromInputMaybe(args.paging.cursor);
2702
+ const limit = fromInputMaybe(args.paging.limit);
2703
+ if (cursor || limit) paging = {
2704
+ cursor: cursor || "",
2705
+ limit: limit || 10
2706
+ };
2707
+ }
2708
+ let result;
2709
+ try {
2710
+ result = await reactorClient.getDocumentModelModules(namespace, paging);
2711
+ } catch (error) {
2712
+ throw new GraphQLError(`Failed to fetch document models: ${error instanceof Error ? error.message : "Unknown error"}`);
2713
+ }
2714
+ try {
2715
+ return toDocumentModelResultPage(result);
2716
+ } catch (error) {
2717
+ throw new GraphQLError(`Failed to convert document models to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
2718
+ }
2719
+ }
2720
+ async function document(reactorClient, args) {
2721
+ let view;
2722
+ if (args.view) view = {
2723
+ branch: fromInputMaybe(args.view.branch),
2724
+ scopes: toMutableArray(fromInputMaybe(args.view.scopes))
2725
+ };
2726
+ let result;
2727
+ try {
2728
+ result = await reactorClient.get(args.identifier, view);
2729
+ } catch (error) {
2730
+ throw new GraphQLError(`Failed to fetch document: ${error instanceof Error ? error.message : "Unknown error"}`);
2731
+ }
2732
+ let children;
2733
+ try {
2734
+ children = await reactorClient.getChildren(args.identifier, view);
2735
+ } catch (error) {
2736
+ throw new GraphQLError(`Failed to fetch children: ${error instanceof Error ? error.message : "Unknown error"}`);
2737
+ }
2738
+ try {
2739
+ return {
2740
+ document: toGqlPhDocument(result),
2741
+ childIds: children.results.map((child) => child.header.id)
2742
+ };
2743
+ } catch (error) {
2744
+ throw new GraphQLError(`Failed to convert document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
2745
+ }
2746
+ }
2747
+ async function documentChildren(reactorClient, args) {
2748
+ let view;
2749
+ if (args.view) view = {
2750
+ branch: fromInputMaybe(args.view.branch),
2751
+ scopes: toMutableArray(fromInputMaybe(args.view.scopes))
2752
+ };
2753
+ let paging;
2754
+ if (args.paging) {
2755
+ const cursor = fromInputMaybe(args.paging.cursor);
2756
+ const limit = fromInputMaybe(args.paging.limit);
2757
+ if (cursor || limit) paging = {
2758
+ cursor: cursor || "",
2759
+ limit: limit || 10
2760
+ };
2761
+ }
2762
+ let result;
2763
+ try {
2764
+ result = await reactorClient.getChildren(args.parentIdentifier, view, paging);
2765
+ } catch (error) {
2766
+ throw new GraphQLError(`Failed to fetch document children: ${error instanceof Error ? error.message : "Unknown error"}`);
2767
+ }
2768
+ try {
2769
+ return toPhDocumentResultPage(result);
2770
+ } catch (error) {
2771
+ throw new GraphQLError(`Failed to convert document children to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
2772
+ }
2773
+ }
2774
+ async function documentParents(reactorClient, args) {
2775
+ let view;
2776
+ if (args.view) view = {
2777
+ branch: fromInputMaybe(args.view.branch),
2778
+ scopes: toMutableArray(fromInputMaybe(args.view.scopes))
2779
+ };
2780
+ let paging;
2781
+ if (args.paging) {
2782
+ const cursor = fromInputMaybe(args.paging.cursor);
2783
+ const limit = fromInputMaybe(args.paging.limit);
2784
+ if (cursor || limit) paging = {
2785
+ cursor: cursor || "",
2786
+ limit: limit || 10
2787
+ };
2788
+ }
2789
+ let result;
2790
+ try {
2791
+ result = await reactorClient.getParents(args.childIdentifier, view, paging);
2792
+ } catch (error) {
2793
+ throw new GraphQLError(`Failed to fetch document parents: ${error instanceof Error ? error.message : "Unknown error"}`);
2794
+ }
2795
+ try {
2796
+ return toPhDocumentResultPage(result);
2797
+ } catch (error) {
2798
+ throw new GraphQLError(`Failed to convert document parents to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
2799
+ }
2800
+ }
2801
+ async function findDocuments(reactorClient, args) {
2802
+ let view;
2803
+ if (args.view) view = {
2804
+ branch: fromInputMaybe(args.view.branch),
2805
+ scopes: toMutableArray(fromInputMaybe(args.view.scopes))
2806
+ };
2807
+ let paging;
2808
+ if (args.paging) {
2809
+ const cursor = fromInputMaybe(args.paging.cursor);
2810
+ const limit = fromInputMaybe(args.paging.limit);
2811
+ if (cursor || limit) paging = {
2812
+ cursor: cursor || "",
2813
+ limit: limit || 10
2814
+ };
2815
+ }
2816
+ const search = {
2817
+ type: fromInputMaybe(args.search.type),
2818
+ parentId: fromInputMaybe(args.search.parentId)
2819
+ };
2820
+ let result;
2821
+ try {
2822
+ result = await reactorClient.find(search, view, paging);
2823
+ } catch (error) {
2824
+ throw new GraphQLError(`Failed to find documents: ${error instanceof Error ? error.message : "Unknown error"}`);
2825
+ }
2826
+ try {
2827
+ return toPhDocumentResultPage(result);
2828
+ } catch (error) {
2829
+ throw new GraphQLError(`Failed to convert documents to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
2830
+ }
2831
+ }
2832
+ async function jobStatus(reactorClient, args) {
2833
+ let result;
2834
+ try {
2835
+ result = await reactorClient.getJobStatus(args.jobId);
2836
+ } catch (error) {
2837
+ throw new GraphQLError(`Failed to fetch job status: ${error instanceof Error ? error.message : "Unknown error"}`);
2838
+ }
2839
+ try {
2840
+ return toGqlJobInfo(result);
2841
+ } catch (error) {
2842
+ throw new GraphQLError(`Failed to convert job status to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
2843
+ }
2844
+ }
2845
+ async function documentOperations(reactorClient, args) {
2846
+ let view;
2847
+ const branch = fromInputMaybe(args.filter.branch);
2848
+ const scopes = toMutableArray(fromInputMaybe(args.filter.scopes));
2849
+ if (branch || scopes) view = {
2850
+ branch,
2851
+ scopes
2852
+ };
2853
+ const actionTypes = toMutableArray(fromInputMaybe(args.filter.actionTypes));
2854
+ const sinceRevision = fromInputMaybe(args.filter.sinceRevision);
2855
+ const timestampFrom = fromInputMaybe(args.filter.timestampFrom);
2856
+ const timestampTo = fromInputMaybe(args.filter.timestampTo);
2857
+ let operationFilter;
2858
+ if (actionTypes && actionTypes.length > 0 || sinceRevision !== void 0 || timestampFrom || timestampTo) operationFilter = {
2859
+ actionTypes: actionTypes && actionTypes.length > 0 ? actionTypes : void 0,
2860
+ sinceRevision,
2861
+ timestampFrom: timestampFrom || void 0,
2862
+ timestampTo: timestampTo || void 0
2863
+ };
2864
+ let paging;
2865
+ if (args.paging) {
2866
+ const cursor = fromInputMaybe(args.paging.cursor);
2867
+ const limit = fromInputMaybe(args.paging.limit);
2868
+ if (cursor || limit) paging = {
2869
+ cursor: cursor || "",
2870
+ limit: limit || 100
2871
+ };
2872
+ }
2873
+ let result;
2874
+ try {
2875
+ result = await reactorClient.getOperations(args.filter.documentId, view, operationFilter, paging);
2876
+ } catch (error) {
2877
+ throw new GraphQLError(`Failed to fetch document operations: ${error instanceof Error ? error.message : "Unknown error"}`);
2878
+ }
2879
+ try {
2880
+ return toOperationResultPage(result);
2881
+ } catch (error) {
2882
+ throw new GraphQLError(`Failed to convert operations to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
2883
+ }
2884
+ }
2885
+ async function createDocument(reactorClient, args) {
2886
+ if (!args.document || typeof args.document !== "object") throw new GraphQLError("Invalid document: must be an object");
2887
+ const document = args.document;
2888
+ if (!document.header || typeof document.header !== "object") throw new GraphQLError("Invalid document: missing or invalid header");
2889
+ const parentIdentifier = fromInputMaybe(args.parentIdentifier);
2890
+ let result;
2891
+ try {
2892
+ if (parentIdentifier) if ((await reactorClient.get(parentIdentifier)).header.documentType === DRIVE_DOCUMENT_TYPE) result = await reactorClient.createDocumentInDrive(parentIdentifier, document);
2893
+ else result = await reactorClient.create(document, parentIdentifier);
2894
+ else result = await reactorClient.create(document);
2895
+ } catch (error) {
2896
+ throw new GraphQLError(`Failed to create document: ${error instanceof Error ? error.message : "Unknown error"}`);
2897
+ }
2898
+ try {
2899
+ return toGqlPhDocument(result);
2900
+ } catch (error) {
2901
+ throw new GraphQLError(`Failed to convert created document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
2902
+ }
2903
+ }
2904
+ async function createEmptyDocument(reactorClient, args) {
2905
+ const parentIdentifier = fromInputMaybe(args.parentIdentifier);
2906
+ const name = fromInputMaybe(args.name);
2907
+ let result;
2908
+ try {
2909
+ if (parentIdentifier) if ((await reactorClient.get(parentIdentifier)).header.documentType === DRIVE_DOCUMENT_TYPE) {
2910
+ const document = (await reactorClient.getDocumentModelModule(args.documentType)).utils.createDocument();
2911
+ if (name) document.header.name = name;
2912
+ result = await reactorClient.createDocumentInDrive(parentIdentifier, document);
2913
+ } else result = await reactorClient.createEmpty(args.documentType, { parentIdentifier });
2914
+ else result = await reactorClient.createEmpty(args.documentType, {});
2915
+ } catch (error) {
2916
+ throw new GraphQLError(`Failed to create empty document: ${error instanceof Error ? error.message : "Unknown error"}`);
2917
+ }
2918
+ try {
2919
+ return toGqlPhDocument(result);
2920
+ } catch (error) {
2921
+ throw new GraphQLError(`Failed to convert created document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
2922
+ }
2923
+ }
2924
+ async function createDocumentWithInitialState(reactorClient, args) {
2925
+ const parentIdentifier = fromInputMaybe(args.parentIdentifier);
2926
+ const name = fromInputMaybe(args.name);
2927
+ const slug = fromInputMaybe(args.slug);
2928
+ const preferredEditor = fromInputMaybe(args.preferredEditor);
2929
+ let module;
2930
+ try {
2931
+ module = await reactorClient.getDocumentModelModule(args.documentType);
2932
+ } catch (error) {
2933
+ throw new GraphQLError(`Document model not found for type ${args.documentType}: ${error instanceof Error ? error.message : "Unknown error"}`);
2934
+ }
2935
+ const document = module.utils.createDocument();
2936
+ const allowedScopes = new Set(Object.keys(module.documentModel.global.specifications.at(-1)?.state ?? {}));
2937
+ const state = document.state;
2938
+ for (const [scope, scopeState] of Object.entries(args.initialState)) if (allowedScopes.has(scope) && scope in state) state[scope] = {
2939
+ ...state[scope],
2940
+ ...scopeState
2941
+ };
2942
+ if (name) document.header.name = name;
2943
+ if (slug) document.header.slug = slug;
2944
+ if (preferredEditor) document.header.meta = {
2945
+ ...document.header.meta,
2946
+ preferredEditor
2947
+ };
2948
+ let result;
2949
+ if (parentIdentifier) {
2950
+ let parent;
2951
+ try {
2952
+ parent = await reactorClient.get(parentIdentifier);
2953
+ } catch (error) {
2954
+ throw new GraphQLError(`Parent document not found: ${error instanceof Error ? error.message : "Unknown error"}`);
2955
+ }
2956
+ if (parent.header.documentType === DRIVE_DOCUMENT_TYPE) try {
2957
+ result = await reactorClient.createDocumentInDrive(parentIdentifier, document);
2958
+ } catch (error) {
2959
+ throw new GraphQLError(`Failed to create document in drive: ${error instanceof Error ? error.message : "Unknown error"}`);
2960
+ }
2961
+ else try {
2962
+ result = await reactorClient.create(document, parentIdentifier);
2963
+ } catch (error) {
2964
+ throw new GraphQLError(`Failed to create document with parent: ${error instanceof Error ? error.message : "Unknown error"}`);
2965
+ }
2966
+ } else try {
2967
+ result = await reactorClient.create(document);
2968
+ } catch (error) {
2969
+ throw new GraphQLError(`Failed to create document: ${error instanceof Error ? error.message : "Unknown error"}`);
2970
+ }
2971
+ try {
2972
+ return toGqlPhDocument(result);
2973
+ } catch (error) {
2974
+ throw new GraphQLError(`Failed to convert created document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
2975
+ }
2976
+ }
2977
+ async function mutateDocument(reactorClient, args) {
2978
+ let validatedActions;
2979
+ try {
2980
+ validatedActions = validateActions(args.actions);
2981
+ } catch (error) {
2982
+ if (error instanceof GraphQLError) throw error;
2983
+ throw new GraphQLError(`Action validation failed: ${error instanceof Error ? error.message : "Unknown error"}`);
2984
+ }
2985
+ const branch = args.view?.branch ?? "main";
2986
+ let result;
2987
+ try {
2988
+ result = await reactorClient.execute(args.documentIdentifier, branch, validatedActions);
2989
+ } catch (error) {
2990
+ throw new GraphQLError(`Failed to mutate document: ${error instanceof Error ? error.message : "Unknown error"}`);
2991
+ }
2992
+ try {
2993
+ return toGqlPhDocument(result);
2994
+ } catch (error) {
2995
+ throw new GraphQLError(`Failed to convert mutated document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
2996
+ }
2997
+ }
2998
+ async function mutateDocumentAsync(reactorClient, args) {
2999
+ let validatedActions;
3000
+ try {
3001
+ validatedActions = validateActions(args.actions);
3002
+ } catch (error) {
3003
+ if (error instanceof GraphQLError) throw error;
3004
+ throw new GraphQLError(`Action validation failed: ${error instanceof Error ? error.message : "Unknown error"}`);
3005
+ }
3006
+ const branch = args.view?.branch ?? "main";
3007
+ let result;
3008
+ try {
3009
+ result = await reactorClient.executeAsync(args.documentIdentifier, branch, validatedActions);
3010
+ } catch (error) {
3011
+ throw new GraphQLError(`Failed to submit document mutation: ${error instanceof Error ? error.message : "Unknown error"}`);
3012
+ }
3013
+ return result.id;
3014
+ }
3015
+ async function renameDocument(reactorClient, args, signal) {
3016
+ const branch = fromInputMaybe(args.branch);
3017
+ let result;
3018
+ try {
3019
+ result = await reactorClient.rename(args.documentIdentifier, args.name, branch, signal);
3020
+ } catch (error) {
3021
+ throw new GraphQLError(`Failed to rename document: ${error instanceof Error ? error.message : "Unknown error"}`);
3022
+ }
3023
+ try {
3024
+ return toGqlPhDocument(result);
3025
+ } catch (error) {
3026
+ throw new GraphQLError(`Failed to convert renamed document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
3027
+ }
3028
+ }
3029
+ async function addChildren(reactorClient, args) {
3030
+ const branch = fromInputMaybe(args.branch);
3031
+ const documentIdentifiers = [...args.documentIdentifiers];
3032
+ let result;
3033
+ try {
3034
+ result = await reactorClient.addChildren(args.parentIdentifier, documentIdentifiers, branch);
3035
+ } catch (error) {
3036
+ throw new GraphQLError(`Failed to add children: ${error instanceof Error ? error.message : "Unknown error"}`);
3037
+ }
3038
+ try {
3039
+ return toGqlPhDocument(result);
3040
+ } catch (error) {
3041
+ throw new GraphQLError(`Failed to convert document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
3042
+ }
3043
+ }
3044
+ async function removeChildren(reactorClient, args) {
3045
+ const branch = fromInputMaybe(args.branch);
3046
+ const documentIdentifiers = [...args.documentIdentifiers];
3047
+ let result;
3048
+ try {
3049
+ result = await reactorClient.removeChildren(args.parentIdentifier, documentIdentifiers, branch);
3050
+ } catch (error) {
3051
+ throw new GraphQLError(`Failed to remove children: ${error instanceof Error ? error.message : "Unknown error"}`);
3052
+ }
3053
+ try {
3054
+ return toGqlPhDocument(result);
3055
+ } catch (error) {
3056
+ throw new GraphQLError(`Failed to convert document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
3057
+ }
3058
+ }
3059
+ async function moveChildren(reactorClient, args) {
3060
+ const branch = fromInputMaybe(args.branch);
3061
+ const documentIdentifiers = [...args.documentIdentifiers];
3062
+ let result;
3063
+ try {
3064
+ result = await reactorClient.moveChildren(args.sourceParentIdentifier, args.targetParentIdentifier, documentIdentifiers, branch);
3065
+ } catch (error) {
3066
+ throw new GraphQLError(`Failed to move children: ${error instanceof Error ? error.message : "Unknown error"}`);
3067
+ }
3068
+ try {
3069
+ return {
3070
+ source: toGqlPhDocument(result.source),
3071
+ target: toGqlPhDocument(result.target)
3072
+ };
3073
+ } catch (error) {
3074
+ throw new GraphQLError(`Failed to convert documents to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
3075
+ }
3076
+ }
3077
+ async function deleteDocument(reactorClient, args) {
3078
+ const propagate = toReactorPropagationMode(args.propagate);
3079
+ try {
3080
+ await reactorClient.deleteDocument(args.identifier, propagate);
3081
+ return true;
3082
+ } catch (error) {
3083
+ throw new GraphQLError(`Failed to delete document: ${error instanceof Error ? error.message : "Unknown error"}`);
3084
+ }
3085
+ }
3086
+ async function deleteDocuments(reactorClient, args) {
3087
+ const propagate = toReactorPropagationMode(args.propagate);
3088
+ const identifiers = [...args.identifiers];
3089
+ try {
3090
+ await reactorClient.deleteDocuments(identifiers, propagate);
3091
+ return true;
3092
+ } catch (error) {
3093
+ throw new GraphQLError(`Failed to delete documents: ${error instanceof Error ? error.message : "Unknown error"}`);
3094
+ }
3095
+ }
3096
+ async function touchChannel(syncManager, args) {
3097
+ try {
3098
+ return {
3099
+ success: true,
3100
+ ackOrdinal: syncManager.getById(args.input.id).channel.inbox.ackOrdinal
3101
+ };
3102
+ } catch {}
3103
+ const filter = {
3104
+ documentId: [...args.input.filter.documentId],
3105
+ scope: [...args.input.filter.scope],
3106
+ branch: args.input.filter.branch
3107
+ };
3108
+ const options = { sinceTimestampUtcMs: args.input.sinceTimestampUtcMs };
3109
+ try {
3110
+ await syncManager.add(args.input.name, args.input.collectionId, {
3111
+ type: "polling",
3112
+ parameters: {}
3113
+ }, filter, options, args.input.id);
3114
+ } catch (error) {
3115
+ throw new GraphQLError(`Failed to create channel: ${error instanceof Error ? error.message : "Unknown error"}`);
3116
+ }
3117
+ return {
3118
+ success: true,
3119
+ ackOrdinal: 0
3120
+ };
3121
+ }
3122
+ /**
3123
+ * Polls the switchboard for new sync envelopes and acknowledges previously
3124
+ * received operations.
3125
+ *
3126
+ * Ordinal frames of reference:
3127
+ * - `outboxAck` / `outboxLatest`: switchboard's ordinals (used to trim/filter
3128
+ * the switchboard's outbox)
3129
+ * - `ackOrdinal` in the response: the pushing client's ordinals (highest
3130
+ * client ordinal the switchboard has successfully applied, so the client
3131
+ * can trim its own outbox)
3132
+ */
3133
+ function pollSyncEnvelopes(syncManager, args) {
3134
+ let remote;
3135
+ try {
3136
+ remote = syncManager.getById(args.channelId);
3137
+ } catch (error) {
3138
+ throw new GraphQLError(`Channel not found: ${error instanceof Error ? error.message : "Unknown error"}`);
3139
+ }
3140
+ const deadLetters = remote.channel.deadLetter.items.map((syncOp) => ({
3141
+ documentId: syncOp.documentId,
3142
+ error: syncOp.error?.message ?? "Unknown error",
3143
+ jobId: syncOp.jobId,
3144
+ branch: syncOp.branch,
3145
+ scopes: syncOp.scopes,
3146
+ operationCount: syncOp.operations.length
3147
+ }));
3148
+ if (args.outboxAck > 0) trimMailboxFromAckOrdinal(remote.channel.outbox, args.outboxAck);
3149
+ let operations = remote.channel.outbox.items;
3150
+ operations = operations.filter((syncOp) => {
3151
+ let maxOrdinal = 0;
3152
+ for (const op of syncOp.operations) maxOrdinal = Math.max(maxOrdinal, op.context.ordinal);
3153
+ if (maxOrdinal > args.outboxLatest) return true;
3154
+ return false;
3155
+ });
3156
+ if (operations.length === 0) return {
3157
+ envelopes: [],
3158
+ ackOrdinal: remote.channel.inbox.ackOrdinal,
3159
+ deadLetters
3160
+ };
3161
+ let maxOrdinal = args.outboxLatest;
3162
+ for (const syncOp of operations) for (const op of syncOp.operations) {
3163
+ const opOrdinal = op.context.ordinal;
3164
+ if (opOrdinal > maxOrdinal) maxOrdinal = opOrdinal;
3165
+ }
3166
+ return {
3167
+ envelopes: sortEnvelopesByFirstOperationTimestamp(operations.map((syncOp) => ({
3168
+ type: "OPERATIONS",
3169
+ channelMeta: { id: args.channelId },
3170
+ operations: syncOp.operations.map((op) => ({
3171
+ operation: serializeOperationForGraphQL(op.operation),
3172
+ context: op.context
3173
+ })),
3174
+ cursor: {
3175
+ remoteName: remote.name,
3176
+ cursorOrdinal: maxOrdinal,
3177
+ lastSyncedAtUtcMs: Date.now().toString()
3178
+ },
3179
+ key: syncOp.jobId || void 0,
3180
+ dependsOn: syncOp.jobDependencies.filter(Boolean).length > 0 ? syncOp.jobDependencies.filter(Boolean) : void 0
3181
+ }))),
3182
+ ackOrdinal: remote.channel.inbox.ackOrdinal,
3183
+ deadLetters
3184
+ };
3185
+ }
3186
+ /**
3187
+ * Receives sync envelopes pushed by a client and adds them to the
3188
+ * appropriate channel inboxes.
3189
+ *
3190
+ * The `ordinal` in each operation's context is the client's local ordinal.
3191
+ * It must be preserved because the inbox mailbox tracks applied ordinals
3192
+ * and returns the highest one as `ackOrdinal` in pollSyncEnvelopes.
3193
+ */
3194
+ function pushSyncEnvelopes(syncManager, args) {
3195
+ const sortedEnvelopes = sortEnvelopesByFirstOperationTimestamp(args.envelopes);
3196
+ const remoteSyncOps = /* @__PURE__ */ new Map();
3197
+ for (const envelope of sortedEnvelopes) {
3198
+ let remote;
3199
+ try {
3200
+ remote = syncManager.getById(envelope.channelMeta.id);
3201
+ } catch (error) {
3202
+ throw new GraphQLError(`Channel not found: ${error instanceof Error ? error.message : "Unknown error"}`);
3203
+ }
3204
+ if (!envelope.operations || envelope.operations.length === 0) continue;
3205
+ const syncOps = envelopesToSyncOperations(envelope, remote.name);
3206
+ if (!remoteSyncOps.has(remote)) remoteSyncOps.set(remote, []);
3207
+ remoteSyncOps.get(remote).push(...syncOps);
3208
+ }
3209
+ for (const [remote, syncOps] of remoteSyncOps) {
3210
+ const consolidated = consolidateSyncOperations(syncOps);
3211
+ const validKeys = new Set(consolidated.map((op) => op.jobId).filter(Boolean));
3212
+ for (const syncOp of consolidated) syncOp.jobDependencies = syncOp.jobDependencies.filter((dep) => validKeys.has(dep));
3213
+ remote.channel.inbox.add(...consolidated);
3214
+ }
3215
+ return Promise.resolve(true);
3216
+ }
3217
+ //#endregion
3218
+ //#region src/graphql/document-model-subgraph.ts
3219
+ /**
3220
+ * New document model subgraph that uses reactorClient instead of legacy reactor.
3221
+ * This class auto-generates GraphQL queries and mutations for a document model.
3222
+ */
3223
+ var DocumentModelSubgraph = class extends BaseSubgraph {
3224
+ documentModel;
3225
+ constructor(documentModel, args) {
3226
+ super(args);
3227
+ this.documentModel = documentModel;
3228
+ this.name = kebabCase(documentModel.documentModel.global.name);
3229
+ this.typeDefs = generateDocumentModelSchema(this.documentModel.documentModel.global, { useNewApi: true });
3230
+ this.resolvers = this.generateResolvers();
3231
+ }
3232
+ /** Returns the typed query resolvers for this document model. */
3233
+ get queryResolvers() {
3234
+ const documentName = getDocumentModelSchemaName(this.documentModel.documentModel.global);
3235
+ return this.resolvers[`${documentName}Queries`];
3236
+ }
3237
+ /** Returns the typed mutation resolvers for this document model. */
3238
+ get mutationResolvers() {
3239
+ const documentName = getDocumentModelSchemaName(this.documentModel.documentModel.global);
3240
+ return this.resolvers[`${documentName}Mutations`];
3241
+ }
3242
+ /**
3243
+ * Generate __resolveType functions for union types found in the document model schema.
3244
+ * Parses the state schema to find union definitions and their member types,
3245
+ * then uses unique field presence to discriminate between member types at runtime.
3246
+ */
3247
+ generateUnionResolvers() {
3248
+ const documentName = getDocumentModelSchemaName(this.documentModel.documentModel.global);
3249
+ const specification = this.documentModel.documentModel.global.specifications.at(-1);
3250
+ if (!specification) return {};
3251
+ const fullSchema = `${specification.state.global.schema ?? ""}\n${specification.state.local.schema ?? ""}`;
3252
+ if (!fullSchema.trim()) return {};
3253
+ let ast;
3254
+ try {
3255
+ ast = parse(fullSchema);
3256
+ } catch {
3257
+ return {};
3258
+ }
3259
+ const objectFieldsMap = /* @__PURE__ */ new Map();
3260
+ for (const def of ast.definitions) if (def.kind === Kind.OBJECT_TYPE_DEFINITION) objectFieldsMap.set(def.name.value, def.fields?.map((f) => f.name.value) ?? []);
3261
+ const resolvers = {};
3262
+ for (const def of ast.definitions) {
3263
+ if (def.kind !== Kind.UNION_TYPE_DEFINITION) continue;
3264
+ const unionName = def.name.value;
3265
+ const memberTypes = def.types?.map((t) => t.name.value) ?? [];
3266
+ if (memberTypes.length === 0) continue;
3267
+ const uniqueFields = {};
3268
+ for (const memberType of memberTypes) {
3269
+ const ownFields = objectFieldsMap.get(memberType) ?? [];
3270
+ const otherFields = new Set(memberTypes.filter((t) => t !== memberType).flatMap((t) => objectFieldsMap.get(t) ?? []));
3271
+ uniqueFields[memberType] = ownFields.filter((f) => !otherFields.has(f));
3272
+ }
3273
+ const prefixedUnionName = `${documentName}_${unionName}`;
3274
+ resolvers[prefixedUnionName] = { __resolveType: (obj) => {
3275
+ for (const memberType of memberTypes) {
3276
+ const fields = uniqueFields[memberType] ?? [];
3277
+ if (fields.length > 0 && fields.some((f) => f in obj)) return `${documentName}_${memberType}`;
3278
+ }
3279
+ return `${documentName}_${memberTypes[0]}`;
3280
+ } };
3281
+ }
3282
+ return resolvers;
3283
+ }
3284
+ /**
3285
+ * Generate resolvers for this document model using reactorClient
3286
+ * Uses flat queries (not nested) consistent with ReactorSubgraph patterns
3287
+ */
3288
+ generateResolvers() {
3289
+ const documentType = this.documentModel.documentModel.global.id;
3290
+ const documentName = getDocumentModelSchemaName(this.documentModel.documentModel.global);
3291
+ const operations = this.documentModel.documentModel.global.specifications.at(-1)?.modules.flatMap((module) => module.operations.filter((op) => op.name)) ?? [];
3292
+ return {
3293
+ ...this.generateUnionResolvers(),
3294
+ Query: { [documentName]: () => ({}) },
3295
+ [`${documentName}Queries`]: {
3296
+ document: async (_, args, ctx) => {
3297
+ const { identifier, view } = args;
3298
+ if (!identifier) throw new GraphQLError("Document identifier is required");
3299
+ const result = await document(this.reactorClient, {
3300
+ identifier,
3301
+ view
3302
+ });
3303
+ if (result.document.documentType !== documentType) throw new GraphQLError(`Document with id ${identifier} is not of type ${documentType}`);
3304
+ await this.assertCanRead(result.document.id, ctx);
3305
+ return result;
3306
+ },
3307
+ documents: async (_, args, ctx) => {
3308
+ const { paging } = args;
3309
+ const result = await findDocuments(this.reactorClient, {
3310
+ search: { type: documentType },
3311
+ paging
3312
+ });
3313
+ if (!this.hasGlobalAdminAccess(ctx) && this.documentPermissionService) {
3314
+ const filteredItems = [];
3315
+ for (const item of result.items) if (await this.canReadDocument(item.id, ctx)) filteredItems.push(item);
3316
+ return {
3317
+ ...result,
3318
+ items: filteredItems,
3319
+ totalCount: filteredItems.length
3320
+ };
3321
+ }
3322
+ return result;
3323
+ },
3324
+ findDocuments: async (_, args, ctx) => {
3325
+ const { search, view, paging } = args;
3326
+ const result = await findDocuments(this.reactorClient, {
3327
+ search: {
3328
+ type: documentType,
3329
+ parentId: search?.parentId
3330
+ },
3331
+ view,
3332
+ paging
3333
+ });
3334
+ if (!this.hasGlobalAdminAccess(ctx) && this.documentPermissionService) {
3335
+ const filteredItems = [];
3336
+ for (const item of result.items) if (await this.canReadDocument(item.id, ctx)) filteredItems.push(item);
3337
+ return {
3338
+ ...result,
3339
+ items: filteredItems,
3340
+ totalCount: filteredItems.length
3341
+ };
3342
+ }
3343
+ return result;
3344
+ },
3345
+ documentChildren: async (_, args, ctx) => {
3346
+ const { parentIdentifier, view, paging } = args;
3347
+ await this.assertCanRead(parentIdentifier, ctx);
3348
+ const result = await documentChildren(this.reactorClient, {
3349
+ parentIdentifier,
3350
+ view,
3351
+ paging
3352
+ });
3353
+ const filteredItems = result.items.filter((item) => item.documentType === documentType);
3354
+ return {
3355
+ ...result,
3356
+ items: filteredItems,
3357
+ totalCount: filteredItems.length
3358
+ };
3359
+ },
3360
+ documentParents: async (_, args, ctx) => {
3361
+ const { childIdentifier, view, paging } = args;
3362
+ await this.assertCanRead(childIdentifier, ctx);
3363
+ return documentParents(this.reactorClient, {
3364
+ childIdentifier,
3365
+ view,
3366
+ paging
3367
+ });
3368
+ }
3369
+ },
3370
+ Mutation: { [documentName]: () => ({}) },
3371
+ [`${documentName}Mutations`]: {
3372
+ createDocument: async (_, args, ctx) => {
3373
+ const { parentIdentifier, name, slug, preferredEditor, initialState } = args;
3374
+ if (parentIdentifier) await this.assertCanWrite(parentIdentifier, ctx);
3375
+ else if (this.authorizationService) {
3376
+ if (!ctx.user?.address) throw new GraphQLError("Forbidden: authentication required to create documents");
3377
+ } else if (!this.hasGlobalAdminAccess(ctx)) throw new GraphQLError("Forbidden: insufficient permissions to create documents");
3378
+ let createdDoc;
3379
+ if (initialState || preferredEditor) createdDoc = await createDocumentWithInitialState(this.reactorClient, {
3380
+ documentType,
3381
+ parentIdentifier,
3382
+ name,
3383
+ slug,
3384
+ preferredEditor,
3385
+ initialState: initialState ?? {}
3386
+ });
3387
+ else createdDoc = await createEmptyDocument(this.reactorClient, {
3388
+ documentType,
3389
+ parentIdentifier,
3390
+ name
3391
+ });
3392
+ if (this.authorizationService && ctx.user?.address && createdDoc?.id) await this.documentPermissionService?.initializeDocumentProtection(createdDoc.id, ctx.user.address, this.authorizationService.config.defaultProtection);
3393
+ if (!initialState && !preferredEditor && name && createdDoc.name !== name) return toGqlPhDocument(await this.reactorClient.execute(createdDoc.id, "main", [setName(name)]));
3394
+ return createdDoc;
3395
+ },
3396
+ createEmptyDocument: async (_, args, ctx) => {
3397
+ const { parentIdentifier } = args;
3398
+ if (parentIdentifier) await this.assertCanWrite(parentIdentifier, ctx);
3399
+ else if (this.authorizationService) {
3400
+ if (!ctx.user?.address) throw new GraphQLError("Forbidden: authentication required to create documents");
3401
+ } else if (!this.hasGlobalAdminAccess(ctx)) throw new GraphQLError("Forbidden: insufficient permissions to create documents");
3402
+ const result = await createEmptyDocument(this.reactorClient, {
3403
+ documentType,
3404
+ parentIdentifier
3405
+ });
3406
+ if (this.authorizationService && ctx.user?.address && result?.id) await this.documentPermissionService?.initializeDocumentProtection(result.id, ctx.user.address, this.authorizationService.config.defaultProtection);
3407
+ return result;
3408
+ },
3409
+ ...operations.reduce((mutations, op) => {
3410
+ mutations[camelCase(op.name)] = async (_, args, ctx) => {
3411
+ const { docId, input } = args;
3412
+ if (!this.authorizationService) await this.assertCanWrite(docId, ctx);
3413
+ await this.assertCanExecuteOperation(docId, op.name, ctx);
3414
+ if ((await this.reactorClient.get(docId)).header.documentType !== documentType) throw new GraphQLError(`Document with id ${docId} is not of type ${documentType}`);
3415
+ const action = this.documentModel.actions[camelCase(op.name)];
3416
+ if (!action) throw new GraphQLError(`Action ${op.name} not found`);
3417
+ try {
3418
+ return toGqlPhDocument(await this.reactorClient.execute(docId, "main", [action(input)]));
3419
+ } catch (error) {
3420
+ throw new GraphQLError(error instanceof Error ? error.message : `Failed to ${op.name}`);
3421
+ }
3422
+ };
3423
+ mutations[`${camelCase(op.name)}Async`] = async (_, args, ctx) => {
3424
+ const { docId, input } = args;
3425
+ if (!this.authorizationService) await this.assertCanWrite(docId, ctx);
3426
+ await this.assertCanExecuteOperation(docId, op.name, ctx);
3427
+ if ((await this.reactorClient.get(docId)).header.documentType !== documentType) throw new GraphQLError(`Document with id ${docId} is not of type ${documentType}`);
3428
+ const action = this.documentModel.actions[camelCase(op.name)];
3429
+ if (!action) throw new GraphQLError(`Action ${op.name} not found`);
3430
+ try {
3431
+ return (await this.reactorClient.executeAsync(docId, "main", [action(input)])).id;
3432
+ } catch (error) {
3433
+ throw new GraphQLError(error instanceof Error ? error.message : `Failed to ${op.name}`);
3434
+ }
3435
+ };
3436
+ return mutations;
3437
+ }, {})
3438
+ }
3439
+ };
3440
+ }
3441
+ };
3442
+ //#endregion
3443
+ //#region src/graphql/sse.ts
3444
+ /**
3445
+ * Create a Fetch-API-compatible SSE handler for GraphQL subscriptions
3446
+ * using the graphql-sse library (graphql-sse protocol).
3447
+ *
3448
+ * This runs alongside the existing WebSocket (graphql-ws) transport
3449
+ * so clients can choose either protocol.
3450
+ *
3451
+ * The returned handler is a standard FetchHandler: (req: Request) => Promise<Response>.
3452
+ * It can be mounted via httpAdapter.mount() like any other handler, and auth
3453
+ * flows through the WeakMap pattern (authFetchMiddleware populates the context
3454
+ * before this handler is called).
3455
+ *
3456
+ * Clients connect via "distinct connections mode" (POST with
3457
+ * `Accept: text/event-stream`). Single-connection mode is disabled
3458
+ * because it adds token-management complexity with no benefit here.
3459
+ */
3460
+ function createGraphQLSSEHandler(options) {
3461
+ const { schema, contextFactory } = options;
3462
+ return createHandler({
3463
+ schema,
3464
+ authenticate: () => null,
3465
+ context: (req) => contextFactory(req.raw)
3466
+ });
3467
+ }
3468
+ //#endregion
3469
+ //#region src/graphql/graphql-manager.ts
3470
+ const DOCUMENT_MODELS_TO_EXCLUDE = [];
3471
+ /**
3472
+ * Check if a document model has any operations with valid schemas.
3473
+ * Document models without valid operation schemas cannot generate valid subgraph schemas.
3474
+ */
3475
+ function hasOperationSchemas(documentModel) {
3476
+ const specification = documentModel.documentModel.global.specifications.at(-1);
3477
+ if (!specification) return false;
3478
+ const hasValidSchema = (schema) => schema && /\b(input|type|enum|union|interface)\s+\w+/.test(schema);
3479
+ return specification.modules.some((module) => module.operations.some((op) => hasValidSchema(op.schema)));
3480
+ }
3481
+ /**
3482
+ * Filter document models to keep only the latest version of each unique document model.
3483
+ */
3484
+ function filterLatestDocumentModelVersions(documentModels) {
3485
+ const latestByName = /* @__PURE__ */ new Map();
3486
+ for (const documentModel of documentModels) {
3487
+ const name = documentModel.documentModel.global.name;
3488
+ const existing = latestByName.get(name);
3489
+ if (!existing) {
3490
+ latestByName.set(name, documentModel);
3491
+ continue;
3492
+ }
3493
+ if ((documentModel.documentModel.global.specifications.at(-1)?.version ?? 0) > (existing.documentModel.global.specifications.at(-1)?.version ?? 0)) latestByName.set(name, documentModel);
3494
+ }
3495
+ return Array.from(latestByName.values());
3496
+ }
3497
+ const DefaultFeatureFlags = { enableDocumentModelSubgraphs: true };
3498
+ var GraphQLManager = class {
3499
+ initialized = false;
3500
+ coreSubgraphsMap = /* @__PURE__ */ new Map();
3501
+ contextFields = {};
3502
+ subgraphs = /* @__PURE__ */ new Map();
3503
+ authService = null;
3504
+ subgraphWsDisposers = /* @__PURE__ */ new Map();
3505
+ #authMiddleware;
3506
+ /** Cached document models for schema generation - updated on init and regenerate */
3507
+ cachedDocumentModels = [];
3508
+ subgraphHandlerCache = /* @__PURE__ */ new Map();
3509
+ constructor(path, httpServer, wsServer, reactorClient, relationalDb, analyticsStore, syncManager, logger, httpAdapter, gatewayAdapter, authConfig, documentPermissionService, featureFlags = DefaultFeatureFlags, port = 4001, authorizationService) {
3510
+ this.path = path;
3511
+ this.httpServer = httpServer;
3512
+ this.wsServer = wsServer;
3513
+ this.reactorClient = reactorClient;
3514
+ this.relationalDb = relationalDb;
3515
+ this.analyticsStore = analyticsStore;
3516
+ this.syncManager = syncManager;
3517
+ this.logger = logger;
3518
+ this.httpAdapter = httpAdapter;
3519
+ this.gatewayAdapter = gatewayAdapter;
3520
+ this.authConfig = authConfig;
3521
+ this.documentPermissionService = documentPermissionService;
3522
+ this.featureFlags = featureFlags;
3523
+ this.port = port;
3524
+ this.authorizationService = authorizationService;
3525
+ if (this.authConfig) this.authService = new AuthService(this.authConfig);
3526
+ }
3527
+ async init(coreSubgraphs, authMiddleware) {
3528
+ this.#authMiddleware = authMiddleware;
3529
+ this.logger.debug(`Initializing Subgraph Manager...`);
3530
+ const models = (await this.reactorClient.getDocumentModelModules()).results;
3531
+ this.cachedDocumentModels = models;
3532
+ if (!models.find((it) => it.documentModel.global.name === "DocumentDrive")) throw new Error("DocumentDrive model required");
3533
+ await this.gatewayAdapter.start(this.httpServer);
3534
+ this.httpAdapter.setupMiddleware({ bodyLimit: "50mb" });
3535
+ const driveRoutePath = path.join(this.path, "d/:drive");
3536
+ const driveMatcher = match(driveRoutePath);
3537
+ this.httpAdapter.mount(driveRoutePath, async (request) => {
3538
+ const url = new URL(request.url);
3539
+ const matched = driveMatcher(url.pathname);
3540
+ const driveIdOrSlug = matched ? matched.params.drive : void 0;
3541
+ if (!driveIdOrSlug) return Response.json({ error: "Drive ID or slug is required" }, { status: 400 });
3542
+ try {
3543
+ const driveDoc = await this.reactorClient.get(driveIdOrSlug);
3544
+ const graphqlEndpoint = `${(request.headers.get("x-forwarded-proto") ?? url.protocol.replace(":", "")) + ":"}//${request.headers.get("host") ?? ""}${this.path === "/" ? "" : this.path}/graphql/r`;
3545
+ return Response.json({
3546
+ id: driveDoc.header.id,
3547
+ slug: driveDoc.header.slug,
3548
+ meta: driveDoc.header.meta,
3549
+ name: driveDoc.state.global.name,
3550
+ icon: driveDoc.state.global.icon ?? void 0,
3551
+ ...graphqlEndpoint && { graphqlEndpoint }
3552
+ });
3553
+ } catch (error) {
3554
+ this.logger.debug(`Drive not found: ${driveIdOrSlug}`, error);
3555
+ return Response.json({ error: "Drive not found" }, { status: 404 });
3556
+ }
3557
+ });
3558
+ this.logger.info(`Registered REST endpoint: GET ${driveRoutePath}`);
3559
+ await this.#setupCoreSubgraphs("graphql", coreSubgraphs);
3560
+ if (this.featureFlags.enableDocumentModelSubgraphs) await this.#setupDocumentModelSubgraphs("graphql", models);
3561
+ await this.#createSupergraphGateway();
3562
+ return this.updateRouter();
3563
+ }
3564
+ /**
3565
+ * Regenerate document model subgraphs when models are dynamically loaded.
3566
+ * Fetches current modules from reactor client (source of truth).
3567
+ */
3568
+ async regenerateDocumentModelSubgraphs() {
3569
+ if (!this.featureFlags.enableDocumentModelSubgraphs) return;
3570
+ try {
3571
+ const models = (await this.reactorClient.getDocumentModelModules()).results;
3572
+ this.cachedDocumentModels = models;
3573
+ await this.#setupDocumentModelSubgraphs("graphql", models);
3574
+ await this.updateRouter();
3575
+ this.logger.info("Regenerated document model subgraphs with @count models", models.length);
3576
+ } catch (error) {
3577
+ this.logger.error("Failed to regenerate document model subgraphs", error);
3578
+ throw error;
3579
+ }
3580
+ }
3581
+ async #setupCoreSubgraphs(supergraph, coreSubgraphs) {
3582
+ for (const subgraph of coreSubgraphs) try {
3583
+ await this.registerSubgraph(subgraph, supergraph, true);
3584
+ } catch (error) {
3585
+ this.logger.error(`Failed to setup core subgraph ${subgraph.name}`, error);
3586
+ }
3587
+ return this.#setupSubgraphs(this.coreSubgraphsMap);
3588
+ }
3589
+ async #setupDocumentModelSubgraphs(supergraph, documentModels) {
3590
+ const latestDocumentModels = filterLatestDocumentModelVersions(documentModels);
3591
+ for (const documentModel of latestDocumentModels) {
3592
+ if (DOCUMENT_MODELS_TO_EXCLUDE.includes(documentModel.documentModel.global.id)) continue;
3593
+ if (!hasOperationSchemas(documentModel)) continue;
3594
+ try {
3595
+ const subgraphInstance = new DocumentModelSubgraph(documentModel, {
3596
+ relationalDb: this.relationalDb,
3597
+ analyticsStore: this.analyticsStore,
3598
+ reactorClient: this.reactorClient,
3599
+ graphqlManager: this,
3600
+ syncManager: this.syncManager,
3601
+ path: this.path,
3602
+ documentPermissionService: this.documentPermissionService,
3603
+ authorizationService: this.authorizationService
3604
+ });
3605
+ await this.#addSubgraphInstance(subgraphInstance, supergraph, false);
3606
+ } catch (error) {
3607
+ this.logger.error(`Failed to setup document model subgraph for ${documentModel.documentModel.global.id}`, error instanceof Error ? error.message : error);
3608
+ this.logger.debug("@error", error);
3609
+ }
3610
+ }
3611
+ }
3612
+ async #addSubgraphInstance(subgraphInstance, supergraph = "", core = false) {
3613
+ const subgraphsMap = core ? this.coreSubgraphsMap : this.subgraphs;
3614
+ const subgraphs = subgraphsMap.get(supergraph) ?? [];
3615
+ const existingSubgraph = subgraphs.find((it) => it.name === subgraphInstance.name);
3616
+ if (existingSubgraph) {
3617
+ this.logger.debug(`Skipping duplicate subgraph: ${subgraphInstance.name}`);
3618
+ return existingSubgraph;
3619
+ }
3620
+ await subgraphInstance.onSetup?.();
3621
+ subgraphs.push(subgraphInstance);
3622
+ subgraphsMap.set(supergraph, subgraphs);
3623
+ if (supergraph !== "" && supergraph !== "graphql") subgraphsMap.get("graphql")?.push(subgraphInstance);
3624
+ this.logger.info(`Registered ${this.path.endsWith("/") ? this.path : this.path + "/"}${supergraph ? supergraph + "/" : ""}${subgraphInstance.name} subgraph.`);
3625
+ return subgraphInstance;
3626
+ }
3627
+ /**
3628
+ * Register a pre-constructed subgraph instance.
3629
+ * Use this when you need to pass custom dependencies to a subgraph.
3630
+ */
3631
+ async registerSubgraphInstance(subgraphInstance, supergraph = "", core = false) {
3632
+ return this.#addSubgraphInstance(subgraphInstance, supergraph, core);
3633
+ }
3634
+ /**
3635
+ * Get the base path used for subgraph registration.
3636
+ */
3637
+ getBasePath() {
3638
+ return this.path;
3639
+ }
3640
+ async registerSubgraph(subgraph, supergraph = "", core = false) {
3641
+ const subgraphInstance = new subgraph({
3642
+ relationalDb: this.relationalDb,
3643
+ analyticsStore: this.analyticsStore,
3644
+ reactorClient: this.reactorClient,
3645
+ graphqlManager: this,
3646
+ syncManager: this.syncManager,
3647
+ path: this.path,
3648
+ documentPermissionService: this.documentPermissionService,
3649
+ authorizationService: this.authorizationService
3650
+ });
3651
+ return this.#addSubgraphInstance(subgraphInstance, supergraph, core);
3652
+ }
3653
+ updateRouter = debounce(this._updateRouter.bind(this), 1e3);
3654
+ async _updateRouter() {
3655
+ this.logger.debug("Updating router");
3656
+ await this.#setupSubgraphs(this.subgraphs);
3657
+ try {
3658
+ await this.gatewayAdapter.updateSupergraph();
3659
+ this.logger.debug("Updated Apollo Gateway supergraph");
3660
+ } catch (error) {
3661
+ this.logger.error("Failed to update Apollo Gateway supergraph", error);
3662
+ }
3663
+ const superGraphPath = path.join(this.path, "graphql");
3664
+ this.#setupSupergraphSSE(superGraphPath);
3665
+ }
3666
+ getAdditionalContextFields = () => {
3667
+ return this.contextFields;
3668
+ };
3669
+ setAdditionalContextFields(fields) {
3670
+ this.contextFields = {
3671
+ ...this.contextFields,
3672
+ ...fields
3673
+ };
3674
+ }
3675
+ async #createWebSocketContext(connectionParams) {
3676
+ let user = null;
3677
+ if (this.authService) user = await this.authService.authenticateWebSocketConnection(connectionParams);
3678
+ const context = {
3679
+ headers: connectionParams,
3680
+ db: this.relationalDb,
3681
+ ...this.getAdditionalContextFields()
3682
+ };
3683
+ if (user) context.user = user;
3684
+ return context;
3685
+ }
3686
+ #makeContextFactory() {
3687
+ return (request) => {
3688
+ const authCtx = getAuthContext(request);
3689
+ const headers = {};
3690
+ request.headers.forEach((v, k) => {
3691
+ headers[k] = v;
3692
+ });
3693
+ return Promise.resolve({
3694
+ headers,
3695
+ db: this.relationalDb,
3696
+ ...this.getAdditionalContextFields(),
3697
+ user: authCtx?.user,
3698
+ isAdmin: authCtx ? (addr) => !authCtx.auth_enabled ? true : authCtx.admins.includes(addr.toLowerCase()) : () => true
3699
+ });
3700
+ };
3701
+ }
3702
+ #makeWsContextFactory() {
3703
+ return (connectionParams) => this.#createWebSocketContext(connectionParams);
3704
+ }
3705
+ setSupergraph(supergraph, subgraphs) {
3706
+ this.subgraphs.set(supergraph, subgraphs);
3707
+ const globalSubgraphs = this.subgraphs.get("graphql");
3708
+ if (globalSubgraphs) this.subgraphs.set("graphql", [...globalSubgraphs, ...subgraphs]);
3709
+ else this.subgraphs.set("graphql", subgraphs);
3710
+ return this.updateRouter();
3711
+ }
3712
+ async shutdown() {
3713
+ this.logger.info("Shutting down GraphQL Manager");
3714
+ for (const disposer of this.subgraphWsDisposers.values()) await disposer.dispose();
3715
+ this.subgraphWsDisposers.clear();
3716
+ await this.gatewayAdapter.stop();
3717
+ return new Promise((resolve) => {
3718
+ this.wsServer.close(() => {
3719
+ this.logger.info("WebSocket server closed");
3720
+ resolve();
3721
+ });
3722
+ });
3723
+ }
3724
+ #getSubgraphPath(subgraph, supergraph) {
3725
+ return path.join(subgraph.path ?? "", supergraph, subgraph.name);
3726
+ }
3727
+ async #setupSubgraphs(subgraphsMap) {
3728
+ for (const [supergraph, subgraphs] of subgraphsMap.entries()) for (const subgraph of subgraphs) {
3729
+ this.logger.debug(`Setting up subgraph ${subgraph.name}`);
3730
+ const subgraphPath = this.#getSubgraphPath(subgraph, supergraph);
3731
+ try {
3732
+ if (this.subgraphHandlerCache.has(subgraphPath)) continue;
3733
+ const schema = createSchema(this.cachedDocumentModels, subgraph.resolvers, subgraph.typeDefs);
3734
+ const rawHandler = await this.gatewayAdapter.createHandler(schema, this.#makeContextFactory());
3735
+ const fetchHandler = this.#authMiddleware ? this.#authMiddleware(rawHandler) : rawHandler;
3736
+ this.subgraphHandlerCache.set(subgraphPath, fetchHandler);
3737
+ this.httpAdapter.mount(subgraphPath, fetchHandler);
3738
+ if (subgraph.hasSubscriptions) {
3739
+ try {
3740
+ const wsDisposer = this.gatewayAdapter.attachWebSocket(this.wsServer, schema, this.#makeWsContextFactory());
3741
+ this.subgraphWsDisposers.set(subgraphPath, wsDisposer);
3742
+ this.logger.debug(`WebSocket subscriptions enabled for ${subgraph.name}`);
3743
+ } catch (error) {
3744
+ this.logger.error("Failed to setup websocket for subgraph @name at path @path: @error", subgraph.name, subgraphPath, error);
3745
+ }
3746
+ try {
3747
+ this.#setupSSEHandler(schema, subgraphPath);
3748
+ this.logger.debug(`SSE subscriptions enabled for ${subgraph.name}`);
3749
+ } catch (error) {
3750
+ this.logger.error("Failed to setup SSE for subgraph @name at path @path: @error", subgraph.name, subgraphPath, error);
3751
+ }
3752
+ }
3753
+ } catch (error) {
3754
+ this.logger.error("Failed to setup subgraph @name at path @path: @error", subgraph.name, subgraphPath, error);
3755
+ }
3756
+ }
3757
+ }
3758
+ #getAllSubgraphs() {
3759
+ const subgraphsMap = /* @__PURE__ */ new Map();
3760
+ for (const [supergraph, subgraphs] of [...this.coreSubgraphsMap.entries(), ...this.subgraphs.entries()]) {
3761
+ if (supergraph === "") continue;
3762
+ for (const subgraph of subgraphs) {
3763
+ const subgraphPath = this.#getSubgraphPath(subgraph, supergraph);
3764
+ subgraphsMap.set(subgraphPath, subgraph);
3765
+ }
3766
+ }
3767
+ return subgraphsMap;
3768
+ }
3769
+ #buildSubgraphSchemaModule(subgraph) {
3770
+ return buildSubgraphSchemaModule(this.cachedDocumentModels, subgraph.resolvers, subgraph.typeDefs);
3771
+ }
3772
+ #getSubgraphDefinitions() {
3773
+ const subgraphs = this.#getAllSubgraphs();
3774
+ const herokuOrLocal = process.env.HEROKU_APP_DEFAULT_DOMAIN_NAME ? `https://${process.env.HEROKU_APP_DEFAULT_DOMAIN_NAME}` : `http://localhost:${this.port}`;
3775
+ return Array.from(subgraphs.entries()).map(([subgraphPath, subgraph]) => ({
3776
+ name: subgraphPath.replace("/", ":"),
3777
+ typeDefs: this.#buildSubgraphSchemaModule(subgraph).typeDefs,
3778
+ url: `${herokuOrLocal}${subgraphPath}`
3779
+ }));
3780
+ }
3781
+ async #createSupergraphGateway() {
3782
+ const superGraphPath = path.join(this.path, "graphql");
3783
+ const rawHandler = await this.gatewayAdapter.createSupergraphHandler(() => this.#getSubgraphDefinitions(), this.httpServer, this.#makeContextFactory());
3784
+ const fetchHandler = this.#authMiddleware ? this.#authMiddleware(rawHandler) : rawHandler;
3785
+ this.httpAdapter.mount(superGraphPath, fetchHandler);
3786
+ this.#setupSupergraphSSE(superGraphPath);
3787
+ if (!this.initialized) {
3788
+ this.logger.info(`Registered ${superGraphPath} supergraph `);
3789
+ this.initialized = true;
3790
+ }
3791
+ }
3792
+ /**
3793
+ * Set up an SSE subscription endpoint at the supergraph level.
3794
+ * Merges the schemas of all subscription-enabled subgraphs so that
3795
+ * clients can subscribe at /graphql/stream without knowing individual
3796
+ * subgraph paths.
3797
+ */
3798
+ #setupSupergraphSSE(superGraphPath) {
3799
+ const allSubgraphs = this.#getAllSubgraphs();
3800
+ const modules = Array.from(allSubgraphs.values()).filter((subgraph) => subgraph.hasSubscriptions).map((subgraph) => this.#buildSubgraphSchemaModule(subgraph));
3801
+ if (modules.length === 0) return;
3802
+ try {
3803
+ const mergedSchema = createMergedSchema(modules);
3804
+ this.#setupSSEHandler(mergedSchema, superGraphPath);
3805
+ this.logger.debug(`SSE subscriptions enabled at supergraph level (merged from ${modules.length} subgraph(s))`);
3806
+ } catch (error) {
3807
+ this.logger.error("Failed to setup supergraph SSE: @error", error);
3808
+ }
3809
+ }
3810
+ /**
3811
+ * Set up a GraphQL-over-SSE handler at `<basePath>/stream`.
3812
+ *
3813
+ * Clients subscribe by sending a POST with `Accept: text/event-stream`
3814
+ * to the `/stream` sub-path. Authentication is handled by the normal
3815
+ * Express middleware (Authorization header), unlike WebSocket which
3816
+ * needs its own connectionParams-based auth.
3817
+ */
3818
+ #setupSSEHandler(schema, basePath) {
3819
+ const ssePath = basePath + "/stream";
3820
+ const rawHandler = createGraphQLSSEHandler({
3821
+ schema,
3822
+ contextFactory: this.#makeContextFactory()
3823
+ });
3824
+ const handler = this.#authMiddleware ? this.#authMiddleware(rawHandler) : rawHandler;
3825
+ this.httpAdapter.mount(ssePath, handler, { exact: true });
3826
+ }
3827
+ };
3828
+ //#endregion
3829
+ //#region src/graphql/packages/schema.graphql
3830
+ var schema_default$1 = "# Packages Subgraph Schema\n# Provides operations for runtime package management\n\nscalar DateTime\n\n# Information about an installed package\ntype InstalledPackage {\n # Package name (e.g., \"@powerhousedao/vetra\")\n name: String!\n # Package version if known\n version: String\n # Registry URL where the package was installed from\n registryUrl: String!\n # Timestamp when the package was installed\n installedAt: DateTime!\n # Document type IDs provided by this package\n documentTypes: [String!]!\n}\n\n# Result returned after installing a package\ntype InstallPackageResult {\n # The installed package information\n package: InstalledPackage!\n # Number of document models loaded from the package\n documentModelsLoaded: Int!\n}\n\ntype Query {\n # Get all installed packages\n installedPackages: [InstalledPackage!]!\n\n # Get information about a specific installed package\n installedPackage(name: String!): InstalledPackage\n}\n\ntype Mutation {\n # Install a package from the registry (requires admin access)\n installPackage(\n # Package name (e.g., \"@powerhousedao/vetra\")\n name: String!\n # Registry URL (uses default if not provided)\n registryUrl: String\n ): InstallPackageResult!\n\n # Uninstall a package (requires admin access)\n uninstallPackage(\n # Package name to uninstall\n name: String!\n ): Boolean!\n}\n";
3831
+ //#endregion
3832
+ //#region src/graphql/packages/resolvers.ts
3833
+ function requireAdmin(ctx) {
3834
+ if (!(ctx.isAdmin?.(ctx.user?.address ?? "") ?? false)) throw new GraphQLError("Admin access required");
3835
+ }
3836
+ function formatPackageInfo(info) {
3837
+ return {
3838
+ name: info.name,
3839
+ version: info.version ?? null,
3840
+ registryUrl: info.registryUrl,
3841
+ installedAt: info.installedAt.toISOString(),
3842
+ documentTypes: info.documentTypes
3843
+ };
3844
+ }
3845
+ async function installedPackages(service) {
3846
+ return (await service.getInstalledPackages()).map(formatPackageInfo);
3847
+ }
3848
+ async function installedPackage(service, args) {
3849
+ const pkg = await service.getInstalledPackage(args.name);
3850
+ return pkg ? formatPackageInfo(pkg) : null;
3851
+ }
3852
+ async function installPackage(service, args, ctx) {
3853
+ requireAdmin(ctx);
3854
+ const result = await service.installPackage(args.name, args.registryUrl ?? void 0);
3855
+ return {
3856
+ package: formatPackageInfo(result.package),
3857
+ documentModelsLoaded: result.documentModelsLoaded
3858
+ };
3859
+ }
3860
+ async function uninstallPackage(service, args, ctx) {
3861
+ requireAdmin(ctx);
3862
+ return service.uninstallPackage(args.name);
3863
+ }
3864
+ //#endregion
3865
+ //#region src/graphql/packages/subgraph.ts
3866
+ var PackagesSubgraph = class extends BaseSubgraph {
3867
+ logger = new ConsoleLogger(["PackagesSubgraph"]);
3868
+ packageManagementService;
3869
+ constructor(args) {
3870
+ super(args);
3871
+ this.packageManagementService = args.packageManagementService;
3872
+ this.logger.verbose(`constructor()`);
3873
+ }
3874
+ name = "packages";
3875
+ hasSubscriptions = false;
3876
+ typeDefs = gql(schema_default$1);
3877
+ resolvers = {
3878
+ Query: {
3879
+ installedPackages: async () => {
3880
+ this.logger.debug("installedPackages");
3881
+ try {
3882
+ return await installedPackages(this.packageManagementService);
3883
+ } catch (error) {
3884
+ this.logger.error("Error in installedPackages:", error);
3885
+ throw error;
3886
+ }
3887
+ },
3888
+ installedPackage: async (_parent, args) => {
3889
+ this.logger.debug("installedPackage", args);
3890
+ try {
3891
+ return await installedPackage(this.packageManagementService, args);
3892
+ } catch (error) {
3893
+ this.logger.error("Error in installedPackage:", error);
3894
+ throw error;
3895
+ }
3896
+ }
3988
3897
  },
3989
- PushSyncEnvelopes(variables, options) {
3990
- return requester(PushSyncEnvelopesDocument, variables, options);
3898
+ Mutation: {
3899
+ installPackage: async (_parent, args, ctx) => {
3900
+ this.logger.debug("installPackage", args);
3901
+ try {
3902
+ return await installPackage(this.packageManagementService, args, ctx);
3903
+ } catch (error) {
3904
+ this.logger.error("Error in installPackage:", error);
3905
+ throw error;
3906
+ }
3907
+ },
3908
+ uninstallPackage: async (_parent, args, ctx) => {
3909
+ this.logger.debug("uninstallPackage", args);
3910
+ try {
3911
+ return await uninstallPackage(this.packageManagementService, args, ctx);
3912
+ } catch (error) {
3913
+ this.logger.error("Error in uninstallPackage:", error);
3914
+ throw error;
3915
+ }
3916
+ }
3991
3917
  }
3992
3918
  };
3919
+ onSetup() {
3920
+ this.logger.debug("Setting up PackagesSubgraph");
3921
+ return Promise.resolve();
3922
+ }
3923
+ };
3924
+ //#endregion
3925
+ //#region src/graphql/playground.ts
3926
+ /**
3927
+ * Pinned CDN versions for GraphiQL playground dependencies.
3928
+ * Using pinned versions avoids unpkg.com redirect issues that can
3929
+ * trigger CORS errors in the browser.
3930
+ */
3931
+ const CDN_VERSIONS = {
3932
+ react: "18.3.1",
3933
+ reactDom: "18.3.1",
3934
+ graphiql: "3.8.3",
3935
+ pluginExplorer: "4.0.0"
3936
+ };
3937
+ function renderGraphqlPlayground(url, query, headers = {}) {
3938
+ return `<!doctype html>
3939
+ <html lang="en">
3940
+ <head>
3941
+ <title>GraphiQL</title>
3942
+ <style>
3943
+ body {
3944
+ height: 100%;
3945
+ margin: 0;
3946
+ width: 100%;
3947
+ overflow: hidden;
3948
+ }
3949
+
3950
+ #graphiql {
3951
+ height: 100vh;
3952
+ }
3953
+ </style>
3954
+ <script
3955
+ src="https://unpkg.com/react@${CDN_VERSIONS.react}/umd/react.production.min.js"
3956
+ ><\/script>
3957
+ <script
3958
+ src="https://unpkg.com/react-dom@${CDN_VERSIONS.reactDom}/umd/react-dom.production.min.js"
3959
+ ><\/script>
3960
+ <script
3961
+ src="https://unpkg.com/graphiql@${CDN_VERSIONS.graphiql}/graphiql.min.js"
3962
+ ><\/script>
3963
+ <link rel="stylesheet" href="https://unpkg.com/graphiql@${CDN_VERSIONS.graphiql}/graphiql.min.css" />
3964
+ <script
3965
+ src="https://unpkg.com/@graphiql/plugin-explorer@${CDN_VERSIONS.pluginExplorer}/dist/index.umd.js"
3966
+ ><\/script>
3967
+ <link
3968
+ rel="stylesheet"
3969
+ href="https://unpkg.com/@graphiql/plugin-explorer@${CDN_VERSIONS.pluginExplorer}/dist/style.css"
3970
+ />
3971
+ </head>
3972
+
3973
+ <body>
3974
+ <div id="graphiql">Loading...</div>
3975
+ <script>
3976
+ var fetcher = GraphiQL.createFetcher({
3977
+ url: '${url}',
3978
+ headers: ${JSON.stringify(headers)}
3979
+ });
3980
+ var defaultQuery = ${query ? `\`${query}\`` : void 0};
3981
+
3982
+ if (defaultQuery) {
3983
+ var sessionQuery = localStorage.getItem("graphiql:query");
3984
+ if (sessionQuery) {
3985
+ localStorage.setItem("graphiql:query", defaultQuery);
3986
+ }
3987
+ }
3988
+
3989
+ var explorerPlugin = GraphiQLPluginExplorer.explorerPlugin();
3990
+
3991
+ function GraphiQLWithExplorer() {
3992
+ return React.createElement(GraphiQL, {
3993
+ fetcher: fetcher,
3994
+ defaultEditorToolsVisibility: true,
3995
+ plugins: [explorerPlugin],
3996
+ defaultQuery
3997
+ });
3998
+ }
3999
+
4000
+ const root = ReactDOM.createRoot(document.getElementById('graphiql'));
4001
+ root.render(React.createElement(GraphiQLWithExplorer));
4002
+ <\/script>
4003
+ </body>
4004
+ </html>`;
3993
4005
  }
3994
4006
  //#endregion
3995
4007
  //#region src/graphql/reactor/requester.ts