@subsquid/solana-stream 1.0.0-portal-api.d5861e → 1.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 (52) hide show
  1. package/lib/builder.d.ts +92 -0
  2. package/lib/builder.d.ts.map +1 -0
  3. package/lib/builder.js +182 -0
  4. package/lib/builder.js.map +1 -0
  5. package/lib/data/fields.d.ts.map +1 -1
  6. package/lib/data/fields.js +0 -14
  7. package/lib/data/fields.js.map +1 -1
  8. package/lib/data/model.d.ts +16 -6
  9. package/lib/data/model.d.ts.map +1 -1
  10. package/lib/data/model.js +5 -0
  11. package/lib/data/model.js.map +1 -1
  12. package/lib/data/request.d.ts +2 -3
  13. package/lib/data/request.d.ts.map +1 -1
  14. package/lib/data/type-util.d.ts +35 -13
  15. package/lib/data/type-util.d.ts.map +1 -1
  16. package/lib/index.d.ts +3 -0
  17. package/lib/index.d.ts.map +1 -1
  18. package/lib/index.js +3 -0
  19. package/lib/index.js.map +1 -1
  20. package/lib/{merge.test.d.ts.map → portal/merge.test.d.ts.map} +1 -1
  21. package/lib/{merge.test.js → portal/merge.test.js} +34 -34
  22. package/lib/portal/merge.test.js.map +1 -0
  23. package/lib/portal/source.d.ts +14 -11
  24. package/lib/portal/source.d.ts.map +1 -1
  25. package/lib/portal/source.js +75 -56
  26. package/lib/portal/source.js.map +1 -1
  27. package/lib/query.d.ts +53 -0
  28. package/lib/query.d.ts.map +1 -0
  29. package/lib/query.js +102 -0
  30. package/lib/query.js.map +1 -0
  31. package/lib/source.d.ts +2 -69
  32. package/lib/source.d.ts.map +1 -1
  33. package/lib/source.js +0 -265
  34. package/lib/source.js.map +1 -1
  35. package/package.json +18 -16
  36. package/src/builder.ts +205 -0
  37. package/src/data/fields.ts +0 -20
  38. package/src/data/model.ts +23 -27
  39. package/src/data/request.ts +2 -3
  40. package/src/data/type-util.ts +39 -37
  41. package/src/index.ts +3 -0
  42. package/src/{merge.test.ts → portal/merge.test.ts} +2 -2
  43. package/src/portal/source.ts +115 -98
  44. package/src/query.ts +112 -0
  45. package/src/source.ts +3 -314
  46. package/lib/merge.test.js.map +0 -1
  47. package/lib/portal/schema.d.ts +0 -167
  48. package/lib/portal/schema.d.ts.map +0 -1
  49. package/lib/portal/schema.js +0 -117
  50. package/lib/portal/schema.js.map +0 -1
  51. package/src/portal/schema.ts +0 -136
  52. /package/lib/{merge.test.d.ts → portal/merge.test.d.ts} +0 -0
