@midnightntwrk/wallet-sdk-indexer-client 1.2.3

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 (78) hide show
  1. package/README.md +86 -0
  2. package/dist/effect/Backpressure.d.ts +97 -0
  3. package/dist/effect/Backpressure.js +146 -0
  4. package/dist/effect/ConnectionHelper.d.ts +4 -0
  5. package/dist/effect/ConnectionHelper.js +41 -0
  6. package/dist/effect/HttpQueryClient.d.ts +4 -0
  7. package/dist/effect/HttpQueryClient.js +50 -0
  8. package/dist/effect/Query.d.ts +39 -0
  9. package/dist/effect/Query.js +47 -0
  10. package/dist/effect/QueryClient.d.ts +15 -0
  11. package/dist/effect/QueryClient.js +15 -0
  12. package/dist/effect/QueryRunner.d.ts +11 -0
  13. package/dist/effect/QueryRunner.js +23 -0
  14. package/dist/effect/Subscription.d.ts +25 -0
  15. package/dist/effect/Subscription.js +56 -0
  16. package/dist/effect/SubscriptionClient.d.ts +23 -0
  17. package/dist/effect/SubscriptionClient.js +15 -0
  18. package/dist/effect/WsSubscriptionClient.d.ts +4 -0
  19. package/dist/effect/WsSubscriptionClient.js +89 -0
  20. package/dist/effect/index.d.ts +8 -0
  21. package/dist/effect/index.js +20 -0
  22. package/dist/effect/test/backpressure.test.d.ts +1 -0
  23. package/dist/effect/test/backpressure.test.js +146 -0
  24. package/dist/effect/test/connectionHelper.test.d.ts +1 -0
  25. package/dist/effect/test/connectionHelper.test.js +34 -0
  26. package/dist/effect/test/httpQueryClient.spied.test.d.ts +1 -0
  27. package/dist/effect/test/httpQueryClient.spied.test.js +37 -0
  28. package/dist/effect/test/httpQueryClient.test.d.ts +1 -0
  29. package/dist/effect/test/httpQueryClient.test.js +32 -0
  30. package/dist/effect/test/queryRunner.spied.test.d.ts +1 -0
  31. package/dist/effect/test/queryRunner.spied.test.js +46 -0
  32. package/dist/effect/test/wsSubscriptionClient.spied.test.d.ts +1 -0
  33. package/dist/effect/test/wsSubscriptionClient.spied.test.js +37 -0
  34. package/dist/effect/test/wsSubscriptionClient.test.d.ts +1 -0
  35. package/dist/effect/test/wsSubscriptionClient.test.js +32 -0
  36. package/dist/graphql/generated/fragment-masking.d.ts +19 -0
  37. package/dist/graphql/generated/fragment-masking.js +16 -0
  38. package/dist/graphql/generated/gql.d.ts +61 -0
  39. package/dist/graphql/generated/gql.js +17 -0
  40. package/dist/graphql/generated/graphql.d.ts +1021 -0
  41. package/dist/graphql/generated/graphql.js +700 -0
  42. package/dist/graphql/generated/index.d.ts +2 -0
  43. package/dist/graphql/generated/index.js +2 -0
  44. package/dist/graphql/queries/BlockHash.d.ts +6 -0
  45. package/dist/graphql/queries/BlockHash.js +24 -0
  46. package/dist/graphql/queries/Connect.d.ts +6 -0
  47. package/dist/graphql/queries/Connect.js +19 -0
  48. package/dist/graphql/queries/Disconnect.d.ts +6 -0
  49. package/dist/graphql/queries/Disconnect.js +19 -0
  50. package/dist/graphql/queries/FetchTermsAndConditions.d.ts +6 -0
  51. package/dist/graphql/queries/FetchTermsAndConditions.js +26 -0
  52. package/dist/graphql/queries/TransactionHistoryDetail.d.ts +6 -0
  53. package/dist/graphql/queries/TransactionHistoryDetail.js +30 -0
  54. package/dist/graphql/queries/TransactionStatus.d.ts +6 -0
  55. package/dist/graphql/queries/TransactionStatus.js +34 -0
  56. package/dist/graphql/queries/index.d.ts +6 -0
  57. package/dist/graphql/queries/index.js +18 -0
  58. package/dist/graphql/queries/test/BlockHash.test.d.ts +1 -0
  59. package/dist/graphql/queries/test/BlockHash.test.js +84 -0
  60. package/dist/graphql/subscriptions/DustLedgerEvents.d.ts +6 -0
  61. package/dist/graphql/subscriptions/DustLedgerEvents.js +24 -0
  62. package/dist/graphql/subscriptions/ShieldedTransactions.d.ts +8 -0
  63. package/dist/graphql/subscriptions/ShieldedTransactions.js +54 -0
  64. package/dist/graphql/subscriptions/UnshieldedTransactions.d.ts +8 -0
  65. package/dist/graphql/subscriptions/UnshieldedTransactions.js +68 -0
  66. package/dist/graphql/subscriptions/ZswapEvents.d.ts +6 -0
  67. package/dist/graphql/subscriptions/ZswapEvents.js +24 -0
  68. package/dist/graphql/subscriptions/index.d.ts +4 -0
  69. package/dist/graphql/subscriptions/index.js +16 -0
  70. package/dist/graphql/subscriptions/test/ShieldedTransactions.test.d.ts +1 -0
  71. package/dist/graphql/subscriptions/test/ShieldedTransactions.test.js +55 -0
  72. package/dist/graphql/subscriptions/test/UnshieldedTransactions.test.d.ts +1 -0
  73. package/dist/graphql/subscriptions/test/UnshieldedTransactions.test.js +52 -0
  74. package/dist/graphql/subscriptions/test/ZswapEvents.test.d.ts +1 -0
  75. package/dist/graphql/subscriptions/test/ZswapEvents.test.js +50 -0
  76. package/dist/index.d.ts +3 -0
  77. package/dist/index.js +15 -0
  78. package/package.json +57 -0
