@curvhex/orm 0.1.2 → 0.1.4

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.
package/README.md CHANGED
@@ -1,485 +1,40 @@
1
- # Curvhex ORM
1
+ <p align="center">
2
+ <img src="docs/public/logo.svg" width="80" alt="Curvhex ORM" />
3
+ </p>
2
4
 
3
- TypeScript ORM for Solana PDA accounts. Query, filter, and aggregate on-chain data with a familiar API — inspired by Prisma, built for Solana.
5
+ <h1 align="center">Curvhex ORM</h1>
4
6
 
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
- ---
7
+ <p align="center">
8
+ TypeScript ORM for Solana PDA accounts.<br/>
9
+ Query, filter, and aggregate on-chain data with a Prisma-style API.
10
+ </p>
27
11
 
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
- ```
12
+ <p align="center">
13
+ <a href="https://curvhex-lab.github.io/orm/">Documentation</a> ·
14
+ <a href="https://curvhex-lab.github.io/orm/guide/quick-start">Quick Start</a> ·
15
+ <a href="https://www.npmjs.com/package/@curvhex/orm">npm</a>
16
+ </p>
46
17
 
47
18
  ---
48
19
 
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
20
  ```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
21
  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
-
22
+ where: { authority: wallet.publicKey, isActive: true },
23
+ orderBy: { balance: "desc" },
24
+ take: 10,
25
+ });
334
26
  ```
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
27
 
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
28
+ ## Installation
462
29
 
463
30
  ```bash
464
- npx tsc --noEmit # type check
465
- npx tsx src/__tests__/integration.test.ts # run tests
31
+ npm install @curvhex/orm @solana/web3.js
466
32
  ```
467
33
 
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
- ```
34
+ ## Documentation
477
35
 