package/src/builder.ts ADDED
@@ -0,0 +1,205 @@
1
+ import {PortalClient, PortalClientOptions} from '@subsquid/portal-client'
2
+ import {getOrGenerateSquidId} from '@subsquid/util-internal-processor-tools'
3
+ import {applyRangeBound, mergeRangeRequests, Range, RangeRequest, RangeRequestList} from '@subsquid/util-internal-range'
4
+ import assert from 'assert'
5
+ import {DEFAULT_FIELDS, FieldSelection} from './data/model'
6
+ import {
7
+ BalanceRequest,
8
+ DataRequest,
9
+ InstructionRequest,
10
+ LogRequest,
11
+ RewardRequest,
12
+ TokenBalanceRequest,
13
+ TransactionRequest,
14
+ } from './data/request'
15
+ import {PortalSolanaDataSource} from './portal/source'
16
+ import {type Query, QueryBuilder} from './query'
17
+ import type {SolanaDataSource} from './source'
18
+
19
+ interface BlockRange {
20
+ range?: Range
21
+ }
22
+
23
+ export class DataSourceBuilder<F extends FieldSelection = typeof DEFAULT_FIELDS> {
24
+ private requests: RangeRequest<DataRequest>[] = []
25
+ private fields?: FieldSelection
26
+ private blockRange?: Range
27
+ private portal?: PortalClient | PortalClientOptions
28
+
29
+ /**
30
+ * Set SQD Network Portal endpoint.
31
+ *
32
+ * SQD Network allows to get data from blocks up to
33
+ * infinite times faster and more efficient than via regular RPC.
34
+ *
35
+ * @example
36
+ * source.setPortal('https://portal.sqd.dev/datasets/solana-mainnet')
37
+ */
38
+ setPortal(portal: string | PortalClientOptions | PortalClient): this {
39
+ if (typeof portal == 'string') {
40
+ this.portal = {url: portal}
41
+ } else {
42
+ this.portal = portal
43
+ }
44
+ return this
45
+ }
46
+
47
+ /**
48
+ * Limits the range of blocks to fetch.
49
+ *
50
+ * Note, that block heights should be used instead of slots.
51
+ */
52
+ setBlockRange(range?: Range): this {
53
+ this.blockRange = range
54
+ return this
55
+ }
56
+
57
+ /**
58
+ * Configure a set of fetched fields
59
+ */
60
+ setFields<F extends FieldSelection>(fields: F): DataSourceBuilder<F> {
61
+ this.fields = fields
62
+ return this as any
63
+ }
64
+
65
+ /**
66
+ * Add a portal query - a set of item filters that share a block range.
67
+ *
68
+ * Accepts either a prebuilt {@link Query} object or a
69
+ * {@link QueryBuilder}, in which case {@link QueryBuilder#build} is
70
+ * invoked automatically.
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * source.addQuery(new QueryBuilder()
75
+ * .setRange({from: 250_000_000})
76
+ * .addInstruction({where: {programId: [PROGRAM_ID]}, include: {logs: true}})
77
+ * .addTransaction({where: {feePayer: [FEE_PAYER]}})
78
+ * )
79
+ * ```
80
+ */
81
+ addQuery(query: Query | QueryBuilder): this {
82
+ this.requests.push(query instanceof QueryBuilder ? query.build() : query)
83
+ return this
84
+ }
85
+
86
+ /**
87
+ * Shorthand for {@link DataSourceBuilder#addQuery} with a query that
88
+ * only sets {@link QueryBuilder#includeAllBlocks} (and optionally a
89
+ * range).
90
+ */
91
+ includeAllBlocks(range?: Range): this {
92
+ return this.addQuery({
93
+ range: range ?? {from: 0},
94
+ request: {includeAllBlocks: true},
95
+ })
96
+ }
97
+
98
+ /**
99
+ * Shorthand for {@link DataSourceBuilder#addQuery} with a query that
100
+ * contains a single transaction filter.
101
+ */
102
+ addTransaction(options: TransactionRequest & BlockRange): this {
103
+ let {range, ...req} = options
104
+ return this.addQuery({
105
+ range: range ?? {from: 0},
106
+ request: {transactions: [req]},
107
+ })
108
+ }
109
+
110
+ /**
111
+ * Shorthand for {@link DataSourceBuilder#addQuery} with a query that
112
+ * contains a single instruction filter.
113
+ */
114
+ addInstruction(options: InstructionRequest & BlockRange): this {
115
+ let {range, ...req} = options
116
+ return this.addQuery({
117
+ range: range ?? {from: 0},
118
+ request: {instructions: [req]},
119
+ })
120
+ }
121
+
122
+ /**
123
+ * Shorthand for {@link DataSourceBuilder#addQuery} with a query that
124
+ * contains a single log filter.
125
+ */
126
+ addLog(options: LogRequest & BlockRange): this {
127
+ let {range, ...req} = options
128
+ return this.addQuery({
129
+ range: range ?? {from: 0},
130
+ request: {logs: [req]},
131
+ })
132
+ }
133
+
134
+ /**
135
+ * Shorthand for {@link DataSourceBuilder#addQuery} with a query that
136
+ * contains a single balance filter.
137
+ */
138
+ addBalance(options: BalanceRequest & BlockRange): this {
139
+ let {range, ...req} = options
140
+ return this.addQuery({
141
+ range: range ?? {from: 0},
142
+ request: {balances: [req]},
143
+ })
144
+ }
145
+
146
+ /**
147
+ * Shorthand for {@link DataSourceBuilder#addQuery} with a query that
148
+ * contains a single token balance filter.
149
+ */
150
+ addTokenBalance(options: TokenBalanceRequest & BlockRange): this {
151
+ let {range, ...req} = options
152
+ return this.addQuery({
153
+ range: range ?? {from: 0},
154
+ request: {tokenBalances: [req]},
155
+ })
156
+ }
157
+
158
+ /**
159
+ * Shorthand for {@link DataSourceBuilder#addQuery} with a query that
160
+ * contains a single reward filter.
161
+ */
162
+ addReward(options: RewardRequest & BlockRange): this {
163
+ let {range, ...req} = options
164
+ return this.addQuery({
165
+ range: range ?? {from: 0},
166
+ request: {rewards: [req]},
167
+ })
168
+ }
169
+
170
+ private getRequests(): RangeRequestList<DataRequest> {
171
+ function concat<T>(a?: T[], b?: T[]): T[] | undefined {
172
+ let result: T[] = []
173
+ if (a) {
174
+ result.push(...a)
175
+ }
176
+ if (b) {
177
+ result.push(...b)
178
+ }
179
+ return result.length == 0 ? undefined : result
180
+ }
181
+
182
+ let requests = mergeRangeRequests(this.requests, (a, b) => {
183
+ return {
184
+ includeAllBlocks: a.includeAllBlocks || b.includeAllBlocks,
185
+ transactions: concat(a.transactions, b.transactions),
186
+ instructions: concat(a.instructions, b.instructions),
187
+ logs: concat(a.logs, b.logs),
188
+ balances: concat(a.balances, b.balances),
189
+ tokenBalances: concat(a.tokenBalances, b.tokenBalances),
190
+ rewards: concat(a.rewards, b.rewards),
191
+ }
192
+ })
193
+
194
+ return applyRangeBound(requests, this.blockRange)
195
+ }
196
+
197
+ build(): SolanaDataSource<F> {
198
+ assert(this.portal, 'Portal settings not set')
199
+
200
+ let portal = this.portal instanceof PortalClient ? this.portal : new PortalClient(this.portal)
201
+ return new PortalSolanaDataSource<F>(portal, this.fields ?? DEFAULT_FIELDS, () => this.getRequests(), {
202
+ squidId: getOrGenerateSquidId(),
203
+ })
204
+ }
205
+ }
@@ -1,26 +1,6 @@
1
- import {DEFAULT_FIELDS, FieldSelection} from './model'
2
1
  import {Selector} from './type-util'
