@curvhex/orm 0.1.2 → 0.1.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.
- package/README.md +21 -466
- package/dist/core/schema.d.ts +1 -0
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +12 -3
- package/dist/core/schema.js.map +1 -1
- package/dist/core/types.d.ts +1 -0
- package/dist/core/types.d.ts.map +1 -1
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -1,485 +1,40 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="docs/public/logo.svg" width="80" alt="Curvhex ORM" />
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<h1 align="center">Curvhex ORM</h1>
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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:
|
|
105
|
-
orderBy: { balance:
|
|
106
|
-
take:
|
|
107
|
-
|
|
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
|
-
|
|
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
|
-
|
|
465
|
-
npx tsx src/__tests__/integration.test.ts # run tests
|
|
31
|
+
npm install @curvhex/orm @solana/web3.js
|
|
466
32
|
```
|
|
467
33
|
|
|
468
|
-
|
|
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
|
-
|
package/dist/core/schema.d.ts
CHANGED
|
@@ -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;
|
|
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,CAuB3E"}
|
package/dist/core/schema.js
CHANGED
|
@@ -25,9 +25,18 @@ function defineModel(input) {
|
|
|
25
25
|
const fields = {};
|
|
26
26
|
let offset = input.discriminator.length;
|
|
27
27
|
for (const [name, field] of Object.entries(input.fields)) {
|
|
28
|
-
const
|
|
29
|
-
fields[name] = { type: field.type, offset };
|
|
30
|
-
|
|
28
|
+
const baseSize = FIELD_SIZES[field.type];
|
|
29
|
+
fields[name] = { type: field.type, offset, maxLen: field.maxLen };
|
|
30
|
+
if (baseSize === 0) {
|
|
31
|
+
// variable-length: 4-byte length prefix + maxLen payload
|
|
32
|
+
if (field.maxLen === undefined) {
|
|
33
|
+
throw new Error(`Field "${name}" is type "${field.type}" — provide maxLen so offsets can be calculated correctly.`);
|
|
34
|
+
}
|
|
35
|
+
offset += 4 + field.maxLen;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
offset += baseSize;
|
|
39
|
+
}
|
|
31
40
|
}
|
|
32
41
|
return { discriminator: input.discriminator, fields };
|
|
33
42
|
}
|
package/dist/core/schema.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/core/schema.ts"],"names":[],"mappings":";;AAIA,wBAKC;AAoBD,
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/core/schema.ts"],"names":[],"mappings":";;AAIA,wBAKC;AAoBD,kCAuBC;AAnDD,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,QAAQ,GAAG,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAEzC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;QAElE,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;YACjB,yDAAyD;YACzD,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;gBAC7B,MAAM,IAAI,KAAK,CACX,UAAU,IAAI,cAAc,KAAK,CAAC,IAAI,4DAA4D,CACrG,CAAC;YACN,CAAC;YACD,MAAM,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC;QAC/B,CAAC;aAAM,CAAC;YACJ,MAAM,IAAI,QAAQ,CAAC;QACvB,CAAC;IACL,CAAC;IAED,OAAO,EAAE,aAAa,EAAE,KAAK,CAAC,aAAa,EAAE,MAAM,EAAE,CAAC;AAC1D,CAAC"}
|
package/dist/core/types.d.ts
CHANGED
package/dist/core/types.d.ts.map
CHANGED
|
@@ -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;
|
|
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.
|
|
3
|
+
"version": "0.1.3",
|
|
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
|
}
|