478
- ---
36
+ Full documentation at **[curvhex-labs.github.io/orm](https://curvhex-lab.github.io/orm/)**.
479
37
 
480
38
  ## License
481
39
 
482
40
  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
-
@@ -33,6 +33,7 @@ export interface ClientFilter {
33
33
  offset: number;
34
34
  };
35
35
  condition: {
36
+ eq?: any;
36
37
  gt?: any;
37
38
  gte?: any;
38
39
  lt?: any;
@@ -1 +1 @@
1
- {"version":3,"file":"filters.d.ts","sourceRoot":"","sources":["../../src/core/filters.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAErD,MAAM,WAAW,YAAY;IACzB,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7C;AAED,MAAM,WAAW,cAAc;IAC3B,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,SAAS,GAAG,YAAY,GAAG,cAAc,CAAC;AAGtD,MAAM,MAAM,aAAa,CAAC,CAAC,IACrB,CAAC,GACD;IAAE,EAAE,CAAC,EAAE,CAAC,CAAC;IAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;CAAE,CAAC;AAExF,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,eAAe,IAAI;KAChD,CAAC,IAAI,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,aAAa,CAAC,GAAG,CAAC;CAChD,CAAC;AA4CF,wBAAgB,YAAY,CAAC,CAAC,SAAS,eAAe,EAClD,KAAK,EAAE,CAAC,EACR,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,GACtB;IAAE,UAAU,EAAE,SAAS,EAAE,CAAC;IAAC,aAAa,EAAE,YAAY,EAAE,CAAA;CAAE,CAoC5D;AAED,MAAM,WAAW,YAAY;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,SAAS,EAAE;QAAE,EAAE,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAC;QAAC,EAAE,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAC;QAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;CACxG;AAED,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC5D,OAAO,EAAE,CAAC,EAAE,EACZ,aAAa,EAAE,YAAY,EAAE,GAC9B,CAAC,EAAE,CAmBL"}
1
+ {"version":3,"file":"filters.d.ts","sourceRoot":"","sources":["../../src/core/filters.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAErD,MAAM,WAAW,YAAY;IACzB,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7C;AAED,MAAM,WAAW,cAAc;IAC3B,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,SAAS,GAAG,YAAY,GAAG,cAAc,CAAC;AAGtD,MAAM,MAAM,aAAa,CAAC,CAAC,IACrB,CAAC,GACD;IAAE,EAAE,CAAC,EAAE,CAAC,CAAC;IAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;CAAE,CAAC;AAExF,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,eAAe,IAAI;KAChD,CAAC,IAAI,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,aAAa,CAAC,GAAG,CAAC;CAChD,CAAC;AA4CF,wBAAgB,YAAY,CAAC,CAAC,SAAS,eAAe,EAClD,KAAK,EAAE,CAAC,EACR,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,GACtB;IAAE,UAAU,EAAE,SAAS,EAAE,CAAC;IAAC,aAAa,EAAE,YAAY,EAAE,CAAA;CAAE,CAqC5D;AAED,MAAM,WAAW,YAAY;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,SAAS,EAAE;QAAE,EAAE,CAAC,EAAE,GAAG,CAAC;QAAC,EAAE,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAC;QAAC,EAAE,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAC;QAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;CAClH;AAED,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC5D,OAAO,EAAE,CAAC,EAAE,EACZ,aAAa,EAAE,YAAY,EAAE,GAC9B,CAAC,EAAE,CAoBL"}
@@ -63,7 +63,8 @@ function buildFilters(model, where) {
63
63
  const eqValue = isDirectValue(condition)
64
64
  ? condition
65
65
  : condition?.eq;
66
- if (eqValue !== undefined) {
66
+ // offset === -1 means field comes after a variable-length field — must filter client-side
67
+ if (eqValue !== undefined && fieldDef.offset !== -1) {
67
68
  const encoded = encodeValue(fieldDef.type, eqValue);
68
69
  rpcFilters.push({
69
70
  memcmp: {
@@ -73,13 +74,15 @@ function buildFilters(model, where) {
73
74
  });
74
75
  continue;
75
76
  }
76
- clientFilters.push({ fieldName, fieldDef, condition: condition });
77
+ clientFilters.push({ fieldName, fieldDef, condition: isDirectValue(condition) ? { eq: condition } : condition });
77
78
  }
78
79
  return { rpcFilters, clientFilters };
79
80
  }
80
81
  function applyClientFilters(records, clientFilters) {
81
82
  return records.filter(record => clientFilters.every(({ fieldName, condition }) => {
82
83
  const val = record[fieldName];
84
+ if (condition.eq !== undefined && val !== condition.eq)
85
+ return false;
83
86
  if (condition.gt !== undefined && !(val > condition.gt))
84
87
  return false;
85
88
  if (condition.gte !== undefined && !(val >= condition.gte))
@@ -1 +1 @@
1
- {"version":3,"file":"filters.js","sourceRoot":"","sources":["../../src/core/filters.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"filters.js","sourceRoot":"","sources":["../../src/core/filters.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,7 @@ import type { FieldType, ModelDefinition } from "./types";
2
2
  export declare function anchor(name: string): number[];
3
3
  type FieldInput = {
4
4
  type: FieldType;
5
+ maxLen?: number;
5
6
  };
6
7
  type ModelInput = {
7
8
  discriminator: number[];
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/core/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAmB,eAAe,EAAE,MAAM,SAAS,CAAC;AAI3E,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAK7C;AAcD,KAAK,UAAU,GAAG;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AACtC,KAAK,UAAU,GAAG;IACd,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACtC,CAAC;AAEF,wBAAgB,WAAW,CAAC,CAAC,SAAS,UAAU,EAAE,KAAK,EAAE,CAAC,GAAG,eAAe,CAa3E"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/core/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAmB,eAAe,EAAE,MAAM,SAAS,CAAC;AAI3E,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAK7C;AAcD,KAAK,UAAU,GAAG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AACvD,KAAK,UAAU,GAAG;IACd,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACtC,CAAC;AAEF,wBAAgB,WAAW,CAAC,CAAC,SAAS,UAAU,EAAE,KAAK,EAAE,CAAC,GAAG,eAAe,CAqB3E"}
@@ -24,10 +24,19 @@ const FIELD_SIZES = {
24
24
  function defineModel(input) {
25
25
  const fields = {};
26
26
  let offset = input.discriminator.length;
27
+ let dynamicOffset = false; // true after first variable-length field
27
28
  for (const [name, field] of Object.entries(input.fields)) {
28
- const size = FIELD_SIZES[field.type];
29
- fields[name] = { type: field.type, offset };
30
- offset += size === 0 ? 4 : size;
29
+ const baseSize = FIELD_SIZES[field.type];
30
+ // -1 signals "offset unknown at compile time — must filter client-side"
31
+ fields[name] = { type: field.type, offset: dynamicOffset ? -1 : offset, maxLen: field.maxLen };
32
+ if (baseSize === 0) {
33
+ // string/bytes: actual on-chain size is dynamic, all subsequent offsets are unknown
34
+ dynamicOffset = true;
35
+ offset = -1;
36
+ }
37
+ else if (!dynamicOffset) {
38
+ offset += baseSize;
39
+ }
31
40
  }
32
41
  return { discriminator: input.discriminator, fields };
33
42
  }
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/core/schema.ts"],"names":[],"mappings":";;AAIA,wBAKC;AAoBD,kCAaC;AAzCD,mCAAoC;AAEpC,oEAAoE;AACpE,SAAgB,MAAM,CAAC,IAAY;IAC/B,MAAM,IAAI,GAAG,IAAA,mBAAU,EAAC,QAAQ,CAAC;SAC5B,MAAM,CAAC,WAAW,IAAI,EAAE,CAAC;SACzB,MAAM,EAAE,CAAC;IACd,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,WAAW,GAA8B;IAC3C,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC;IACZ,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC;IACd,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC;IACd,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC;IACd,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE;IAClB,IAAI,EAAE,CAAC;IACP,SAAS,EAAE,EAAE;IACb,MAAM,EAAE,CAAC;IACT,KAAK,EAAE,CAAC;CACX,CAAC;AAQF,SAAgB,WAAW,CAAuB,KAAQ;IACtD,MAAM,MAAM,GAAoC,EAAE,CAAC;IACnD,IAAI,MAAM,GAAG,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC;IAExC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;QACvD,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAErC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;QAE5C,MAAM,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACpC,CAAC;IAED,OAAO,EAAE,aAAa,EAAE,KAAK,CAAC,aAAa,EAAE,MAAM,EAAE,CAAC;AAC1D,CAAC"}
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/core/schema.ts"],"names":[],"mappings":";;AAIA,wBAKC;AAoBD,kCAqBC;AAjDD,mCAAoC;AAEpC,oEAAoE;AACpE,SAAgB,MAAM,CAAC,IAAY;IAC/B,MAAM,IAAI,GAAG,IAAA,mBAAU,EAAC,QAAQ,CAAC;SAC5B,MAAM,CAAC,WAAW,IAAI,EAAE,CAAC;SACzB,MAAM,EAAE,CAAC;IACd,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,WAAW,GAA8B;IAC3C,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC;IACZ,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC;IACd,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC;IACd,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC;IACd,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE;IAClB,IAAI,EAAE,CAAC;IACP,SAAS,EAAE,EAAE;IACb,MAAM,EAAE,CAAC;IACT,KAAK,EAAE,CAAC;CACX,CAAC;AAQF,SAAgB,WAAW,CAAuB,KAAQ;IACtD,MAAM,MAAM,GAAoC,EAAE,CAAC;IACnD,IAAI,MAAM,GAAG,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC;IACxC,IAAI,aAAa,GAAG,KAAK,CAAC,CAAC,yCAAyC;IAEpE,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;QACvD,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAEzC,wEAAwE;QACxE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;QAE/F,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;YACjB,oFAAoF;YACpF,aAAa,GAAG,IAAI,CAAC;YACrB,MAAM,GAAG,CAAC,CAAC,CAAC;QAChB,CAAC;aAAM,IAAI,CAAC,aAAa,EAAE,CAAC;YACxB,MAAM,IAAI,QAAQ,CAAC;QACvB,CAAC;IACL,CAAC;IAED,OAAO,EAAE,aAAa,EAAE,KAAK,CAAC,aAAa,EAAE,MAAM,EAAE,CAAC;AAC1D,CAAC"}
@@ -2,6 +2,7 @@ export type FieldType = 'u8' | 'u16' | 'u32' | 'u64' | 'u128' | 'i8' | 'i16' | '
2
2
  export interface FieldDefinition {
3
3
  type: FieldType;
4
4
  offset: number;
5
+ maxLen?: number;
5
6
  }
6
7
  export interface ModelDefinition {
7
8
  discriminator: number[];
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GACf,IAAI,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GACrC,IAAI,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GACrC,MAAM,GACN,WAAW,GACX,QAAQ,GACR,OAAO,CAAC;AAEd,MAAM,WAAW,eAAe;IAC5B,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAClB;AAGD,MAAM,WAAW,eAAe;IAC5B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;CAC3C;AAED,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,SAAS,IAC1C,CAAC,SAAS,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAClD,CAAC,SAAS,IAAI,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAC9D,CAAC,SAAS,MAAM,GAAG,OAAO,GAC1B,CAAC,SAAS,WAAW,GAAG,MAAM,GAC9B,CAAC,SAAS,QAAQ,GAAG,OAAO,GAAG,MAAM,GACrC,KAAK,CAAC;AAGV,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,eAAe,IAAI;KAC/C,CAAC,IAAI,MAAM,CAAC,CAAC,QAAQ,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;CACnE,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GACf,IAAI,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GACrC,IAAI,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GACrC,MAAM,GACN,WAAW,GACX,QAAQ,GACR,OAAO,CAAC;AAEd,MAAM,WAAW,eAAe;IAC5B,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAGD,MAAM,WAAW,eAAe;IAC5B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;CAC3C;AAED,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,SAAS,IAC1C,CAAC,SAAS,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAClD,CAAC,SAAS,IAAI,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAC9D,CAAC,SAAS,MAAM,GAAG,OAAO,GAC1B,CAAC,SAAS,WAAW,GAAG,MAAM,GAC9B,CAAC,SAAS,QAAQ,GAAG,OAAO,GAAG,MAAM,GACrC,KAAK,CAAC;AAGV,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,eAAe,IAAI;KAC/C,CAAC,IAAI,MAAM,CAAC,CAAC,QAAQ,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;CACnE,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@curvhex/orm",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "TypeScript ORM for Solana PDA accounts",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -12,7 +12,10 @@
12
12
  },
13
13
  "scripts": {
14
14
  "build": "tsc",
15
- "prepublishOnly": "npm run build"
15
+ "prepublishOnly": "npm run build",
16
+ "docs:dev": "vitepress dev docs",
17
+ "docs:build": "vitepress build docs",
18
+ "docs:preview": "vitepress preview docs"
16
19
  },
17
20
  "keywords": [
18
21
  "solana",
@@ -29,6 +32,7 @@
29
32
  "@solana/web3.js": "^1.98.0",
30
33
  "@types/node": "^25.9.3",
31
34
  "tsx": "^4.19.4",
32
- "typescript": "^6.0.3"
35
+ "typescript": "^6.0.3",
36
+ "vitepress": "^1.6.4"
33
37
  }
34
38
  }