3
2
 
4
3
 
5
- function merge<Keys extends string>(def: Selector<Keys>, requested: Selector<Keys> = {}): Selector<Keys> {
6
- let fields: Selector<Keys> = {}
7
-
8
- for (let key in def) {
9
- if (requested[key] !== false) {
10
- fields[key] = def[key]
11
- }
12
- }
13
-
14
- for (let key in requested) {
15
- if (requested[key]) {
16
- fields[key] = true
17
- }
18
- }
19
-
20
- return fields
21
- }
22
-
23
-
24
4
  export function project<T>(fields: Selector<keyof T> | undefined, obj: T): Partial<T> {
25
5
  if (fields == null) return {}
26
6
  let result: Partial<T> = {}
package/src/data/model.ts CHANGED
@@ -10,16 +10,16 @@ import type {
10
10
  } from './partial'
11
11
  import type {GetFields, Select, Selector, Simplify} from './type-util'
12
12
 
13
- /**
14
- * Hex encoded binary string
15
- */
13
+ /** Hex encoded binary string. */
16
14
  export type Bytes = string
17
15
 
18
- /**
19
- * Base58 encoded binary string
20
- */
16
+ /** Base58 encoded binary string. */
21
17
  export type Base58Bytes = string
22
18
 
19
+ /**
20
+ * User-facing field selection. Each section is an optional `Selector`
21
+ * over that section's non-required fields.
22
+ */
23
23
  export interface FieldSelection {
24
24
  block?: Selector<Exclude<keyof data.BlockHeader, BlockRequiredFields>>
25
25
  transaction?: Selector<Exclude<keyof data.Transaction, TransactionRequiredFields>>
@@ -30,6 +30,13 @@ export interface FieldSelection {
30
30
  reward?: Selector<Exclude<keyof data.Reward, RewardRequiredFields>>
31
31
  }
32
32
 
33
+ export type FieldKey = keyof FieldSelection
34
+
35
+ /**
36
+ * Default field set applied when {@link DataSourceBuilder#setFields} is not
37
+ * called. `as const` preserves the literal `true` values so they feed through
38
+ * {@link GetFields} correctly.
39
+ */
33
40
  export const DEFAULT_FIELDS = {
34
41
  block: {
35
42
  timestamp: true,
@@ -67,38 +74,27 @@ export const DEFAULT_FIELDS = {
67
74
  lamports: true,
68
75
  rewardType: true,
69
76
  },
70
- } as const
77
+ } as const satisfies FieldSelection
71
78
 
72
- type Item<Data, RequiredFields extends keyof Data, F extends FieldSelection, K extends keyof FieldSelection> = Simplify<
73
- Pick<Data, RequiredFields> & Select<Data, GetFields<FieldSelection, typeof DEFAULT_FIELDS, F, K>>
79
+ /**
80
+ * A concrete item type derived from a raw data shape, the always-present
81
+ * required keys, and a per-section field selection.
82
+ */
83
+ type Item<Data, Required extends keyof Data, F extends FieldSelection, K extends FieldKey> = Simplify<
84
+ Pick<Data, Required> & Select<Data, GetFields<F, K>>
74
85
  >
75
86
 
76
87
  export type BlockHeader<F extends FieldSelection = {}> = Item<data.BlockHeader, BlockRequiredFields, F, 'block'>
77
88
 
78
- export type Transaction<F extends FieldSelection = {}> = Item<
79
- data.Transaction,
80
- TransactionRequiredFields,
81
- F,
82
- 'transaction'
83
- >
89
+ export type Transaction<F extends FieldSelection = {}> = Item<data.Transaction, TransactionRequiredFields, F, 'transaction'>
84
90
 
85
- export type Instruction<F extends FieldSelection = {}> = Item<
86
- data.Instruction,
87
- InstructionRequiredFields,
88
- F,
89
- 'instruction'
90
- >
91
+ export type Instruction<F extends FieldSelection = {}> = Item<data.Instruction, InstructionRequiredFields, F, 'instruction'>
91
92
 
92
93
  export type LogMessage<F extends FieldSelection = {}> = Item<data.LogMessage, LogRequiredFields, F, 'log'>
93
94
 
94
95
  export type Balance<F extends FieldSelection = {}> = Item<data.Balance, BalanceRequiredFields, F, 'balance'>
95
96
 
96
- export type TokenBalance<F extends FieldSelection = {}> = Item<
97
- data.TokenBalance,
98
- TokenBalanceRequiredFields,
99
- F,
100
- 'tokenBalance'
101
- >
97
+ export type TokenBalance<F extends FieldSelection = {}> = Item<data.TokenBalance, TokenBalanceRequiredFields, F, 'tokenBalance'>
102
98
 
103
99
  export type Reward<F extends FieldSelection = {}> = Item<data.Reward, RewardRequiredFields, F, 'reward'>
104
100
 
@@ -1,9 +1,8 @@
1
1
  import {LogMessage} from '@subsquid/solana-normalization'
2
- import {Base58Bytes, FieldSelection} from './model'
2
+ import {Base58Bytes} from './model'
3
3
 
4
4
 
5
- export interface DataRequest<F extends FieldSelection> {
6
- fields?: F
5
+ export interface DataRequest {
7
6
  includeAllBlocks?: boolean
8
7
  transactions?: TransactionRequest[]
9
8
  instructions?: InstructionRequest[]
@@ -1,42 +1,44 @@
1
- export type Simplify<T> = {
2
- [K in keyof T]: T[K]
3
- } & {}
4
-
5
-
6
- export type ExcludeUndefined<T> = {
7
- [K in keyof T as undefined extends T[K] ? never : K]: T[K]
8
- } & {}
9
-
10
-
11
- export type GetFields<
12
- FieldSelectionType,
13
- Defaults extends FieldSelectionType,
14
- Selection extends FieldSelectionType,
15
- K extends keyof FieldSelectionType
16
- > = TrueFields<MergeDefault<Selection[K], Defaults[K]>>
17
-
18
-
19
- type MergeDefault<T, D> = Simplify<
20
- undefined extends T ? D : Omit<D, keyof ExcludeUndefined<T>> & ExcludeUndefined<T>
21
- >
22
-
23
-
24
- type TrueFields<F> = keyof {
25
- [K in keyof F as true extends F[K] ? K : never]: true
26
- }
27
-
28
-
1
+ /**
2
+ * Flattens an intersection type into a single object shape for nicer IDE hover
3
+ * output. The trailing `& {}` is a no-op at the type level but forces the
4
+ * compiler to normalize the representation.
5
+ */
6
+ export type Simplify<T> = {[K in keyof T]: T[K]} & {}
7
+
8
+ /**
9
+ * Extracts the string keys of `F` whose values include the literal `true`.
10
+ *
11
+ * @example
12
+ * type Keys = TrueFields<{a: true; b: false; c?: boolean}>
13
+ * // "a" | "c" -- `c`'s type includes `true` through `boolean`
14
+ */
15
+ export type TrueFields<F> = {
16
+ [K in keyof F]-?: true extends F[K] ? K : never
17
+ }[keyof F]
18
+
19
+ /**
20
+ * Picks the set of truthy field keys for a given section `K` of a
21
+ * `FieldSelection`-shaped type `F`.
22
+ */
23
+ export type GetFields<F, K extends keyof F> = TrueFields<F[K]>
24
+
25
+ /**
26
+ * Picks `Fields` from `T` while distributing over unions, so members of a
27
+ * discriminated union are narrowed independently.
28
+ */
29
29
  export type Select<T, Fields> = T extends any ? Simplify<Pick<T, Extract<keyof T, Fields>>> : never
30
30
 
31
-
32
- export type Selector<Fields extends string | number | symbol> = {
31
+ /**
32
+ * Optional boolean flag map over a finite set of string keys.
33
+ *
34
+ * Shape: `{[K in Fields]?: boolean}` - equivalent to `Partial<Record<Fields, boolean>>`.
35
+ */
36
+ export type Selector<Fields extends PropertyKey> = {
33
37
  [P in Fields]?: boolean
34
38
  }
35
39
 
36
-
37
- export type MakePartial<T, Required extends keyof T> = Simplify<
38
- Pick<T, Required> &
39
- {
40
- [K in keyof T as K extends Required ? never : K]+?: T[K]
41
- }
42
- >
40
+ /**
41
+ * Produces a type with `Required` keys kept as-is and all other keys made
42
+ * optional.
43
+ */
44
+ export type MakePartial<T, Required extends keyof T> = Simplify<Pick<T, Required> & Partial<Omit<T, Required>>>
package/src/index.ts CHANGED
@@ -1,3 +1,6 @@
1
1
  export * from './data/model'
2
2
  export * from './instruction'
3
+ export * from './builder'
4
+ export * from './query'
3
5
  export * from './source'
6
+ export * from './portal/source'
@@ -1,4 +1,4 @@
1
- import {describe, it} from 'node:test'
1
+ import { describe, it } from 'vitest'
2
2
  import assert from 'assert'
3
3
  import {
4
4
  mergeItems,
@@ -8,7 +8,7 @@ import {
8
8
  BALANCE_FILTER_KEYS,
9
9
  TOKEN_BALANCE_FILTER_KEYS,
10
10
  REWARD_FILTER_KEYS,
11
- } from './portal/merge'
11
+ } from './merge'
12
12
 
13
13
  describe('mergeItems', function () {
14
14
  describe('instructions', function () {