diff --git a/deno.json b/deno.json index b43e9e5..277077a 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,6 @@ { "name": "@rotu/structview", - "version": "0.14.1", + "version": "0.15.0", "license": "MIT", "tasks": { "dev": "deno test --watch", diff --git a/fields.ts b/fields.ts index 61df69f..eaf0a40 100644 --- a/fields.ts +++ b/fields.ts @@ -273,13 +273,36 @@ export function bool(fieldOffset: number): StructPropertyDescriptor { /** * Define a descriptor based on a dataview of the struct - * @param fieldGetter function which, given a dataview, returns - * @returns + * @param fieldGetter function which, given a dataview, returns the field value + * @param fieldSetter optional function which, given a dataview and a value, sets the field value + * @returns an enumerable property descriptor; readonly if no setter is provided */ export function fromDataView( fieldGetter: (dv: DataView) => T, -): StructPropertyDescriptor & ReadOnlyAccessorDescriptor { + fieldSetter: (dv: DataView, value: T) => void, +): StructPropertyDescriptor +export function fromDataView( + fieldGetter: (dv: DataView) => T, +): StructPropertyDescriptor & ReadOnlyAccessorDescriptor +export function fromDataView( + fieldGetter: (dv: DataView) => T, + fieldSetter?: (dv: DataView, value: T) => void, +): StructPropertyDescriptor { + if (fieldSetter !== undefined) { + return { + enumerable: true, + get() { + const dv = structDataView(this) + return fieldGetter(dv) + }, + set(value) { + const dv = structDataView(this) + fieldSetter(dv, value) + }, + } + } return { + enumerable: true, get() { const dv = structDataView(this) return fieldGetter(dv) diff --git a/mod_test.ts b/mod_test.ts index b68ad09..77b457b 100644 --- a/mod_test.ts +++ b/mod_test.ts @@ -5,6 +5,7 @@ import { f16, f32, f64, + fromDataView, i16, i32, i64, @@ -449,3 +450,49 @@ Deno.test("alloc", () => { // ensure correct typing (that alloc doesn't return a bare Struct) const _zz: Sized = z }) + +Deno.test("fromDataView getter-only is readonly and enumerable", () => { + class S extends defineStruct({ + val: fromDataView((dv) => dv.getUint8(0)), + }) {} + const buf = new Uint8Array([42]) + const obj = new S(buf) + assertEquals(obj.val, 42) + + // type test: val is readonly + assertThrows(() => { + // @ts-expect-error assigning to readonly property + obj.val = 1 + }) + + // the descriptor should be enumerable + const keys: string[] = [] + for (const k in S.prototype) { + keys.push(k) + } + assert(keys.includes("val")) +}) + +Deno.test("fromDataView with setter is writable and enumerable", () => { + class S extends defineStruct({ + val: fromDataView( + (dv) => dv.getUint8(0), + (dv, v) => dv.setUint8(0, v), + ), + }) {} + const buf = new Uint8Array([0]) + const obj = new S(buf) + obj.val = 99 + assertEquals(obj.val, 99) + assertEquals(buf[0], 99) + + // type test: val is writable (no @ts-expect-error needed) + const _: number = obj.val + + // the descriptor should be enumerable + const keys: string[] = [] + for (const k in S.prototype) { + keys.push(k) + } + assert(keys.includes("val")) +})