package/README.md ADDED
@@ -0,0 +1,86 @@
1
+ # @midnightntwrk/wallet-sdk-indexer-client
2
+
3
+ Client for communicating with the Midnight indexer service.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @midnightntwrk/wallet-sdk-indexer-client
9
+ ```
10
+
11
+ ## Overview
12
+
13
+ This package provides a GraphQL-based client for interacting with the Midnight indexer service. The indexer aggregates
14
+ blockchain data and provides efficient access to:
15
+
16
+ - **Subscriptions** for real-time event streaming:
17
+ - Shielded transactions (Zswap)
18
+ - Unshielded transactions
19
+ - Zswap events
20
+ - Dust ledger events
21
+
22
+ - **Queries** for fetching blockchain data:
23
+ - Block information (height, hash, timestamp)
24
+ - Ledger parameters (including cost model)
25
+ - Connection management
26
+
27
+ ## Usage
28
+
29
+ ### Subscriptions
30
+
31
+ Subscribe to real-time events from the indexer:
32
+
33
+ ```typescript
34
+ import {
35
+ ShieldedTransactions,
36
+ UnshieldedTransactions,
37
+ ZswapEvents,
38
+ DustLedgerEvents,
39
+ } from '@midnightntwrk/wallet-sdk-indexer-client';
40
+
41
+ // Subscribe to shielded transaction events
42
+ // Subscribe to unshielded transaction events
43
+ // Subscribe to Zswap events
44
+ // Subscribe to dust ledger events
45
+ ```
46
+
47
+ ### Queries
48
+
49
+ Query blockchain data:
50
+
51
+ ```typescript
52
+ import { BlockHash } from '@midnightntwrk/wallet-sdk-indexer-client';
53
+
54
+ // Query block information including:
55
+ // - Block height
56
+ // - Block hash
57
+ // - Ledger parameters (cost model, etc.)
58
+ // - Timestamp
59
+ ```
60
+
61
+ ## Exports
62
+
63
+ ### Default Export
64
+
65
+ - GraphQL queries: `Connect`, `Disconnect`, `BlockHash`
66
+ - GraphQL subscriptions: `ShieldedTransactions`, `UnshieldedTransactions`, `ZswapEvents`, `DustLedgerEvents`
67
+ - Generated GraphQL types
68
+
69
+ ### Effect Submodule (`/effect`)
70
+
71
+ Effect.ts-based implementation for functional programming patterns:
72
+
73
+ ```typescript
74
+ import {
75
+ Query,
76
+ Subscription,
77
+ QueryClient,
78
+ SubscriptionClient,
79
+ HttpQueryClient,
80
+ WsSubscriptionClient,
81
+ } from '@midnightntwrk/wallet-sdk-indexer-client/effect';
82
+ ```
83
+
84
+ ## License
85
+
86
+ Apache-2.0
@@ -0,0 +1,97 @@
1
+ import { Stream } from 'effect';
2
+ /**
3
+ * Options controlling the bounded backpressure behavior.
4
+ *
5
+ * The implementation caps the number of in-flight (emitted but not yet consumed downstream) items. Once
6
+ * {@link bufferSize} is reached the underlying source is disposed; when the consumer drains back down to
7
+ * {@link resumeThreshold} a fresh subscription is opened with `variables(cursor)` for the running cursor.
8
+ *
9
+ * Emitted items are guaranteed strictly monotonic by {@link key}. Dedup uses a single exclusive watermark: any item with
10
+ * a key `<= watermark` is dropped, and each emitted key advances the watermark. The watermark seeds from {@link from},
11
+ * so the item _at_ `from` is treated as already-seen and dropped. This is what makes resume safe over an inclusive
12
+ * cursor: the boundary item a resume re-delivers has a key equal to the watermark and is dropped — without it, the
13
+ * first item after a resume would duplicate the last one emitted before the pause. A caller that needs the item at its
14
+ * logical cursor emitted (e.g. to let an already-caught-up consumer observe its tip) passes `from` one below that
15
+ * cursor.
16
+ *
17
+ * The cursor is the single source of truth for both initial open and resume — no separate "initial variables" field;
18
+ * `variables(from)` opens the first subscription, `variables(key(lastItem))` opens every subsequent one.
19
+ *
20
+ * No items above the watermark are dropped — the producer is paused, never thrown away.
21
+ *
22
+ * **Totality contract:** {@link key} and {@link variables} MUST be total — they must not throw for any input of their
23
+ * declared types. The wrapper does not catch exceptions from these closures: a throw from `key` propagates back through
24
+ * the source's sync callback (undefined behavior depending on the source's internals), and a throw from `variables`
25
+ * surfaces as an unhandled defect on the resulting `Stream` rather than a typed `E`. The signatures `(cursor: bigint)
26
+ * => V` and `(item: Item) => bigint` are the contract; honor them.
27
+ */
28
+ export interface BackpressureOptions<Item, V> {
29
+ /** Pause the underlying subscription once in-flight reaches this count. */
30
+ readonly bufferSize: number;
31
+ /**
32
+ * Resume once in-flight drains back to this count. Must be strictly less than `bufferSize` to give the consumer real
33
+ * hysteresis.
34
+ */
35
+ readonly resumeThreshold: number;
36
+ /** Initial cursor (bigint watermark). The first subscription opens with `variables(from)`. */
37
+ readonly from: bigint;
38
+ /** Derives subscription variables from a cursor. Used for the initial open and on every resume. */
39
+ readonly variables: (cursor: bigint) => V;
40
+ /** Monotonic key extractor — items with a key not strictly greater than the running watermark are dropped. */
41
+ readonly key: (result: Item) => bigint;
42
+ }
43
+ /**
44
+ * A push-based source. Each call opens a fresh session and returns a dispose handle. Items, errors and completion flow
45
+ * through the supplied callbacks.
46
+ *
47
+ * The source MUST stop delivering callbacks after `dispose()` is invoked. The caller pauses by disposing and resumes by
48
+ * calling the factory again with new variables; the source has no other backpressure signalling.
49
+ */
50
+ export type Source<Item, V, E> = (params: {
51
+ readonly variables: V;
52
+ readonly onItem: (item: Item) => void;
53
+ readonly onError: (error: E) => void;
54
+ readonly onComplete: () => void;
55
+ }) => () => void;
56
+ /** @internal — exported only for unit tests; not part of the public API. */
57
+ export type BPState = {
58
+ readonly paused: boolean;
59
+ readonly inFlight: number;
60
+ readonly lastKey: bigint;
61
+ readonly generation: number;
62
+ readonly terminal: boolean;
63
+ };
64
+ /** @internal */
65
+ export type ItemDecision = {
66
+ readonly emit: boolean;
67
+ readonly pause: boolean;
68
+ };
69
+ /** @internal */
70
+ export declare const initialBPState: (from: bigint) => BPState;
71
+ /**
72
+ * @internal Decide what to do with an item arriving on `onItem`.
73
+ *
74
+ * The watermark is exclusive throughout: an item is dropped iff its key is `<= lastKey`, and each emitted key advances
75
+ * the watermark. The watermark seeds from `from`, so the item at `from` itself is treated as already-seen and dropped —
76
+ * a caller that wants the item at its logical cursor emitted seeds `from` one below it. The same exclusive rule drops
77
+ * the boundary item an inclusive-cursor resume re-delivers (its key equals `lastKey`).
78
+ */
79
+ export declare const decideItem: (s: BPState, k: bigint, myGen: number, bufferSize: number) => readonly [ItemDecision, BPState];
80
+ /** @internal Decide whether an error callback should surface (false for stale/terminal). */
81
+ export declare const decideTerminate: (s: BPState, myGen: number) => readonly [boolean, BPState];
82
+ /** @internal Decide whether a completion is consumer-visible end-of-stream or dispose-induced. */
83
+ export declare const decideComplete: (s: BPState, myGen: number) => readonly [boolean, BPState];
84
+ /**
85
+ * @internal Decide whether to resume the source after a downstream consume. Paused implies we've emitted at least
86
+ * bufferSize items, so lastKey is meaningful. Returns true when the caller should open a new session — the caller
87
+ * derives the resume variables from `state.lastKey` itself.
88
+ */
89
+ export declare const decideResume: (s: BPState, resumeThreshold: number) => readonly [boolean, BPState];
90
+ /**
91
+ * Wrap a push-based {@link Source} with bounded backpressure. The source is paused (disposed) when the in-flight count
92
+ * reaches `bufferSize` and resumed (re-opened with `variables(lastKey)`) once the consumer drains back to
93
+ * `resumeThreshold`.
94
+ *
95
+ * See {@link BackpressureOptions} for the monotonic-key dedup invariant.
96
+ */
97
+ export declare const withBackpressure: <Item, V, E>(source: Source<Item, V, E>, options: BackpressureOptions<Item, V>) => Stream.Stream<Item, E>;
@@ -0,0 +1,146 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ //
14
+ // Bounded backpressure over a push-only source whose only knob is "dispose and
15
+ // re-open from a cursor". The classic shape for graphql-ws / SSE-style feeds
16
+ // that don't support pull or pause signalling.
17
+ //
18
+ // Composition:
19
+ // - Pure state machine (decideItem/decideTerminate/decideComplete/decideResume)
20
+ // contains all transition rules and is unit-testable in isolation.
21
+ // - `withBackpressure` wires the state machine to a {@link Source}, an
22
+ // internal Stream.asyncPush queue, and a Stream.tap drain detector.
23
+ // - Source authors translate their callbacks to the {@link Source} contract
24
+ // once; backpressure is independent of the underlying transport.
25
+ import { Effect, Stream, Ref } from 'effect';
26
+ /** @internal */
27
+ export const initialBPState = (from) => ({
28
+ paused: false,
29
+ inFlight: 0,
30
+ lastKey: from,
31
+ generation: 0,
32
+ terminal: false,
33
+ });
34
+ /**
35
+ * @internal Decide what to do with an item arriving on `onItem`.
36
+ *
37
+ * The watermark is exclusive throughout: an item is dropped iff its key is `<= lastKey`, and each emitted key advances
38
+ * the watermark. The watermark seeds from `from`, so the item at `from` itself is treated as already-seen and dropped —
39
+ * a caller that wants the item at its logical cursor emitted seeds `from` one below it. The same exclusive rule drops
40
+ * the boundary item an inclusive-cursor resume re-delivers (its key equals `lastKey`).
41
+ */
42
+ export const decideItem = (s, k, myGen, bufferSize) => {
43
+ if (s.terminal || s.generation !== myGen)
44
+ return [{ emit: false, pause: false }, s];
45
+ if (k <= s.lastKey)
46
+ return [{ emit: false, pause: false }, s];
47
+ const inFlight = s.inFlight + 1;
48
+ const pause = inFlight >= bufferSize && !s.paused;
49
+ return [
50
+ { emit: true, pause },
51
+ { ...s, inFlight, lastKey: k, paused: s.paused || pause },
52
+ ];
53
+ };
54
+ /** @internal Decide whether an error callback should surface (false for stale/terminal). */
55
+ export const decideTerminate = (s, myGen) => s.terminal || s.generation !== myGen ? [false, s] : [true, { ...s, terminal: true }];
56
+ /** @internal Decide whether a completion is consumer-visible end-of-stream or dispose-induced. */
57
+ export const decideComplete = (s, myGen) => {
58
+ if (s.paused || s.terminal || s.generation !== myGen)
59
+ return [false, s];
60
+ return [true, { ...s, terminal: true }];
61
+ };
62
+ /**
63
+ * @internal Decide whether to resume the source after a downstream consume. Paused implies we've emitted at least
64
+ * bufferSize items, so lastKey is meaningful. Returns true when the caller should open a new session — the caller
65
+ * derives the resume variables from `state.lastKey` itself.
66
+ */
67
+ export const decideResume = (s, resumeThreshold) => {
68
+ if (s.terminal)
69
+ return [false, s];
70
+ const inFlight = Math.max(0, s.inFlight - 1);
71
+ if (s.paused && inFlight <= resumeThreshold) {
72
+ return [true, { ...s, inFlight, paused: false, generation: s.generation + 1 }];
73
+ }
74
+ return [false, { ...s, inFlight }];
75
+ };
76
+ // ============================================================================
77
+ // Stream wrapper
78
+ // ============================================================================
79
+ /**
80
+ * Wrap a push-based {@link Source} with bounded backpressure. The source is paused (disposed) when the in-flight count
81
+ * reaches `bufferSize` and resumed (re-opened with `variables(lastKey)`) once the consumer drains back to
82
+ * `resumeThreshold`.
83
+ *
84
+ * See {@link BackpressureOptions} for the monotonic-key dedup invariant.
85
+ */
86
+ export const withBackpressure = (source, options) => {
87
+ const { bufferSize, resumeThreshold, key, from, variables } = options;
88
+ // Stream.unwrap re-runs the factory per subscription, so each consumer of
89
+ // the returned Stream gets its own Refs and its own source session.
90
+ return Stream.unwrap(Effect.gen(function* () {
91
+ // Three refs cover the inherently mutable bridge between sync source
92
+ // callbacks and Effect: the state machine, the current dispose handle,
93
+ // and the asyncPush emit handle (only bound while a consumer is
94
+ // attached).
95
+ const stateRef = yield* Ref.make(initialBPState(from));
96
+ const disposerRef = yield* Ref.make(null);
97
+ const emitRef = yield* Ref.make(null);
98
+ // Source callbacks are sync JS. Ref ops are sync internally, so runSync
99
+ // is safe here. Confined to these two helpers for auditability.
100
+ const modify = (f) => Effect.runSync(Ref.modify(stateRef, f));
101
+ const takeDisposer = () => Effect.runSync(Ref.getAndSet(disposerRef, null));
102
+ const openSession = () => Effect.gen(function* () {
103
+ const state = yield* Ref.get(stateRef);
104
+ if (state.terminal)
105
+ return;
106
+ const emit = yield* Ref.get(emitRef);
107
+ if (emit === null)
108
+ return;
109
+ const myGen = state.generation;
110
+ const dispose = source({
111
+ variables: variables(state.lastKey),
112
+ onItem: (item) => {
113
+ const action = modify((s) => decideItem(s, key(item), myGen, bufferSize));
114
+ if (!action.emit)
115
+ return;
116
+ emit.single(item);
117
+ if (action.pause)
118
+ takeDisposer()?.();
119
+ },
120
+ onError: (error) => {
121
+ if (modify((s) => decideTerminate(s, myGen)))
122
+ emit.fail(error);
123
+ },
124
+ onComplete: () => {
125
+ if (modify((s) => decideComplete(s, myGen)))
126
+ emit.end();
127
+ },
128
+ });
129
+ yield* Ref.set(disposerRef, dispose);
130
+ });
131
+ return Stream.asyncPush((emit) => Effect.acquireRelease(Effect.gen(function* () {
132
+ yield* Ref.set(emitRef, emit);
133
+ yield* openSession();
134
+ }), () => Effect.gen(function* () {
135
+ yield* Ref.update(stateRef, (s) => ({ ...s, terminal: true }));
136
+ const d = yield* Ref.getAndSet(disposerRef, null);
137
+ if (d !== null)
138
+ yield* Effect.sync(() => d());
139
+ yield* Ref.set(emitRef, null);
140
+ })), { bufferSize: 'unbounded' }).pipe(Stream.tap(() => Effect.gen(function* () {
141
+ const shouldResume = yield* Ref.modify(stateRef, (s) => decideResume(s, resumeThreshold));
142
+ if (shouldResume)
143
+ yield* openSession();
144
+ })));
145
+ }));
146
+ };
@@ -0,0 +1,4 @@
1
+ import * as Either from 'effect/Either';
2
+ import { type URLError } from '@midnightntwrk/wallet-sdk-utilities/networking';
3
+ export declare const deriveWebSocketUrl: (url: URL | string) => Either.Either<string, URLError>;
4
+ export declare const createWebSocketUrl: (httpUrl: URL | string, wsUrl?: string) => Either.Either<string, URLError>;
@@ -0,0 +1,41 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ import * as Either from 'effect/Either';
14
+ import { FailedToDeriveWebSocketUrlError } from '@midnightntwrk/wallet-sdk-utilities/networking';
15
+ export const deriveWebSocketUrl = (url) => {
16
+ const httpUrl = typeof url === 'string' ? new URL(url) : url;
17
+ try {
18
+ const wsUrl = new URL(httpUrl);
19
+ // Convert protocol
20
+ wsUrl.protocol = httpUrl.protocol === 'https:' ? 'wss:' : 'ws:';
21
+ // Ensure pathname ends with '/ws'
22
+ if (!wsUrl.pathname.endsWith('/')) {
23
+ wsUrl.pathname += '/';
24
+ }
25
+ wsUrl.pathname += 'ws';
26
+ return Either.right(wsUrl.toString());
27
+ }
28
+ catch (err) {
29
+ const error = err instanceof Error ? err : new Error(String(err));
30
+ return Either.left(new FailedToDeriveWebSocketUrlError({
31
+ message: `Failed to derive WebSocket URL from ${httpUrl.toString()}`,
32
+ cause: error,
33
+ }));
34
+ }
35
+ };
36
+ export const createWebSocketUrl = (httpUrl, wsUrl) => {
37
+ if (wsUrl) {
38
+ return Either.right(wsUrl);
39
+ }
40
+ return deriveWebSocketUrl(httpUrl);
41
+ };
@@ -0,0 +1,4 @@
1
+ import { Layer, type Scope } from 'effect';
2
+ import { QueryClient } from './QueryClient.js';
3
+ import { type InvalidProtocolSchemeError } from '@midnightntwrk/wallet-sdk-utilities/networking';
4
+ export declare const layer: (config: QueryClient.ServerConfig) => Layer.Layer<QueryClient, InvalidProtocolSchemeError, Scope.Scope>;
@@ -0,0 +1,50 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ import { Effect, Layer } from 'effect';
14
+ import { print } from 'graphql';
15
+ import { createClient } from 'graphql-http';
16
+ import { QueryClient } from './QueryClient.js';
17
+ import { ClientError, ServerError, HttpURL, } from '@midnightntwrk/wallet-sdk-utilities/networking';
18
+ export const layer = (config) => Layer.effect(QueryClient, HttpURL.make(config.url).pipe(Effect.flatMap((url) => Effect.acquireRelease(Effect.sync(() => createClient({
19
+ url: url.toString(),
20
+ shouldRetry: (error, retries) => {
21
+ const statusCode = error.response?.status ?? 500;
22
+ return Promise.resolve(retries < 3 && statusCode >= 502 && statusCode <= 504);
23
+ },
24
+ })), (client) => Effect.sync(() => client.dispose()))), Effect.map((client) => new HttpQueryClientImpl(client))));
25
+ class HttpQueryClientImpl {
26
+ constructor(client) {
27
+ this.client = client;
28
+ }
29
+ client;
30
+ query(document, variables) {
31
+ return Effect.async((resume) => {
32
+ let result;
33
+ const dispose = this.client.subscribe({ query: print(document), variables: variables }, {
34
+ next: (data) => (result = data),
35
+ error: (error) => {
36
+ const statusCode = error.response?.status ?? 500;
37
+ const message = error.response?.statusText ?? 'An unknown error occurred';
38
+ resume(Effect.fail(statusCode >= 400 && statusCode < 500
39
+ ? new ClientError({ message, cause: error })
40
+ : new ServerError({ message })));
41
+ },
42
+ complete: () => resume(result.errors
43
+ ? Effect.fail(new ClientError({ message: result.errors[0].message, cause: result.errors }))
44
+ : Effect.succeed(result.data)),
45
+ });
46
+ // Ensure we dispose of the query if the running Fiber is terminated.
47
+ return Effect.sync(dispose);
48
+ });
49
+ }
50
+ }
@@ -0,0 +1,39 @@
1
+ import { Effect, Context } from 'effect';
2
+ import { type ClientError, type ServerError } from '@midnightntwrk/wallet-sdk-utilities/networking';
3
+ import type { TypedDocumentNode } from '@graphql-typed-document-node/core';
4
+ import { QueryClient } from './QueryClient.js';
5
+ /** Describes an invocable GraphQL query. */
6
+ export interface Query<R, V, F extends Query.QueryFn<R, V> = Query.QueryFn<R, V>> extends Effect.Effect<F> {
7
+ readonly tag: Context.Tag<Query<R, V>, F>;
8
+ readonly run: F;
9
+ }
10
+ export declare namespace Query {
11
+ /**
12
+ * A GraphQL query (that may be parameterized with variables), that returns a typed document.
13
+ *
14
+ * @remarks
15
+ * `Document` is a simple type alias for `TypedDocumentNode`.
16
+ * @typeParam R The type returned by the GraphQL query.
17
+ * @typeParam V A type that describes the variables present in the GraphQL query.
18
+ */
19
+ type Document<R, V> = TypedDocumentNode<R, V>;
20
+ /** The variables of a {@link Document}. */
21
+ type Variables<T> = T extends Document<any, infer V> ? V : never;
22
+ /** The expected result of executing a {@link Document}. */
23
+ type Result<T> = T extends Document<infer R, any> ? R : never;
24
+ /**
25
+ * Describes a function that executes a GraphQL query over some given variables.
26
+ *
27
+ * @param variables The variables to be used in the GraphQL query.
28
+ * @returns An `Effect` that will yield the result of the GraphQL query.
29
+ */
30
+ type QueryFn<R, V> = (variables: V) => Effect.Effect<R, ClientError | ServerError, QueryClient>;
31
+ }
32
+ /**
33
+ * Constructs a new invocable GraphQL query.
34
+ *
35
+ * @param name The name of the tag to be associated with this query.
36
+ * @param document A parsed GraphQL query document that represents the query.
37
+ * @returns A {@link Query}.
38
+ */
39
+ export declare const make: <Name extends string, T extends Query.Document<R, V>, R = Query.Result<T>, V = Query.Variables<T>>(name: Name, document: T) => Query<R, V>;
@@ -0,0 +1,47 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ import { Effect, Context, Effectable, Option, identity } from 'effect';
14
+ import { QueryClient } from './QueryClient.js';
15
+ /**
16
+ * Constructs a new invocable GraphQL query.
17
+ *
18
+ * @param name The name of the tag to be associated with this query.
19
+ * @param document A parsed GraphQL query document that represents the query.
20
+ * @returns A {@link Query}.
21
+ */
22
+ export const make = (name, document) => new QueryImpl(`${name}Query`, document);
23
+ class QueryImpl extends Effectable.Class {
24
+ name;
25
+ document;
26
+ constructor(name, document) {
27
+ super();
28
+ this.document = document;
29
+ this.name = name;
30
+ this.tag = Context.GenericTag(name);
31
+ this.run = ((variables) => Effect.flatMap(this, (f) => f(variables)));
32
+ }
33
+ tag;
34
+ run;
35
+ commit() {
36
+ const self = this; // eslint-disable-line @typescript-eslint/no-this-alias
37
+ return Effect.gen(function* () {
38
+ return Option.match(yield* Effect.serviceOption(self.tag), {
39
+ onSome: identity,
40
+ onNone: () => self.defaultFn.bind(self),
41
+ });
42
+ });
43
+ }
44
+ defaultFn(variables) {
45
+ return QueryClient.pipe(Effect.flatMap((client) => client.query(this.document, variables)));
46
+ }
47
+ }
@@ -0,0 +1,15 @@
1
+ import { type Effect, Context } from 'effect';
2
+ import { type ClientError, type ServerError } from '@midnightntwrk/wallet-sdk-utilities/networking';
3
+ import type { Query } from './Query.js';
4
+ declare const QueryClient_base: Context.TagClass<QueryClient, "@midnight-ntwrk/indexer-client#QueryClient", QueryClient.Service>;
5
+ export declare class QueryClient extends QueryClient_base {
6
+ }
7
+ export declare namespace QueryClient {
8
+ interface ServerConfig {
9
+ readonly url: URL | string;
10
+ }
11
+ interface Service {
12
+ query<R, V, T extends Query.Document<R, V> = Query.Document<R, V>>(document: T, variables: V): Effect.Effect<Query.Result<T>, ClientError | ServerError>;
13
+ }
14
+ }
15
+ export {};
@@ -0,0 +1,15 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ import { Context } from 'effect';
14
+ export class QueryClient extends Context.Tag('@midnight-ntwrk/indexer-client#QueryClient')() {
15
+ }
@@ -0,0 +1,11 @@
1
+ import type { Query } from './Query.js';
2
+ import type { QueryClient } from './QueryClient.js';
3
+ /**
4
+ * Executes a {@link Query} as a `Promise` using the {@link HttpQueryClient}.
5
+ *
6
+ * @param query The query to execute.
7
+ * @param variables The variables to supply to the query.
8
+ * @param config The server configuration (URL) for the HTTP query client.
9
+ * @returns A `Promise` that resolves with the query result.
10
+ */
11
+ export declare const runPromise: <R, V>(query: Query<R, V>, variables: V, config: QueryClient.ServerConfig) => Promise<R>;
@@ -0,0 +1,23 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ import { Effect } from 'effect';
14
+ import * as HttpQueryClient from './HttpQueryClient.js';
15
+ /**
16
+ * Executes a {@link Query} as a `Promise` using the {@link HttpQueryClient}.
17
+ *
18
+ * @param query The query to execute.
19
+ * @param variables The variables to supply to the query.
20
+ * @param config The server configuration (URL) for the HTTP query client.
21
+ * @returns A `Promise` that resolves with the query result.
22
+ */
23
+ export const runPromise = (query, variables, config) => query.run(variables).pipe(Effect.provide(HttpQueryClient.layer(config)), Effect.scoped, Effect.runPromise);
@@ -0,0 +1,25 @@
1
+ import { Effect, Stream, Context } from 'effect';
2
+ import { type ClientError, type ServerError } from '@midnightntwrk/wallet-sdk-utilities/networking';
3
+ import { SubscriptionClient } from './SubscriptionClient.js';
4
+ import type { Query } from './Query.js';
5
+ /** Describes a subscription of elements from an invocable GraphQL query. */
6
+ export interface Subscription<R, V, F extends Subscription.SubscriptionFn<R, V> = Subscription.SubscriptionFn<R, V>> extends Effect.Effect<F> {
7
+ readonly tag: Context.Tag<Subscription<R, V>, F>;
8
+ readonly run: F;
9
+ /**
10
+ * Like {@link run}, but caps the in-flight item count by disposing the underlying GraphQL subscription when the
11
+ * consumer can't keep up, and re-opens it with `variables(cursor)` for the running monotonic cursor once the queue
12
+ * drains. Items are never dropped.
13
+ */
14
+ readonly runWithBackpressure: (options: SubscriptionClient.BackpressureOptions<R, V>) => Stream.Stream<R, ClientError | ServerError, SubscriptionClient>;
15
+ }
16
+ export declare namespace Subscription {
17
+ /**
18
+ * Describes a function that streams a GraphQL subscription for some given variables.
19
+ *
20
+ * @param variables The variables to be used in the GraphQL query.
21
+ * @returns A `Stream` that will yield the elements of the GraphQL subscription.
22
+ */
23
+ type SubscriptionFn<R, V> = (variables: V) => Stream.Stream<R, ClientError | ServerError, SubscriptionClient>;
24
+ }
25
+ export declare const make: <Name extends string, T extends Query.Document<R, V>, R = Query.Result<T>, V = Query.Variables<T>>(name: Name, document: T) => Subscription<R, V>;