@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 +1733 -1721
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -10
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/
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
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
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
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
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
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
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
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
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
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
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
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
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
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
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
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
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
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
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
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
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
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
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
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
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
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
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
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
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
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
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
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
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
}
|
|
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
|
-
|
|
3990
|
-
|
|
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
|