@o1-labs/mina-archive-node-graphql 0.0.6 → 2.0.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.
Files changed (176) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +92 -268
  3. package/build/src/blockchain/utils.js.map +1 -1
  4. package/build/src/config.js +86 -0
  5. package/build/src/config.js.map +1 -0
  6. package/build/src/db/archive-node-adapter/archive-node-adapter.js +29 -2
  7. package/build/src/db/archive-node-adapter/archive-node-adapter.js.map +1 -1
  8. package/build/src/db/archive-node-adapter/postgres-options.js +35 -0
  9. package/build/src/db/archive-node-adapter/postgres-options.js.map +1 -0
  10. package/build/src/db/sql/best-chain.js +5 -0
  11. package/build/src/db/sql/best-chain.js.map +1 -0
  12. package/build/src/db/sql/events-actions/queries.js +102 -21
  13. package/build/src/db/sql/events-actions/queries.js.map +1 -1
  14. package/build/src/db/sql/verification-key-updates/queries.js +100 -0
  15. package/build/src/db/sql/verification-key-updates/queries.js.map +1 -0
  16. package/build/src/db/sql/zkapp-commands/queries.js +363 -0
  17. package/build/src/db/sql/zkapp-commands/queries.js.map +1 -0
  18. package/build/src/db/sql/zkapp-commands/types.js +2 -0
  19. package/build/src/db/sql/zkapp-commands/types.js.map +1 -0
  20. package/build/src/errors/error.js +4 -1
  21. package/build/src/errors/error.js.map +1 -1
  22. package/build/src/index.js +44 -7
  23. package/build/src/index.js.map +1 -1
  24. package/build/src/resolvers.js +42 -23
  25. package/build/src/resolvers.js.map +1 -1
  26. package/build/src/schema-version.js +2 -0
  27. package/build/src/schema-version.js.map +1 -0
  28. package/build/src/server/cors.js +41 -0
  29. package/build/src/server/cors.js.map +1 -0
  30. package/build/src/server/graceful-shutdown.js +42 -0
  31. package/build/src/server/graceful-shutdown.js.map +1 -0
  32. package/build/src/server/graphql-armor.js +39 -0
  33. package/build/src/server/graphql-armor.js.map +1 -0
  34. package/build/src/server/logger.js +45 -0
  35. package/build/src/server/logger.js.map +1 -0
  36. package/build/src/server/metrics.js +72 -0
  37. package/build/src/server/metrics.js.map +1 -0
  38. package/build/src/server/plugins.js +31 -15
  39. package/build/src/server/plugins.js.map +1 -1
  40. package/build/src/server/rate-limit.js +143 -0
  41. package/build/src/server/rate-limit.js.map +1 -0
  42. package/build/src/server/readiness.js +45 -0
  43. package/build/src/server/readiness.js.map +1 -0
  44. package/build/src/server/request-logging.js +58 -0
  45. package/build/src/server/request-logging.js.map +1 -0
  46. package/build/src/server/server.js +38 -13
  47. package/build/src/server/server.js.map +1 -1
  48. package/build/src/services/actions-service/actions-service.js +26 -15
  49. package/build/src/services/actions-service/actions-service.js.map +1 -1
  50. package/build/src/services/blocks-service/blocks-service.js +10 -9
  51. package/build/src/services/blocks-service/blocks-service.js.map +1 -1
  52. package/build/src/services/network-service/network-service.js +17 -9
  53. package/build/src/services/network-service/network-service.js.map +1 -1
  54. package/build/src/services/verification-key-updates-service/verification-key-updates-service.interface.js +2 -0
  55. package/build/src/services/verification-key-updates-service/verification-key-updates-service.interface.js.map +1 -0
  56. package/build/src/services/verification-key-updates-service/verification-key-updates-service.js +41 -0
  57. package/build/src/services/verification-key-updates-service/verification-key-updates-service.js.map +1 -0
  58. package/build/src/services/zkapp-commands-service/zkapp-commands-service.interface.js +2 -0
  59. package/build/src/services/zkapp-commands-service/zkapp-commands-service.interface.js.map +1 -0
  60. package/build/src/services/zkapp-commands-service/zkapp-commands-service.js +115 -0
  61. package/build/src/services/zkapp-commands-service/zkapp-commands-service.js.map +1 -0
  62. package/build/src/tracing/jaeger-tracing.js +2 -1
  63. package/build/src/tracing/jaeger-tracing.js.map +1 -1
  64. package/build/tests/devnet-dump/action-state-invariants.js +165 -0
  65. package/build/tests/devnet-dump/action-state-invariants.js.map +1 -0
  66. package/build/tests/devnet-dump/devnet-dump.test.js +24 -10
  67. package/build/tests/devnet-dump/devnet-dump.test.js.map +1 -1
  68. package/build/tests/devnet-dump/setup.js +63 -17
  69. package/build/tests/devnet-dump/setup.js.map +1 -1
  70. package/build/tests/integration/action-state-ordering.test.js +139 -0
  71. package/build/tests/integration/action-state-ordering.test.js.map +1 -0
  72. package/build/tests/integration/action-state-setup.js +48 -0
  73. package/build/tests/integration/action-state-setup.js.map +1 -0
  74. package/build/tests/integration/consensus-vs-chain.test.js +81 -0
  75. package/build/tests/integration/consensus-vs-chain.test.js.map +1 -0
  76. package/build/tests/integration/integration.test.js +166 -2
  77. package/build/tests/integration/integration.test.js.map +1 -1
  78. package/build/tests/integration/postgres-timeout.test.js +92 -0
  79. package/build/tests/integration/postgres-timeout.test.js.map +1 -0
  80. package/build/tests/integration/setup.js +58 -1
  81. package/build/tests/integration/setup.js.map +1 -1
  82. package/build/tests/integration/verification-key-setup.js +49 -0
  83. package/build/tests/integration/verification-key-setup.js.map +1 -0
  84. package/build/tests/integration/verification-key-updates.test.js +159 -0
  85. package/build/tests/integration/verification-key-updates.test.js.map +1 -0
  86. package/build/tests/live-api/events.test.js +11 -7
  87. package/build/tests/live-api/events.test.js.map +1 -1
  88. package/build/tests/live-api/networkState.test.js +1 -0
  89. package/build/tests/live-api/networkState.test.js.map +1 -1
  90. package/build/tests/live-network/live-network.test.js +1 -0
  91. package/build/tests/live-network/live-network.test.js.map +1 -1
  92. package/build/tests/resolvers.test.js +65 -37
  93. package/build/tests/resolvers.test.js.map +1 -1
  94. package/build/tests/services/verification-key-updates-service/verification-key-updates-service.test.js +35 -0
  95. package/build/tests/services/verification-key-updates-service/verification-key-updates-service.test.js.map +1 -0
  96. package/build/tests/services/zkapp-commands-service/zkapp-commands-service.test.js +159 -0
  97. package/build/tests/services/zkapp-commands-service/zkapp-commands-service.test.js.map +1 -0
  98. package/build/tests/test-helpers.js +15 -0
  99. package/build/tests/test-helpers.js.map +1 -1
  100. package/build/tests/unit/best-chain-anchor.test.js +70 -0
  101. package/build/tests/unit/best-chain-anchor.test.js.map +1 -0
  102. package/build/tests/unit/config.test.js +106 -0
  103. package/build/tests/unit/config.test.js.map +1 -0
  104. package/build/tests/unit/cors.test.js +95 -0
  105. package/build/tests/unit/cors.test.js.map +1 -0
  106. package/build/tests/unit/error-masking.test.js +119 -0
  107. package/build/tests/unit/error-masking.test.js.map +1 -0
  108. package/build/tests/unit/graceful-shutdown.test.js +99 -0
  109. package/build/tests/unit/graceful-shutdown.test.js.map +1 -0
  110. package/build/tests/unit/graphql-armor.test.js +121 -0
  111. package/build/tests/unit/graphql-armor.test.js.map +1 -0
  112. package/build/tests/unit/logging.test.js +120 -0
  113. package/build/tests/unit/logging.test.js.map +1 -0
  114. package/build/tests/unit/metrics.test.js +114 -0
  115. package/build/tests/unit/metrics.test.js.map +1 -0
  116. package/build/tests/unit/network-service.test.js +66 -0
  117. package/build/tests/unit/network-service.test.js.map +1 -0
  118. package/build/tests/unit/postgres-options.test.js +56 -0
  119. package/build/tests/unit/postgres-options.test.js.map +1 -0
  120. package/build/tests/unit/rate-limit.test.js +271 -0
  121. package/build/tests/unit/rate-limit.test.js.map +1 -0
  122. package/build/tests/unit/readiness.test.js +75 -0
  123. package/build/tests/unit/readiness.test.js.map +1 -0
  124. package/build/tests/unit/schema-nullability.test.js +40 -0
  125. package/build/tests/unit/schema-nullability.test.js.map +1 -0
  126. package/build/tests/unit/schema-version.test.js +48 -0
  127. package/build/tests/unit/schema-version.test.js.map +1 -0
  128. package/build/tests/unit/yoga-http-contract.test.js +30 -0
  129. package/build/tests/unit/yoga-http-contract.test.js.map +1 -0
  130. package/build/tests/unit/zkapp-commands-gating.test.js +38 -0
  131. package/build/tests/unit/zkapp-commands-gating.test.js.map +1 -0
  132. package/build/zkapp/utils.js +2 -0
  133. package/build/zkapp/utils.js.map +1 -1
  134. package/package.json +33 -14
  135. package/schema.graphql +126 -8
  136. package/src/blockchain/types.ts +65 -1
  137. package/src/blockchain/utils.ts +26 -2
  138. package/src/config.ts +133 -0
  139. package/src/db/archive-node-adapter/archive-node-adapter.interface.ts +26 -3
  140. package/src/db/archive-node-adapter/archive-node-adapter.ts +65 -2
  141. package/src/db/archive-node-adapter/postgres-options.ts +90 -0
  142. package/src/db/sql/best-chain.ts +34 -0
  143. package/src/db/sql/events-actions/queries.ts +168 -24
  144. package/src/db/sql/verification-key-updates/queries.ts +132 -0
  145. package/src/db/sql/zkapp-commands/queries.ts +406 -0
  146. package/src/db/sql/zkapp-commands/types.ts +43 -0
  147. package/src/envionment.d.ts +20 -0
  148. package/src/errors/error.ts +17 -1
  149. package/src/index.ts +64 -7
  150. package/src/resolvers-types.ts +311 -24
  151. package/src/resolvers.ts +70 -34
  152. package/src/schema-version.ts +24 -0
  153. package/src/server/cors.ts +97 -0
  154. package/src/server/graceful-shutdown.ts +76 -0
  155. package/src/server/graphql-armor.ts +78 -0
  156. package/src/server/logger.ts +72 -0
  157. package/src/server/metrics.ts +104 -0
  158. package/src/server/plugins.ts +53 -19
  159. package/src/server/rate-limit.ts +274 -0
  160. package/src/server/readiness.ts +78 -0
  161. package/src/server/request-logging.ts +83 -0
  162. package/src/server/server.ts +70 -15
  163. package/src/services/actions-service/actions-service.ts +87 -24
  164. package/src/services/blocks-service/blocks-service.ts +16 -14
  165. package/src/services/network-service/network-service.ts +30 -12
  166. package/src/services/verification-key-updates-service/verification-key-updates-service.interface.ts +9 -0
  167. package/src/services/verification-key-updates-service/verification-key-updates-service.ts +72 -0
  168. package/src/services/zkapp-commands-service/zkapp-commands-service.interface.ts +9 -0
  169. package/src/services/zkapp-commands-service/zkapp-commands-service.ts +181 -0
  170. package/src/tracing/jaeger-tracing.ts +2 -1
  171. package/build/tests/consensus/run-compare.js +0 -40
  172. package/build/tests/consensus/run-compare.js.map +0 -1
  173. package/build/tests/consensus/select-consensus-precomputed.js +0 -80
  174. package/build/tests/consensus/select-consensus-precomputed.js.map +0 -1
  175. package/build/tests/consensus/types.js +0 -15
  176. package/build/tests/consensus/types.js.map +0 -1
