@curvhex/orm 0.1.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 (47) hide show
  1. package/LICENSE +143 -0
  2. package/README.md +485 -0
  3. package/dist/adapters/RpcAdapter.d.ts +12 -0
  4. package/dist/adapters/RpcAdapter.d.ts.map +1 -0
  5. package/dist/adapters/RpcAdapter.js +67 -0
  6. package/dist/adapters/RpcAdapter.js.map +1 -0
  7. package/dist/adapters/abstract/QueryAdapter.d.ts +61 -0
  8. package/dist/adapters/abstract/QueryAdapter.d.ts.map +1 -0
  9. package/dist/adapters/abstract/QueryAdapter.js +3 -0
  10. package/dist/adapters/abstract/QueryAdapter.js.map +1 -0
  11. package/dist/client/CurvhexClient.d.ts +19 -0
  12. package/dist/client/CurvhexClient.d.ts.map +1 -0
  13. package/dist/client/CurvhexClient.js +123 -0
  14. package/dist/client/CurvhexClient.js.map +1 -0
  15. package/dist/client/CurvhexORM.d.ts +19 -0
  16. package/dist/client/CurvhexORM.d.ts.map +1 -0
  17. package/dist/client/CurvhexORM.js +21 -0
  18. package/dist/client/CurvhexORM.js.map +1 -0
  19. package/dist/client/VertexClient.d.ts +19 -0
  20. package/dist/client/VertexClient.d.ts.map +1 -0
  21. package/dist/client/VertexClient.js +123 -0
  22. package/dist/client/VertexClient.js.map +1 -0
  23. package/dist/client/VertexORM.d.ts +19 -0
  24. package/dist/client/VertexORM.d.ts.map +1 -0
  25. package/dist/client/VertexORM.js +21 -0
  26. package/dist/client/VertexORM.js.map +1 -0
  27. package/dist/core/deserializer.d.ts +3 -0
  28. package/dist/core/deserializer.d.ts.map +1 -0
  29. package/dist/core/deserializer.js +74 -0
  30. package/dist/core/deserializer.js.map +1 -0
  31. package/dist/core/filters.d.ts +46 -0
  32. package/dist/core/filters.d.ts.map +1 -0
  33. package/dist/core/filters.js +121 -0
  34. package/dist/core/filters.js.map +1 -0
  35. package/dist/core/schema.d.ts +12 -0
  36. package/dist/core/schema.d.ts.map +1 -0
  37. package/dist/core/schema.js +34 -0
  38. package/dist/core/schema.js.map +1 -0
  39. package/dist/core/types.d.ts +16 -0
  40. package/dist/core/types.d.ts.map +1 -0
  41. package/dist/core/types.js +3 -0
  42. package/dist/core/types.js.map +1 -0
  43. package/dist/index.d.ts +8 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +12 -0
  46. package/dist/index.js.map +1 -0
  47. package/package.json +33 -0
