@ocap/gql 1.28.8 → 1.29.0
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/esm/index.d.mts +76 -0
- package/esm/index.mjs +254 -0
- package/esm/ws.d.mts +37 -0
- package/esm/ws.mjs +41 -0
- package/lib/_virtual/rolldown_runtime.cjs +29 -0
- package/lib/index.cjs +261 -0
- package/lib/index.d.cts +76 -0
- package/lib/ws.cjs +43 -0
- package/lib/ws.d.cts +37 -0
- package/package.json +33 -8
- package/lib/index.js +0 -167
- package/lib/ws.js +0 -31
package/esm/index.d.mts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Server } from "node:http";
|
|
2
|
+
import { GraphQLError } from "graphql";
|
|
3
|
+
import * as http0 from "http";
|
|
4
|
+
import { IResolver, IResolverContext, TIndexedAccountState, TIndexedAssetState, TIndexedDelegationState, TIndexedFactoryState, TIndexedRollupBlock, TIndexedRollupState, TIndexedStakeState, TIndexedTokenState, TIndexedTransaction, TPageInfo } from "@ocap/types";
|
|
5
|
+
import { Express } from "express";
|
|
6
|
+
|
|
7
|
+
//#region src/index.d.ts
|
|
8
|
+
interface ResolverResult {
|
|
9
|
+
paging?: TPageInfo;
|
|
10
|
+
[key: string]: any;
|
|
11
|
+
}
|
|
12
|
+
interface WrapResolverResult {
|
|
13
|
+
code: string;
|
|
14
|
+
page: TPageInfo | Record<string, never> | null;
|
|
15
|
+
[key: string]: any;
|
|
16
|
+
}
|
|
17
|
+
declare const wrapResolver: (_name: string, keys: string | string[], callback: () => Promise<ResolverResult | null> | ResolverResult | null | string) => Promise<WrapResolverResult>;
|
|
18
|
+
interface RequestContext {
|
|
19
|
+
headers?: Record<string, string>;
|
|
20
|
+
ip?: string;
|
|
21
|
+
_remoteAddress?: string;
|
|
22
|
+
connection?: {
|
|
23
|
+
remoteAddress?: string;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
declare const formatContext: (ctx?: RequestContext) => IResolverContext;
|
|
27
|
+
type Resolver = IResolver;
|
|
28
|
+
type ResolverFn = (args: unknown, ctx: unknown) => Promise<WrapResolverResult>;
|
|
29
|
+
declare const createResolvers: (resolver: Resolver) => Record<string, ResolverFn>;
|
|
30
|
+
interface HttpHandlerOptions {
|
|
31
|
+
resolver?: IResolver;
|
|
32
|
+
onError?: (err: GraphQLError) => void;
|
|
33
|
+
graphiql?: boolean;
|
|
34
|
+
}
|
|
35
|
+
declare const createHttpHandler: ({
|
|
36
|
+
resolver,
|
|
37
|
+
onError,
|
|
38
|
+
graphiql
|
|
39
|
+
}: HttpHandlerOptions) => (request: http0.IncomingMessage & {
|
|
40
|
+
url: string;
|
|
41
|
+
}, response: http0.ServerResponse & {
|
|
42
|
+
json?: (data: unknown) => void;
|
|
43
|
+
}) => Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* IndexDB table with event subscription for WebSocket broadcasts
|
|
46
|
+
* Generic type T represents the indexed state type for the table
|
|
47
|
+
*/
|
|
48
|
+
interface IndexdbTable<T = unknown> {
|
|
49
|
+
on: (event: string, callback: (data: T) => void) => void;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* IndexDB interface for WebSocket event broadcasting
|
|
53
|
+
* Uses specific indexed state types from @ocap/types
|
|
54
|
+
*/
|
|
55
|
+
interface Indexdb {
|
|
56
|
+
account: IndexdbTable<TIndexedAccountState>;
|
|
57
|
+
asset: IndexdbTable<TIndexedAssetState>;
|
|
58
|
+
delegation: IndexdbTable<TIndexedDelegationState>;
|
|
59
|
+
rollup: IndexdbTable<TIndexedRollupState>;
|
|
60
|
+
stake: IndexdbTable<TIndexedStakeState>;
|
|
61
|
+
tx: IndexdbTable<TIndexedTransaction>;
|
|
62
|
+
token: IndexdbTable<TIndexedTokenState>;
|
|
63
|
+
rollupBlock: IndexdbTable<TIndexedRollupBlock>;
|
|
64
|
+
factory: IndexdbTable<TIndexedFactoryState>;
|
|
65
|
+
[key: string]: IndexdbTable;
|
|
66
|
+
}
|
|
67
|
+
interface SocketHandlerOptions {
|
|
68
|
+
app: Express;
|
|
69
|
+
indexdb: Indexdb;
|
|
70
|
+
}
|
|
71
|
+
declare const createSocketHandler: ({
|
|
72
|
+
app,
|
|
73
|
+
indexdb
|
|
74
|
+
}: SocketHandlerOptions) => Server;
|
|
75
|
+
//#endregion
|
|
76
|
+
export { type Resolver, createHttpHandler, createResolvers, createSocketHandler, formatContext, wrapResolver };
|
package/esm/index.mjs
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import createWebsocketServer from "./ws.mjs";
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import schemaSource from "@ocap/schema";
|
|
4
|
+
import { graphqlHTTP } from "express-graphql";
|
|
5
|
+
import { buildSchema } from "graphql";
|
|
6
|
+
import get from "lodash/get.js";
|
|
7
|
+
|
|
8
|
+
//#region src/index.ts
|
|
9
|
+
const wrapResolver = async (_name, keys, callback) => {
|
|
10
|
+
const result = await callback();
|
|
11
|
+
const resultsMap = {};
|
|
12
|
+
if (Array.isArray(keys)) keys.forEach((key) => {
|
|
13
|
+
resultsMap[key] = result[key];
|
|
14
|
+
});
|
|
15
|
+
else resultsMap[keys] = result;
|
|
16
|
+
return {
|
|
17
|
+
code: "OK",
|
|
18
|
+
page: result ? result.paging || {} : null,
|
|
19
|
+
...resultsMap
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
const formatContext = (ctx = {}) => {
|
|
23
|
+
return {
|
|
24
|
+
featureSwitch: {},
|
|
25
|
+
gasStakeHeaders: {
|
|
26
|
+
token: get(ctx, "headers[x-gas-payer-sig]", ""),
|
|
27
|
+
pk: get(ctx, "headers[x-gas-payer-pk]", "")
|
|
28
|
+
},
|
|
29
|
+
request: {
|
|
30
|
+
userAgent: get(ctx, "headers.user-agent"),
|
|
31
|
+
ip: get(ctx, "headers.x-real-ip") || ctx.ip || ctx._remoteAddress || ctx.connection?.remoteAddress || "-",
|
|
32
|
+
id: get(ctx, "headers[x-request-id]") || get(ctx, "headers[X-Request-ID]") || "-"
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
const resolvers = [
|
|
37
|
+
{
|
|
38
|
+
fn: "sendTx",
|
|
39
|
+
dataKey: "hash"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
fn: "subscribe",
|
|
43
|
+
dataKey: "value"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
fn: "unsubscribe",
|
|
47
|
+
dataKey: "result"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
fn: "getAccountState",
|
|
51
|
+
dataKey: "state"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
fn: "getAssetState",
|
|
55
|
+
dataKey: "state"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
fn: "getFactoryState",
|
|
59
|
+
dataKey: "state"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
fn: "getDelegateState",
|
|
63
|
+
dataKey: "state"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
fn: "getTokenState",
|
|
67
|
+
dataKey: "state"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
fn: "getForgeState",
|
|
71
|
+
dataKey: "state"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
fn: "getStakeState",
|
|
75
|
+
dataKey: "state"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
fn: "getEvidenceState",
|
|
79
|
+
dataKey: "state"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
fn: "getRollupState",
|
|
83
|
+
dataKey: "state"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
fn: "getRollupBlock",
|
|
87
|
+
dataKey: "block"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
fn: "getTokenFactoryState",
|
|
91
|
+
dataKey: "state"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
fn: "getAccountTokens",
|
|
95
|
+
dataKey: "tokens"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
fn: "getBlock",
|
|
99
|
+
dataKey: "block"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
fn: "getBlocks",
|
|
103
|
+
dataKey: "blocks"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
fn: "getChainInfo",
|
|
107
|
+
dataKey: "info"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
fn: "getConfig",
|
|
111
|
+
dataKey: "config"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
fn: "getForgeStats",
|
|
115
|
+
dataKey: "forgeStats"
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
fn: "getNetInfo",
|
|
119
|
+
dataKey: "netInfo"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
fn: "getNodeInfo",
|
|
123
|
+
dataKey: "info"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
fn: "getTx",
|
|
127
|
+
dataKey: "info"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
fn: "getUnconfirmedTxs",
|
|
131
|
+
dataKey: "unconfirmedTxs"
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
fn: "getValidatorsInfo",
|
|
135
|
+
dataKey: "validatorsInfo"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
fn: "getTokenDistribution",
|
|
139
|
+
dataKey: "data"
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
fn: "listTransactions",
|
|
143
|
+
dataKey: ["transactions"]
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
fn: "listAssets",
|
|
147
|
+
dataKey: ["assets", "account"]
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
fn: "listAssetTransactions",
|
|
151
|
+
dataKey: ["transactions"]
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
fn: "listFactories",
|
|
155
|
+
dataKey: ["factories"]
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
fn: "listTokens",
|
|
159
|
+
dataKey: ["tokens"]
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
fn: "listTokenFactories",
|
|
163
|
+
dataKey: ["tokenFactories"]
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
fn: "listTopAccounts",
|
|
167
|
+
dataKey: ["accounts"]
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
fn: "listBlocks",
|
|
171
|
+
dataKey: ["blocks"]
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
fn: "listStakes",
|
|
175
|
+
dataKey: ["stakes"]
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
fn: "listRollups",
|
|
179
|
+
dataKey: ["rollups"]
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
fn: "listRollupBlocks",
|
|
183
|
+
dataKey: ["blocks"]
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
fn: "listRollupValidators",
|
|
187
|
+
dataKey: ["validators"]
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
fn: "listDelegations",
|
|
191
|
+
dataKey: ["delegations"]
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
fn: "listTokenFlows",
|
|
195
|
+
dataKey: "data"
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
fn: "verifyAccountRisk",
|
|
199
|
+
dataKey: "data"
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
fn: "search",
|
|
203
|
+
dataKey: ["results"]
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
fn: "estimateGas",
|
|
207
|
+
dataKey: ["estimate"]
|
|
208
|
+
}
|
|
209
|
+
];
|
|
210
|
+
const createResolvers = (resolver) => resolvers.reduce((acc, { fn, dataKey }) => {
|
|
211
|
+
acc[fn] = (args, ctx) => wrapResolver(fn, dataKey, () => {
|
|
212
|
+
const method = resolver[fn];
|
|
213
|
+
if (typeof method === "function") return method.call(resolver, args, formatContext(ctx));
|
|
214
|
+
return null;
|
|
215
|
+
});
|
|
216
|
+
return acc;
|
|
217
|
+
}, {});
|
|
218
|
+
const defaultErrorHandler = (err) => {
|
|
219
|
+
if (process.env.NODE_ENV !== "test") console.error("GraphQLError", err.originalError || err);
|
|
220
|
+
};
|
|
221
|
+
const MAX_BATCH_SIZE = 40;
|
|
222
|
+
const createHttpHandler = ({ resolver = {}, onError = defaultErrorHandler, graphiql = true }) => graphqlHTTP({
|
|
223
|
+
schema: buildSchema(schemaSource),
|
|
224
|
+
rootValue: createResolvers(resolver),
|
|
225
|
+
graphiql,
|
|
226
|
+
customValidateFn: (_schema, query) => {
|
|
227
|
+
if (query.definitions.map((x) => {
|
|
228
|
+
return (get(x, "selectionSet.selections") || []).map((y) => get(y, "name.value")).filter((name) => typeof name === "string");
|
|
229
|
+
}).reduce((arr, names) => {
|
|
230
|
+
arr.push(...names);
|
|
231
|
+
return arr;
|
|
232
|
+
}, []).length > MAX_BATCH_SIZE) throw new Error(`Batch query size exceeded allowed maximum batch size of ${MAX_BATCH_SIZE}`);
|
|
233
|
+
return true;
|
|
234
|
+
},
|
|
235
|
+
customFormatErrorFn: (err) => {
|
|
236
|
+
onError(err);
|
|
237
|
+
const isProd = process.env.NODE_ENV === "production";
|
|
238
|
+
return {
|
|
239
|
+
code: get(err, "originalError.code", "INTERNAL"),
|
|
240
|
+
message: err.message,
|
|
241
|
+
locations: err.locations || [],
|
|
242
|
+
stack: isProd ? [] : get(err, "originalError.stack", "").split("\n"),
|
|
243
|
+
path: err.path
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
const createSocketHandler = ({ app, indexdb }) => {
|
|
248
|
+
const httpServer = createServer(app);
|
|
249
|
+
createWebsocketServer({ indexdb }).attach(httpServer);
|
|
250
|
+
return httpServer;
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
//#endregion
|
|
254
|
+
export { createHttpHandler, createResolvers, createSocketHandler, formatContext, wrapResolver };
|
package/esm/ws.d.mts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Server } from "node:http";
|
|
2
|
+
import { TIndexedAccountState, TIndexedAssetState, TIndexedDelegationState, TIndexedFactoryState, TIndexedRollupBlock, TIndexedRollupState, TIndexedStakeState, TIndexedTokenState, TIndexedTransaction } from "@ocap/types";
|
|
3
|
+
|
|
4
|
+
//#region src/ws.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* IndexDB table with event subscription for WebSocket broadcasts
|
|
8
|
+
*/
|
|
9
|
+
interface IndexdbTable<T = unknown> {
|
|
10
|
+
on: (event: string, callback: (data: T) => void) => void;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* IndexDB interface for WebSocket event broadcasting
|
|
14
|
+
*/
|
|
15
|
+
interface Indexdb {
|
|
16
|
+
account: IndexdbTable<TIndexedAccountState>;
|
|
17
|
+
asset: IndexdbTable<TIndexedAssetState>;
|
|
18
|
+
delegation: IndexdbTable<TIndexedDelegationState>;
|
|
19
|
+
rollup: IndexdbTable<TIndexedRollupState>;
|
|
20
|
+
stake: IndexdbTable<TIndexedStakeState>;
|
|
21
|
+
tx: IndexdbTable<TIndexedTransaction>;
|
|
22
|
+
token: IndexdbTable<TIndexedTokenState>;
|
|
23
|
+
rollupBlock: IndexdbTable<TIndexedRollupBlock>;
|
|
24
|
+
factory: IndexdbTable<TIndexedFactoryState>;
|
|
25
|
+
[key: string]: IndexdbTable;
|
|
26
|
+
}
|
|
27
|
+
interface WsServerInstance {
|
|
28
|
+
broadcast: (topic: string, data: unknown) => void;
|
|
29
|
+
attach: (server: Server) => void;
|
|
30
|
+
}
|
|
31
|
+
declare function createWebsocketServer({
|
|
32
|
+
indexdb
|
|
33
|
+
}: {
|
|
34
|
+
indexdb: Indexdb;
|
|
35
|
+
}): WsServerInstance;
|
|
36
|
+
//#endregion
|
|
37
|
+
export { createWebsocketServer as default };
|
package/esm/ws.mjs
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { WsServer } from "@arcblock/ws";
|
|
2
|
+
|
|
3
|
+
//#region src/ws.ts
|
|
4
|
+
function createWebsocketServer({ indexdb }) {
|
|
5
|
+
const wsServer = new WsServer({});
|
|
6
|
+
const mutableTables = [
|
|
7
|
+
"account",
|
|
8
|
+
"asset",
|
|
9
|
+
"delegation",
|
|
10
|
+
"rollup",
|
|
11
|
+
"stake"
|
|
12
|
+
];
|
|
13
|
+
const appendOnlyTables = [
|
|
14
|
+
"tx",
|
|
15
|
+
"token",
|
|
16
|
+
"rollupBlock",
|
|
17
|
+
"factory"
|
|
18
|
+
];
|
|
19
|
+
const map = {
|
|
20
|
+
insert: "create",
|
|
21
|
+
update: "update"
|
|
22
|
+
};
|
|
23
|
+
mutableTables.forEach((table) => {
|
|
24
|
+
["insert", "update"].forEach((action) => {
|
|
25
|
+
indexdb[table].on(action, (data) => wsServer.broadcast([table, map[action]].join("."), data));
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
appendOnlyTables.forEach((table) => {
|
|
29
|
+
["insert"].forEach((action) => {
|
|
30
|
+
indexdb[table].on(action, (data) => wsServer.broadcast([table, map[action]].join("."), data));
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
indexdb.tx.on("insert", (tx) => {
|
|
34
|
+
const typeUrl = (tx.tx?.itxJson)?.type_url?.split(":").pop();
|
|
35
|
+
if (typeUrl) wsServer.broadcast(`tx.${typeUrl}`, tx);
|
|
36
|
+
});
|
|
37
|
+
return wsServer;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
export { createWebsocketServer as default };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region rolldown:runtime
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
13
|
+
__defProp(to, key, {
|
|
14
|
+
get: ((k) => from[k]).bind(null, key),
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
23
|
+
value: mod,
|
|
24
|
+
enumerable: true
|
|
25
|
+
}) : target, mod));
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
|
|
29
|
+
exports.__toESM = __toESM;
|
package/lib/index.cjs
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
|
|
2
|
+
const require_ws = require('./ws.cjs');
|
|
3
|
+
let node_http = require("node:http");
|
|
4
|
+
let _ocap_schema = require("@ocap/schema");
|
|
5
|
+
_ocap_schema = require_rolldown_runtime.__toESM(_ocap_schema);
|
|
6
|
+
let express_graphql = require("express-graphql");
|
|
7
|
+
let graphql = require("graphql");
|
|
8
|
+
let lodash_get = require("lodash/get");
|
|
9
|
+
lodash_get = require_rolldown_runtime.__toESM(lodash_get);
|
|
10
|
+
|
|
11
|
+
//#region src/index.ts
|
|
12
|
+
const wrapResolver = async (_name, keys, callback) => {
|
|
13
|
+
const result = await callback();
|
|
14
|
+
const resultsMap = {};
|
|
15
|
+
if (Array.isArray(keys)) keys.forEach((key) => {
|
|
16
|
+
resultsMap[key] = result[key];
|
|
17
|
+
});
|
|
18
|
+
else resultsMap[keys] = result;
|
|
19
|
+
return {
|
|
20
|
+
code: "OK",
|
|
21
|
+
page: result ? result.paging || {} : null,
|
|
22
|
+
...resultsMap
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
const formatContext = (ctx = {}) => {
|
|
26
|
+
return {
|
|
27
|
+
featureSwitch: {},
|
|
28
|
+
gasStakeHeaders: {
|
|
29
|
+
token: (0, lodash_get.default)(ctx, "headers[x-gas-payer-sig]", ""),
|
|
30
|
+
pk: (0, lodash_get.default)(ctx, "headers[x-gas-payer-pk]", "")
|
|
31
|
+
},
|
|
32
|
+
request: {
|
|
33
|
+
userAgent: (0, lodash_get.default)(ctx, "headers.user-agent"),
|
|
34
|
+
ip: (0, lodash_get.default)(ctx, "headers.x-real-ip") || ctx.ip || ctx._remoteAddress || ctx.connection?.remoteAddress || "-",
|
|
35
|
+
id: (0, lodash_get.default)(ctx, "headers[x-request-id]") || (0, lodash_get.default)(ctx, "headers[X-Request-ID]") || "-"
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
const resolvers = [
|
|
40
|
+
{
|
|
41
|
+
fn: "sendTx",
|
|
42
|
+
dataKey: "hash"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
fn: "subscribe",
|
|
46
|
+
dataKey: "value"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
fn: "unsubscribe",
|
|
50
|
+
dataKey: "result"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
fn: "getAccountState",
|
|
54
|
+
dataKey: "state"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
fn: "getAssetState",
|
|
58
|
+
dataKey: "state"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
fn: "getFactoryState",
|
|
62
|
+
dataKey: "state"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
fn: "getDelegateState",
|
|
66
|
+
dataKey: "state"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
fn: "getTokenState",
|
|
70
|
+
dataKey: "state"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
fn: "getForgeState",
|
|
74
|
+
dataKey: "state"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
fn: "getStakeState",
|
|
78
|
+
dataKey: "state"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
fn: "getEvidenceState",
|
|
82
|
+
dataKey: "state"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
fn: "getRollupState",
|
|
86
|
+
dataKey: "state"
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
fn: "getRollupBlock",
|
|
90
|
+
dataKey: "block"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
fn: "getTokenFactoryState",
|
|
94
|
+
dataKey: "state"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
fn: "getAccountTokens",
|
|
98
|
+
dataKey: "tokens"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
fn: "getBlock",
|
|
102
|
+
dataKey: "block"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
fn: "getBlocks",
|
|
106
|
+
dataKey: "blocks"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
fn: "getChainInfo",
|
|
110
|
+
dataKey: "info"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
fn: "getConfig",
|
|
114
|
+
dataKey: "config"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
fn: "getForgeStats",
|
|
118
|
+
dataKey: "forgeStats"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
fn: "getNetInfo",
|
|
122
|
+
dataKey: "netInfo"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
fn: "getNodeInfo",
|
|
126
|
+
dataKey: "info"
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
fn: "getTx",
|
|
130
|
+
dataKey: "info"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
fn: "getUnconfirmedTxs",
|
|
134
|
+
dataKey: "unconfirmedTxs"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
fn: "getValidatorsInfo",
|
|
138
|
+
dataKey: "validatorsInfo"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
fn: "getTokenDistribution",
|
|
142
|
+
dataKey: "data"
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
fn: "listTransactions",
|
|
146
|
+
dataKey: ["transactions"]
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
fn: "listAssets",
|
|
150
|
+
dataKey: ["assets", "account"]
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
fn: "listAssetTransactions",
|
|
154
|
+
dataKey: ["transactions"]
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
fn: "listFactories",
|
|
158
|
+
dataKey: ["factories"]
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
fn: "listTokens",
|
|
162
|
+
dataKey: ["tokens"]
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
fn: "listTokenFactories",
|
|
166
|
+
dataKey: ["tokenFactories"]
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
fn: "listTopAccounts",
|
|
170
|
+
dataKey: ["accounts"]
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
fn: "listBlocks",
|
|
174
|
+
dataKey: ["blocks"]
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
fn: "listStakes",
|
|
178
|
+
dataKey: ["stakes"]
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
fn: "listRollups",
|
|
182
|
+
dataKey: ["rollups"]
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
fn: "listRollupBlocks",
|
|
186
|
+
dataKey: ["blocks"]
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
fn: "listRollupValidators",
|
|
190
|
+
dataKey: ["validators"]
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
fn: "listDelegations",
|
|
194
|
+
dataKey: ["delegations"]
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
fn: "listTokenFlows",
|
|
198
|
+
dataKey: "data"
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
fn: "verifyAccountRisk",
|
|
202
|
+
dataKey: "data"
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
fn: "search",
|
|
206
|
+
dataKey: ["results"]
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
fn: "estimateGas",
|
|
210
|
+
dataKey: ["estimate"]
|
|
211
|
+
}
|
|
212
|
+
];
|
|
213
|
+
const createResolvers = (resolver) => resolvers.reduce((acc, { fn, dataKey }) => {
|
|
214
|
+
acc[fn] = (args, ctx) => wrapResolver(fn, dataKey, () => {
|
|
215
|
+
const method = resolver[fn];
|
|
216
|
+
if (typeof method === "function") return method.call(resolver, args, formatContext(ctx));
|
|
217
|
+
return null;
|
|
218
|
+
});
|
|
219
|
+
return acc;
|
|
220
|
+
}, {});
|
|
221
|
+
const defaultErrorHandler = (err) => {
|
|
222
|
+
if (process.env.NODE_ENV !== "test") console.error("GraphQLError", err.originalError || err);
|
|
223
|
+
};
|
|
224
|
+
const MAX_BATCH_SIZE = 40;
|
|
225
|
+
const createHttpHandler = ({ resolver = {}, onError = defaultErrorHandler, graphiql = true }) => (0, express_graphql.graphqlHTTP)({
|
|
226
|
+
schema: (0, graphql.buildSchema)(_ocap_schema.default),
|
|
227
|
+
rootValue: createResolvers(resolver),
|
|
228
|
+
graphiql,
|
|
229
|
+
customValidateFn: (_schema, query) => {
|
|
230
|
+
if (query.definitions.map((x) => {
|
|
231
|
+
return ((0, lodash_get.default)(x, "selectionSet.selections") || []).map((y) => (0, lodash_get.default)(y, "name.value")).filter((name) => typeof name === "string");
|
|
232
|
+
}).reduce((arr, names) => {
|
|
233
|
+
arr.push(...names);
|
|
234
|
+
return arr;
|
|
235
|
+
}, []).length > MAX_BATCH_SIZE) throw new Error(`Batch query size exceeded allowed maximum batch size of ${MAX_BATCH_SIZE}`);
|
|
236
|
+
return true;
|
|
237
|
+
},
|
|
238
|
+
customFormatErrorFn: (err) => {
|
|
239
|
+
onError(err);
|
|
240
|
+
const isProd = process.env.NODE_ENV === "production";
|
|
241
|
+
return {
|
|
242
|
+
code: (0, lodash_get.default)(err, "originalError.code", "INTERNAL"),
|
|
243
|
+
message: err.message,
|
|
244
|
+
locations: err.locations || [],
|
|
245
|
+
stack: isProd ? [] : (0, lodash_get.default)(err, "originalError.stack", "").split("\n"),
|
|
246
|
+
path: err.path
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
const createSocketHandler = ({ app, indexdb }) => {
|
|
251
|
+
const httpServer = (0, node_http.createServer)(app);
|
|
252
|
+
require_ws.default({ indexdb }).attach(httpServer);
|
|
253
|
+
return httpServer;
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
//#endregion
|
|
257
|
+
exports.createHttpHandler = createHttpHandler;
|
|
258
|
+
exports.createResolvers = createResolvers;
|
|
259
|
+
exports.createSocketHandler = createSocketHandler;
|
|
260
|
+
exports.formatContext = formatContext;
|
|
261
|
+
exports.wrapResolver = wrapResolver;
|
package/lib/index.d.cts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as http0 from "http";
|
|
2
|
+
import { Server } from "node:http";
|
|
3
|
+
import { IResolver, IResolverContext, TIndexedAccountState, TIndexedAssetState, TIndexedDelegationState, TIndexedFactoryState, TIndexedRollupBlock, TIndexedRollupState, TIndexedStakeState, TIndexedTokenState, TIndexedTransaction, TPageInfo } from "@ocap/types";
|
|
4
|
+
import { Express } from "express";
|
|
5
|
+
import { GraphQLError } from "graphql";
|
|
6
|
+
|
|
7
|
+
//#region src/index.d.ts
|
|
8
|
+
interface ResolverResult {
|
|
9
|
+
paging?: TPageInfo;
|
|
10
|
+
[key: string]: any;
|
|
11
|
+
}
|
|
12
|
+
interface WrapResolverResult {
|
|
13
|
+
code: string;
|
|
14
|
+
page: TPageInfo | Record<string, never> | null;
|
|
15
|
+
[key: string]: any;
|
|
16
|
+
}
|
|
17
|
+
declare const wrapResolver: (_name: string, keys: string | string[], callback: () => Promise<ResolverResult | null> | ResolverResult | null | string) => Promise<WrapResolverResult>;
|
|
18
|
+
interface RequestContext {
|
|
19
|
+
headers?: Record<string, string>;
|
|
20
|
+
ip?: string;
|
|
21
|
+
_remoteAddress?: string;
|
|
22
|
+
connection?: {
|
|
23
|
+
remoteAddress?: string;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
declare const formatContext: (ctx?: RequestContext) => IResolverContext;
|
|
27
|
+
type Resolver = IResolver;
|
|
28
|
+
type ResolverFn = (args: unknown, ctx: unknown) => Promise<WrapResolverResult>;
|
|
29
|
+
declare const createResolvers: (resolver: Resolver) => Record<string, ResolverFn>;
|
|
30
|
+
interface HttpHandlerOptions {
|
|
31
|
+
resolver?: IResolver;
|
|
32
|
+
onError?: (err: GraphQLError) => void;
|
|
33
|
+
graphiql?: boolean;
|
|
34
|
+
}
|
|
35
|
+
declare const createHttpHandler: ({
|
|
36
|
+
resolver,
|
|
37
|
+
onError,
|
|
38
|
+
graphiql
|
|
39
|
+
}: HttpHandlerOptions) => (request: http0.IncomingMessage & {
|
|
40
|
+
url: string;
|
|
41
|
+
}, response: http0.ServerResponse & {
|
|
42
|
+
json?: (data: unknown) => void;
|
|
43
|
+
}) => Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* IndexDB table with event subscription for WebSocket broadcasts
|
|
46
|
+
* Generic type T represents the indexed state type for the table
|
|
47
|
+
*/
|
|
48
|
+
interface IndexdbTable<T = unknown> {
|
|
49
|
+
on: (event: string, callback: (data: T) => void) => void;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* IndexDB interface for WebSocket event broadcasting
|
|
53
|
+
* Uses specific indexed state types from @ocap/types
|
|
54
|
+
*/
|
|
55
|
+
interface Indexdb {
|
|
56
|
+
account: IndexdbTable<TIndexedAccountState>;
|
|
57
|
+
asset: IndexdbTable<TIndexedAssetState>;
|
|
58
|
+
delegation: IndexdbTable<TIndexedDelegationState>;
|
|
59
|
+
rollup: IndexdbTable<TIndexedRollupState>;
|
|
60
|
+
stake: IndexdbTable<TIndexedStakeState>;
|
|
61
|
+
tx: IndexdbTable<TIndexedTransaction>;
|
|
62
|
+
token: IndexdbTable<TIndexedTokenState>;
|
|
63
|
+
rollupBlock: IndexdbTable<TIndexedRollupBlock>;
|
|
64
|
+
factory: IndexdbTable<TIndexedFactoryState>;
|
|
65
|
+
[key: string]: IndexdbTable;
|
|
66
|
+
}
|
|
67
|
+
interface SocketHandlerOptions {
|
|
68
|
+
app: Express;
|
|
69
|
+
indexdb: Indexdb;
|
|
70
|
+
}
|
|
71
|
+
declare const createSocketHandler: ({
|
|
72
|
+
app,
|
|
73
|
+
indexdb
|
|
74
|
+
}: SocketHandlerOptions) => Server;
|
|
75
|
+
//#endregion
|
|
76
|
+
export { type Resolver, createHttpHandler, createResolvers, createSocketHandler, formatContext, wrapResolver };
|
package/lib/ws.cjs
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
|
+
const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
|
|
3
|
+
let _arcblock_ws = require("@arcblock/ws");
|
|
4
|
+
|
|
5
|
+
//#region src/ws.ts
|
|
6
|
+
function createWebsocketServer({ indexdb }) {
|
|
7
|
+
const wsServer = new _arcblock_ws.WsServer({});
|
|
8
|
+
const mutableTables = [
|
|
9
|
+
"account",
|
|
10
|
+
"asset",
|
|
11
|
+
"delegation",
|
|
12
|
+
"rollup",
|
|
13
|
+
"stake"
|
|
14
|
+
];
|
|
15
|
+
const appendOnlyTables = [
|
|
16
|
+
"tx",
|
|
17
|
+
"token",
|
|
18
|
+
"rollupBlock",
|
|
19
|
+
"factory"
|
|
20
|
+
];
|
|
21
|
+
const map = {
|
|
22
|
+
insert: "create",
|
|
23
|
+
update: "update"
|
|
24
|
+
};
|
|
25
|
+
mutableTables.forEach((table) => {
|
|
26
|
+
["insert", "update"].forEach((action) => {
|
|
27
|
+
indexdb[table].on(action, (data) => wsServer.broadcast([table, map[action]].join("."), data));
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
appendOnlyTables.forEach((table) => {
|
|
31
|
+
["insert"].forEach((action) => {
|
|
32
|
+
indexdb[table].on(action, (data) => wsServer.broadcast([table, map[action]].join("."), data));
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
indexdb.tx.on("insert", (tx) => {
|
|
36
|
+
const typeUrl = (tx.tx?.itxJson)?.type_url?.split(":").pop();
|
|
37
|
+
if (typeUrl) wsServer.broadcast(`tx.${typeUrl}`, tx);
|
|
38
|
+
});
|
|
39
|
+
return wsServer;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
//#endregion
|
|
43
|
+
exports.default = createWebsocketServer;
|
package/lib/ws.d.cts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Server } from "node:http";
|
|
2
|
+
import { TIndexedAccountState, TIndexedAssetState, TIndexedDelegationState, TIndexedFactoryState, TIndexedRollupBlock, TIndexedRollupState, TIndexedStakeState, TIndexedTokenState, TIndexedTransaction } from "@ocap/types";
|
|
3
|
+
|
|
4
|
+
//#region src/ws.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* IndexDB table with event subscription for WebSocket broadcasts
|
|
8
|
+
*/
|
|
9
|
+
interface IndexdbTable<T = unknown> {
|
|
10
|
+
on: (event: string, callback: (data: T) => void) => void;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* IndexDB interface for WebSocket event broadcasting
|
|
14
|
+
*/
|
|
15
|
+
interface Indexdb {
|
|
16
|
+
account: IndexdbTable<TIndexedAccountState>;
|
|
17
|
+
asset: IndexdbTable<TIndexedAssetState>;
|
|
18
|
+
delegation: IndexdbTable<TIndexedDelegationState>;
|
|
19
|
+
rollup: IndexdbTable<TIndexedRollupState>;
|
|
20
|
+
stake: IndexdbTable<TIndexedStakeState>;
|
|
21
|
+
tx: IndexdbTable<TIndexedTransaction>;
|
|
22
|
+
token: IndexdbTable<TIndexedTokenState>;
|
|
23
|
+
rollupBlock: IndexdbTable<TIndexedRollupBlock>;
|
|
24
|
+
factory: IndexdbTable<TIndexedFactoryState>;
|
|
25
|
+
[key: string]: IndexdbTable;
|
|
26
|
+
}
|
|
27
|
+
interface WsServerInstance {
|
|
28
|
+
broadcast: (topic: string, data: unknown) => void;
|
|
29
|
+
attach: (server: Server) => void;
|
|
30
|
+
}
|
|
31
|
+
declare function createWebsocketServer({
|
|
32
|
+
indexdb
|
|
33
|
+
}: {
|
|
34
|
+
indexdb: Indexdb;
|
|
35
|
+
}): WsServerInstance;
|
|
36
|
+
//#endregion
|
|
37
|
+
export { createWebsocketServer as default };
|
package/package.json
CHANGED
|
@@ -3,13 +3,36 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.29.0",
|
|
7
7
|
"description": "Resolver middleware for ocap adapters",
|
|
8
|
-
"
|
|
8
|
+
"type": "module",
|
|
9
|
+
"main": "./lib/index.cjs",
|
|
10
|
+
"module": "./esm/index.mjs",
|
|
11
|
+
"types": "./esm/index.d.mts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./esm/index.d.mts",
|
|
15
|
+
"import": "./esm/index.mjs",
|
|
16
|
+
"default": "./lib/index.cjs"
|
|
17
|
+
},
|
|
18
|
+
"./lib/*.js": {
|
|
19
|
+
"types": "./esm/*.d.mts",
|
|
20
|
+
"import": "./esm/*.mjs",
|
|
21
|
+
"default": "./lib/*.cjs"
|
|
22
|
+
},
|
|
23
|
+
"./lib/*": {
|
|
24
|
+
"types": "./esm/*.d.mts",
|
|
25
|
+
"import": "./esm/*.mjs",
|
|
26
|
+
"default": "./lib/*.cjs"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
9
29
|
"files": [
|
|
10
|
-
"lib"
|
|
30
|
+
"lib",
|
|
31
|
+
"esm"
|
|
11
32
|
],
|
|
12
33
|
"scripts": {
|
|
34
|
+
"build": "tsdown",
|
|
35
|
+
"prebuild": "rm -rf lib esm",
|
|
13
36
|
"lint": "biome check",
|
|
14
37
|
"lint:fix": "biome check --write",
|
|
15
38
|
"test": "bun test",
|
|
@@ -22,14 +45,16 @@
|
|
|
22
45
|
],
|
|
23
46
|
"license": "MIT",
|
|
24
47
|
"dependencies": {
|
|
25
|
-
"@arcblock/ws": "1.
|
|
26
|
-
"@ocap/schema": "1.
|
|
27
|
-
"debug": "^4.3
|
|
48
|
+
"@arcblock/ws": "1.29.0",
|
|
49
|
+
"@ocap/schema": "1.29.0",
|
|
50
|
+
"debug": "^4.4.3",
|
|
28
51
|
"express-graphql": "^0.12.0",
|
|
29
52
|
"graphql": "16.5.0",
|
|
30
|
-
"lodash": "^4.17.
|
|
53
|
+
"lodash": "^4.17.23"
|
|
31
54
|
},
|
|
32
55
|
"devDependencies": {
|
|
33
|
-
"express": "^4.
|
|
56
|
+
"@types/express": "^4.17.23",
|
|
57
|
+
"@types/lodash": "^4.17.13",
|
|
58
|
+
"express": "^4.22.1"
|
|
34
59
|
}
|
|
35
60
|
}
|
package/lib/index.js
DELETED
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
const get = require('lodash/get');
|
|
2
|
-
const { createServer } = require('node:http');
|
|
3
|
-
const { graphqlHTTP } = require('express-graphql');
|
|
4
|
-
const { buildSchema } = require('graphql');
|
|
5
|
-
const schemaSource = require('@ocap/schema');
|
|
6
|
-
|
|
7
|
-
// const debug = require('debug')(require('../package.json').name);
|
|
8
|
-
const createWebSocketServer = require('./ws');
|
|
9
|
-
|
|
10
|
-
const wrapResolver = async (_name, keys, callback) => {
|
|
11
|
-
const result = await callback();
|
|
12
|
-
|
|
13
|
-
const resultsMap = {};
|
|
14
|
-
if (Array.isArray(keys)) {
|
|
15
|
-
keys.forEach((key) => {
|
|
16
|
-
resultsMap[key] = result[key];
|
|
17
|
-
});
|
|
18
|
-
} else {
|
|
19
|
-
resultsMap[keys] = result;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return {
|
|
23
|
-
code: 'OK',
|
|
24
|
-
page: result ? result.paging || {} : null,
|
|
25
|
-
...resultsMap,
|
|
26
|
-
};
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const formatContext = (ctx = {}) => {
|
|
30
|
-
const featureSwitch = {};
|
|
31
|
-
const gasStakeHeaders = {
|
|
32
|
-
token: get(ctx, 'headers[x-gas-payer-sig]', ''),
|
|
33
|
-
pk: get(ctx, 'headers[x-gas-payer-pk]', ''),
|
|
34
|
-
};
|
|
35
|
-
const request = {
|
|
36
|
-
userAgent: get(ctx, 'headers.user-agent'),
|
|
37
|
-
ip: get(ctx, 'headers.x-real-ip') || ctx.ip || ctx._remoteAddress || ctx.connection?.remoteAddress || '-',
|
|
38
|
-
id: get(ctx, 'headers[x-request-id]') || get(ctx, 'headers[X-Request-ID]') || '-',
|
|
39
|
-
};
|
|
40
|
-
return {
|
|
41
|
-
featureSwitch,
|
|
42
|
-
gasStakeHeaders,
|
|
43
|
-
request,
|
|
44
|
-
};
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const resolvers = [
|
|
48
|
-
{ fn: 'sendTx', dataKey: 'hash' },
|
|
49
|
-
|
|
50
|
-
{ fn: 'subscribe', dataKey: 'value' },
|
|
51
|
-
{ fn: 'unsubscribe', dataKey: 'result' },
|
|
52
|
-
|
|
53
|
-
{ fn: 'getAccountState', dataKey: 'state' },
|
|
54
|
-
{ fn: 'getAssetState', dataKey: 'state' },
|
|
55
|
-
{ fn: 'getFactoryState', dataKey: 'state' },
|
|
56
|
-
{ fn: 'getDelegateState', dataKey: 'state' },
|
|
57
|
-
{ fn: 'getTokenState', dataKey: 'state' },
|
|
58
|
-
{ fn: 'getForgeState', dataKey: 'state' },
|
|
59
|
-
{ fn: 'getStakeState', dataKey: 'state' },
|
|
60
|
-
{ fn: 'getEvidenceState', dataKey: 'state' },
|
|
61
|
-
{ fn: 'getRollupState', dataKey: 'state' },
|
|
62
|
-
{ fn: 'getRollupBlock', dataKey: 'block' },
|
|
63
|
-
{ fn: 'getTokenFactoryState', dataKey: 'state' },
|
|
64
|
-
|
|
65
|
-
{ fn: 'getAccountTokens', dataKey: 'tokens' },
|
|
66
|
-
{ fn: 'getBlock', dataKey: 'block' },
|
|
67
|
-
{ fn: 'getBlocks', dataKey: 'blocks' },
|
|
68
|
-
{ fn: 'getChainInfo', dataKey: 'info' },
|
|
69
|
-
{ fn: 'getConfig', dataKey: 'config' },
|
|
70
|
-
{ fn: 'getForgeStats', dataKey: 'forgeStats' },
|
|
71
|
-
{ fn: 'getNetInfo', dataKey: 'netInfo' },
|
|
72
|
-
{ fn: 'getNodeInfo', dataKey: 'info' },
|
|
73
|
-
{ fn: 'getTx', dataKey: 'info' },
|
|
74
|
-
{ fn: 'getUnconfirmedTxs', dataKey: 'unconfirmedTxs' },
|
|
75
|
-
{ fn: 'getValidatorsInfo', dataKey: 'validatorsInfo' },
|
|
76
|
-
{ fn: 'getTokenDistribution', dataKey: 'data' },
|
|
77
|
-
|
|
78
|
-
{ fn: 'listTransactions', dataKey: ['transactions'] },
|
|
79
|
-
{ fn: 'listAssets', dataKey: ['assets', 'account'] },
|
|
80
|
-
{ fn: 'listAssetTransactions', dataKey: ['transactions'] },
|
|
81
|
-
{ fn: 'listFactories', dataKey: ['factories'] },
|
|
82
|
-
{ fn: 'listTokens', dataKey: ['tokens'] },
|
|
83
|
-
{ fn: 'listTokenFactories', dataKey: ['tokenFactories'] },
|
|
84
|
-
{ fn: 'listTopAccounts', dataKey: ['accounts'] },
|
|
85
|
-
{ fn: 'listBlocks', dataKey: ['blocks'] },
|
|
86
|
-
{ fn: 'listStakes', dataKey: ['stakes'] },
|
|
87
|
-
{ fn: 'listRollups', dataKey: ['rollups'] },
|
|
88
|
-
{ fn: 'listRollupBlocks', dataKey: ['blocks'] },
|
|
89
|
-
{ fn: 'listRollupValidators', dataKey: ['validators'] },
|
|
90
|
-
{ fn: 'listDelegations', dataKey: ['delegations'] },
|
|
91
|
-
{ fn: 'listTokenFlows', dataKey: 'data' },
|
|
92
|
-
{ fn: 'verifyAccountRisk', dataKey: 'data' },
|
|
93
|
-
|
|
94
|
-
{ fn: 'search', dataKey: ['results'] },
|
|
95
|
-
|
|
96
|
-
{ fn: 'estimateGas', dataKey: ['estimate'] },
|
|
97
|
-
];
|
|
98
|
-
|
|
99
|
-
const createResolvers = (resolver) =>
|
|
100
|
-
resolvers.reduce((acc, { fn, dataKey }) => {
|
|
101
|
-
acc[fn] = (args, ctx) => wrapResolver(fn, dataKey, () => resolver[fn](args, formatContext(ctx)));
|
|
102
|
-
return acc;
|
|
103
|
-
}, {});
|
|
104
|
-
|
|
105
|
-
const defaultErrorHandler = (err) => {
|
|
106
|
-
if (process.env.NODE_ENV !== 'test') {
|
|
107
|
-
console.error('GraphQLError', err.originalError || err);
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
const MAX_BATCH_SIZE = 40;
|
|
112
|
-
const createHttpHandler = ({ resolver, onError = defaultErrorHandler, graphiql = true }) =>
|
|
113
|
-
graphqlHTTP({
|
|
114
|
-
schema: buildSchema(schemaSource),
|
|
115
|
-
rootValue: createResolvers(resolver),
|
|
116
|
-
graphiql,
|
|
117
|
-
// NOTE: following is required because of following gql query validation issue
|
|
118
|
-
// https://stackoverflow.com/questions/56695262/graphql-error-fieldsconflict-fields-have-different-list-shapes
|
|
119
|
-
customValidateFn: (_schema, query) => {
|
|
120
|
-
const operations = query.definitions
|
|
121
|
-
.map((x) => {
|
|
122
|
-
const selections = get(x, 'selectionSet.selections') || [];
|
|
123
|
-
const names = selections.map((y) => get(y, 'name.value'));
|
|
124
|
-
return names;
|
|
125
|
-
})
|
|
126
|
-
.reduce((arr, names) => {
|
|
127
|
-
arr.push(...names);
|
|
128
|
-
return arr;
|
|
129
|
-
}, []);
|
|
130
|
-
|
|
131
|
-
if (operations.length > MAX_BATCH_SIZE) {
|
|
132
|
-
throw new Error(`Batch query size exceeded allowed maximum batch size of ${MAX_BATCH_SIZE}`);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return true;
|
|
136
|
-
},
|
|
137
|
-
|
|
138
|
-
// format custom error code
|
|
139
|
-
customFormatErrorFn: (err) => {
|
|
140
|
-
onError(err);
|
|
141
|
-
|
|
142
|
-
const isProd = process.env.NODE_ENV === 'production';
|
|
143
|
-
return {
|
|
144
|
-
code: get(err, 'originalError.code', 'INTERNAL'),
|
|
145
|
-
message: err.message,
|
|
146
|
-
locations: err.locations || [],
|
|
147
|
-
stack: isProd ? [] : get(err, 'originalError.stack', '').split('\n'),
|
|
148
|
-
path: err.path,
|
|
149
|
-
};
|
|
150
|
-
},
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
const createSocketHandler = ({ app, indexdb }) => {
|
|
154
|
-
const httpServer = createServer(app);
|
|
155
|
-
const wsServer = createWebSocketServer({ indexdb });
|
|
156
|
-
wsServer.attach(httpServer);
|
|
157
|
-
|
|
158
|
-
return httpServer;
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
module.exports = {
|
|
162
|
-
createHttpHandler,
|
|
163
|
-
createSocketHandler,
|
|
164
|
-
wrapResolver,
|
|
165
|
-
createResolvers,
|
|
166
|
-
formatContext,
|
|
167
|
-
};
|
package/lib/ws.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
const { WsServer } = require('@arcblock/ws');
|
|
2
|
-
|
|
3
|
-
module.exports = function createWebsocketServer({ indexdb }) {
|
|
4
|
-
const wsServer = new WsServer({});
|
|
5
|
-
|
|
6
|
-
const mutableTables = ['account', 'asset', 'delegation', 'rollup', 'stake'];
|
|
7
|
-
const appendOnlyTables = ['tx', 'token', 'rollupBlock', 'factory'];
|
|
8
|
-
const map = { insert: 'create', update: 'update' };
|
|
9
|
-
|
|
10
|
-
// For entities that support updating
|
|
11
|
-
mutableTables.forEach((table) => {
|
|
12
|
-
['insert', 'update'].forEach((action) => {
|
|
13
|
-
indexdb[table].on(action, (data) => wsServer.broadcast([table, map[action]].join('.'), data));
|
|
14
|
-
});
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
// For entities that are immutable after creation
|
|
18
|
-
appendOnlyTables.forEach((table) => {
|
|
19
|
-
['insert'].forEach((action) => {
|
|
20
|
-
indexdb[table].on(action, (data) => wsServer.broadcast([table, map[action]].join('.'), data));
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
// For tx sub topic
|
|
25
|
-
indexdb.tx.on('insert', (tx) => {
|
|
26
|
-
const typeUrl = tx.tx.itxJson.type_url.split(':').pop();
|
|
27
|
-
wsServer.broadcast(`tx.${typeUrl}`, tx);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
return wsServer;
|
|
31
|
-
};
|