@@ -0,0 +1,99 @@
1
+ import { describe, test } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { createGracefulShutdown } from '../../src/server/graceful-shutdown.js';
4
+ const silent = () => { };
5
+ describe('Graceful shutdown', () => {
6
+ test('drains the server, runs closers in order, then exits 0', async () => {
7
+ const calls = [];
8
+ const exits = [];
9
+ const shutdown = createGracefulShutdown({
10
+ closeServer: async () => {
11
+ calls.push('server');
12
+ },
13
+ closers: [
14
+ async () => {
15
+ calls.push('traces');
16
+ },
17
+ async () => {
18
+ calls.push('db');
19
+ },
20
+ ],
21
+ timeoutMs: 1000,
22
+ onExit: (code) => exits.push(code),
23
+ log: silent,
24
+ });
25
+ await shutdown('SIGTERM');
26
+ assert.deepStrictEqual(calls, ['server', 'traces', 'db']);
27
+ assert.deepStrictEqual(exits, [0]);
28
+ });
29
+ test('is idempotent — a second signal does nothing', async () => {
30
+ let serverCloses = 0;
31
+ const exits = [];
32
+ const shutdown = createGracefulShutdown({
33
+ closeServer: async () => {
34
+ serverCloses += 1;
35
+ },
36
+ timeoutMs: 1000,
37
+ onExit: (code) => exits.push(code),
38
+ log: silent,
39
+ });
40
+ await shutdown('SIGTERM');
41
+ await shutdown('SIGINT');
42
+ assert.strictEqual(serverCloses, 1);
43
+ assert.deepStrictEqual(exits, [0]);
44
+ });
45
+ test('a failing closer is logged but does not abort the rest', async () => {
46
+ const calls = [];
47
+ const exits = [];
48
+ const shutdown = createGracefulShutdown({
49
+ closeServer: async () => { },
50
+ closers: [
51
+ async () => {
52
+ throw new Error('flush failed');
53
+ },
54
+ async () => {
55
+ calls.push('db');
56
+ },
57
+ ],
58
+ timeoutMs: 1000,
59
+ onExit: (code) => exits.push(code),
60
+ log: silent,
61
+ });
62
+ await shutdown('SIGTERM');
63
+ assert.deepStrictEqual(calls, ['db']);
64
+ assert.deepStrictEqual(exits, [0]);
65
+ });
66
+ test('a crash-initiated shutdown drains cleanly but still exits non-zero', async () => {
67
+ const calls = [];
68
+ const exits = [];
69
+ const shutdown = createGracefulShutdown({
70
+ closeServer: async () => {
71
+ calls.push('server');
72
+ },
73
+ closers: [
74
+ async () => {
75
+ calls.push('traces');
76
+ },
77
+ ],
78
+ timeoutMs: 1000,
79
+ onExit: (code) => exits.push(code),
80
+ log: silent,
81
+ });
82
+ await shutdown('uncaughtException', 1);
83
+ assert.deepStrictEqual(calls, ['server', 'traces']);
84
+ assert.deepStrictEqual(exits, [1]);
85
+ });
86
+ test('exits 1 when draining exceeds the timeout, and only once', async () => {
87
+ const exits = [];
88
+ const shutdown = createGracefulShutdown({
89
+ closeServer: () => new Promise(() => { }),
90
+ timeoutMs: 20,
91
+ onExit: (code) => exits.push(code),
92
+ log: silent,
93
+ });
94
+ shutdown('SIGTERM');
95
+ await new Promise((resolve) => setTimeout(resolve, 60));
96
+ assert.deepStrictEqual(exits, [1]);
97
+ });
98
+ });
99
+ //# sourceMappingURL=graceful-shutdown.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graceful-shutdown.test.js","sourceRoot":"","sources":["../../../tests/unit/graceful-shutdown.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,121 @@
1
+ import { describe, test } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { createYoga } from 'graphql-yoga';
4
+ import { buildArmorPlugins, resolveArmorConfig, ARMOR_DEFAULTS, } from '../../src/server/graphql-armor.js';
5
+ import { schema } from '../../src/resolvers.js';
6
+ async function runQuery(query, env) {
7
+ const yoga = createYoga({
8
+ schema,
9
+ plugins: buildArmorPlugins(env),
10
+ graphqlEndpoint: '/graphql',
11
+ });
12
+ const response = await yoga.fetch('http://localhost/graphql', {
13
+ method: 'POST',
14
+ headers: { 'content-type': 'application/json' },
15
+ body: JSON.stringify({ query }),
16
+ });
17
+ return response.json();
18
+ }
19
+ describe('GraphQL armor configuration', () => {
20
+ describe('resolveArmorConfig', () => {
21
+ test('uses conservative defaults when no env vars are set', () => {
22
+ assert.deepStrictEqual(resolveArmorConfig({}), ARMOR_DEFAULTS);
23
+ });
24
+ test('reads valid overrides from the environment', () => {
25
+ const config = resolveArmorConfig({
26
+ GRAPHQL_MAX_DEPTH: '8',
27
+ GRAPHQL_MAX_ALIASES: '20',
28
+ GRAPHQL_MAX_TOKENS: '2000',
29
+ GRAPHQL_MAX_COST: '8000',
30
+ });
31
+ assert.deepStrictEqual(config, {
32
+ maxDepth: 8,
33
+ maxAliases: 20,
34
+ maxTokens: 2000,
35
+ maxCost: 8000,
36
+ });
37
+ });
38
+ test('falls back to defaults on malformed or non-positive values', () => {
39
+ const config = resolveArmorConfig({
40
+ GRAPHQL_MAX_DEPTH: '0',
41
+ GRAPHQL_MAX_ALIASES: '-5',
42
+ GRAPHQL_MAX_TOKENS: 'abc',
43
+ GRAPHQL_MAX_COST: '',
44
+ });
45
+ assert.deepStrictEqual(config, ARMOR_DEFAULTS);
46
+ });
47
+ });
48
+ describe('buildArmorPlugins', () => {
49
+ test('returns the five armor plugins as envelop plugins', () => {
50
+ const plugins = buildArmorPlugins({});
51
+ assert.ok(plugins.length >= 5);
52
+ for (const plugin of plugins) {
53
+ assert.strictEqual(typeof plugin, 'object');
54
+ assert.ok(plugin !== null);
55
+ }
56
+ });
57
+ });
58
+ describe('enforcement (end-to-end through Yoga)', () => {
59
+ const deepQuery = '{ networkState { maxBlockHeight { canonicalMaxBlockHeight } } }';
60
+ test('rejects a query that exceeds the depth limit before execution', async () => {
61
+ const result = await runQuery(deepQuery, { GRAPHQL_MAX_DEPTH: '1' });
62
+ assert.ok(result.errors?.some((e) => /depth/i.test(e.message)), `expected a max-depth error, got: ${JSON.stringify(result.errors)}`);
63
+ });
64
+ test('does not raise a depth error when within the limit', async () => {
65
+ const result = await runQuery(deepQuery, { GRAPHQL_MAX_DEPTH: '10' });
66
+ const depthError = result.errors?.some((e) => /depth/i.test(e.message));
67
+ assert.ok(!depthError, 'a within-limit query must not be depth-rejected');
68
+ });
69
+ });
70
+ describe('real downstream client queries pass the default limits', () => {
71
+ const downstreamQueries = {
72
+ searchTransactionFull: `
73
+ query SearchTransaction($limit: Int!) {
74
+ blocks(limit: $limit, sortBy: BLOCKHEIGHT_DESC) {
75
+ blockHeight stateHash dateTime
76
+ transactions {
77
+ userCommands { hash kind from to amount fee memo nonce failureReason }
78
+ zkappCommands {
79
+ hash
80
+ failureReasons { failures }
81
+ zkappCommand {
82
+ memo
83
+ feePayer { body { publicKey fee } }
84
+ accountUpdates { body { publicKey } }
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }`,
90
+ analytics: `
91
+ query BlocksAnalytics($limit: Int, $dateTime_gte: DateTime) {
92
+ blocks(query: { canonical: true, dateTime_gte: $dateTime_gte }, sortBy: BLOCKHEIGHT_DESC, limit: $limit) {
93
+ blockHeight dateTime txFees
94
+ transactions { userCommands { hash } zkappCommands { hash } }
95
+ }
96
+ }`,
97
+ blocksFullPaginatedBestChain: `
98
+ query GetBlocksFULLPaginatedBestChain($limit: Int!, $maxBlockHeight: Int!) {
99
+ blocks(query: { blockHeight_lt: $maxBlockHeight, inBestChain: true }, limit: $limit, sortBy: BLOCKHEIGHT_DESC) {
100
+ blockHeight stateHash creator dateTime
101
+ protocolState { consensusState { epoch slot slotSinceGenesis } }
102
+ transactions { coinbase userCommands { hash } zkappCommands { hash } }
103
+ }
104
+ networkState { maxBlockHeight { canonicalMaxBlockHeight pendingMaxBlockHeight } }
105
+ }`,
106
+ readiness: '{ __typename }',
107
+ };
108
+ for (const [name, query] of Object.entries(downstreamQueries)) {
109
+ test(`${name} is not rejected by any armor limit`, async () => {
110
+ const result = await runQuery(query, {});
111
+ const armorError = result.errors?.find((e) => /Syntax Error: (Query depth limit|Query Cost limit|Token limit|Aliases limit)/.test(e.message));
112
+ assert.strictEqual(armorError, undefined, `armor rejected a production query: ${armorError?.message}`);
113
+ });
114
+ }
115
+ test('field-suggestion blocking keeps "Cannot query field" verbatim', async () => {
116
+ const result = await runQuery('{ blocks(limit: 1) { protocolState { consensusState { epoch } } } }', {});
117
+ assert.ok(result.errors?.some((e) => e.message.startsWith('Cannot query field "protocolState" on type "Block".')), `tier-fallback marker lost: ${JSON.stringify(result.errors)}`);
118
+ });
119
+ });
120
+ });
121
+ //# sourceMappingURL=graphql-armor.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graphql-armor.test.js","sourceRoot":"","sources":["../../../tests/unit/graphql-armor.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,120 @@
1
+ import { describe, test } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { createYoga } from 'graphql-yoga';
4
+ import { createLogger, resolveYogaLogLevel, } from '../../src/server/logger.js';
5
+ import { useRequestLogging } from '../../src/server/request-logging.js';
6
+ import { schema } from '../../src/resolvers.js';
7
+ function captureLogger(entries) {
8
+ return {
9
+ info: (obj, msg) => entries.push({ obj, msg }),
10
+ };
11
+ }
12
+ function serverWith(entries) {
13
+ return createYoga({
14
+ schema,
15
+ graphqlEndpoint: '/',
16
+ plugins: [useRequestLogging(captureLogger(entries))],
17
+ });
18
+ }
19
+ describe('createLogger', () => {
20
+ test('honours a valid LOG_LEVEL', () => {
21
+ assert.strictEqual(createLogger({ LOG_LEVEL: 'debug' }).level, 'debug');
22
+ });
23
+ test('falls back to info for an unrecognised level', () => {
24
+ assert.strictEqual(createLogger({ LOG_LEVEL: 'bogus' }).level, 'info');
25
+ });
26
+ test('maps LOG_LEVEL values to Yoga-supported levels', () => {
27
+ assert.strictEqual(resolveYogaLogLevel({ LOG_LEVEL: 'trace' }), 'debug');
28
+ assert.strictEqual(resolveYogaLogLevel({ LOG_LEVEL: 'fatal' }), 'error');
29
+ assert.strictEqual(resolveYogaLogLevel({ LOG_LEVEL: 'silent' }), false);
30
+ });
31
+ });
32
+ describe('useRequestLogging', () => {
33
+ test('emits one structured access line per request', async () => {
34
+ const entries = [];
35
+ const yoga = serverWith(entries);
36
+ await yoga.fetch('http://localhost/', {
37
+ method: 'POST',
38
+ headers: { 'content-type': 'application/json' },
39
+ body: JSON.stringify({ query: '{ __typename }' }),
40
+ });
41
+ assert.strictEqual(entries.length, 1);
42
+ const { obj, msg } = entries[0];
43
+ assert.strictEqual(msg, 'request completed');
44
+ assert.strictEqual(obj.method, 'POST');
45
+ assert.strictEqual(obj.path, '/');
46
+ assert.strictEqual(obj.status, 200);
47
+ assert.strictEqual(typeof obj.requestId, 'string');
48
+ assert.strictEqual(typeof obj.durationMs, 'number');
49
+ });
50
+ test('honours an inbound X-Request-Id for correlation', async () => {
51
+ const entries = [];
52
+ const yoga = serverWith(entries);
53
+ await yoga.fetch('http://localhost/', {
54
+ method: 'POST',
55
+ headers: {
56
+ 'content-type': 'application/json',
57
+ 'x-request-id': 'trace-123',
58
+ },
59
+ body: JSON.stringify({ query: '{ __typename }' }),
60
+ });
61
+ assert.strictEqual(entries[0].obj.requestId, 'trace-123');
62
+ });
63
+ test('caps and sanitises an inbound X-Request-Id', async () => {
64
+ const entries = [];
65
+ const yoga = serverWith(entries);
66
+ await yoga.fetch('http://localhost/', {
67
+ method: 'POST',
68
+ headers: {
69
+ 'content-type': 'application/json',
70
+ 'x-request-id': `bad"\\\u0007id${'A'.repeat(500)}`,
71
+ },
72
+ body: JSON.stringify({ query: '{ __typename }' }),
73
+ });
74
+ const id = entries[0].obj.requestId;
75
+ assert.strictEqual(id.length, 128);
76
+ assert.match(id, /^[\x20-\x7e]+$/);
77
+ assert.ok(!id.includes('"'));
78
+ assert.ok(!id.includes('\\'));
79
+ });
80
+ test('generates an id when the inbound X-Request-Id is blank', async () => {
81
+ const entries = [];
82
+ const yoga = serverWith(entries);
83
+ await yoga.fetch('http://localhost/', {
84
+ method: 'POST',
85
+ headers: { 'content-type': 'application/json', 'x-request-id': ' ' },
86
+ body: JSON.stringify({ query: '{ __typename }' }),
87
+ });
88
+ assert.match(entries[0].obj.requestId, /^[0-9a-f-]{36}$/);
89
+ });
90
+ test('backfills request metadata for CORS preflight responses', async () => {
91
+ const entries = [];
92
+ const origin = 'https://explorer.example.com';
93
+ const yoga = createYoga({
94
+ schema,
95
+ graphqlEndpoint: '/',
96
+ cors: { origin, methods: ['GET', 'POST'] },
97
+ plugins: [useRequestLogging(captureLogger(entries))],
98
+ });
99
+ await yoga.fetch('http://localhost/', {
100
+ method: 'OPTIONS',
101
+ headers: {
102
+ origin,
103
+ 'access-control-request-method': 'POST',
104
+ 'access-control-request-headers': 'content-type',
105
+ },
106
+ });
107
+ assert.strictEqual(entries.length, 1);
108
+ assert.strictEqual(entries[0].obj.method, 'OPTIONS');
109
+ assert.strictEqual(entries[0].obj.status, 204);
110
+ assert.strictEqual(typeof entries[0].obj.requestId, 'string');
111
+ assert.strictEqual(typeof entries[0].obj.durationMs, 'number');
112
+ });
113
+ test('does not access-log probe endpoints', async () => {
114
+ const entries = [];
115
+ const yoga = serverWith(entries);
116
+ await yoga.fetch('http://localhost/healthcheck');
117
+ assert.strictEqual(entries.length, 0);
118
+ });
119
+ });
120
+ //# sourceMappingURL=logging.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logging.test.js","sourceRoot":"","sources":["../../../tests/unit/logging.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,114 @@
1
+ import { describe, test } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { createYoga } from 'graphql-yoga';
4
+ import { createMetrics, useMetrics } from '../../src/server/metrics.js';
5
+ import { buildPlugins } from '../../src/server/plugins.js';
6
+ import { schema } from '../../src/resolvers.js';
7
+ function serverWithFreshMetrics() {
8
+ const metrics = createMetrics({ collectDefault: false });
9
+ return createYoga({
10
+ schema,
11
+ graphqlEndpoint: '/',
12
+ plugins: [useMetrics(metrics)],
13
+ });
14
+ }
15
+ async function graphql(yoga) {
16
+ return yoga.fetch('http://localhost/', {
17
+ method: 'POST',
18
+ headers: { 'content-type': 'application/json' },
19
+ body: JSON.stringify({ query: '{ __typename }' }),
20
+ });
21
+ }
22
+ async function serverWithBuiltPlugins() {
23
+ const built = await buildPlugins();
24
+ return createYoga({
25
+ schema,
26
+ graphqlEndpoint: '/',
27
+ plugins: built.plugins,
28
+ });
29
+ }
30
+ function restoreEnv(variable, value) {
31
+ if (value === undefined) {
32
+ delete process.env[variable];
33
+ }
34
+ else {
35
+ process.env[variable] = value;
36
+ }
37
+ }
38
+ describe('Prometheus metrics', () => {
39
+ test('buildPlugins gates /metrics behind ENABLE_METRICS', async () => {
40
+ const previousMetrics = process.env.ENABLE_METRICS;
41
+ const previousLogging = process.env.ENABLE_LOGGING;
42
+ try {
43
+ delete process.env.ENABLE_METRICS;
44
+ delete process.env.ENABLE_LOGGING;
45
+ const disabled = await serverWithBuiltPlugins();
46
+ const disabledBody = await (await disabled.fetch('http://localhost/metrics')).text();
47
+ assert.doesNotMatch(disabledBody, /http_requests_total/);
48
+ process.env.ENABLE_METRICS = 'true';
49
+ const enabled = await serverWithBuiltPlugins();
50
+ const response = await enabled.fetch('http://localhost/metrics');
51
+ assert.strictEqual(response.status, 200);
52
+ assert.match(await response.text(), /http_requests_total/);
53
+ }
54
+ finally {
55
+ restoreEnv('ENABLE_METRICS', previousMetrics);
56
+ restoreEnv('ENABLE_LOGGING', previousLogging);
57
+ }
58
+ });
59
+ test('serves the exposition format at /metrics', async () => {
60
+ const yoga = serverWithFreshMetrics();
61
+ const response = await yoga.fetch('http://localhost/metrics');
62
+ assert.strictEqual(response.status, 200);
63
+ assert.match(response.headers.get('content-type') ?? '', /text\/plain/);
64
+ const body = await response.text();
65
+ assert.match(body, /http_requests_total/);
66
+ assert.match(body, /http_request_duration_seconds/);
67
+ assert.match(body, /http_requests_in_flight/);
68
+ });
69
+ test('counts requests by route and status', async () => {
70
+ const yoga = serverWithFreshMetrics();
71
+ await graphql(yoga);
72
+ await graphql(yoga);
73
+ const body = await (await yoga.fetch('http://localhost/metrics')).text();
74
+ const line = body
75
+ .split('\n')
76
+ .find((l) => l.startsWith('http_requests_total{') &&
77
+ l.includes('route="/"') &&
78
+ l.includes('status="200"'));
79
+ assert.ok(line, `expected a counter line for route="/", got:\n${body}`);
80
+ assert.strictEqual(line?.trim().endsWith(' 2'), true);
81
+ });
82
+ test('does not count the /metrics scrape itself', async () => {
83
+ const yoga = serverWithFreshMetrics();
84
+ await yoga.fetch('http://localhost/metrics');
85
+ const body = await (await yoga.fetch('http://localhost/metrics')).text();
86
+ assert.ok(!body.includes('route="/metrics"'), 'the /metrics scrape must not be counted');
87
+ });
88
+ test('in-flight gauge is balanced for short-circuited requests', async () => {
89
+ const metrics = createMetrics({ collectDefault: false });
90
+ const yoga = createYoga({
91
+ schema,
92
+ graphqlEndpoint: '/',
93
+ healthCheckEndpoint: '/healthcheck',
94
+ cors: { origin: '*', methods: ['GET', 'POST'] },
95
+ plugins: [useMetrics(metrics)],
96
+ });
97
+ await yoga.fetch('http://localhost/healthcheck');
98
+ await yoga.fetch('http://localhost/', {
99
+ method: 'OPTIONS',
100
+ headers: {
101
+ origin: 'https://explorer.test',
102
+ 'access-control-request-method': 'POST',
103
+ 'access-control-request-headers': 'content-type',
104
+ },
105
+ });
106
+ await graphql(yoga);
107
+ const body = await (await yoga.fetch('http://localhost/metrics')).text();
108
+ const line = body
109
+ .split('\n')
110
+ .find((l) => l.startsWith('http_requests_in_flight '));
111
+ assert.strictEqual(line, 'http_requests_in_flight 0');
112
+ });
113
+ });
114
+ //# sourceMappingURL=metrics.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.test.js","sourceRoot":"","sources":["../../../tests/unit/metrics.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ import { describe, test } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { NetworkService } from '../../src/services/network-service/network-service.js';
4
+ import { TracingState } from '../../src/tracing/tracer.js';
5
+ function buildService(rows) {
6
+ const svc = new NetworkService({});
7
+ svc.executeNetworkStateQuery = async () => rows;
8
+ return svc;
9
+ }
10
+ const nullOptions = { tracingState: new TracingState(undefined) };
11
+ describe('NetworkService.getNetworkState', () => {
12
+ test('returns maxBlockHeight=null when archive has no indexed blocks', async () => {
13
+ const svc = buildService([]);
14
+ const state = await svc.getNetworkState(nullOptions);
15
+ assert.strictEqual(state.maxBlockHeight, null);
16
+ });
17
+ test('returns both heights when canonical and pending rows are present', async () => {
18
+ const svc = buildService([
19
+ { chain_status: 'canonical', height: 100 },
20
+ { chain_status: 'pending', height: 110 },
21
+ ]);
22
+ const state = await svc.getNetworkState(nullOptions);
23
+ assert.deepStrictEqual(state.maxBlockHeight, {
24
+ canonicalMaxBlockHeight: 100,
25
+ pendingMaxBlockHeight: 110,
26
+ });
27
+ });
28
+ test('falls back pendingMaxBlockHeight = canonicalMaxBlockHeight when pending row missing', async () => {
29
+ const svc = buildService([{ chain_status: 'canonical', height: 100 }]);
30
+ const state = await svc.getNetworkState(nullOptions);
31
+ assert.deepStrictEqual(state.maxBlockHeight, {
32
+ canonicalMaxBlockHeight: 100,
33
+ pendingMaxBlockHeight: 100,
34
+ });
35
+ });
36
+ test('falls back canonicalMaxBlockHeight = 0 when canonical row missing', async () => {
37
+ const svc = buildService([{ chain_status: 'pending', height: 50 }]);
38
+ const state = await svc.getNetworkState(nullOptions);
39
+ assert.deepStrictEqual(state.maxBlockHeight, {
40
+ canonicalMaxBlockHeight: 0,
41
+ pendingMaxBlockHeight: 50,
42
+ });
43
+ });
44
+ test('coerces stringified heights from postgres to numbers', async () => {
45
+ const svc = buildService([
46
+ { chain_status: 'canonical', height: '7' },
47
+ { chain_status: 'pending', height: '9' },
48
+ ]);
49
+ const state = await svc.getNetworkState(nullOptions);
50
+ assert.strictEqual(state.maxBlockHeight.canonicalMaxBlockHeight, 7);
51
+ assert.strictEqual(state.maxBlockHeight.pendingMaxBlockHeight, 9);
52
+ });
53
+ test('ignores extra rows with unexpected chain_status values', async () => {
54
+ const svc = buildService([
55
+ { chain_status: 'canonical', height: 100 },
56
+ { chain_status: 'orphaned', height: 99 },
57
+ { chain_status: 'pending', height: 110 },
58
+ ]);
59
+ const state = await svc.getNetworkState(nullOptions);
60
+ assert.deepStrictEqual(state.maxBlockHeight, {
61
+ canonicalMaxBlockHeight: 100,
62
+ pendingMaxBlockHeight: 110,
63
+ });
64
+ });
65
+ });
66
+ //# sourceMappingURL=network-service.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"network-service.test.js","sourceRoot":"","sources":["../../../tests/unit/network-service.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ import { describe, test } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { buildPostgresOptions, resolvePoolConfig, POOL_DEFAULTS, } from '../../src/db/archive-node-adapter/postgres-options.js';
4
+ describe('Postgres pool options', () => {
5
+ describe('resolvePoolConfig', () => {
6
+ test('uses conservative defaults when no env vars are set', () => {
7
+ assert.deepStrictEqual(resolvePoolConfig({}), POOL_DEFAULTS);
8
+ });
9
+ test('reads valid overrides from the environment', () => {
10
+ const config = resolvePoolConfig({
11
+ PG_MAX_CONNECTIONS: '25',
12
+ PG_IDLE_TIMEOUT: '60',
13
+ PG_CONNECT_TIMEOUT: '5',
14
+ PG_STATEMENT_TIMEOUT: '15000',
15
+ });
16
+ assert.deepStrictEqual(config, {
17
+ max: 25,
18
+ idleTimeout: 60,
19
+ connectTimeout: 5,
20
+ statementTimeout: 15000,
21
+ });
22
+ });
23
+ test('falls back to defaults on malformed values', () => {
24
+ const config = resolvePoolConfig({
25
+ PG_MAX_CONNECTIONS: 'abc',
26
+ PG_IDLE_TIMEOUT: '-1',
27
+ PG_CONNECT_TIMEOUT: '1.5',
28
+ PG_STATEMENT_TIMEOUT: '',
29
+ });
30
+ assert.deepStrictEqual(config, POOL_DEFAULTS);
31
+ });
32
+ test('clamps max to at least 1 so the pool can never be empty', () => {
33
+ assert.strictEqual(resolvePoolConfig({ PG_MAX_CONNECTIONS: '0' }).max, 1);
34
+ });
35
+ test('allows statement timeout of 0 to disable the limit', () => {
36
+ assert.strictEqual(resolvePoolConfig({ PG_STATEMENT_TIMEOUT: '0' }).statementTimeout, 0);
37
+ });
38
+ });
39
+ describe('buildPostgresOptions', () => {
40
+ test('maps config onto the postgres() options shape', () => {
41
+ const options = buildPostgresOptions({
42
+ PG_MAX_CONNECTIONS: '12',
43
+ PG_IDLE_TIMEOUT: '45',
44
+ PG_CONNECT_TIMEOUT: '7',
45
+ PG_STATEMENT_TIMEOUT: '20000',
46
+ });
47
+ assert.strictEqual(options.max, 12);
48
+ assert.strictEqual(options.idle_timeout, 45);
49
+ assert.strictEqual(options.connect_timeout, 7);
50
+ assert.deepStrictEqual(options.connection, {
51
+ statement_timeout: '20000',
52
+ });
53
+ });
54
+ });
55
+ });
56
+ //# sourceMappingURL=postgres-options.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres-options.test.js","sourceRoot":"","sources":["../../../tests/unit/postgres-options.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,aAAa,GACd,MAAM,uDAAuD,CAAC;AAE/D,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;QACjC,IAAI,CAAC,qDAAqD,EAAE,GAAG,EAAE;YAC/D,MAAM,CAAC,eAAe,CAAC,iBAAiB,CAAC,EAAE,CAAC,EAAE,aAAa,CAAC,CAAC;QAC/D,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,4CAA4C,EAAE,GAAG,EAAE;YACtD,MAAM,MAAM,GAAG,iBAAiB,CAAC;gBAC/B,kBAAkB,EAAE,IAAI;gBACxB,eAAe,EAAE,IAAI;gBACrB,kBAAkB,EAAE,GAAG;gBACvB,oBAAoB,EAAE,OAAO;aAC9B,CAAC,CAAC;YACH,MAAM,CAAC,eAAe,CAAC,MAAM,EAAE;gBAC7B,GAAG,EAAE,EAAE;gBACP,WAAW,EAAE,EAAE;gBACf,cAAc,EAAE,CAAC;gBACjB,gBAAgB,EAAE,KAAK;aACxB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,4CAA4C,EAAE,GAAG,EAAE;YACtD,MAAM,MAAM,GAAG,iBAAiB,CAAC;gBAC/B,kBAAkB,EAAE,KAAK;gBACzB,eAAe,EAAE,IAAI;gBACrB,kBAAkB,EAAE,KAAK;gBACzB,oBAAoB,EAAE,EAAE;aACzB,CAAC,CAAC;YACH,MAAM,CAAC,eAAe,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;QAChD,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,yDAAyD,EAAE,GAAG,EAAE;YACnE,MAAM,CAAC,WAAW,CAAC,iBAAiB,CAAC,EAAE,kBAAkB,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAC5E,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,oDAAoD,EAAE,GAAG,EAAE;YAC9D,MAAM,CAAC,WAAW,CAChB,iBAAiB,CAAC,EAAE,oBAAoB,EAAE,GAAG,EAAE,CAAC,CAAC,gBAAgB,EACjE,CAAC,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;QACpC,IAAI,CAAC,+CAA+C,EAAE,GAAG,EAAE;YACzD,MAAM,OAAO,GAAG,oBAAoB,CAAC;gBACnC,kBAAkB,EAAE,IAAI;gBACxB,eAAe,EAAE,IAAI;gBACrB,kBAAkB,EAAE,GAAG;gBACvB,oBAAoB,EAAE,OAAO;aAC9B,CAAC,CAAC;YACH,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YACpC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;YAC7C,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC;YAE/C,MAAM,CAAC,eAAe,CAAC,OAAO,CAAC,UAAU,EAAE;gBACzC,iBAAiB,EAAE,OAAO;aAC3B,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}