package/LICENSE ADDED
@@ -0,0 +1,143 @@
1
+ Apache License 2.0 with Commons Clause
2
+
3
+ "Commons Clause" License Condition v1.0
4
+
5
+ The Software is provided to you by the Licensor under the License, as defined
6
+ below, subject to the following condition.
7
+
8
+ Without limiting other conditions in the License, the grant of rights under
9
+ the License will not include, and the License does not grant to you, the right
10
+ to Sell the Software.
11
+
12
+ For purposes of the foregoing, "Sell" means practicing any or all of the
13
+ rights granted to you under the License to provide to third parties, for a fee
14
+ or other consideration (including without limitation fees for hosting or
15
+ consulting / support services related to the Software), a product or service
16
+ whose value derives, entirely or substantially, from the functionality of the
17
+ Software. Any license notice or attribution required by the License must also
18
+ include this Commons Clause License Condition notice.
19
+
20
+ Software: curvhex-orm
21
+ License: Apache 2.0
22
+ Licensor: Bugra Okumus
23
+
24
+ ---
25
+
26
+ Apache License
27
+ Version 2.0, January 2004
28
+ http://www.apache.org/licenses/
29
+
30
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
31
+
32
+ 1. Definitions.
33
+
34
+ "License" shall mean the terms and conditions for use, reproduction,
35
+ and distribution as defined by Sections 1 through 9 of this document.
36
+
37
+ "Licensor" shall mean the copyright owner or entity authorized by
38
+ the copyright owner that is granting the License.
39
+
40
+ "Legal Entity" shall mean the union of the acting entity and all
41
+ other entities that control, are controlled by, or are under common
42
+ control with that entity.
43
+
44
+ "You" (or "Your") shall mean an individual or Legal Entity
45
+ exercising permissions granted by this License.
46
+
47
+ "Source" form shall mean the preferred form for making modifications,
48
+ including but not limited to software source code, documentation
49
+ source, and configuration files.
50
+
51
+ "Object" form shall mean any form resulting from mechanical
52
+ transformation or translation of a Source form, including but
53
+ not limited to compiled object code, generated documentation,
54
+ and conversions to other media types.
55
+
56
+ "Work" shall mean the work of authorship made available under
57
+ the License, as indicated by a copyright notice that is included
58
+ in or attached to the work.
59
+
60
+ "Derivative Works" shall mean any work, whether in Source or Object
61
+ form, that is based on (or derived from) the Work and for which the
62
+ editorial revisions, annotations, elaborations, or other
63
+ transformations represent, as a whole, an original work of authorship.
64
+
65
+ "Contribution" shall mean any work of authorship, including
66
+ the original version of the Work and any modifications or additions
67
+ to that Work or Derivative Works of the Work, that is intentionally
68
+ submitted to the Licensor for inclusion in the Work by the copyright
69
+ owner or by an individual or Legal Entity authorized to submit on
70
+ behalf of the copyright owner.
71
+
72
+ "Contributor" shall mean Licensor and any Legal Entity on behalf of
73
+ whom a Contribution has been received by the Licensor and included
74
+ within the Work.
75
+
76
+ 2. Grant of Copyright License. Subject to the terms and conditions of
77
+ this License, each Contributor hereby grants to You a perpetual,
78
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
79
+ copyright license to reproduce, prepare Derivative Works of,
80
+ publicly display, publicly perform, sublicense, and distribute the
81
+ Work and such Derivative Works in Source or Object form.
82
+
83
+ 3. Grant of Patent License. Subject to the terms and conditions of
84
+ this License, each Contributor hereby grants to You a perpetual,
85
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
86
+ patent license to make, use, sell, offer for sale, import, and
87
+ otherwise transfer the Work.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or Derivative
95
+ Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work; and
103
+
104
+ (d) If the Work includes a "NOTICE" text file, You must include a
105
+ readable copy of the attribution notices contained within such
106
+ NOTICE file.
107
+
108
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
109
+ any Contribution intentionally submitted for inclusion in the Work
110
+ by You to the Licensor shall be under the terms and conditions of
111
+ this License, without any additional terms or conditions.
112
+
113
+ 6. Trademarks. This License does not grant permission to use the trade
114
+ names, trademarks, service marks, or product names of the Licensor.
115
+
116
+ 7. Disclaimer of Warranty. Unless required by applicable law or agreed
117
+ to in writing, Licensor provides the Work on an "AS IS" BASIS,
118
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
119
+ implied.
120
+
121
+ 8. Limitation of Liability. In no event and under no legal theory,
122
+ whether in tort (including negligence), contract, or otherwise,
123
+ unless required by applicable law, shall any Contributor be liable
124
+ to You for damages, including any direct, indirect, special,
125
+ incidental, or exemplary damages of any character arising as a
126
+ result of this License or out of the use or inability to use the
127
+ Work.
128
+
129
+ 9. Accepting Warranty or Additional Liability. While redistributing
130
+ the Work or Derivative Works thereof, You may choose to offer
131
+ additional warranty, liability, or other obligations consistent
132
+ with this License. However, in accepting such obligations, You may
133
+ act only on Your own behalf and on Your sole responsibility.
134
+
135
+ END OF TERMS AND CONDITIONS
136
+
137
+ Copyright 2026 Bugra Okumus
138
+
139
+ Licensed under the Apache License, Version 2.0 (the "License");
140
+ you may not use this file except in compliance with the License.
141
+ You may obtain a copy of the License at
142
+
143
+ http://www.apache.org/licenses/LICENSE-2.0
package/README.md ADDED
@@ -0,0 +1,485 @@
1
+ # Curvhex ORM
2
+
3
+ TypeScript ORM for Solana PDA accounts. Query, filter, and aggregate on-chain data with a familiar API — inspired by Prisma, built for Solana.
4
+
5
+ ```typescript
6
+ const users = await orm.models.userAccount.findMany({
7
+ where: { authority: wallet.publicKey, isActive: true },
8
+ orderBy: { balance: 'desc' },
9
+ take: 10,
10
+ })
11
+ ```
12
+
13
+ ---
14
+
15
+ ## The Problem
16
+
17
+ Solana stores program state in accounts (PDAs). Querying them is painful:
18
+
19
+ - `getProgramAccounts` only supports exact byte matching (`memcmp`)
20
+ - No range queries, sorting, aggregation, or relations at the RPC level
21
+ - Every project reimplements the same deserialization + filtering boilerplate
22
+ - Switching from a public RPC to an indexer (Helius, your own Postgres) requires rewriting query logic
23
+
24
+ Curvhex ORM solves this with a single, adapter-agnostic query API.
25
+
26
+ ---
27
+
28
+ ## How It Works
29
+
30
+ ```
31
+ Your Code → CurvhexORM → QueryAdapter → Data Source
32
+ ├── RpcAdapter (getProgramAccounts)
33
+ ├── HeliusAdapter (DAS API) [soon]
34
+ └── PostgresAdapter (your indexer) [soon]
35
+ ```
36
+
37
+ Define your schema once. Write queries once. Swap the adapter as your needs grow — from a quick prototype on public RPC to a production indexer — without changing a single query.
38
+
39
+ ---
40
+
41
+ ## Installation
42
+
43
+ ```bash
44
+ npm install curvhex-orm @solana/web3.js
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Quick Start
50
+
51
+ ### 1. Define your schema
52
+
53
+ ```typescript
54
+ import { defineModel, anchor } from 'curvhex-orm'
55
+
56
+ const UserAccount = defineModel({
57
+ // Anchor programs: use anchor() helper
58
+ discriminator: anchor('UserAccount'),
59
+
60
+ // Native programs: provide raw bytes
61
+ // discriminator: [1, 2, 3, 4, 5, 6, 7, 8],
62
+
63
+ fields: {
64
+ authority: { type: 'publicKey' },
65
+ balance: { type: 'u64' },
66
+ tier: { type: 'u8' },
67
+ isActive: { type: 'bool' },
68
+ name: { type: 'string' },
69
+ }
70
+ })
71
+ ```
72
+
73
+ Field offsets are calculated automatically from the discriminator length and field order — no manual byte counting.
74
+
75
+ ### 2. Create the ORM
76
+
77
+ ```typescript
78
+ import { CurvhexORM, RpcAdapter } from 'curvhex-orm'
79
+ import { Connection, PublicKey } from '@solana/web3.js'
80
+
81
+ const connection = new Connection('https://api.mainnet-beta.solana.com')
82
+
83
+ const orm = new CurvhexORM({
84
+ connection,
85
+ programId: 'YOUR_PROGRAM_ID',
86
+ models: { UserAccount },
87
+ })
88
+ ```
89
+
90
+ ### 3. Query
91
+
92
+ ```typescript
93
+ // Find by PDA seeds
94
+ const user = await orm.models.userAccount.findByPda([
95
+ Buffer.from('user'),
96
+ wallet.publicKey.toBuffer(),
97
+ ])
98
+
99
+ // Find by address
100
+ const user = await orm.models.userAccount.findByAddress('Abc123...')
101
+
102
+ // Find many with filters
103
+ const users = await orm.models.userAccount.findMany({
104
+ where: { isActive: true, authority: wallet.publicKey.toBase58() },
105
+ orderBy: { balance: 'desc' },
106
+ take: 20,
107
+ skip: 0,
108
+ })
109
+ ```
110
+
111
+ ---
112
+
113
+ ## API Reference
114
+
115
+ ### `defineModel(input)`
116
+
117
+ Defines an account schema. Offsets are computed automatically.
118
+
119
+ ```typescript
120
+ const MyAccount = defineModel({
121
+ discriminator: anchor('MyAccount'), // or raw number[]
122
+ fields: {
123
+ owner: { type: 'publicKey' },
124
+ amount: { type: 'u64' },
125
+ enabled: { type: 'bool' },
126
+ }
127
+ })
128
+ ```
129
+
130
+ **Supported field types:**
131
+
132
+ | Type | Size | TypeScript type |
133
+ |------|------|-----------------|
134
+ | `u8` `u16` `u32` | 1–4 bytes | `number` |
135
+ | `i8` `i16` `i32` | 1–4 bytes | `number` |
136
+ | `u64` `u128` | 8–16 bytes | `bigint` |
137
+ | `i64` `i128` | 8–16 bytes | `bigint` |
138
+ | `bool` | 1 byte | `boolean` |
139
+ | `publicKey` | 32 bytes | `string` (base58) |
140
+ | `string` | 4 + N bytes | `string` |
141
+ | `bytes` | 4 + N bytes | `string` (hex) |
142
+
143
+ ### `anchor(name)`
144
+
145
+ Computes the 8-byte Anchor discriminator for an account name.
146
+
147
+ ```typescript
148
+ import { anchor } from 'curvhex-orm'
149
+
150
+ anchor('UserAccount') // → [149, 88, 201, ...]
151
+ ```
152
+
153
+ ### `findMany(options?)`
154
+
155
+ ```typescript
156
+ const results = await orm.models.userAccount.findMany({
157
+ where: {
158
+ // Equality (on-chain memcmp — fast)
159
+ authority: 'Abc123...',
160
+ isActive: true,
161
+ tier: 2,
162
+
163
+ // Range operators (client-side filter)
164
+ balance: { gte: 100n, lte: 10_000n },
165
+ balance: { gt: 0n },
166
+ tier: { in: [1, 2, 3] },
167
+ balance: { between: [100n, 1000n] },
168
+ tier: { not: 0 },
169
+ },
170
+ orderBy: { balance: 'desc' },
171
+ take: 10,
172
+ skip: 0,
173
+ dataSize: 165, // optional: filter by account byte size
174
+ })
175
+ ```
176
+
177
+ > **On-chain vs client-side:** Equality filters (`authority`, `isActive`, `tier`) are sent as `memcmp` filters to the RPC node — only matching accounts are transferred. Range operators (`gt`, `gte`, `lt`, `lte`, `between`, `in`) are applied after accounts are received. Combine both for best performance.
178
+
179
+ ### `findFirst(options?)`
180
+
181
+ Returns the first matching account or `null`.
182
+
183
+ ```typescript
184
+ const user = await orm.models.userAccount.findFirst({
185
+ where: { authority: wallet.publicKey.toBase58() }
186
+ })
187
+ ```
188
+
189
+ ### `findByAddress(address)`
190
+
191
+ Fetches a single account by its public key string.
192
+
193
+ ```typescript
194
+ const user = await orm.models.userAccount.findByAddress('Abc123...')
195
+ ```
196
+
197
+ ### `findByPda(seeds)`
198
+
199
+ Derives the PDA and fetches the account.
200
+
201
+ ```typescript
202
+ const user = await orm.models.userAccount.findByPda([
203
+ Buffer.from('user'),
204
+ wallet.publicKey.toBuffer(),
205
+ ])
206
+ ```
207
+
208
+ ### `count(where?)`
209
+
210
+ ```typescript
211
+ const total = await orm.models.userAccount.count({ isActive: true })
212
+ ```
213
+
214
+ ### `aggregate(options)`
215
+
216
+ ```typescript
217
+ const stats = await orm.models.userAccount.aggregate({
218
+ where: { isActive: true },
219
+ _count: true,
220
+ _sum: { balance: true },
221
+ _avg: { balance: true },
222
+ _min: { balance: true },
223
+ _max: { balance: true },
224
+ })
225
+ // → { _count: 142, _sum: { balance: 5_000_000n }, _avg: ..., _min: ..., _max: ... }
226
+ ```
227
+
228
+ ### `groupBy(options)`
229
+
230
+ ```typescript
231
+ const byTier = await orm.models.userAccount.groupBy({
232
+ by: ['tier'],
233
+ where: { isActive: true },
234
+ _count: true,
235
+ _sum: { balance: true },
236
+ })
237
+ // → [
238
+ // { tier: 2, _count: 40, _sum: { balance: 2_000_000n } },
239
+ // { tier: 1, _count: 102, _sum: { balance: 3_000_000n } },
240
+ // ]
241
+ ```
242
+
243
+ ### `findMany` with `include` (Relations)
244
+
245
+ Fetch related accounts in a single call. Deduplicates addresses automatically — if 100 vaults share the same owner, the owner account is fetched once.
246
+
247
+ ```typescript
248
+ const VaultAccount = defineModel({
249
+ discriminator: anchor('VaultAccount'),
250
+ fields: {
251
+ ownerPubkey: { type: 'publicKey' },
252
+ totalLocked: { type: 'u64' },
253
+ }
254
+ })
255
+
256
+ const vaults = await orm.models.vaultAccount.findMany({
257
+ where: { totalLocked: { gt: 1000n } },
258
+ include: {
259
+ owner: {
260
+ model: UserAccount,
261
+ foreignKey: 'ownerPubkey',
262
+ }
263
+ }
264
+ })
265
+ // → [{ address, totalLocked, owner: { authority, balance, tier } }]
266
+ ```
267
+
268
+ ---
269
+
270
+ ## Adapters
271
+
272
+ The query API is adapter-agnostic. Swap the data source without changing your queries.
273
+
274
+ ### RpcAdapter (default)
275
+
276
+ Works with any Solana RPC endpoint. No setup required.
277
+
278
+ ```typescript
279
+ import { CurvhexORM, RpcAdapter } from 'curvhex-orm'
280
+
281
+ const orm = new CurvhexORM({
282
+ connection,
283
+ programId: 'YOUR_PROGRAM_ID',
284
+ models: { UserAccount },
285
+ // adapter defaults to RpcAdapter
286
+ })
287
+ ```
288
+
289
+ **Limitations:** `getProgramAccounts` is rate-limited or blocked on many public endpoints for large programs. Range queries require fetching all matching accounts first.
290
+
291
+ ### HeliusAdapter *(coming soon)*
292
+
293
+ Uses the Helius DAS API. Faster, higher rate limits, better support for large programs.
294
+
295
+ ```typescript
296
+ import { HeliusAdapter } from 'curvhex-orm/adapters'
297
+
298
+ const orm = new CurvhexORM({
299
+ connection,
300
+ programId: 'YOUR_PROGRAM_ID',
301
+ models: { UserAccount },
302
+ adapter: new HeliusAdapter({ apiKey: 'your-helius-key' }),
303
+ })
304
+ ```
305
+
306
+ ### PostgresAdapter *(coming soon)*
307
+
308
+ Query your own Geyser-indexed database. Enables true range queries, sorting, and aggregation at the database level.
309
+
310
+ ```typescript
311
+ import { PostgresAdapter } from 'curvhex-orm/adapters'
312
+
313
+ const orm = new CurvhexORM({
314
+ connection,
315
+ programId: 'YOUR_PROGRAM_ID',
316
+ models: { UserAccount },
317
+ adapter: new PostgresAdapter({
318
+ connectionString: 'postgresql://user:pass@localhost:5432/mydb',
319
+ table: 'user_accounts',
320
+ }),
321
+ })
322
+ ```
323
+
324
+ ---
325
+
326
+ ## Architecture
327
+
328
+ ### Why adapters?
329
+
330
+ Solana's native RPC (`getProgramAccounts`) only supports exact byte matching. Range queries, sorting, and aggregation require off-chain infrastructure.
331
+
332
+ Rather than picking one solution, Curvhex ORM abstracts the data source:
333
+
334
+ ```
335
+ findMany({ where: { balance: { gt: 100n } } })
336
+
337
+ ├── RpcAdapter → fetch all + client-side filter
338
+ ├── HeliusAdapter → Helius API query
339
+ └── PostgresAdapter → SELECT * WHERE balance > 100
340
+ ```
341
+
342
+ As the Solana ecosystem matures (Triton's RPC 2.0, indexers), adapters can be upgraded independently.
343
+
344
+ ### On-chain vs off-chain filtering
345
+
346
+ | Filter type | RpcAdapter | HeliusAdapter | PostgresAdapter |
347
+ |-------------|-----------|---------------|-----------------|
348
+ | Equality (`authority = X`) | ✅ on-chain memcmp | ✅ | ✅ |
349
+ | Range (`balance > 100`) | ⚠️ client-side | ✅ | ✅ |
350
+ | Sorting | ⚠️ client-side | ✅ | ✅ |
351
+ | Aggregation | ⚠️ client-side | partial | ✅ |
352
+ | Relations (`include`) | ⚠️ N+1 fetches | ⚠️ N+1 fetches | ✅ JOIN |
353
+
354
+ ### Data consistency
355
+
356
+ When using off-chain adapters (Helius, Postgres), there is an inherent indexer lag of 1–2 slots (~400–800ms). For critical reads (balance checks before transactions), use `findByAddress` or `findByPda` which always hit the RPC directly.
357
+
358
+ ---
359
+
360
+ ## Roadmap
361
+
362
+ - [x] Schema definition with automatic offset calculation
363
+ - [x] Borsh deserialization (all primitive types)
364
+ - [x] `findMany` — memcmp filters + client-side range operators
365
+ - [x] `findFirst`, `findByAddress`, `findByPda`
366
+ - [x] `count`, `aggregate`, `groupBy`
367
+ - [x] `include` — relation loading with deduplication
368
+ - [x] Anchor discriminator helper (`anchor()`)
369
+ - [x] Adapter pattern (`QueryAdapter` interface)
370
+ - [x] `RpcAdapter`
371
+ - [ ] `HeliusAdapter`
372
+ - [ ] `PostgresAdapter` + Geyser sync guide
373
+ - [ ] Cursor-based pagination
374
+ - [ ] WebSocket subscriptions (`watch`)
375
+ - [ ] Enum field support
376
+ - [ ] Option<T> field support
377
+ - [ ] Vec<T> field support
378
+ - [ ] CLI: generate schema from IDL
379
+ - [ ] RPC 2.0 adapter (Triton)
380
+
381
+ ---
382
+
383
+ ## Contributing
384
+
385
+ Contributions are welcome. Here's how to get started:
386
+
387
+ ### Setup
388
+
389
+ ```bash
390
+ git clone https://github.com/your-username/curvhex-orm
391
+ cd curvhex-orm
392
+ npm install
393
+ ```
394
+
395
+ ### Project structure
396
+
397
+ ```
398
+ src/
399
+ ├── core/
400
+ │ ├── types.ts — field types, ModelDefinition, InferModel
401
+ │ ├── schema.ts — defineModel, anchor(), offset calculation
402
+ │ ├── deserializer.ts — Buffer → TypeScript object
403
+ │ └── filters.ts — memcmp builder, client-side filter logic
404
+ ├── adapters/
405
+ │ ├── abstract/
406
+ │ │ └── QueryAdapter.ts — adapter interface
407
+ │ └── RpcAdapter.ts — getProgramAccounts implementation
408
+ ├── client/
409
+ │ ├── CurvhexClient.ts — findMany, findFirst, aggregate, groupBy, include
410
+ │ └── CurvhexORM.ts — entry point, wires models to adapter
411
+ └── __tests__/
412
+ └── integration.test.ts
413
+ ```
414
+
415
+ ### Where to contribute
416
+
417
+ Good first issues:
418
+
419
+ - **`HeliusAdapter`** — implement `QueryAdapter` using the Helius DAS API
420
+ - **`PostgresAdapter`** — implement `QueryAdapter` using `pg` or `postgres` client
421
+ - **`Vec<T>` field support** — extend `FieldType` and the deserializer
422
+ - **`Option<T>` field support** — handle Borsh option prefix byte
423
+ - **Enum fields** — map `u8` values to string variants via schema
424
+ - **Cursor-based pagination** — add `cursor` to `FindManyOptions`
425
+ - **`watch()`** — WebSocket subscription wrapping `onAccountChange`
426
+
427
+ ### Implementing a new adapter
428
+
429
+ Create a file under `src/adapters/` and implement the `QueryAdapter` interface:
430
+
431
+ ```typescript
432
+ import { QueryAdapter, FindManyOptions } from './abstract/QueryAdapter'
433
+ import { ModelDefinition, InferModel } from '../core/types'
434
+
435
+ export class MyAdapter implements QueryAdapter {
436
+ async findMany<M extends ModelDefinition>(
437
+ model: M,
438
+ options: FindManyOptions<M>
439
+ ): Promise<InferModel<M>[]> {
440
+ // your implementation
441
+ }
442
+
443
+ async findByAddress<M extends ModelDefinition>(
444
+ model: M,
445
+ address: string
446
+ ): Promise<InferModel<M> | null> {
447
+ // your implementation
448
+ }
449
+
450
+ async findByPda<M extends ModelDefinition>(
451
+ model: M,
452
+ seeds: (Buffer | Uint8Array)[]
453
+ ): Promise<InferModel<M> | null> {
454
+ // your implementation
455
+ }
456
+ }
457
+ ```
458
+
459
+ Then export it from `src/index.ts` and add a test case to `integration.test.ts`.
460
+
461
+ ### Before submitting a PR
462
+
463
+ ```bash
464
+ npx tsc --noEmit # type check
465
+ npx tsx src/__tests__/integration.test.ts # run tests
466
+ ```
467
+
468
+ All 8 integration test cases must pass. If you're adding a new adapter, add at least `findByAddress` and `findMany` test cases using a mock or a real endpoint.
469
+
470
+ ### Commit style
471
+
472
+ ```
473
+ feat: add HeliusAdapter
474
+ fix: handle empty discriminator in buildFilters
475
+ docs: add Vec<T> field type to README
476
+ ```
477
+
478
+ ---
479
+
480
+ ## License
481
+
482
+ Apache 2.0 with Commons Clause.
483
+
484
+ Free to use in your own projects. You may not sell this software or offer it as a hosted service without a commercial license.
485
+
@@ -0,0 +1,12 @@
1
+ import { Connection, PublicKey } from '@solana/web3.js';
2
+ import { ModelDefinition, InferModel } from '../core/types';
3
+ import { QueryAdapter, FindManyOptions } from './abstract/QueryAdapter';
4
+ export declare class RpcAdapter implements QueryAdapter {
5
+ private connection;
6
+ private programId;
7
+ constructor(connection: Connection, programId: PublicKey);
8
+ findMany<M extends ModelDefinition>(model: M, options?: FindManyOptions<M>): Promise<InferModel<M>[]>;
9
+ findByAddress<M extends ModelDefinition>(model: M, address: string): Promise<InferModel<M> | null>;
10
+ findByPda<M extends ModelDefinition>(model: M, seeds: (Buffer | Uint8Array)[]): Promise<InferModel<M> | null>;
11
+ }
12
+ //# sourceMappingURL=RpcAdapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RpcAdapter.d.ts","sourceRoot":"","sources":["../../src/adapters/RpcAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAA4B,MAAM,iBAAiB,CAAC;AAClF,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAG5D,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAExE,qBAAa,UAAW,YAAW,YAAY;IAEvC,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,SAAS;gBADT,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,SAAS;IAG1B,QAAQ,CAAC,CAAC,SAAS,eAAe,EACpC,KAAK,EAAE,CAAC,EACR,OAAO,GAAE,eAAe,CAAC,CAAC,CAAM,GACjC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;IA2CrB,aAAa,CAAC,CAAC,SAAS,eAAe,EACzC,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,MAAM,GAChB,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAO1B,SAAS,CAAC,CAAC,SAAS,eAAe,EACrC,KAAK,EAAE,CAAC,EACR,KAAK,EAAE,CAAC,MAAM,GAAG,UAAU,CAAC,EAAE,GAC/B,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;CAMnC"}