@powerhousedao/reactor-api 6.0.2-staging.5 → 6.0.2-staging.7
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.d.mts +36 -7
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +347 -199
- package/dist/index.mjs.map +1 -1
- package/dist/src/packages/https-hooks.mjs +4 -1
- package/dist/src/packages/https-hooks.mjs.map +1 -1
- package/dist/src/packages/vite-loader.mjs +5 -2
- package/dist/src/packages/vite-loader.mjs.map +1 -1
- package/dist/{utils-CVrD_vPF.mjs → utils-CHCRSWig.mjs} +4 -2
- package/dist/utils-CHCRSWig.mjs.map +1 -0
- package/package.json +17 -25
- package/dist/utils-CVrD_vPF.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="81df4612-dfa1-5785-acec-bca9f4770f2d")}catch(e){}}();
|
|
3
|
+
import { a as isSubgraphClass, c as loadDocumentModels, d as BaseSubgraph, i as buildGraphqlOperations, l as loadProcessors, n as buildGraphQlDriveDocument, o as debounce, r as buildGraphqlOperation, t as buildGraphQlDocument, u as loadSubgraphs } from "./utils-CHCRSWig.mjs";
|
|
2
4
|
import { AnalyticsQueryEngine } from "@powerhousedao/analytics-engine-core";
|
|
3
5
|
import { AnalyticsModel, AnalyticsResolvers, typedefs } from "@powerhousedao/analytics-engine-graphql";
|
|
4
6
|
import { gql } from "graphql-tag";
|
|
@@ -25,19 +27,19 @@ import { match } from "path-to-regexp";
|
|
|
25
27
|
import fastifyCors from "@fastify/cors";
|
|
26
28
|
import fastifyFormbody from "@fastify/formbody";
|
|
27
29
|
import fastifyMiddie from "@fastify/middie";
|
|
28
|
-
import { execSync } from "node:child_process";
|
|
29
30
|
import { verifyAuthBearerToken } from "@renown/sdk";
|
|
30
31
|
import { buildSubgraphSchema } from "@apollo/subgraph";
|
|
31
32
|
import { typeDefs } from "@powerhousedao/document-engineering/graphql";
|
|
32
33
|
import { camelCase, kebabCase, pascalCase } from "change-case";
|
|
33
34
|
import { GraphQLJSONObject } from "graphql-type-json";
|
|
34
35
|
import { setName } from "@powerhousedao/shared/document-model";
|
|
35
|
-
import { PropagationMode as PropagationMode$1, consolidateSyncOperations, driveIdFromUrl, envelopesToSyncOperations, parseDriveUrl
|
|
36
|
+
import { PropagationMode as PropagationMode$1, consolidateSyncOperations, driveIdFromUrl, envelopesToSyncOperations, parseDriveUrl } from "@powerhousedao/reactor";
|
|
36
37
|
import * as z$1 from "zod";
|
|
37
38
|
import { z } from "zod";
|
|
38
39
|
import { createHandler } from "graphql-sse/lib/use/fetch";
|
|
39
40
|
import { PubSub, withFilter } from "graphql-subscriptions";
|
|
40
41
|
import dotenv from "dotenv";
|
|
42
|
+
import { buildTreeUrl } from "@powerhousedao/shared";
|
|
41
43
|
import { getConfig } from "@powerhousedao/config/node";
|
|
42
44
|
import { driveDocumentModelModule } from "@powerhousedao/shared/document-drive";
|
|
43
45
|
import EventEmitter from "node:events";
|
|
@@ -49,15 +51,6 @@ import { tmpdir } from "node:os";
|
|
|
49
51
|
import { WebSocketServer } from "ws";
|
|
50
52
|
import { createRelationalDb } from "@powerhousedao/shared/processors";
|
|
51
53
|
import { Kysely, Migrator, sql } from "kysely";
|
|
52
|
-
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
|
|
53
|
-
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
|
|
54
|
-
import { PrometheusExporter } from "@opentelemetry/exporter-prometheus";
|
|
55
|
-
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
56
|
-
import { Resource } from "@opentelemetry/resources";
|
|
57
|
-
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
|
|
58
|
-
import { NodeSDK } from "@opentelemetry/sdk-node";
|
|
59
|
-
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
60
|
-
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
|
|
61
54
|
import { PGlite } from "@electric-sql/pglite";
|
|
62
55
|
import knex from "knex";
|
|
63
56
|
import ClientPgLite from "knex-pglite";
|
|
@@ -563,6 +556,7 @@ var ApolloGatewayAdapter = class {
|
|
|
563
556
|
schema,
|
|
564
557
|
logger: this.#logger,
|
|
565
558
|
introspection: true,
|
|
559
|
+
stopOnTerminationSignals: false,
|
|
566
560
|
plugins: [ApolloServerPluginInlineTraceDisabled(), ApolloServerPluginLandingPageLocalDefault()]
|
|
567
561
|
});
|
|
568
562
|
await server.start();
|
|
@@ -582,6 +576,7 @@ var ApolloGatewayAdapter = class {
|
|
|
582
576
|
}),
|
|
583
577
|
logger: this.#logger,
|
|
584
578
|
introspection: true,
|
|
579
|
+
stopOnTerminationSignals: false,
|
|
585
580
|
plugins: [
|
|
586
581
|
ApolloServerPluginDrainHttpServer({ httpServer }),
|
|
587
582
|
ApolloServerPluginInlineTraceDisabled(),
|
|
@@ -926,7 +921,7 @@ function parseBodyLimit(limit) {
|
|
|
926
921
|
var FastifyHttpAdapter = class {
|
|
927
922
|
#fetchRoutes = [];
|
|
928
923
|
#getRoutes = /* @__PURE__ */ new Map();
|
|
929
|
-
#nodeRoutes =
|
|
924
|
+
#nodeRoutes = [];
|
|
930
925
|
#setupOps = [];
|
|
931
926
|
#instance;
|
|
932
927
|
get handle() {
|
|
@@ -953,8 +948,11 @@ var FastifyHttpAdapter = class {
|
|
|
953
948
|
});
|
|
954
949
|
}
|
|
955
950
|
mountNodeRoute(method, path, handler) {
|
|
956
|
-
|
|
957
|
-
|
|
951
|
+
this.#nodeRoutes.push({
|
|
952
|
+
method,
|
|
953
|
+
matcher: match(normalizePath(path)),
|
|
954
|
+
handler
|
|
955
|
+
});
|
|
958
956
|
}
|
|
959
957
|
mountRawMiddleware(middleware) {
|
|
960
958
|
if (this.#instance) this.#instance.use(middleware);
|
|
@@ -1024,14 +1022,14 @@ var FastifyHttpAdapter = class {
|
|
|
1024
1022
|
#dispatch(req, reply) {
|
|
1025
1023
|
const pathname = new URL(req.url, "http://localhost").pathname;
|
|
1026
1024
|
const method = req.method.toUpperCase();
|
|
1027
|
-
const
|
|
1028
|
-
|
|
1029
|
-
const
|
|
1030
|
-
if (
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1025
|
+
for (const entry of this.#nodeRoutes) {
|
|
1026
|
+
if (entry.method !== method) continue;
|
|
1027
|
+
const result = entry.matcher(pathname);
|
|
1028
|
+
if (!result) continue;
|
|
1029
|
+
req.raw.params = result.params;
|
|
1030
|
+
reply.hijack();
|
|
1031
|
+
entry.handler(req.raw, reply.raw, req.body);
|
|
1032
|
+
return;
|
|
1035
1033
|
}
|
|
1036
1034
|
if (method === "GET") {
|
|
1037
1035
|
for (const entry of this.#getRoutes.values()) if (entry.matcher(pathname)) return this.#serveGetEntry(entry, req, reply);
|
|
@@ -2719,9 +2717,13 @@ function matchesJobFilter(payload, args) {
|
|
|
2719
2717
|
return payload.jobId === args.jobId;
|
|
2720
2718
|
}
|
|
2721
2719
|
//#endregion
|
|
2722
|
-
//#region src/graphql/reactor/
|
|
2720
|
+
//#region src/graphql/reactor/constants.ts
|
|
2721
|
+
/**
|
|
2722
|
+
* Document-type sentinel for drive documents. Drives are the unit
|
|
2723
|
+
* the LB shards on (via the `Drive-Id` request header) and the unit
|
|
2724
|
+
* the drive-ownership cache tracks on each switchboard instance.
|
|
2725
|
+
*/
|
|
2723
2726
|
const DRIVE_DOCUMENT_TYPE = "powerhouse/document-drive";
|
|
2724
|
-
const POLL_SYNC_ENVELOPES_MAX_LIMIT = 100;
|
|
2725
2727
|
async function documentModels(reactorClient, args) {
|
|
2726
2728
|
const namespace = fromInputMaybe(args.namespace);
|
|
2727
2729
|
let paging;
|
|
@@ -2917,7 +2919,7 @@ async function createDocument(reactorClient, args) {
|
|
|
2917
2919
|
const parentIdentifier = fromInputMaybe(args.parentIdentifier);
|
|
2918
2920
|
let result;
|
|
2919
2921
|
try {
|
|
2920
|
-
if (parentIdentifier) if ((await reactorClient.get(parentIdentifier)).header.documentType ===
|
|
2922
|
+
if (parentIdentifier) if ((await reactorClient.get(parentIdentifier)).header.documentType === "powerhouse/document-drive") result = await reactorClient.drives.addFile(parentIdentifier, document);
|
|
2921
2923
|
else result = await reactorClient.create(document, parentIdentifier);
|
|
2922
2924
|
else result = await reactorClient.create(document);
|
|
2923
2925
|
} catch (error) {
|
|
@@ -2934,7 +2936,7 @@ async function createEmptyDocument(reactorClient, args) {
|
|
|
2934
2936
|
const name = fromInputMaybe(args.name);
|
|
2935
2937
|
let result;
|
|
2936
2938
|
try {
|
|
2937
|
-
if (parentIdentifier) if ((await reactorClient.get(parentIdentifier)).header.documentType ===
|
|
2939
|
+
if (parentIdentifier) if ((await reactorClient.get(parentIdentifier)).header.documentType === "powerhouse/document-drive") {
|
|
2938
2940
|
const document = (await reactorClient.getDocumentModelModule(args.documentType)).utils.createDocument();
|
|
2939
2941
|
if (name) document.header.name = name;
|
|
2940
2942
|
result = await reactorClient.drives.addFile(parentIdentifier, document);
|
|
@@ -2981,7 +2983,7 @@ async function createDocumentWithInitialState(reactorClient, args) {
|
|
|
2981
2983
|
} catch (error) {
|
|
2982
2984
|
throw new GraphQLError(`Parent document not found: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
2983
2985
|
}
|
|
2984
|
-
if (parent.header.documentType ===
|
|
2986
|
+
if (parent.header.documentType === "powerhouse/document-drive") try {
|
|
2985
2987
|
result = await reactorClient.drives.addFile(parentIdentifier, document);
|
|
2986
2988
|
} catch (error) {
|
|
2987
2989
|
throw new GraphQLError(`Failed to create document in drive: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
@@ -3172,14 +3174,22 @@ function pollSyncEnvelopes(syncManager, args) {
|
|
|
3172
3174
|
scopes: syncOp.scopes,
|
|
3173
3175
|
operationCount: syncOp.operations.length
|
|
3174
3176
|
}));
|
|
3175
|
-
if (args.outboxAck > 0)
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
for (const
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3177
|
+
if (args.outboxAck > 0) {
|
|
3178
|
+
const ackOrdinal = args.outboxAck;
|
|
3179
|
+
const outbox = remote.channel.outbox;
|
|
3180
|
+
const toRemove = [];
|
|
3181
|
+
for (const syncOp of outbox.items) {
|
|
3182
|
+
if (!((syncOp.emittedCount ?? 0) >= syncOp.operations.length)) continue;
|
|
3183
|
+
let maxOrdinal = 0;
|
|
3184
|
+
for (const op of syncOp.operations) if (op.context.ordinal > maxOrdinal) maxOrdinal = op.context.ordinal;
|
|
3185
|
+
if (maxOrdinal <= ackOrdinal) toRemove.push(syncOp);
|
|
3186
|
+
}
|
|
3187
|
+
if (toRemove.length > 0) {
|
|
3188
|
+
for (const syncOp of toRemove) syncOp.executed();
|
|
3189
|
+
outbox.remove(...toRemove);
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
const operations = remote.channel.outbox.items;
|
|
3183
3193
|
if (operations.length === 0) return {
|
|
3184
3194
|
envelopes: [],
|
|
3185
3195
|
ackOrdinal: remote.channel.inbox.ackOrdinal,
|
|
@@ -3189,29 +3199,66 @@ function pollSyncEnvelopes(syncManager, args) {
|
|
|
3189
3199
|
const sorted = [...operations].sort((a, b) => {
|
|
3190
3200
|
return (a.operations[0]?.context.ordinal ?? 0) - (b.operations[0]?.context.ordinal ?? 0);
|
|
3191
3201
|
});
|
|
3192
|
-
const
|
|
3193
|
-
|
|
3202
|
+
const envelopes = [];
|
|
3203
|
+
let pageOps = 0;
|
|
3204
|
+
let hasMore = false;
|
|
3194
3205
|
let maxOrdinal = args.outboxLatest;
|
|
3195
|
-
for (const syncOp of
|
|
3196
|
-
|
|
3197
|
-
|
|
3206
|
+
outer: for (const syncOp of sorted) {
|
|
3207
|
+
if (pageOps >= 100) {
|
|
3208
|
+
hasMore = true;
|
|
3209
|
+
break;
|
|
3210
|
+
}
|
|
3211
|
+
syncOp.deliveredCount ??= 0;
|
|
3212
|
+
syncOp.emittedCount ??= 0;
|
|
3213
|
+
while (syncOp.deliveredCount < syncOp.emittedCount && syncOp.operations[syncOp.deliveredCount].context.ordinal <= args.outboxLatest) syncOp.deliveredCount += 1;
|
|
3214
|
+
const remaining = syncOp.operations.slice(syncOp.deliveredCount);
|
|
3215
|
+
if (remaining.length === 0) continue;
|
|
3216
|
+
let prevPartKey;
|
|
3217
|
+
let partIdx = 0;
|
|
3218
|
+
let i = 0;
|
|
3219
|
+
while (i < remaining.length) {
|
|
3220
|
+
if (pageOps >= 100) {
|
|
3221
|
+
hasMore = true;
|
|
3222
|
+
break outer;
|
|
3223
|
+
}
|
|
3224
|
+
const remainingPage = 100 - pageOps;
|
|
3225
|
+
const chunkSize = Math.max(1, Math.min(25, remainingPage, remaining.length - i));
|
|
3226
|
+
const slice = remaining.slice(i, i + chunkSize);
|
|
3227
|
+
const isOnly = remaining.length <= 25 && remaining.length <= remainingPage;
|
|
3228
|
+
const baseKey = syncOp.jobId || void 0;
|
|
3229
|
+
const partKey = isOnly ? baseKey : baseKey ? `${baseKey}__p${partIdx}` : void 0;
|
|
3230
|
+
const partDeps = partIdx === 0 ? syncOp.jobDependencies.filter(Boolean) : prevPartKey ? [prevPartKey] : [];
|
|
3231
|
+
for (const op of slice) if (op.context.ordinal > maxOrdinal) maxOrdinal = op.context.ordinal;
|
|
3232
|
+
envelopes.push({
|
|
3233
|
+
type: "OPERATIONS",
|
|
3234
|
+
channelMeta: { id: args.channelId },
|
|
3235
|
+
operations: slice.map((op) => ({
|
|
3236
|
+
operation: serializeOperationForGraphQL(op.operation),
|
|
3237
|
+
context: op.context
|
|
3238
|
+
})),
|
|
3239
|
+
cursor: {
|
|
3240
|
+
remoteName: remote.name,
|
|
3241
|
+
cursorOrdinal: 0,
|
|
3242
|
+
lastSyncedAtUtcMs: Date.now().toString()
|
|
3243
|
+
},
|
|
3244
|
+
key: partKey,
|
|
3245
|
+
dependsOn: partDeps.length > 0 ? partDeps : void 0
|
|
3246
|
+
});
|
|
3247
|
+
pageOps += slice.length;
|
|
3248
|
+
i += slice.length;
|
|
3249
|
+
prevPartKey = partKey;
|
|
3250
|
+
partIdx++;
|
|
3251
|
+
const emittedThrough = syncOp.deliveredCount + i;
|
|
3252
|
+
if (emittedThrough > syncOp.emittedCount) syncOp.emittedCount = emittedThrough;
|
|
3253
|
+
if (i < remaining.length && pageOps >= 100) {
|
|
3254
|
+
hasMore = true;
|
|
3255
|
+
break outer;
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3198
3258
|
}
|
|
3259
|
+
for (const envelope of envelopes) if (envelope.cursor) envelope.cursor.cursorOrdinal = maxOrdinal;
|
|
3199
3260
|
return {
|
|
3200
|
-
envelopes
|
|
3201
|
-
type: "OPERATIONS",
|
|
3202
|
-
channelMeta: { id: args.channelId },
|
|
3203
|
-
operations: syncOp.operations.map((op) => ({
|
|
3204
|
-
operation: serializeOperationForGraphQL(op.operation),
|
|
3205
|
-
context: op.context
|
|
3206
|
-
})),
|
|
3207
|
-
cursor: {
|
|
3208
|
-
remoteName: remote.name,
|
|
3209
|
-
cursorOrdinal: maxOrdinal,
|
|
3210
|
-
lastSyncedAtUtcMs: Date.now().toString()
|
|
3211
|
-
},
|
|
3212
|
-
key: syncOp.jobId || void 0,
|
|
3213
|
-
dependsOn: syncOp.jobDependencies.filter(Boolean).length > 0 ? syncOp.jobDependencies.filter(Boolean) : void 0
|
|
3214
|
-
}))),
|
|
3261
|
+
envelopes,
|
|
3215
3262
|
ackOrdinal: remote.channel.inbox.ackOrdinal,
|
|
3216
3263
|
deadLetters,
|
|
3217
3264
|
hasMore
|
|
@@ -3226,9 +3273,8 @@ function pollSyncEnvelopes(syncManager, args) {
|
|
|
3226
3273
|
* and returns the highest one as `ackOrdinal` in pollSyncEnvelopes.
|
|
3227
3274
|
*/
|
|
3228
3275
|
function pushSyncEnvelopes(syncManager, args) {
|
|
3229
|
-
const sortedEnvelopes = sortEnvelopesByFirstOperationTimestamp(args.envelopes);
|
|
3230
3276
|
const remoteSyncOps = /* @__PURE__ */ new Map();
|
|
3231
|
-
for (const envelope of
|
|
3277
|
+
for (const envelope of args.envelopes) {
|
|
3232
3278
|
let remote;
|
|
3233
3279
|
try {
|
|
3234
3280
|
remote = syncManager.getById(envelope.channelMeta.id);
|
|
@@ -3476,6 +3522,110 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
3476
3522
|
}
|
|
3477
3523
|
};
|
|
3478
3524
|
//#endregion
|
|
3525
|
+
//#region src/graphql/gateway/drive-middleware.ts
|
|
3526
|
+
const DRIVE_ID_HEADER = "drive-id";
|
|
3527
|
+
/**
|
|
3528
|
+
* Operations that legitimately run before the target drive exists in the
|
|
3529
|
+
* cache. Drive creation is the obvious case (a brand-new drive cannot be
|
|
3530
|
+
* in the ownership set yet); other create-shaped operations that may
|
|
3531
|
+
* synthesize a drive must be added here too.
|
|
3532
|
+
*/
|
|
3533
|
+
const CACHE_BYPASS_OPERATIONS = new Set(["createDocument", "createEmptyDocument"]);
|
|
3534
|
+
const driveIdMap = /* @__PURE__ */ new WeakMap();
|
|
3535
|
+
/** Internal — only `graphql-manager.ts` should call this. */
|
|
3536
|
+
function getRequestDriveId(request) {
|
|
3537
|
+
return driveIdMap.get(request);
|
|
3538
|
+
}
|
|
3539
|
+
/**
|
|
3540
|
+
* Returns a fetch middleware that validates the `Drive-Id` header against
|
|
3541
|
+
* the in-memory ownership cache. Layout:
|
|
3542
|
+
*
|
|
3543
|
+
* - No header → pass through. The LB has already round-robined; nothing
|
|
3544
|
+
* to validate here.
|
|
3545
|
+
* - Header present and drive in cache → record on the request map (for
|
|
3546
|
+
* the context factory to read into `context.driveId`) and pass through.
|
|
3547
|
+
* - Header present, drive missing, but the operation is `createDocument`
|
|
3548
|
+
* or `createEmptyDocument` → pass through. The drive may be in the
|
|
3549
|
+
* process of being created.
|
|
3550
|
+
* - Otherwise → return `421 Misdirected Request` with a structured body.
|
|
3551
|
+
* The client (or LB) can surface this as a wrong-shard signal.
|
|
3552
|
+
*/
|
|
3553
|
+
function createDriveFetchMiddleware(cache) {
|
|
3554
|
+
return (next) => async (request) => {
|
|
3555
|
+
const driveId = request.headers.get(DRIVE_ID_HEADER) ?? "";
|
|
3556
|
+
if (driveId === "") return next(request);
|
|
3557
|
+
if (cache.has(driveId)) {
|
|
3558
|
+
driveIdMap.set(request, driveId);
|
|
3559
|
+
return next(request);
|
|
3560
|
+
}
|
|
3561
|
+
if (await isCacheBypassOperation(request)) return next(request);
|
|
3562
|
+
return wrongShardResponse(driveId);
|
|
3563
|
+
};
|
|
3564
|
+
}
|
|
3565
|
+
async function isCacheBypassOperation(request) {
|
|
3566
|
+
if (request.method !== "POST") return false;
|
|
3567
|
+
try {
|
|
3568
|
+
const body = await request.clone().json();
|
|
3569
|
+
if (typeof body.operationName === "string") return CACHE_BYPASS_OPERATIONS.has(body.operationName);
|
|
3570
|
+
if (typeof body.query === "string") return CACHE_BYPASS_OPERATIONS.has(extractOperationName(body.query));
|
|
3571
|
+
return false;
|
|
3572
|
+
} catch {
|
|
3573
|
+
return false;
|
|
3574
|
+
}
|
|
3575
|
+
}
|
|
3576
|
+
const OPERATION_NAME_PATTERN = /\b(?:mutation|query|subscription)\s+(\w+)/;
|
|
3577
|
+
function extractOperationName(query) {
|
|
3578
|
+
const match = OPERATION_NAME_PATTERN.exec(query);
|
|
3579
|
+
return match ? match[1] : "";
|
|
3580
|
+
}
|
|
3581
|
+
function wrongShardResponse(driveId) {
|
|
3582
|
+
return new globalThis.Response(JSON.stringify({
|
|
3583
|
+
error: "wrong-shard",
|
|
3584
|
+
driveId
|
|
3585
|
+
}), {
|
|
3586
|
+
status: 421,
|
|
3587
|
+
headers: { "content-type": "application/json" }
|
|
3588
|
+
});
|
|
3589
|
+
}
|
|
3590
|
+
//#endregion
|
|
3591
|
+
//#region src/graphql/gateway/drive-ownership-cache.ts
|
|
3592
|
+
/**
|
|
3593
|
+
* In-memory record of which drives this switchboard instance owns.
|
|
3594
|
+
*
|
|
3595
|
+
* Populated at startup by walking the reactor for documents of type
|
|
3596
|
+
* `powerhouse/document-drive`. Mutated explicitly by resolver hooks
|
|
3597
|
+
* after successful drive create / delete operations. Read by the
|
|
3598
|
+
* drive-validation fetch middleware to short-circuit wrong-shard
|
|
3599
|
+
* requests with a structured 421 response.
|
|
3600
|
+
*/
|
|
3601
|
+
var DriveOwnershipCache = class {
|
|
3602
|
+
drives = /* @__PURE__ */ new Set();
|
|
3603
|
+
constructor(reactorClient) {
|
|
3604
|
+
this.reactorClient = reactorClient;
|
|
3605
|
+
}
|
|
3606
|
+
async init() {
|
|
3607
|
+
this.drives.clear();
|
|
3608
|
+
let page = await this.reactorClient.find({ type: DRIVE_DOCUMENT_TYPE });
|
|
3609
|
+
while (true) {
|
|
3610
|
+
for (const drive of page.results) this.drives.add(drive.header.id);
|
|
3611
|
+
if (!page.next) return;
|
|
3612
|
+
page = await page.next();
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
has(driveId) {
|
|
3616
|
+
return this.drives.has(driveId);
|
|
3617
|
+
}
|
|
3618
|
+
add(driveId) {
|
|
3619
|
+
this.drives.add(driveId);
|
|
3620
|
+
}
|
|
3621
|
+
remove(driveId) {
|
|
3622
|
+
this.drives.delete(driveId);
|
|
3623
|
+
}
|
|
3624
|
+
size() {
|
|
3625
|
+
return this.drives.size;
|
|
3626
|
+
}
|
|
3627
|
+
};
|
|
3628
|
+
//#endregion
|
|
3479
3629
|
//#region src/graphql/sse.ts
|
|
3480
3630
|
/**
|
|
3481
3631
|
* Create a Fetch-API-compatible SSE handler for GraphQL subscriptions
|
|
@@ -3539,6 +3689,8 @@ var GraphQLManager = class {
|
|
|
3539
3689
|
authService = null;
|
|
3540
3690
|
subgraphWsDisposers = /* @__PURE__ */ new Map();
|
|
3541
3691
|
#authMiddleware;
|
|
3692
|
+
#driveMiddleware;
|
|
3693
|
+
driveOwnershipCache;
|
|
3542
3694
|
/** Cached document models for schema generation - updated on init and regenerate */
|
|
3543
3695
|
cachedDocumentModels = [];
|
|
3544
3696
|
subgraphHandlerCache = /* @__PURE__ */ new Map();
|
|
@@ -3559,11 +3711,15 @@ var GraphQLManager = class {
|
|
|
3559
3711
|
this.port = port;
|
|
3560
3712
|
this.authorizationService = authorizationService;
|
|
3561
3713
|
if (this.authConfig) this.authService = new AuthService(this.authConfig);
|
|
3714
|
+
this.driveOwnershipCache = new DriveOwnershipCache(this.reactorClient);
|
|
3562
3715
|
this.wsServer.setMaxListeners(0);
|
|
3563
3716
|
}
|
|
3564
3717
|
async init(coreSubgraphs, authMiddleware) {
|
|
3565
3718
|
this.#authMiddleware = authMiddleware;
|
|
3566
3719
|
this.logger.debug(`Initializing Subgraph Manager...`);
|
|
3720
|
+
await this.driveOwnershipCache.init();
|
|
3721
|
+
this.#driveMiddleware = createDriveFetchMiddleware(this.driveOwnershipCache);
|
|
3722
|
+
this.logger.debug(`Drive ownership cache populated with ${this.driveOwnershipCache.size()} drives`);
|
|
3567
3723
|
const models = (await this.reactorClient.getDocumentModelModules()).results;
|
|
3568
3724
|
this.cachedDocumentModels = models;
|
|
3569
3725
|
if (!models.find((it) => it.documentModel.global.name === "DocumentDrive")) throw new Error("DocumentDrive model required");
|
|
@@ -3578,12 +3734,12 @@ var GraphQLManager = class {
|
|
|
3578
3734
|
if (!driveIdOrSlug) return Response.json({ error: "Drive ID or slug is required" }, { status: 400 });
|
|
3579
3735
|
try {
|
|
3580
3736
|
const driveDoc = await this.reactorClient.get(driveIdOrSlug);
|
|
3581
|
-
const graphqlEndpoint = `${(request.headers.get("x-forwarded-proto") ?? url.protocol.replace(":", "")) + ":"}//${request.headers.get("host") ?? ""}${this.path === "/" ? "" : this.path}/graphql/r`;
|
|
3737
|
+
const graphqlEndpoint = `${(request.headers.get("x-forwarded-proto")?.split(",")[0].trim() ?? url.protocol.replace(":", "")) + ":"}//${request.headers.get("x-forwarded-host")?.split(",")[0].trim() ?? request.headers.get("host") ?? ""}${(request.headers.get("x-forwarded-prefix")?.split(",")[0].trim().replace(/\/$/, "") ?? "") + (this.path === "/" ? "" : this.path)}/graphql/r`;
|
|
3582
3738
|
return Response.json({
|
|
3583
3739
|
id: driveDoc.header.id,
|
|
3584
3740
|
slug: driveDoc.header.slug,
|
|
3585
3741
|
meta: driveDoc.header.meta,
|
|
3586
|
-
name: driveDoc.state.global.name,
|
|
3742
|
+
name: driveDoc.state.global.name || driveDoc.header.name,
|
|
3587
3743
|
icon: driveDoc.state.global.icon ?? void 0,
|
|
3588
3744
|
...graphqlEndpoint && { graphqlEndpoint }
|
|
3589
3745
|
});
|
|
@@ -3723,6 +3879,7 @@ var GraphQLManager = class {
|
|
|
3723
3879
|
#makeContextFactory() {
|
|
3724
3880
|
return (request) => {
|
|
3725
3881
|
const authCtx = getAuthContext(request);
|
|
3882
|
+
const driveId = getRequestDriveId(request);
|
|
3726
3883
|
const headers = {};
|
|
3727
3884
|
request.headers.forEach((v, k) => {
|
|
3728
3885
|
headers[k] = v;
|
|
@@ -3731,6 +3888,7 @@ var GraphQLManager = class {
|
|
|
3731
3888
|
headers,
|
|
3732
3889
|
db: this.relationalDb,
|
|
3733
3890
|
...this.getAdditionalContextFields(),
|
|
3891
|
+
driveId,
|
|
3734
3892
|
user: authCtx?.user,
|
|
3735
3893
|
isAdmin: authCtx ? (addr) => !authCtx.auth_enabled ? true : authCtx.admins.includes(addr.toLowerCase()) : () => true
|
|
3736
3894
|
});
|
|
@@ -3769,7 +3927,7 @@ var GraphQLManager = class {
|
|
|
3769
3927
|
if (this.subgraphHandlerCache.has(subgraphPath)) continue;
|
|
3770
3928
|
const schema = createSchema(this.cachedDocumentModels, subgraph.resolvers, subgraph.typeDefs);
|
|
3771
3929
|
const rawHandler = await this.gatewayAdapter.createHandler(schema, this.#makeContextFactory());
|
|
3772
|
-
const fetchHandler = this.#
|
|
3930
|
+
const fetchHandler = this.#composeFetchMiddleware(rawHandler);
|
|
3773
3931
|
this.subgraphHandlerCache.set(subgraphPath, fetchHandler);
|
|
3774
3932
|
this.httpAdapter.mount(subgraphPath, fetchHandler);
|
|
3775
3933
|
if (subgraph.hasSubscriptions) {
|
|
@@ -3819,7 +3977,7 @@ var GraphQLManager = class {
|
|
|
3819
3977
|
async #createSupergraphGateway() {
|
|
3820
3978
|
const superGraphPath = path.join(this.path, "graphql");
|
|
3821
3979
|
const rawHandler = await this.gatewayAdapter.createSupergraphHandler(() => this.#getSubgraphDefinitions(), this.httpServer, this.#makeContextFactory());
|
|
3822
|
-
const fetchHandler = this.#
|
|
3980
|
+
const fetchHandler = this.#composeFetchMiddleware(rawHandler);
|
|
3823
3981
|
this.httpAdapter.mount(superGraphPath, fetchHandler);
|
|
3824
3982
|
this.#setupSupergraphSSE(superGraphPath);
|
|
3825
3983
|
if (!this.initialized) {
|
|
@@ -3859,9 +4017,20 @@ var GraphQLManager = class {
|
|
|
3859
4017
|
schema,
|
|
3860
4018
|
contextFactory: this.#makeContextFactory()
|
|
3861
4019
|
});
|
|
3862
|
-
const handler = this.#
|
|
4020
|
+
const handler = this.#composeFetchMiddleware(rawHandler);
|
|
3863
4021
|
this.httpAdapter.mount(ssePath, handler, { exact: true });
|
|
3864
4022
|
}
|
|
4023
|
+
/**
|
|
4024
|
+
* Compose the request-level fetch middleware chain. Auth runs first
|
|
4025
|
+
* (so we don't validate shard before knowing the request is even
|
|
4026
|
+
* authorized), drive-ownership validation runs after.
|
|
4027
|
+
*/
|
|
4028
|
+
#composeFetchMiddleware(rawHandler) {
|
|
4029
|
+
let handler = rawHandler;
|
|
4030
|
+
if (this.#driveMiddleware) handler = this.#driveMiddleware(handler);
|
|
4031
|
+
if (this.#authMiddleware) handler = this.#authMiddleware(handler);
|
|
4032
|
+
return handler;
|
|
4033
|
+
}
|
|
3865
4034
|
};
|
|
3866
4035
|
//#endregion
|
|
3867
4036
|
//#region src/graphql/packages/resolvers.ts
|
|
@@ -4377,6 +4546,21 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4377
4546
|
await this.assertCanExecuteOperation(documentId, operationType, ctx);
|
|
4378
4547
|
}
|
|
4379
4548
|
}
|
|
4549
|
+
/**
|
|
4550
|
+
* Returns the drive id when the given identifier (id or slug) refers
|
|
4551
|
+
* to a drive document, otherwise undefined. Used by deleteDocument to
|
|
4552
|
+
* decide whether to invalidate the drive-ownership cache after a
|
|
4553
|
+
* successful delete.
|
|
4554
|
+
*/
|
|
4555
|
+
async #resolveDriveId(identifier) {
|
|
4556
|
+
try {
|
|
4557
|
+
const doc = await this.reactorClient.get(identifier);
|
|
4558
|
+
if (doc.header.documentType === "powerhouse/document-drive") return doc.header.id;
|
|
4559
|
+
return;
|
|
4560
|
+
} catch {
|
|
4561
|
+
return;
|
|
4562
|
+
}
|
|
4563
|
+
}
|
|
4380
4564
|
typeDefs = gql(schema_default);
|
|
4381
4565
|
resolvers = {
|
|
4382
4566
|
PHDocument: { operations: async (parent, args, ctx) => {
|
|
@@ -4517,6 +4701,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4517
4701
|
if (!ctx.user?.address) throw new GraphQLError("Forbidden: authentication required to create documents");
|
|
4518
4702
|
} else if (!this.hasGlobalAdminAccess(ctx)) throw new GraphQLError("Forbidden: insufficient permissions to create documents");
|
|
4519
4703
|
const result = await createDocument(this.reactorClient, args);
|
|
4704
|
+
if (result?.id && result.documentType === "powerhouse/document-drive") this.graphqlManager.driveOwnershipCache.add(result.id);
|
|
4520
4705
|
if (this.authorizationService && ctx.user?.address && result?.id) await this.documentPermissionService?.initializeDocumentProtection(result.id, ctx.user.address, this.authorizationService.config.defaultProtection);
|
|
4521
4706
|
return result;
|
|
4522
4707
|
} catch (error) {
|
|
@@ -4534,6 +4719,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4534
4719
|
if (!ctx.user?.address) throw new GraphQLError("Forbidden: authentication required to create documents");
|
|
4535
4720
|
} else if (!this.hasGlobalAdminAccess(ctx)) throw new GraphQLError("Forbidden: insufficient permissions to create documents");
|
|
4536
4721
|
const result = await createEmptyDocument(this.reactorClient, args);
|
|
4722
|
+
if (result?.id && result.documentType === "powerhouse/document-drive") this.graphqlManager.driveOwnershipCache.add(result.id);
|
|
4537
4723
|
if (this.authorizationService && ctx.user?.address && result?.id) await this.documentPermissionService?.initializeDocumentProtection(result.id, ctx.user.address, this.authorizationService.config.defaultProtection);
|
|
4538
4724
|
return result;
|
|
4539
4725
|
} catch (error) {
|
|
@@ -4608,7 +4794,10 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4608
4794
|
this.logger.debug("deleteDocument(@args)", args);
|
|
4609
4795
|
try {
|
|
4610
4796
|
await this.assertCanWrite(args.identifier, ctx);
|
|
4611
|
-
|
|
4797
|
+
const driveIdToInvalidate = await this.#resolveDriveId(args.identifier);
|
|
4798
|
+
const result = await deleteDocument(this.reactorClient, args);
|
|
4799
|
+
if (result && driveIdToInvalidate) this.graphqlManager.driveOwnershipCache.remove(driveIdToInvalidate);
|
|
4800
|
+
return result;
|
|
4612
4801
|
} catch (error) {
|
|
4613
4802
|
this.logger.error("Error in deleteDocument(@args): @Error", error);
|
|
4614
4803
|
throw error;
|
|
@@ -4713,29 +4902,14 @@ dotenv.config();
|
|
|
4713
4902
|
const ADMIN_USERS = getAdminUsers();
|
|
4714
4903
|
//#endregion
|
|
4715
4904
|
//#region src/graphql/system/version.ts
|
|
4716
|
-
let cachedVersion;
|
|
4717
|
-
let cachedGitHash;
|
|
4718
4905
|
function getVersion() {
|
|
4719
|
-
|
|
4720
|
-
cachedVersion = process.env.PH_VERSION ?? process.env.npm_package_version ?? "unknown";
|
|
4721
|
-
return cachedVersion;
|
|
4906
|
+
return "6.0.2-staging.7";
|
|
4722
4907
|
}
|
|
4723
4908
|
function getGitHash() {
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
}
|
|
4729
|
-
try {
|
|
4730
|
-
cachedGitHash = execSync("git rev-parse HEAD", { stdio: [
|
|
4731
|
-
"ignore",
|
|
4732
|
-
"pipe",
|
|
4733
|
-
"ignore"
|
|
4734
|
-
] }).toString().trim();
|
|
4735
|
-
} catch {
|
|
4736
|
-
cachedGitHash = "unknown";
|
|
4737
|
-
}
|
|
4738
|
-
return cachedGitHash;
|
|
4909
|
+
return "6b981a6f1934d98ad53dde1f68a46d9758d7c962";
|
|
4910
|
+
}
|
|
4911
|
+
function getGitUrl() {
|
|
4912
|
+
return buildTreeUrl(getGitHash());
|
|
4739
4913
|
}
|
|
4740
4914
|
//#endregion
|
|
4741
4915
|
//#region src/graphql/system/subgraph.ts
|
|
@@ -4746,6 +4920,7 @@ var SystemSubgraph = class extends BaseSubgraph {
|
|
|
4746
4920
|
type SystemInfo {
|
|
4747
4921
|
version: String!
|
|
4748
4922
|
gitHash: String!
|
|
4923
|
+
gitUrl: String
|
|
4749
4924
|
}
|
|
4750
4925
|
|
|
4751
4926
|
type Query {
|
|
@@ -4754,7 +4929,8 @@ var SystemSubgraph = class extends BaseSubgraph {
|
|
|
4754
4929
|
`;
|
|
4755
4930
|
resolvers = { Query: { system: () => ({
|
|
4756
4931
|
version: getVersion(),
|
|
4757
|
-
gitHash: getGitHash()
|
|
4932
|
+
gitHash: getGitHash(),
|
|
4933
|
+
gitUrl: getGitUrl()
|
|
4758
4934
|
}) } };
|
|
4759
4935
|
};
|
|
4760
4936
|
//#endregion
|
|
@@ -5895,102 +6071,6 @@ var DocumentPermissionService = class {
|
|
|
5895
6071
|
}
|
|
5896
6072
|
};
|
|
5897
6073
|
//#endregion
|
|
5898
|
-
//#region src/tracing.ts
|
|
5899
|
-
const TEMPO_ENDPOINT = process.env.TEMPO_ENDPOINT || "http://tempo.monitoring.svc.cluster.local:4318/v1/traces";
|
|
5900
|
-
const SERVICE_NAME = process.env.OTEL_SERVICE_NAME || "reactor-api";
|
|
5901
|
-
const SERVICE_VERSION = process.env.npm_package_version || "unknown";
|
|
5902
|
-
const TENANT_ID = process.env.TENANT_ID || "default";
|
|
5903
|
-
const METRICS_ENDPOINT = process.env.METRICS_ENDPOINT;
|
|
5904
|
-
const METRICS_EXPORT_INTERVAL_MS = parseInt(process.env.METRICS_EXPORT_INTERVAL_MS || "30000", 10);
|
|
5905
|
-
const TRACING_ENABLED = process.env.ENABLE_TRACING === "true" || process.env.NODE_ENV === "production";
|
|
5906
|
-
if (TRACING_ENABLED) {
|
|
5907
|
-
console.log(`Initializing OpenTelemetry tracing for ${SERVICE_NAME}...`);
|
|
5908
|
-
console.log(` Tempo endpoint: ${TEMPO_ENDPOINT}`);
|
|
5909
|
-
console.log(` Service: ${SERVICE_NAME}`);
|
|
5910
|
-
console.log(` Tenant: ${TENANT_ID}`);
|
|
5911
|
-
const traceExporter = new OTLPTraceExporter({
|
|
5912
|
-
url: TEMPO_ENDPOINT,
|
|
5913
|
-
headers: {}
|
|
5914
|
-
});
|
|
5915
|
-
const resource = new Resource({
|
|
5916
|
-
[ATTR_SERVICE_NAME]: SERVICE_NAME,
|
|
5917
|
-
[ATTR_SERVICE_VERSION]: SERVICE_VERSION,
|
|
5918
|
-
"tenant.id": TENANT_ID,
|
|
5919
|
-
"deployment.environment": process.env.NODE_ENV || "development"
|
|
5920
|
-
});
|
|
5921
|
-
const PROMETHEUS_METRICS_PORT = process.env.PROMETHEUS_METRICS_PORT;
|
|
5922
|
-
let metricReader;
|
|
5923
|
-
if (PROMETHEUS_METRICS_PORT) {
|
|
5924
|
-
const port = parseInt(PROMETHEUS_METRICS_PORT, 10);
|
|
5925
|
-
metricReader = new PrometheusExporter({
|
|
5926
|
-
port,
|
|
5927
|
-
preventServerStart: false
|
|
5928
|
-
});
|
|
5929
|
-
console.log(` Prometheus /metrics on port ${port}`);
|
|
5930
|
-
} else if (METRICS_ENDPOINT) {
|
|
5931
|
-
metricReader = new PeriodicExportingMetricReader({
|
|
5932
|
-
exporter: new OTLPMetricExporter({ url: METRICS_ENDPOINT }),
|
|
5933
|
-
exportIntervalMillis: METRICS_EXPORT_INTERVAL_MS
|
|
5934
|
-
});
|
|
5935
|
-
console.log(` Metrics OTLP endpoint: ${METRICS_ENDPOINT}`);
|
|
5936
|
-
console.log(` Metrics interval: ${METRICS_EXPORT_INTERVAL_MS}ms`);
|
|
5937
|
-
}
|
|
5938
|
-
const sdk = new NodeSDK({
|
|
5939
|
-
resource,
|
|
5940
|
-
spanProcessors: [new BatchSpanProcessor(traceExporter)],
|
|
5941
|
-
metricReader,
|
|
5942
|
-
instrumentations: [getNodeAutoInstrumentations({
|
|
5943
|
-
"@opentelemetry/instrumentation-http": {
|
|
5944
|
-
enabled: true,
|
|
5945
|
-
ignoreIncomingRequestHook: (req) => {
|
|
5946
|
-
return req.url === "/health" || req.url === "/ready";
|
|
5947
|
-
},
|
|
5948
|
-
requireParentforIncomingSpans: false,
|
|
5949
|
-
requireParentforOutgoingSpans: false,
|
|
5950
|
-
requestHook: (span, request) => {
|
|
5951
|
-
span.setAttribute("http.route", request.url || "");
|
|
5952
|
-
},
|
|
5953
|
-
responseHook: (span, response) => {
|
|
5954
|
-
if (response.statusCode) span.setAttribute("http.status_code", response.statusCode);
|
|
5955
|
-
}
|
|
5956
|
-
},
|
|
5957
|
-
"@opentelemetry/instrumentation-express": {
|
|
5958
|
-
enabled: true,
|
|
5959
|
-
requestHook: (span, info) => {
|
|
5960
|
-
if (info.route) span.setAttribute("http.route", info.route);
|
|
5961
|
-
}
|
|
5962
|
-
},
|
|
5963
|
-
"@opentelemetry/instrumentation-graphql": {
|
|
5964
|
-
enabled: true,
|
|
5965
|
-
mergeItems: true,
|
|
5966
|
-
allowValues: true
|
|
5967
|
-
},
|
|
5968
|
-
"@opentelemetry/instrumentation-pg": {
|
|
5969
|
-
enabled: true,
|
|
5970
|
-
enhancedDatabaseReporting: true
|
|
5971
|
-
},
|
|
5972
|
-
"@opentelemetry/instrumentation-redis-4": {
|
|
5973
|
-
enabled: true,
|
|
5974
|
-
dbStatementSerializer: (cmdName, cmdArgs) => {
|
|
5975
|
-
return cmdName;
|
|
5976
|
-
}
|
|
5977
|
-
}
|
|
5978
|
-
})]
|
|
5979
|
-
});
|
|
5980
|
-
sdk.start();
|
|
5981
|
-
console.log("✓ OpenTelemetry tracing initialized");
|
|
5982
|
-
process.on("SIGTERM", () => {
|
|
5983
|
-
sdk.shutdown().then(() => console.log("Tracing terminated")).catch((error) => console.log("Error terminating tracing", error)).finally(() => process.exit(0));
|
|
5984
|
-
});
|
|
5985
|
-
}
|
|
5986
|
-
async function initTracing() {}
|
|
5987
|
-
function isTracingEnabled() {
|
|
5988
|
-
return TRACING_ENABLED;
|
|
5989
|
-
}
|
|
5990
|
-
async function trace(_name, _options, fn) {
|
|
5991
|
-
return fn();
|
|
5992
|
-
}
|
|
5993
|
-
//#endregion
|
|
5994
6074
|
//#region src/utils/db.ts
|
|
5995
6075
|
function isPG(connectionString) {
|
|
5996
6076
|
if (connectionString.startsWith("postgresql://") || connectionString.startsWith("postgres://")) return true;
|
|
@@ -5999,7 +6079,8 @@ function isPG(connectionString) {
|
|
|
5999
6079
|
function getDbClient(connectionString = void 0, pgliteFactory) {
|
|
6000
6080
|
const isPg = connectionString && isPG(connectionString);
|
|
6001
6081
|
const client = isPg ? "pg" : ClientPgLite;
|
|
6002
|
-
const
|
|
6082
|
+
const pgliteInstance = isPg ? void 0 : pgliteFactory ? pgliteFactory(connectionString) : new PGlite(connectionString);
|
|
6083
|
+
const connection = isPg ? { connectionString } : { pglite: pgliteInstance };
|
|
6003
6084
|
if (connectionString && !isPg) {
|
|
6004
6085
|
const dirPath = path.resolve(connectionString, "..");
|
|
6005
6086
|
if (!fs.existsSync(dirPath)) fs.mkdirSync(dirPath, { recursive: true });
|
|
@@ -6013,7 +6094,8 @@ function getDbClient(connectionString = void 0, pgliteFactory) {
|
|
|
6013
6094
|
knex: knexInstance,
|
|
6014
6095
|
kyselySubDialect: new PGColdDialect()
|
|
6015
6096
|
}) }),
|
|
6016
|
-
knex: knexInstance
|
|
6097
|
+
knex: knexInstance,
|
|
6098
|
+
pglite: pgliteInstance
|
|
6017
6099
|
};
|
|
6018
6100
|
}
|
|
6019
6101
|
const initAnalyticsStoreSql = [
|
|
@@ -6086,19 +6168,36 @@ function resolveAttachmentStoragePath(options) {
|
|
|
6086
6168
|
return path.join(tmpdir(), "reactor-attachments");
|
|
6087
6169
|
}
|
|
6088
6170
|
/**
|
|
6089
|
-
* Initializes the database and analytics store
|
|
6171
|
+
* Initializes the database and analytics store. The returned `closers` are
|
|
6172
|
+
* idempotent thunks that release the underlying knex pool and PGlite instance
|
|
6173
|
+
* (when on-disk PGlite is in use); callers are expected to run them as part
|
|
6174
|
+
* of API teardown so PGlite WAL is flushed and the data-dir lock is released.
|
|
6090
6175
|
*/
|
|
6091
6176
|
async function initializeDatabaseAndAnalytics(dbPath, pgliteFactory) {
|
|
6092
|
-
const { db, knex } = getDbClient(dbPath, pgliteFactory);
|
|
6177
|
+
const { db, knex, pglite } = getDbClient(dbPath, pgliteFactory);
|
|
6093
6178
|
const relationalDb = createRelationalDb(db);
|
|
6094
6179
|
const analyticsStore = new PostgresAnalyticsStore({ knex });
|
|
6095
6180
|
for (const sql of initAnalyticsStoreSql) await knex.raw(sql);
|
|
6096
6181
|
return {
|
|
6097
6182
|
relationalDb,
|
|
6098
|
-
analyticsStore
|
|
6183
|
+
analyticsStore,
|
|
6184
|
+
closers: makeDbClosers(knex, pglite)
|
|
6099
6185
|
};
|
|
6100
6186
|
}
|
|
6101
6187
|
/**
|
|
6188
|
+
* Builds best-effort closers for a knex/PGlite pair returned by
|
|
6189
|
+
* {@link getDbClient}. Order is significant: knex first releases its pool
|
|
6190
|
+
* (which is what the application talks to), then PGlite flushes WAL and
|
|
6191
|
+
* unlocks the data dir.
|
|
6192
|
+
*/
|
|
6193
|
+
function makeDbClosers(knexInstance, pglite) {
|
|
6194
|
+
const closers = [() => knexInstance.destroy()];
|
|
6195
|
+
if (pglite) closers.push(async () => {
|
|
6196
|
+
if (!pglite.closed) await pglite.close();
|
|
6197
|
+
});
|
|
6198
|
+
return closers;
|
|
6199
|
+
}
|
|
6200
|
+
/**
|
|
6102
6201
|
* Sets up the subgraph manager and registers subgraphs
|
|
6103
6202
|
*/
|
|
6104
6203
|
async function setupGraphQLManager(httpAdapter, authFetchMiddleware, httpServer, wsServer, client, relationalDb, analyticsStore, syncManager, subgraphs, logger, auth, documentPermissionService, enableDocumentModelSubgraphs, port, authorizationService) {
|
|
@@ -6173,7 +6272,6 @@ async function startServer(httpAdapter, port, httpsOptions, logger) {
|
|
|
6173
6272
|
* This includes auth configuration, database setup, and package manager initialization.
|
|
6174
6273
|
*/
|
|
6175
6274
|
async function _setupCommonInfrastructure(options) {
|
|
6176
|
-
if (isTracingEnabled()) await initTracing();
|
|
6177
6275
|
const port = options.port ?? DEFAULT_PORT;
|
|
6178
6276
|
const { adapter: httpAdapter } = createHttpAdapter("express");
|
|
6179
6277
|
let admins = [];
|
|
@@ -6216,10 +6314,13 @@ async function _setupCommonInfrastructure(options) {
|
|
|
6216
6314
|
});
|
|
6217
6315
|
authFetchMiddleware = createAuthFetchMiddleware(authService);
|
|
6218
6316
|
}
|
|
6219
|
-
const
|
|
6317
|
+
const dbClosers = [];
|
|
6318
|
+
const { relationalDb, analyticsStore, closers: analyticsClosers } = await initializeDatabaseAndAnalytics(options.dbPath, options.pgliteFactory);
|
|
6319
|
+
dbClosers.push(...analyticsClosers);
|
|
6220
6320
|
let documentPermissionService = options.documentPermissionService;
|
|
6221
6321
|
if (!documentPermissionService && DOCUMENT_PERMISSIONS_ENABLED === "true") {
|
|
6222
|
-
const { db } = getDbClient(options.dbPath, options.pgliteFactory);
|
|
6322
|
+
const { db, knex, pglite } = getDbClient(options.dbPath, options.pgliteFactory);
|
|
6323
|
+
dbClosers.push(...makeDbClosers(knex, pglite));
|
|
6223
6324
|
await runMigrations(db);
|
|
6224
6325
|
logger.info("Document permission migrations completed");
|
|
6225
6326
|
documentPermissionService = new DocumentPermissionService(db, { defaultProtection });
|
|
@@ -6235,7 +6336,8 @@ async function _setupCommonInfrastructure(options) {
|
|
|
6235
6336
|
}
|
|
6236
6337
|
const attachmentStoragePath = resolveAttachmentStoragePath(options);
|
|
6237
6338
|
await mkdir(attachmentStoragePath, { recursive: true });
|
|
6238
|
-
const { db: attachmentDb } = getDbClient(options.dbPath, options.pgliteFactory);
|
|
6339
|
+
const { db: attachmentDb, knex: attachmentKnex, pglite: attachmentPglite } = getDbClient(options.dbPath, options.pgliteFactory);
|
|
6340
|
+
dbClosers.push(...makeDbClosers(attachmentKnex, attachmentPglite));
|
|
6239
6341
|
const attachments = await new AttachmentBuilder(attachmentDb, attachmentStoragePath).build();
|
|
6240
6342
|
logger.info("Attachment service initialized");
|
|
6241
6343
|
const packages = new PackageManager(options.packageLoaders ?? [new ImportPackageLoader()], {
|
|
@@ -6256,13 +6358,14 @@ async function _setupCommonInfrastructure(options) {
|
|
|
6256
6358
|
documentPermissionService,
|
|
6257
6359
|
authorizationService,
|
|
6258
6360
|
attachments,
|
|
6259
|
-
packages
|
|
6361
|
+
packages,
|
|
6362
|
+
dbClosers
|
|
6260
6363
|
};
|
|
6261
6364
|
}
|
|
6262
6365
|
/**
|
|
6263
6366
|
* Private helper function containing common setup logic for API initialization
|
|
6264
6367
|
*/
|
|
6265
|
-
async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, httpAdapter, authFetchMiddleware, authService, port, packages, relationalDb, analyticsStore, documentPermissionService, processors, subgraphs, options, auth, processorApp, readModels, attachments, authorizationService, documentModelRegistry) {
|
|
6368
|
+
async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, httpAdapter, authFetchMiddleware, authService, port, packages, relationalDb, analyticsStore, documentPermissionService, processors, subgraphs, options, auth, processorApp, readModels, attachments, authorizationService, documentModelRegistry, dbClosers = []) {
|
|
6266
6369
|
const hostModule = {
|
|
6267
6370
|
relationalDb,
|
|
6268
6371
|
analyticsStore,
|
|
@@ -6328,7 +6431,51 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
|
|
|
6328
6431
|
graphqlManager,
|
|
6329
6432
|
packages,
|
|
6330
6433
|
attachments,
|
|
6331
|
-
authService
|
|
6434
|
+
authService,
|
|
6435
|
+
dispose: buildApiDispose({
|
|
6436
|
+
graphqlManager,
|
|
6437
|
+
httpServer,
|
|
6438
|
+
wsServer,
|
|
6439
|
+
dbClosers,
|
|
6440
|
+
logger
|
|
6441
|
+
})
|
|
6442
|
+
};
|
|
6443
|
+
}
|
|
6444
|
+
/**
|
|
6445
|
+
* Composes the lifecycle teardown for an API instance. Steps run in
|
|
6446
|
+
* dependency order so that draining HTTP/GraphQL surfaces happens before the
|
|
6447
|
+
* underlying knex pool and PGlite WAL are released. Each step is wrapped in
|
|
6448
|
+
* its own try/catch — one failure must not strand the rest of the chain,
|
|
6449
|
+
* since this runs on the way to process exit.
|
|
6450
|
+
*/
|
|
6451
|
+
function buildApiDispose(args) {
|
|
6452
|
+
const { graphqlManager, httpServer, wsServer, dbClosers, logger } = args;
|
|
6453
|
+
let disposed = false;
|
|
6454
|
+
return async () => {
|
|
6455
|
+
if (disposed) return;
|
|
6456
|
+
disposed = true;
|
|
6457
|
+
try {
|
|
6458
|
+
await graphqlManager.shutdown();
|
|
6459
|
+
} catch (error) {
|
|
6460
|
+
logger.error("API dispose: graphqlManager.shutdown failed:", error);
|
|
6461
|
+
}
|
|
6462
|
+
try {
|
|
6463
|
+
for (const client of wsServer.clients) client.terminate();
|
|
6464
|
+
await new Promise((resolve) => wsServer.close(() => resolve()));
|
|
6465
|
+
} catch (error) {
|
|
6466
|
+
logger.error("API dispose: wsServer.close failed:", error);
|
|
6467
|
+
}
|
|
6468
|
+
if (httpServer.listening) try {
|
|
6469
|
+
httpServer.closeAllConnections();
|
|
6470
|
+
await new Promise((resolve, reject) => httpServer.close((err) => err ? reject(err) : resolve()));
|
|
6471
|
+
} catch (error) {
|
|
6472
|
+
logger.error("API dispose: httpServer.close failed:", error);
|
|
6473
|
+
}
|
|
6474
|
+
for (const close of dbClosers) try {
|
|
6475
|
+
await close();
|
|
6476
|
+
} catch (error) {
|
|
6477
|
+
logger.error("API dispose: db closer failed:", error);
|
|
6478
|
+
}
|
|
6332
6479
|
};
|
|
6333
6480
|
}
|
|
6334
6481
|
/**
|
|
@@ -6342,9 +6489,9 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
|
|
|
6342
6489
|
* @returns The API server components along with the created client instances.
|
|
6343
6490
|
*/
|
|
6344
6491
|
async function initializeAndStartAPI(clientInitializer, options, processorApp) {
|
|
6345
|
-
const { port, httpAdapter, authFetchMiddleware, authService, auth, relationalDb, analyticsStore, documentPermissionService, authorizationService, attachments, packages } = await
|
|
6346
|
-
const { documentModels, processors, subgraphs } = await
|
|
6347
|
-
const reactorClientModule = await
|
|
6492
|
+
const { port, httpAdapter, authFetchMiddleware, authService, auth, relationalDb, analyticsStore, documentPermissionService, authorizationService, attachments, packages, dbClosers } = await _setupCommonInfrastructure(options);
|
|
6493
|
+
const { documentModels, processors, subgraphs } = await packages.init();
|
|
6494
|
+
const reactorClientModule = await clientInitializer(documentModels);
|
|
6348
6495
|
const reactorClient = reactorClientModule.client;
|
|
6349
6496
|
const syncManager = reactorClientModule.reactorModule?.syncModule?.syncManager;
|
|
6350
6497
|
if (!syncManager) throw new Error("SyncManager not available from ReactorClientModule");
|
|
@@ -6353,7 +6500,7 @@ async function initializeAndStartAPI(clientInitializer, options, processorApp) {
|
|
|
6353
6500
|
const documentModelRegistry = reactorClientModule.reactorModule?.documentModelRegistry;
|
|
6354
6501
|
if (!documentModelRegistry) throw new Error("DocumentModelRegistry not available from ReactorClientModule");
|
|
6355
6502
|
return {
|
|
6356
|
-
...await _setupAPI(reactorClient, syncManager, reactorProcessorManager, httpAdapter, authFetchMiddleware, authService, port, packages, relationalDb, analyticsStore, documentPermissionService, processors, subgraphs, options, auth, processorApp, (reactorClientModule.reactorModule?.readModelCoordinator)?.readModels ?? [], attachments, authorizationService, documentModelRegistry),
|
|
6503
|
+
...await _setupAPI(reactorClient, syncManager, reactorProcessorManager, httpAdapter, authFetchMiddleware, authService, port, packages, relationalDb, analyticsStore, documentPermissionService, processors, subgraphs, options, auth, processorApp, (reactorClientModule.reactorModule?.readModelCoordinator)?.readModels ?? [], attachments, authorizationService, documentModelRegistry, dbClosers),
|
|
6357
6504
|
client: reactorClient,
|
|
6358
6505
|
syncManager,
|
|
6359
6506
|
documentModelRegistry
|
|
@@ -6459,6 +6606,7 @@ var PackageManagementService = class {
|
|
|
6459
6606
|
}
|
|
6460
6607
|
};
|
|
6461
6608
|
//#endregion
|
|
6462
|
-
export { ADMIN_USERS, ActionContextInputSchema, ActionInputSchema, AddRelationshipDocument, AnalyticsSubgraph, AttachmentInputSchema, AuthService, AuthSubgraph, BaseSubgraph, ChannelMetaInputSchema, CreateDocumentDocument, CreateEmptyDocumentDocument, DeleteDocumentDocument, DeleteDocumentsDocument, DocumentChangeType, DocumentChangeTypeSchema, DocumentChangesDocument, DocumentOperationsFilterInputSchema, DocumentPermissionService, FindDocumentsDocument, GetDocumentDocument, GetDocumentIncomingRelationshipsDocument, GetDocumentModelsDocument, GetDocumentOperationsDocument, GetDocumentOutgoingRelationshipsDocument, GetDocumentWithOperationsDocument, GetJobStatusDocument, GraphQLManager, HttpDocumentModelLoader, HttpPackageLoader, ImportPackageLoader, InMemoryPackageStorage, JobChangesDocument, MoveRelationshipDocument, MutateDocumentAsyncDocument, MutateDocumentDocument, OperationContextInputSchema, OperationInputSchema, OperationWithContextInputSchema, OperationsFilterInputSchema, PackageManagementService, PackageManager, PackagesSubgraph, PagingInputSchema, PhDocumentFieldsFragmentDoc, PollSyncEnvelopesDocument, PropagationMode, PropagationModeSchema, PushSyncEnvelopesDocument, ReactorSignerAppInputSchema, ReactorSignerInputSchema, ReactorSignerUserInputSchema, ReactorSubgraph, RemoteCursorInputSchema, RemoteFilterInputSchema, RemoveRelationshipDocument, RenameDocumentDocument, SearchFilterInputSchema, SyncEnvelopeInputSchema, SyncEnvelopeType, SyncEnvelopeTypeSchema, SystemSubgraph, TouchChannelDocument, TouchChannelInputSchema, ViewFilterInputSchema, buildGraphQlDocument, buildGraphQlDriveDocument, buildGraphqlOperation, buildGraphqlOperations, buildSubgraphSchemaModule, createAuthFetchMiddleware, createGatewayAdapter, createHttpAdapter, createMergedSchema, createReactorGraphQLClient, createSchema, definedNonNullAnySchema, driveIdFromUrl, generateDocumentModelSchema, getAuthContext, getDbClient, getDocumentModelSchemaName, getDocumentModelTypeDefs, getGitHash, getSdk, getUniqueDocumentModels, getVersion, initAnalyticsStoreSql,
|
|
6609
|
+
export { ADMIN_USERS, ActionContextInputSchema, ActionInputSchema, AddRelationshipDocument, AnalyticsSubgraph, AttachmentInputSchema, AuthService, AuthSubgraph, BaseSubgraph, ChannelMetaInputSchema, CreateDocumentDocument, CreateEmptyDocumentDocument, DeleteDocumentDocument, DeleteDocumentsDocument, DocumentChangeType, DocumentChangeTypeSchema, DocumentChangesDocument, DocumentOperationsFilterInputSchema, DocumentPermissionService, FindDocumentsDocument, GetDocumentDocument, GetDocumentIncomingRelationshipsDocument, GetDocumentModelsDocument, GetDocumentOperationsDocument, GetDocumentOutgoingRelationshipsDocument, GetDocumentWithOperationsDocument, GetJobStatusDocument, GraphQLManager, HttpDocumentModelLoader, HttpPackageLoader, ImportPackageLoader, InMemoryPackageStorage, JobChangesDocument, MoveRelationshipDocument, MutateDocumentAsyncDocument, MutateDocumentDocument, OperationContextInputSchema, OperationInputSchema, OperationWithContextInputSchema, OperationsFilterInputSchema, PackageManagementService, PackageManager, PackagesSubgraph, PagingInputSchema, PhDocumentFieldsFragmentDoc, PollSyncEnvelopesDocument, PropagationMode, PropagationModeSchema, PushSyncEnvelopesDocument, ReactorSignerAppInputSchema, ReactorSignerInputSchema, ReactorSignerUserInputSchema, ReactorSubgraph, RemoteCursorInputSchema, RemoteFilterInputSchema, RemoveRelationshipDocument, RenameDocumentDocument, SearchFilterInputSchema, SyncEnvelopeInputSchema, SyncEnvelopeType, SyncEnvelopeTypeSchema, SystemSubgraph, TouchChannelDocument, TouchChannelInputSchema, ViewFilterInputSchema, buildGraphQlDocument, buildGraphQlDriveDocument, buildGraphqlOperation, buildGraphqlOperations, buildSubgraphSchemaModule, createAuthFetchMiddleware, createGatewayAdapter, createHttpAdapter, createMergedSchema, createReactorGraphQLClient, createSchema, definedNonNullAnySchema, driveIdFromUrl, generateDocumentModelSchema, getAuthContext, getDbClient, getDocumentModelSchemaName, getDocumentModelTypeDefs, getGitHash, getGitUrl, getSdk, getUniqueDocumentModels, getVersion, initAnalyticsStoreSql, initializeAndStartAPI, isDefinedNonNullAny, isSubgraphClass, parseDriveUrl, renderGraphqlPlayground };
|
|
6463
6610
|
|
|
6464
|
-
//# sourceMappingURL=index.mjs.map
|
|
6611
|
+
//# sourceMappingURL=index.mjs.map
|
|
6612
|
+
//# debugId=81df4612-dfa1-5785-acec-bca9f4770f2d
|