@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.
@@ -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;
@@ -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.28.8",
6
+ "version": "1.29.0",
7
7
  "description": "Resolver middleware for ocap adapters",
8
- "main": "lib/index.js",
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.28.8",
26
- "@ocap/schema": "1.28.8",
27
- "debug": "^4.3.6",
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.21"
53
+ "lodash": "^4.17.23"
31
54
  },
32
55
  "devDependencies": {
33
- "express": "^4.19.2"
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
- };