@obelusfi/bun-cstruct 0.0.1
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 +267 -0
- package/index.ts +376 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# @obelusfi/bun-cstruct
|
|
2
|
+
|
|
3
|
+
A small convenience layer over `bun:ffi` for working with C structs using TypeScript classes and decorators.
|
|
4
|
+
|
|
5
|
+
It lets you describe C memory layouts declaratively and access native memory through strongly typed class instances — without manually calculating offsets.
|
|
6
|
+
|
|
7
|
+
This library exists as a practical solution while waiting for an official Bun API for structured C memory access.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- Declarative struct definitions via decorators
|
|
12
|
+
- Deterministic C-compatible memory layout
|
|
13
|
+
- Nested inline structs
|
|
14
|
+
- Pointer-backed struct references
|
|
15
|
+
- Fixed-size arrays
|
|
16
|
+
- Fixed-size inline strings
|
|
17
|
+
- C string (`char*`) support
|
|
18
|
+
- Enumerable fields (works with `Object.keys`)
|
|
19
|
+
- JSON serialization support
|
|
20
|
+
- Manual struct allocation
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
bun add @obelusfi/bun-cstruct
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
add `"experimentalDecorators": true` to your `tsconfig.json`
|
|
29
|
+
|
|
30
|
+
## Basic Usage
|
|
31
|
+
|
|
32
|
+
### Define Structs
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import {
|
|
36
|
+
CStruct,
|
|
37
|
+
i32,
|
|
38
|
+
u16,
|
|
39
|
+
f32,
|
|
40
|
+
chars,
|
|
41
|
+
array,
|
|
42
|
+
struct,
|
|
43
|
+
ref,
|
|
44
|
+
refPointer,
|
|
45
|
+
string,
|
|
46
|
+
} from "@obelusfi/bun-cstruct";
|
|
47
|
+
|
|
48
|
+
import type { Pointer } from "bun:ffi";
|
|
49
|
+
|
|
50
|
+
class Ref extends CStruct {
|
|
51
|
+
@i32 prop!: number;
|
|
52
|
+
@f32 pi!: number;
|
|
53
|
+
@string greeting!: string; // char*
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
class Nested extends CStruct {
|
|
57
|
+
@i32 z!: number;
|
|
58
|
+
@chars(5) text!: string; // inline char[5]
|
|
59
|
+
@array(i32, 3) list!: number[]; // inline int[3]
|
|
60
|
+
@ref(Ref) someReference!: Ref; // Ref*
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
class SomeStruct extends CStruct {
|
|
64
|
+
@i32 a!: number;
|
|
65
|
+
@u16 b!: number;
|
|
66
|
+
@struct(Nested) child!: Nested; // inline struct
|
|
67
|
+
@ref(Ref) someReference!: Ref; // Ref*
|
|
68
|
+
|
|
69
|
+
// Pointer to the ref is available via `$` prefix
|
|
70
|
+
@refPointer $someReference!: Pointer;
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Load a Native Library
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { dlopen, type Pointer } from "bun:ffi";
|
|
78
|
+
|
|
79
|
+
const { symbols: lib, close } = dlopen("./libexample.dylib", {
|
|
80
|
+
doSomethingWithStruct: {
|
|
81
|
+
args: ["pointer"],
|
|
82
|
+
},
|
|
83
|
+
getStruct: {
|
|
84
|
+
returns: "pointer",
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Use a Struct
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
const ptr: Pointer = lib.getStruct();
|
|
93
|
+
const s = new SomeStruct(ptr);
|
|
94
|
+
|
|
95
|
+
console.log(s.a); // read from native memory
|
|
96
|
+
s.a = 10; // write to native memory
|
|
97
|
+
|
|
98
|
+
console.log(s.child.z); // nested struct
|
|
99
|
+
console.log(s.child.list[0]); // inline array
|
|
100
|
+
|
|
101
|
+
s.child.text = "hello world"; // safely truncated
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Allocate a Struct
|
|
105
|
+
|
|
106
|
+
The static method `alloc(): Pointer` allows you to allocate memory for a struct using its computed size.
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
const ptr: Pointer = SomeStruct.alloc();
|
|
110
|
+
lib.doSomethingWithStruct(ptr);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Get size of struct
|
|
114
|
+
|
|
115
|
+
Extending `CStruct` adds a static prop `size` to your class
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
SomeStruct.size; // in bytes
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Supported Field Decorators
|
|
122
|
+
|
|
123
|
+
### Primitives
|
|
124
|
+
|
|
125
|
+
- `@i8`
|
|
126
|
+
- `@i16`
|
|
127
|
+
- `@i32`
|
|
128
|
+
- `@i64`
|
|
129
|
+
- `@u8`
|
|
130
|
+
- `@u16`
|
|
131
|
+
- `@u32`
|
|
132
|
+
- `@u64`
|
|
133
|
+
- `@f32`
|
|
134
|
+
- `@f64`
|
|
135
|
+
- `@intptr`
|
|
136
|
+
- `@ptr`
|
|
137
|
+
|
|
138
|
+
### Inline Fixed-Size String
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
{
|
|
142
|
+
// ...
|
|
143
|
+
@chars(10) name!: string;
|
|
144
|
+
// ...
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
- Stored as `char[10]`
|
|
149
|
+
- Truncates safely on overflow
|
|
150
|
+
|
|
151
|
+
### C String (`char*`)
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
{
|
|
155
|
+
// ....
|
|
156
|
+
@string label!: string;
|
|
157
|
+
// ....
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
- Reads/writes null-terminated strings via pointer
|
|
162
|
+
- ⚠️ Writes do **not** free the overwritten pointer (memory management is your responsibility)
|
|
163
|
+
|
|
164
|
+
### Inline Array
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
{
|
|
168
|
+
//...
|
|
169
|
+
@array(i32, 4) values!: number[];
|
|
170
|
+
//...
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
- Fixed-length
|
|
175
|
+
- Safe truncation on assignment
|
|
176
|
+
- Writable by index or full replacement
|
|
177
|
+
|
|
178
|
+
### Inline Struct
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
{
|
|
182
|
+
//...
|
|
183
|
+
@struct(OtherStruct) child!: OtherStruct;
|
|
184
|
+
//...
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Equivalent to:
|
|
189
|
+
|
|
190
|
+
```c
|
|
191
|
+
struct Parent {
|
|
192
|
+
struct OtherStruct child;
|
|
193
|
+
};
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Struct Pointer
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
{
|
|
200
|
+
//...
|
|
201
|
+
@ref(OtherStruct) ref!: OtherStruct;
|
|
202
|
+
//...
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Equivalent to:
|
|
207
|
+
|
|
208
|
+
```c
|
|
209
|
+
struct Parent {
|
|
210
|
+
struct OtherStruct* ref;
|
|
211
|
+
};
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Multiple references to the same pointer share memory.
|
|
215
|
+
|
|
216
|
+
### Raw Pointer Access
|
|
217
|
+
|
|
218
|
+
When using `@ref(...)`, you can access the underlying pointer via `$yourKey`.
|
|
219
|
+
|
|
220
|
+
The decorator `@refPointer` is a pass-through helper. It improves type checking and validates that you are referencing an existing `@ref` field.
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
{
|
|
224
|
+
//...
|
|
225
|
+
@ref(OtherStruct) ref!: OtherStruct;
|
|
226
|
+
@refPointer $ref!: Pointer;
|
|
227
|
+
//...
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Memory Layout
|
|
232
|
+
|
|
233
|
+
- Field order defines memory order.
|
|
234
|
+
- Layout matches C struct layout expectations.
|
|
235
|
+
- Nested structs are inlined.
|
|
236
|
+
- `@ref()` fields store pointers.
|
|
237
|
+
- Arrays and fixed strings reserve fixed space.
|
|
238
|
+
- Offsets are computed once at class definition time.
|
|
239
|
+
- Packed structs are not supported.
|
|
240
|
+
|
|
241
|
+
## Performance
|
|
242
|
+
|
|
243
|
+
This library is a convenience abstraction.
|
|
244
|
+
|
|
245
|
+
- Field access uses getters/setters.
|
|
246
|
+
- There is function call overhead.
|
|
247
|
+
- It is not zero-cost.
|
|
248
|
+
- It is not faster than manual pointer math.
|
|
249
|
+
|
|
250
|
+
The overhead is usually negligible compared to:
|
|
251
|
+
|
|
252
|
+
- FFI boundary crossings
|
|
253
|
+
- Native library calls
|
|
254
|
+
- IO operations
|
|
255
|
+
|
|
256
|
+
If you are writing extremely tight loops where every nanosecond matters, raw buffer access may be more appropriate.
|
|
257
|
+
|
|
258
|
+
## What This Library Is
|
|
259
|
+
|
|
260
|
+
- A structured way to describe C memory layouts
|
|
261
|
+
- A safer alternative to manual offset math
|
|
262
|
+
- A developer-friendly wrapper around `bun:ffi`
|
|
263
|
+
- A stopgap solution until Bun provides official struct support
|
|
264
|
+
|
|
265
|
+
## License
|
|
266
|
+
|
|
267
|
+
MIT
|
package/index.ts
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import { CString, type Pointer, read, ptr as pt, toBuffer } from 'bun:ffi';
|
|
2
|
+
import { endianness } from 'os';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
function alignUp(n: number, alignment: number) {
|
|
6
|
+
return (n + alignment - 1) & ~(alignment - 1);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const p = Symbol.for('p');
|
|
10
|
+
const o = Symbol.for('o');
|
|
11
|
+
const e = endianness();
|
|
12
|
+
|
|
13
|
+
type Writes = keyof Buffer;
|
|
14
|
+
|
|
15
|
+
const read2w = {
|
|
16
|
+
"f32": `writeFloat${e}` as const,
|
|
17
|
+
'f64': `writeDouble${e}` as const,
|
|
18
|
+
'i16': `writeInt16${e}` as const,
|
|
19
|
+
'u16': `writeUInt16${e}` as const,
|
|
20
|
+
'i32': `writeInt32${e}` as const,
|
|
21
|
+
'u32': `writeUInt32${e}` as const,
|
|
22
|
+
'i64': `writeBigInt64${e}` as const,
|
|
23
|
+
'u64': `writeBigUInt64${e}` as const,
|
|
24
|
+
|
|
25
|
+
'intptr': `writeUInt32${e}` as const, // see if we can find the size to use
|
|
26
|
+
'ptr': `writeBigUInt64${e}` as const, // see if we can find the size to use
|
|
27
|
+
|
|
28
|
+
'i8': 'writeInt8' as const,
|
|
29
|
+
'u8': 'writeUInt8' as const,
|
|
30
|
+
} satisfies Record<keyof typeof read, Writes>;
|
|
31
|
+
|
|
32
|
+
type Layout = {
|
|
33
|
+
cursor: number
|
|
34
|
+
alignment: number
|
|
35
|
+
}
|
|
36
|
+
const layoutMap = new WeakMap<Function, Layout>();
|
|
37
|
+
|
|
38
|
+
export abstract class CStruct {
|
|
39
|
+
private [p]: Pointer;
|
|
40
|
+
private [o]: number;
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
private static getLayout(this: typeof CStruct): Layout {
|
|
44
|
+
let layout = layoutMap.get(this);
|
|
45
|
+
if (!layout) {
|
|
46
|
+
layout = { cursor: 0, alignment: 1 };
|
|
47
|
+
layoutMap.set(this, layout);
|
|
48
|
+
}
|
|
49
|
+
return layout;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
static get size() {
|
|
53
|
+
const { cursor, alignment } = this.getLayout();
|
|
54
|
+
return alignUp(cursor, alignment);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static get alignement() {
|
|
58
|
+
return this.getLayout().alignment;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
static reader(ptr: Pointer, offset: number) {
|
|
62
|
+
//@ts-ignore
|
|
63
|
+
return new this(ptr, offset);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
static alloc(): Pointer {
|
|
67
|
+
const buff = Buffer.alloc(this.size);
|
|
68
|
+
return pt(buff);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
constructor(ptr: Pointer, offset = 0) {
|
|
73
|
+
this[p] = ptr;
|
|
74
|
+
this[o] = offset;
|
|
75
|
+
Object.defineProperties(this, Object.getOwnPropertyDescriptors(Object.getPrototypeOf(this)))
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function chars(length: number) {
|
|
80
|
+
const alignment = 1;
|
|
81
|
+
const size = length;
|
|
82
|
+
const reader = (p: Pointer, offset: number) => {
|
|
83
|
+
return new CString(p, offset).toString();
|
|
84
|
+
}
|
|
85
|
+
const writer = (v: string, p: Pointer, offset: number) => {
|
|
86
|
+
toBuffer(p, offset, size).write(v.padEnd(length, '\0'));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const deco = function (target: CStruct, key: string) {
|
|
90
|
+
const ctr = target.constructor as typeof CStruct;
|
|
91
|
+
const layout = ctr["getLayout"]();
|
|
92
|
+
const offset = alignUp(layout.cursor, alignment);
|
|
93
|
+
|
|
94
|
+
layout.cursor = offset + size;
|
|
95
|
+
layout.alignment = Math.max(layout.alignment, alignment);
|
|
96
|
+
|
|
97
|
+
Object.defineProperty(target, key, {
|
|
98
|
+
get() {
|
|
99
|
+
return reader(this[p], offset + this[o]);
|
|
100
|
+
},
|
|
101
|
+
set(v: string) {
|
|
102
|
+
writer(v, this[p], offset + this[o]);
|
|
103
|
+
},
|
|
104
|
+
enumerable: true,
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
(deco as any).reader = reader;
|
|
108
|
+
(deco as any).writer = writer;
|
|
109
|
+
(deco as any).size = length;
|
|
110
|
+
(deco as any).alignment = 1;
|
|
111
|
+
|
|
112
|
+
return deco as TypedDecorator<string>;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function struct<T extends typeof CStruct>(cls: T) {
|
|
116
|
+
const reader = (p: Pointer, offset: number) => {
|
|
117
|
+
return cls.reader(p, offset);
|
|
118
|
+
}
|
|
119
|
+
const deco = function (target: CStruct, key: string) {
|
|
120
|
+
const ctr = target.constructor as typeof CStruct;
|
|
121
|
+
const layout = ctr['getLayout']();
|
|
122
|
+
|
|
123
|
+
const alignment = cls['getLayout']().alignment;
|
|
124
|
+
const offset = alignUp(layout.cursor, alignment);
|
|
125
|
+
|
|
126
|
+
layout.cursor = offset + cls.size;
|
|
127
|
+
layout.alignment = Math.max(layout.alignment, alignment);
|
|
128
|
+
|
|
129
|
+
Object.defineProperty(target, key, {
|
|
130
|
+
get() {
|
|
131
|
+
const ptr: Pointer = this[p];
|
|
132
|
+
return reader(ptr, offset + this[o]);
|
|
133
|
+
},
|
|
134
|
+
set(v) {
|
|
135
|
+
throw new Error("Can only write to fields");
|
|
136
|
+
},
|
|
137
|
+
enumerable: true,
|
|
138
|
+
});
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
(deco as any).size = cls.size;
|
|
142
|
+
(deco as any).alignment = cls['getLayout']().alignment;
|
|
143
|
+
(deco as any).reader = reader;
|
|
144
|
+
return deco as unknown as TypedDecorator<InstanceType<T>>;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function array<T extends (typeof CStruct | TypedDecorator<any>)>(itemType: T, count: number) {
|
|
148
|
+
|
|
149
|
+
const size = (itemType as any).size * count;
|
|
150
|
+
const alignment = (itemType as any).alignment;
|
|
151
|
+
|
|
152
|
+
let proxyMemo: any[];
|
|
153
|
+
|
|
154
|
+
const reader = (ptr: Pointer, offset: number) => {
|
|
155
|
+
if (proxyMemo) return proxyMemo;
|
|
156
|
+
proxyMemo = new Proxy(Array(count), {
|
|
157
|
+
get(target, p, receiver) {
|
|
158
|
+
if (typeof p === "symbol" || Number.isNaN(Number(p))) {
|
|
159
|
+
return target[p as any]
|
|
160
|
+
};
|
|
161
|
+
const i = Number(p);
|
|
162
|
+
if (i > count - 1) {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
return (itemType as any).reader(ptr, offset + i * (itemType as any).size)
|
|
166
|
+
},
|
|
167
|
+
set(target, p, newValue, receiver) {
|
|
168
|
+
const i = Number(p);
|
|
169
|
+
if (i > count - 1) {
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
(itemType as any).writer(newValue, ptr, offset + i * (itemType as any).size)
|
|
173
|
+
return true
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
// const arr = [];
|
|
177
|
+
// for (let i = 0; i < count; i++) {
|
|
178
|
+
// arr.push((itemType as any).reader(ptr, offset + i * (itemType as any).size));
|
|
179
|
+
// }
|
|
180
|
+
return proxyMemo;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const writer = (v: any[], ptr: Pointer, offset: number) => {
|
|
184
|
+
for (let i = 0; i < Math.min(v.length, count); i++) {
|
|
185
|
+
(itemType as any).writer(v[i], ptr, offset + i * (itemType as any).size)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const deco = function (target: CStruct, key: string) {
|
|
190
|
+
const ctr = target.constructor as typeof CStruct;
|
|
191
|
+
const layout = ctr['getLayout']();
|
|
192
|
+
|
|
193
|
+
const offset = alignUp(layout.cursor, alignment);
|
|
194
|
+
layout.cursor = offset + size;
|
|
195
|
+
layout.alignment = Math.max(layout.alignment, alignment);
|
|
196
|
+
|
|
197
|
+
Object.defineProperty(target, key, {
|
|
198
|
+
get() {
|
|
199
|
+
return reader(this[p], offset + this[o]);
|
|
200
|
+
},
|
|
201
|
+
set(v: any[]) {
|
|
202
|
+
writer(v, this[p], offset + this[o])
|
|
203
|
+
},
|
|
204
|
+
enumerable: true,
|
|
205
|
+
});
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
(deco as any).size = size;
|
|
209
|
+
(deco as any).alignment = alignment;
|
|
210
|
+
(deco as any).reader = reader;
|
|
211
|
+
(deco as any).writer = writer;
|
|
212
|
+
|
|
213
|
+
return deco as T extends typeof CStruct ? TypedDecorator<InstanceType<T>[]> :
|
|
214
|
+
TypedDecorator<Extract<T>[]>;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
export function ref<T extends (typeof CStruct | TypedDecorator<any>)>(type: T) {
|
|
219
|
+
const SIZE = 8;
|
|
220
|
+
const ALIGNMENT = 8;
|
|
221
|
+
const reader = (ptr: Pointer, offset: number) => {
|
|
222
|
+
const addr = read.ptr(ptr, offset);
|
|
223
|
+
if (!addr) return null;
|
|
224
|
+
return (type as any).reader(addr, 0)
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const writer = (v: any, ptr: Pointer, offset: number) => {
|
|
228
|
+
const addr = read.ptr(ptr, offset);
|
|
229
|
+
(type as any).writer(v, addr, 0);
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const deco = function (target: CStruct, key: string) {
|
|
233
|
+
const ctr = target.constructor as typeof CStruct;
|
|
234
|
+
const layout = ctr['getLayout']();
|
|
235
|
+
|
|
236
|
+
const offset = alignUp(layout.cursor, ALIGNMENT);
|
|
237
|
+
layout.cursor = offset + SIZE;
|
|
238
|
+
layout.alignment = Math.max(layout.alignment, ALIGNMENT);
|
|
239
|
+
Object.defineProperty(target, key, {
|
|
240
|
+
get() {
|
|
241
|
+
return reader(this[p], offset + this[o]);
|
|
242
|
+
},
|
|
243
|
+
set(v) {
|
|
244
|
+
writer(v, this[p], offset + this[o])
|
|
245
|
+
},
|
|
246
|
+
enumerable: true,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
Object.defineProperty(target, `$${key}`, {
|
|
250
|
+
get() {
|
|
251
|
+
return read.ptr(this[p], offset + this[o]);
|
|
252
|
+
},
|
|
253
|
+
set(v) {
|
|
254
|
+
toBuffer(this[p], offset + this[o], SIZE)[read2w['ptr']](BigInt(v));
|
|
255
|
+
},
|
|
256
|
+
enumerable: false,
|
|
257
|
+
})
|
|
258
|
+
};
|
|
259
|
+
(deco as any).size = SIZE;
|
|
260
|
+
(deco as any).alignment = ALIGNMENT;
|
|
261
|
+
(deco as any).reader = reader;
|
|
262
|
+
(deco as any).writer = writer;
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
return deco as T extends typeof CStruct ?
|
|
266
|
+
TypedDecorator<InstanceType<T>> : T;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function stringDecorator() {
|
|
270
|
+
const SIZE = 8;
|
|
271
|
+
const ALIGNMENT = 8;
|
|
272
|
+
const reader = (ptr: Pointer, offset: number) => {
|
|
273
|
+
const addr = read.ptr(ptr, offset) as Pointer;
|
|
274
|
+
if (!addr) return null;
|
|
275
|
+
return new CString(addr, 0).toString()
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const writer = (v: string, ptr: Pointer, offset: number) => {
|
|
279
|
+
const toWrite = Buffer.from(`${v}\0`);
|
|
280
|
+
const val = pt(toWrite)
|
|
281
|
+
toBuffer(ptr, offset, SIZE)[read2w['ptr']](BigInt(val));
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const deco = function (target: CStruct, key: string) {
|
|
285
|
+
const ctr = target.constructor as typeof CStruct;
|
|
286
|
+
const layout = ctr['getLayout']();
|
|
287
|
+
|
|
288
|
+
const offset = alignUp(layout.cursor, ALIGNMENT);
|
|
289
|
+
layout.cursor = offset + SIZE;
|
|
290
|
+
layout.alignment = Math.max(layout.alignment, ALIGNMENT);
|
|
291
|
+
|
|
292
|
+
Object.defineProperty(target, key, {
|
|
293
|
+
get() {
|
|
294
|
+
return reader(this[p], offset + this[o]);
|
|
295
|
+
},
|
|
296
|
+
set(v) {
|
|
297
|
+
writer(v, this[p], offset + this[o])
|
|
298
|
+
},
|
|
299
|
+
enumerable: true,
|
|
300
|
+
});
|
|
301
|
+
};
|
|
302
|
+
(deco as any).size = SIZE;
|
|
303
|
+
(deco as any).alignment = ALIGNMENT;
|
|
304
|
+
(deco as any).reader = reader;
|
|
305
|
+
(deco as any).writer = writer;
|
|
306
|
+
|
|
307
|
+
return deco as TypedDecorator<string>
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
function primitiveDecorator(
|
|
313
|
+
size: number,
|
|
314
|
+
readerKey: keyof typeof read,
|
|
315
|
+
alignment = size
|
|
316
|
+
) {
|
|
317
|
+
const r = read[readerKey];
|
|
318
|
+
const reader = (p: Pointer, offset: number) => {
|
|
319
|
+
return r(p, offset)
|
|
320
|
+
}
|
|
321
|
+
const writer = (v: any, p: Pointer, offset: number) => {
|
|
322
|
+
toBuffer(p, offset, size)[read2w[readerKey]](v as never);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const deco = function (target: CStruct, key: string) {
|
|
326
|
+
const ctr = target.constructor as typeof CStruct;
|
|
327
|
+
const layout = ctr['getLayout']();
|
|
328
|
+
|
|
329
|
+
const offset = alignUp(layout.cursor, alignment);
|
|
330
|
+
layout.cursor = offset + size;
|
|
331
|
+
layout.alignment = Math.max(layout.alignment, alignment);
|
|
332
|
+
|
|
333
|
+
Object.defineProperty(target, key, {
|
|
334
|
+
get() {
|
|
335
|
+
return reader(this[p], offset + this[o]);
|
|
336
|
+
},
|
|
337
|
+
set(v) {
|
|
338
|
+
writer(v, this[p], offset + this[o]);
|
|
339
|
+
},
|
|
340
|
+
enumerable: true,
|
|
341
|
+
});
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
(deco as any).size = size;
|
|
345
|
+
(deco as any).alignment = alignment;
|
|
346
|
+
(deco as any).reader = reader;
|
|
347
|
+
(deco as any).writer = writer;
|
|
348
|
+
|
|
349
|
+
return deco;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export const i8 = primitiveDecorator(1, 'i8', 1) as TypedDecorator<number>;
|
|
353
|
+
export const u8 = primitiveDecorator(1, 'u8', 1) as TypedDecorator<number>;
|
|
354
|
+
export const i16 = primitiveDecorator(2, 'i16', 2) as TypedDecorator<number>;
|
|
355
|
+
export const u16 = primitiveDecorator(2, 'u16', 2) as TypedDecorator<number>;
|
|
356
|
+
|
|
357
|
+
export const i32 = primitiveDecorator(4, 'i32', 4) as TypedDecorator<number>;
|
|
358
|
+
export const u32 = primitiveDecorator(4, 'u32', 4) as TypedDecorator<number>;
|
|
359
|
+
export const f32 = primitiveDecorator(4, 'f32', 4) as TypedDecorator<number>;
|
|
360
|
+
|
|
361
|
+
export const i64 = primitiveDecorator(8, 'i64', 8) as TypedDecorator<number>;
|
|
362
|
+
export const u64 = primitiveDecorator(8, 'u64', 8) as TypedDecorator<number>;
|
|
363
|
+
export const f64 = primitiveDecorator(8, 'f64', 8) as TypedDecorator<number>;
|
|
364
|
+
|
|
365
|
+
export const ptr = primitiveDecorator(8, 'ptr', 8) as TypedDecorator<number>;
|
|
366
|
+
|
|
367
|
+
export const string = stringDecorator();
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
export const refPointer = ((a: CStruct, b: string) => { }) as <T extends CStruct, K extends keyof T>(target: T, key: K, ...this_pointer_doesnt_exist_in_this_struct: CheckRefPointer<T, K>) => void;
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
type CheckRefPointer<T, K extends keyof T> = K extends `$${infer U}` ? U extends keyof T ? [] : [U] : [K];
|
|
374
|
+
type Checks<T, K extends keyof T, Expected> = T[K] extends Expected ? [] : [Expected]
|
|
375
|
+
type TypedDecorator<Expected> = <T extends CStruct, K extends keyof T>(target: T, key: K, ...property_and_decorator_type_mismatch: Checks<T, K, Expected>) => void;
|
|
376
|
+
type Extract<T> = T extends TypedDecorator<infer U> ? U : never
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@obelusfi/bun-cstruct",
|
|
3
|
+
"module": "index.ts",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"version": "0.0.1",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/ObelusFi/bun-cstruct.git"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"index.ts"
|
|
12
|
+
],
|
|
13
|
+
"description": "A lightweight convenience layer for working with C structs in bun:ffi using TypeScript decorators.",
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/bun": "latest"
|
|
16
|
+
},
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"typescript": "^5"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"keywords": [
|
|
22
|
+
"bun",
|
|
23
|
+
"ffi",
|
|
24
|
+
"c-struct",
|
|
25
|
+
"typescript",
|
|
26
|
+
"decorators",
|
|
27
|
+
"native",
|
|
28
|
+
"pointer",
|
|
29
|
+
"struct",
|
|
30
|
+
"memory",
|
|
31
|
+
"interop"
|
|
32
|
+
]
|
|
33
|
+
}
|