diff --git a/README.md b/README.md index 12d9796..21a37ab 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ To properly enable type inference, **TypeScript 4.5** and up is required because # Basic usage ```ts -import { Parsed, object, arrayOf, i32, string, bool } from "typed-binary"; +import { Parsed, object, arrayOf, i32, string, bool } from 'typed-binary'; const GameState = object({ nickname: string, // Variable-length string @@ -84,7 +84,7 @@ type GameState = Parsed; //... -import { BufferReader, BufferWriter } from "typed-binary"; +import { BufferReader, BufferWriter } from 'typed-binary'; /** * Responsible for retrieving the saved game state. @@ -92,14 +92,14 @@ import { BufferReader, BufferWriter } from "typed-binary"; */ async function loadGameState(): Promise { try { - const buffer = await fs.readFile("./savedState.bin"); + const buffer = await fs.readFile('./savedState.bin'); const reader = new BufferReader(buffer); return GameState.read(reader); } catch (e) { // Returning the default state if no saved state found. return { - nickname: "Default", + nickname: 'Default', stage: 1, newGamePlus: false, collectables: [], @@ -117,11 +117,11 @@ async function loadGameState(): Promise { */ async function saveGameState(state: GameState): Promise { try { - const buffer = Buffer.alloc(GameState.sizeOf(state)); + const buffer = Buffer.alloc(GameState.measure(state).size); const writer = new BufferWriter(buffer); GameState.write(writer, state); - await fs.writeFile("./savedState.bin", buffer); + await fs.writeFile('./savedState.bin', buffer); } catch (e) { console.error(`Error occurred during the saving process.`); console.error(e); @@ -153,16 +153,16 @@ There's a couple primitives to choose from: - A string of characters followed by a '\0' terminal character. ```ts -import { BufferWriter, BufferReader, byte, string } from "typed-binary"; +import { BufferWriter, BufferReader, byte, string } from 'typed-binary'; const buffer = Buffer.alloc(16); const writer = new BufferWriter(buffer); const reader = new BufferReader(buffer); // Writing four bytes into the buffer -byte.write(writer, "W".charCodeAt(0)); -byte.write(writer, "o".charCodeAt(0)); -byte.write(writer, "w".charCodeAt(0)); +byte.write(writer, 'W'.charCodeAt(0)); +byte.write(writer, 'o'.charCodeAt(0)); +byte.write(writer, 'w'.charCodeAt(0)); byte.write(writer, 0); console.log(string.read(reader)); // Wow @@ -175,7 +175,7 @@ Objects store their properties in key-ascending-alphabetical order, one next to ### Simple objects ```ts -import { BufferWriter, BufferReader, i32, string, object } from "typed-binary"; +import { BufferWriter, BufferReader, i32, string, object } from 'typed-binary'; const buffer = Buffer.alloc(16); const writer = new BufferWriter(buffer); @@ -190,8 +190,8 @@ const Person = object({ // Writing a Person Person.write(writer, { - firstName: "John", - lastName: "Doe", + firstName: 'John', + lastName: 'Doe', age: 43, }); @@ -213,7 +213,7 @@ import { bool, generic, object, -} from "typed-binary"; +} from 'typed-binary'; // Generic object schema const Animal = generic( @@ -230,7 +230,7 @@ const Animal = generic( // Animal can be a cat striped: bool, }), - } + }, ); // A buffer to serialize into/out of @@ -240,10 +240,10 @@ const reader = new BufferReader(buffer); // Writing an Animal Animal.write(writer, { - type: "cat", // We're specyfing which concrete type we want this object to be. + type: 'cat', // We're specyfing which concrete type we want this object to be. // Base properties - nickname: "James", + nickname: 'James', age: 5, // Concrete type specific properties @@ -257,11 +257,11 @@ console.log(JSON.stringify(animal)); // { "age": 5, "striped": true ... } // -- Type checking works here! -- // animal.type => 'cat' | 'dog' -if (animal.type === "cat") { +if (animal.type === 'cat') { // animal.type => 'cat' console.log("It's a cat!"); // animal.striped => bool - console.log(animal.striped ? "Striped" : "Not striped"); + console.log(animal.striped ? 'Striped' : 'Not striped'); } else { // animal.type => 'dog' console.log("It's a dog!"); @@ -358,7 +358,7 @@ import { string, object, optional, -} from "typed-binary"; +} from 'typed-binary'; const buffer = Buffer.alloc(16); const writer = new BufferWriter(buffer); @@ -381,20 +381,20 @@ const Person = object({ // Writing a Person (no address) Person.write(writer, { - firstName: "John", - lastName: "Doe", + firstName: 'John', + lastName: 'Doe', age: 43, }); // Writing a Person (with an address) Person.write(writer, { - firstName: "Jesse", - lastName: "Doe", + firstName: 'Jesse', + lastName: 'Doe', age: 38, address: { - city: "New York", - street: "Binary St.", - postalCode: "11-111", + city: 'New York', + street: 'Binary St.', + postalCode: '11-111', }, }); @@ -422,21 +422,21 @@ If you want an object type to be able to contain one of itself (recursion), then * This is because references are resolved recursively once the method * passed as the 2nd argument to 'keyed' returns the schema. */ -const Recursive = keyed("recursive-key", (Recursive) => +const Recursive = keyed('recursive-key', (Recursive) => object({ value: i32, next: optional(Recursive), - }) + }), ); ``` ### Recursive types alongside generics ```ts -import { i32, string, object, keyed } from "typed-binary"; +import { i32, string, object, keyed } from 'typed-binary'; type Expression = Parsed; -const Expression = keyed("expression", (Expression) => +const Expression = keyed('expression', (Expression) => generic( {}, { @@ -450,21 +450,21 @@ const Expression = keyed("expression", (Expression) => int_literal: object({ value: i32, }), - } - ) + }, + ), ); const expr: Parsed = { - type: "multiply", + type: 'multiply', a: { - type: "negate", + type: 'negate', inner: { - type: "int_literal", + type: 'int_literal', value: 15, }, }, b: { - type: "int_literal", + type: 'int_literal', value: 2, }, }; @@ -480,16 +480,12 @@ import { ISerialOutput, Schema, IRefResolver, -} from "typed-binary"; +} from 'typed-binary'; /** * A schema storing radians with 2 bytes of precision. */ class RadiansSchema extends Schema { - resolve(ctx: IRefResolver): void { - // No inner references to resolve - } - read(input: ISerialInput): number { const low = input.readByte(); const high = input.readByte(); @@ -501,7 +497,7 @@ class RadiansSchema extends Schema { write(output: ISerialOutput, value: number): void { // The value will be wrapped to be in range of [0, Math.PI) const wrapped = ((value % Math.PI) + Math.PI) % Math.PI; - // Discretising the value to be ints in range of [0, 65535] + // Quantizing the value to range of [0, 65535] const discrete = Math.min(Math.floor((wrapped / Math.PI) * 65535), 65535); const low = discrete & 0xff; @@ -511,16 +507,16 @@ class RadiansSchema extends Schema { output.writeByte(high); } - sizeOf(_: number): number { + measure(_: number, measurer: IMeasurer = new Measurer()): IMeasurer { // The size of the data serialized by this schema // doesn't depend on the actual value. It's always 2 bytes. - return 2; + return measurer.add(2); } } // Creating a singleton instance of the schema, // since it has no configuration properties. -export const RADIANS = new RadiansSchema(); +export const radians = new RadiansSchema(); ``` # Serialization and Deserialization @@ -539,13 +535,13 @@ read(input: ISerialInput): T; /** * Estimates the size of the value (according to the schema's structure) */ -sizeOf(value: T): number; +measure(value: T | MaxValue, measurer: IMeasurer): IMeasurer; ``` The `ISerialInput/Output` interfaces have a basic built-in implementation that reads/writes to a buffer: ```ts -import { BufferReader, BufferWriter } from "typed-binary"; +import { BufferReader, BufferWriter } from 'typed-binary'; // Creating a fixed-length buffer of arbitrary size (64 bytes). const buffer = Buffer.alloc(64); // Or new ArrayBuffer(64); on browsers. diff --git a/package-lock.json b/package-lock.json index b2625d5..25eb78a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "typed-binary", - "version": "1.2.2", + "version": "3.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "typed-binary", - "version": "1.2.2", + "version": "3.0.0", "license": "MIT", "devDependencies": { "@rollup/plugin-commonjs": "^21.0.1", @@ -24,6 +24,8 @@ "rollup-plugin-dts": "^4.1.0", "rollup-plugin-peer-deps-external": "^2.2.4", "rollup-plugin-terser": "^7.0.2", + "socket.io": "^4.7.2", + "socket.io-client": "^4.7.2", "ts-node": "^10.4.0", "tslib": "^2.4.0", "typescript": "^4.5.4" @@ -251,6 +253,12 @@ "rollup": "^1.20.0||^2.0.0" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", + "dev": true + }, "node_modules/@tsconfig/node10": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", @@ -281,6 +289,21 @@ "integrity": "sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==", "dev": true }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "dev": true + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/estree": { "version": "0.0.39", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", @@ -504,6 +527,19 @@ "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", "dev": true }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.7.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", @@ -629,6 +665,15 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true, + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -810,6 +855,28 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -949,6 +1016,49 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "node_modules/engine.io": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz", + "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==", + "dev": true, + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-client": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz", + "integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==", + "dev": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0", + "xmlhttprequest-ssl": "~2.0.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", + "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -1926,6 +2036,27 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -2070,6 +2201,15 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -2079,6 +2219,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -2499,6 +2648,61 @@ "node": ">=8" } }, + "node_modules/socket.io": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.2.tgz", + "integrity": "sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", + "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "dev": true, + "dependencies": { + "ws": "~8.11.0" + } + }, + "node_modules/socket.io-client": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.2.tgz", + "integrity": "sha512-vtA0uD4ibrYD793SOIAwlo8cj6haOeMHrGvwPxJsxH7CeIksqJ+3Zc06RvWTIFgiSqx4A3sOnTXpfAEE2Zyz6w==", + "dev": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dev": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/source-map": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", @@ -2776,6 +2980,15 @@ "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -2862,6 +3075,36 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -3109,6 +3352,12 @@ "picomatch": "^2.2.2" } }, + "@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", + "dev": true + }, "@tsconfig/node10": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", @@ -3139,6 +3388,21 @@ "integrity": "sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==", "dev": true }, + "@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "dev": true + }, + "@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/estree": { "version": "0.0.39", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", @@ -3273,6 +3537,16 @@ "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", "dev": true }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, "acorn": { "version": "8.7.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", @@ -3365,6 +3639,12 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true + }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -3511,6 +3791,22 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, "create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -3611,6 +3907,43 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "engine.io": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz", + "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==", + "dev": true, + "requires": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0" + } + }, + "engine.io-client": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz", + "integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==", + "dev": true, + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0", + "xmlhttprequest-ssl": "~2.0.0" + } + }, + "engine.io-parser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", + "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==", + "dev": true + }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -4347,6 +4680,21 @@ "picomatch": "^2.2.3" } }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "requires": { + "mime-db": "1.52.0" + } + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -4452,12 +4800,24 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -4722,6 +5082,52 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, + "socket.io": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.2.tgz", + "integrity": "sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw==", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + } + }, + "socket.io-adapter": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", + "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "dev": true, + "requires": { + "ws": "~8.11.0" + } + }, + "socket.io-client": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.2.tgz", + "integrity": "sha512-vtA0uD4ibrYD793SOIAwlo8cj6haOeMHrGvwPxJsxH7CeIksqJ+3Zc06RvWTIFgiSqx4A3sOnTXpfAEE2Zyz6w==", + "dev": true, + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.5.2", + "socket.io-parser": "~4.2.4" + } + }, + "socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dev": true, + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + } + }, "source-map": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", @@ -4912,6 +5318,12 @@ "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4976,6 +5388,19 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "dev": true, + "requires": {} + }, + "xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", + "dev": true + }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index fbb4b9d..856cbb7 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,8 @@ "rollup-plugin-dts": "^4.1.0", "rollup-plugin-peer-deps-external": "^2.2.4", "rollup-plugin-terser": "^7.0.2", + "socket.io": "^4.7.2", + "socket.io-client": "^4.7.2", "ts-node": "^10.4.0", "tslib": "^2.4.0", "typescript": "^4.5.4" diff --git a/src/describe/index.ts b/src/describe/index.ts index e700700..5c5fc80 100644 --- a/src/describe/index.ts +++ b/src/describe/index.ts @@ -4,9 +4,9 @@ import { OptionalSchema } from '../structure/optional'; import { GenericObjectSchema } from '../structure/object'; import { TupleSchema } from '../structure/tuple'; import { - ISchema, + IUnstableSchema, ISchemaWithProperties, - IStableSchema, + ISchema, Ref, SchemaMap, } from '../structure/types'; @@ -40,18 +40,17 @@ export const genericEnum = < subTypeMap: S, ) => new GenericObjectSchema(SubTypeKey.ENUM, properties, subTypeMap); -export const arrayOf = >(elementType: T) => - new ArraySchema(elementType); - -export const tupleOf = >( +export const arrayOf = >( + elementType: T, +) => new ArraySchema(elementType); +export const tupleOf = >( elementType: T, length: number, ) => new TupleSchema(elementType, length); - -export const optional = (innerType: ISchema) => +export const optional = (innerType: IUnstableSchema) => new OptionalSchema(innerType); -export const keyed = >( +export const keyed = >( key: K, - inner: (ref: ISchema>) => P, + inner: (ref: IUnstableSchema>) => P, ) => new KeyedSchema(key, inner); diff --git a/src/io/bufferIOBase.ts b/src/io/bufferIOBase.ts index fe56dce..52ef902 100644 --- a/src/io/bufferIOBase.ts +++ b/src/io/bufferIOBase.ts @@ -22,13 +22,15 @@ export class BufferIOBase { protected byteOffset = 0; constructor(buffer: ArrayBufferLike, options?: BufferIOOptions) { + this.byteOffset = options?.byteOffset ?? 0; + if (typeof Buffer !== 'undefined' && buffer instanceof Buffer) { // Getting rid of the outer shell, which causes the Uint8Array line to create a copy, instead of a view. + this.byteOffset += buffer.byteOffset; buffer = buffer.buffer; } this.uint8View = new Uint8Array(buffer, 0); - this.byteOffset = options?.byteOffset ?? 0; const helperBuffer = new ArrayBuffer(4); this.helperInt32View = new Int32Array(helperBuffer); diff --git a/src/structure/array.ts b/src/structure/array.ts index d51fe88..a9113d4 100644 --- a/src/structure/array.ts +++ b/src/structure/array.ts @@ -7,21 +7,21 @@ import { import { u32 } from './baseTypes'; import { IRefResolver, + IUnstableSchema, ISchema, - IStableSchema, Schema, MaxValue, } from './types'; export class ArraySchema extends Schema { - public elementType: IStableSchema; + public elementType: ISchema; - constructor(private readonly _unstableElementType: ISchema) { + constructor(private readonly _unstableElementType: IUnstableSchema) { super(); // In case this array isn't part of a keyed chain, // let's assume the inner type is stable. - this.elementType = _unstableElementType as IStableSchema; + this.elementType = _unstableElementType as ISchema; } resolve(ctx: IRefResolver): void { diff --git a/src/structure/baseTypes.ts b/src/structure/baseTypes.ts index 3ad7a5b..c274aea 100644 --- a/src/structure/baseTypes.ts +++ b/src/structure/baseTypes.ts @@ -6,10 +6,6 @@ import { Schema, MaxValue } from './types'; //// export class BoolSchema extends Schema { - resolve(): void { - /* Nothing to resolve */ - } - read(input: ISerialInput): boolean { return input.readBool(); } @@ -33,10 +29,6 @@ export const bool = new BoolSchema(); //// export class StringSchema extends Schema { - resolve(): void { - /* Nothing to resolve */ - } - read(input: ISerialInput): string { return input.readString(); } @@ -64,10 +56,6 @@ export const string = new StringSchema(); //// export class ByteSchema extends Schema { - resolve(): void { - /* Nothing to resolve */ - } - read(input: ISerialInput): number { return input.readByte(); } @@ -91,10 +79,6 @@ export const byte = new ByteSchema(); //// export class Int32Schema extends Schema { - resolve(): void { - /* Nothing to resolve */ - } - read(input: ISerialInput): number { return input.readInt32(); } @@ -118,10 +102,6 @@ export const i32 = new Int32Schema(); //// export class Uint32Schema extends Schema { - resolve(): void { - /* Nothing to resolve */ - } - read(input: ISerialInput): number { return input.readUint32(); } @@ -145,10 +125,6 @@ export const u32 = new Uint32Schema(); //// export class Float32Schema extends Schema { - resolve(): void { - /* Nothing to resolve */ - } - read(input: ISerialInput): number { return input.readFloat32(); } diff --git a/src/structure/chars.ts b/src/structure/chars.ts index 8188882..93a8b62 100644 --- a/src/structure/chars.ts +++ b/src/structure/chars.ts @@ -1,4 +1,9 @@ -import type { IMeasurer, ISerialInput, ISerialOutput } from '../io'; +import { + Measurer, + type IMeasurer, + type ISerialInput, + type ISerialOutput, +} from '../io'; import { TypedBinaryError } from '../error'; import { Schema } from './types'; @@ -7,10 +12,6 @@ export class CharsSchema extends Schema { super(); } - resolve(): void { - /* Nothing to resolve */ - } - write(output: ISerialOutput, value: string): void { if (value.length !== this.length) { throw new TypedBinaryError( @@ -33,7 +34,7 @@ export class CharsSchema extends Schema { return content; } - measure(_: string, measurer: IMeasurer): IMeasurer { + measure(_: string, measurer: IMeasurer = new Measurer()): IMeasurer { return measurer.add(this.length); } } diff --git a/src/structure/index.ts b/src/structure/index.ts index 36dbf93..26b7e14 100644 --- a/src/structure/index.ts +++ b/src/structure/index.ts @@ -9,8 +9,8 @@ export { Ref, IRefResolver, Schema, + IUnstableSchema, ISchema, - IStableSchema, ISchemaWithProperties, Keyed, KeyedSchema, diff --git a/src/structure/keyed.ts b/src/structure/keyed.ts index bcfd78b..a30b6d1 100644 --- a/src/structure/keyed.ts +++ b/src/structure/keyed.ts @@ -3,14 +3,14 @@ import { TypedBinaryError } from '../error'; import { IMeasurer, ISerialInput, ISerialOutput, Measurer } from '../io'; import { IRefResolver, + IUnstableSchema, ISchema, - IStableSchema, Keyed, Ref, MaxValue, } from './types'; -class RefSchema implements IStableSchema> { +class RefSchema implements ISchema> { public readonly _infered!: Ref; public readonly ref: Ref; @@ -44,22 +44,22 @@ class RefSchema implements IStableSchema> { } class RefResolve implements IRefResolver { - private registry: { [key: string]: IStableSchema } = {}; + private registry: { [key: string]: ISchema } = {}; hasKey(key: string): boolean { return this.registry[key] !== undefined; } - register(key: K, schema: IStableSchema): void { + register(key: K, schema: ISchema): void { this.registry[key] = schema; } - resolve(unstableSchema: ISchema): IStableSchema { + resolve(unstableSchema: IUnstableSchema): ISchema { if (unstableSchema instanceof RefSchema) { const ref = unstableSchema.ref; const key = ref.key as string; if (this.registry[key] !== undefined) { - return this.registry[key] as IStableSchema; + return this.registry[key] as ISchema; } throw new TypedBinaryError( @@ -68,21 +68,21 @@ class RefResolve implements IRefResolver { } // Since it's not a RefSchema, we assume it can be resolved. - (unstableSchema as IStableSchema).resolve(this); + (unstableSchema as ISchema).resolve(this); - return unstableSchema as IStableSchema; + return unstableSchema as ISchema; } } -export class KeyedSchema> - implements ISchema> +export class KeyedSchema> + implements IUnstableSchema> { public readonly _infered!: Keyed; public innerType: S; constructor( public readonly key: K, - innerResolver: (ref: ISchema>) => S, + innerResolver: (ref: IUnstableSchema>) => S, ) { this.innerType = innerResolver(new RefSchema(key)); this._infered = new Keyed(key, this.innerType); diff --git a/src/structure/optional.ts b/src/structure/optional.ts index a92e067..5e61454 100644 --- a/src/structure/optional.ts +++ b/src/structure/optional.ts @@ -6,21 +6,21 @@ import { } from '../io'; import { IRefResolver, + IUnstableSchema, ISchema, - IStableSchema, MaxValue, Schema, } from './types'; export class OptionalSchema extends Schema { - private innerSchema: IStableSchema; + private innerSchema: ISchema; - constructor(private readonly _innerUnstableSchema: ISchema) { + constructor(private readonly _innerUnstableSchema: IUnstableSchema) { super(); // In case this optional isn't part of a keyed chain, // let's assume the inner type is stable. - this.innerSchema = _innerUnstableSchema as IStableSchema; + this.innerSchema = _innerUnstableSchema as ISchema; } resolve(ctx: IRefResolver): void { diff --git a/src/structure/tuple.ts b/src/structure/tuple.ts index 2f0511d..4c334f3 100644 --- a/src/structure/tuple.ts +++ b/src/structure/tuple.ts @@ -1,8 +1,8 @@ import { TypedBinaryError } from '../error'; import { IRefResolver, + IUnstableSchema, ISchema, - IStableSchema, MaxValue, Schema, } from './types'; @@ -14,17 +14,17 @@ import { } from '../io'; export class TupleSchema extends Schema { - private elementSchema: IStableSchema; + private elementSchema: ISchema; constructor( - private readonly _unstableElementSchema: ISchema, + private readonly _unstableElementSchema: IUnstableSchema, public readonly length: number, ) { super(); // In case this array isn't part of a keyed chain, // let's assume the inner type is stable. - this.elementSchema = _unstableElementSchema as IStableSchema; + this.elementSchema = _unstableElementSchema as ISchema; } resolve(ctx: IRefResolver): void { diff --git a/src/structure/types.ts b/src/structure/types.ts index 456f6ff..f27c1da 100644 --- a/src/structure/types.ts +++ b/src/structure/types.ts @@ -8,26 +8,29 @@ export const MaxValue = Symbol('The maximum value a schema can hold'); * Distinguishing between a "stable" schema, and an "unstable" schema * helps to avoid errors in usage of unresolved schemas (the lack of utility functions). */ -export interface ISchema { +export interface IUnstableSchema { readonly _infered: I; } export interface ISchemaWithProperties - extends ISchema { + extends IUnstableSchema { readonly properties: StableSchemaMap; } -export interface IStableSchema extends ISchema { +export interface ISchema extends IUnstableSchema { resolve(ctx: IRefResolver): void; write(output: ISerialOutput, value: I): void; read(input: ISerialInput): I; measure(value: I | typeof MaxValue, measurer?: IMeasurer): IMeasurer; } -export abstract class Schema implements IStableSchema { +export abstract class Schema implements ISchema { readonly _infered!: I; - abstract resolve(ctx: IRefResolver): void; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + resolve(ctx: IRefResolver): void { + // override this if you need to resolve internal references. + } abstract write(output: ISerialOutput, value: I): void; abstract read(input: ISerialInput): I; abstract measure(value: I | typeof MaxValue, measurer?: IMeasurer): IMeasurer; @@ -37,7 +40,7 @@ export class Ref { constructor(public readonly key: K) {} } -export class Keyed> { +export class Keyed> { constructor(public readonly key: K, public readonly innerType: S) {} } @@ -53,13 +56,13 @@ export enum SubTypeKey { export interface IRefResolver { hasKey(key: string): boolean; - resolve(schemaOrRef: ISchema): IStableSchema; - register(key: K, schema: IStableSchema): void; + resolve(schemaOrRef: IUnstableSchema): ISchema; + register(key: K, schema: ISchema): void; } //// // Alias types //// -export type SchemaMap = { [key in keyof T]: ISchema }; -export type StableSchemaMap = { [key in keyof T]: IStableSchema }; +export type SchemaMap = { [key in keyof T]: IUnstableSchema }; +export type StableSchemaMap = { [key in keyof T]: ISchema }; diff --git a/src/test/array.test.ts b/src/test/array.test.ts index 79d9fcc..ba8120b 100644 --- a/src/test/array.test.ts +++ b/src/test/array.test.ts @@ -3,7 +3,7 @@ import * as chai from 'chai'; import { ArraySchema, i32, MaxValue } from '../structure'; import { arrayOf } from '../describe'; import { randIntBetween } from './random'; -import { makeIO } from './_mock.test'; +import { makeIO } from './helpers/mock'; const expect = chai.expect; diff --git a/src/test/bool.test.ts b/src/test/bool.test.ts index 7cba9ed..59a46eb 100644 --- a/src/test/bool.test.ts +++ b/src/test/bool.test.ts @@ -1,6 +1,6 @@ import * as chai from 'chai'; import { bool } from '../structure'; -import { encodeAndDecode } from './_mock.test'; +import { encodeAndDecode } from './helpers/mock'; const expect = chai.expect; describe('BoolSchema', () => { diff --git a/src/test/byte.test.ts b/src/test/byte.test.ts index b8fa37b..0f25b77 100644 --- a/src/test/byte.test.ts +++ b/src/test/byte.test.ts @@ -1,7 +1,7 @@ import * as chai from 'chai'; import { randIntBetween } from './random'; import { byte } from '../structure'; -import { encodeAndDecode } from './_mock.test'; +import { encodeAndDecode } from './helpers/mock'; const expect = chai.expect; describe('ByteSchema', () => { diff --git a/src/test/chars.test.ts b/src/test/chars.test.ts index ec446e4..936c329 100644 --- a/src/test/chars.test.ts +++ b/src/test/chars.test.ts @@ -1,24 +1,27 @@ import * as chai from 'chai'; import { randIntBetween } from './random'; -import { makeIO } from './_mock.test'; +import { encodeAndDecode } from './helpers/mock'; import { CharsSchema } from '../structure'; const expect = chai.expect; describe('CharsSchema', () => { - it('should encode and decode fixed-size char array', () => { - const length = randIntBetween(0, 100); - let value = ''; - const ranges = [['A', 'Z'], ['a', 'z'], ['0', '9']]; - for (let i = 0; i < length; ++i) { - const range = ranges[randIntBetween(0, ranges.length) % ranges.length]; - value += String.fromCharCode(randIntBetween(range[0].charCodeAt(0), range[1].charCodeAt(0))); - } + it('should encode and decode fixed-size char array', () => { + const length = randIntBetween(0, 100); + let value = ''; + const ranges = [ + ['A', 'Z'], + ['a', 'z'], + ['0', '9'], + ]; + for (let i = 0; i < length; ++i) { + const range = ranges[randIntBetween(0, ranges.length) % ranges.length]; + value += String.fromCharCode( + randIntBetween(range[0].charCodeAt(0), range[1].charCodeAt(0)), + ); + } - const description = new CharsSchema(value.length); - - const { output, input } = makeIO(length); - description.write(output, value); - expect(description.read(input)).to.equal(value); - }); -}); \ No newline at end of file + const description = new CharsSchema(value.length); + expect(encodeAndDecode(description, value)).to.equal(value); + }); +}); diff --git a/src/test/float.test.ts b/src/test/float.test.ts index e2d35f7..b2368e7 100644 --- a/src/test/float.test.ts +++ b/src/test/float.test.ts @@ -1,7 +1,7 @@ import * as chai from 'chai'; import { randBetween } from './random'; import { f32 } from '../structure'; -import { encodeAndDecode } from './_mock.test'; +import { encodeAndDecode } from './helpers/mock'; const expect = chai.expect; describe('Float32Schema', () => { diff --git a/src/test/_mock.test.ts b/src/test/helpers/mock.ts similarity index 65% rename from src/test/_mock.test.ts rename to src/test/helpers/mock.ts index 76e886b..9357f85 100644 --- a/src/test/_mock.test.ts +++ b/src/test/helpers/mock.ts @@ -1,6 +1,6 @@ -import { BufferReader, BufferWriter } from '../io'; -import { IStableSchema } from '../structure/types'; -import { Parsed } from '../utilityTypes'; +import { BufferReader, BufferWriter } from '../../io'; +import { ISchema } from '../../structure/types'; +import { Parsed } from '../../utilityTypes'; export function makeIO(bufferSize: number) { const buffer = Buffer.alloc(bufferSize); @@ -10,7 +10,7 @@ export function makeIO(bufferSize: number) { }; } -export function encodeAndDecode>( +export function encodeAndDecode>( schema: T, value: Parsed, ): Parsed { diff --git a/src/test/int.test.ts b/src/test/int.test.ts index dc83180..87423fd 100644 --- a/src/test/int.test.ts +++ b/src/test/int.test.ts @@ -1,7 +1,7 @@ import * as chai from 'chai'; import { randIntBetween } from './random'; import { i32, u32 } from '../structure'; -import { encodeAndDecode } from './_mock.test'; +import { encodeAndDecode } from './helpers/mock'; const expect = chai.expect; diff --git a/src/test/keyed.test.ts b/src/test/keyed.test.ts index 4cb6ecf..eeddcc6 100644 --- a/src/test/keyed.test.ts +++ b/src/test/keyed.test.ts @@ -1,5 +1,5 @@ import * as chai from 'chai'; -import { encodeAndDecode } from './_mock.test'; +import { encodeAndDecode } from './helpers/mock'; import { i32, string } from '../structure/baseTypes'; import { keyed, object, generic, genericEnum, optional } from '../describe'; import { Parsed } from '..'; diff --git a/src/test/object.test.ts b/src/test/object.test.ts index 8020712..2638b6d 100644 --- a/src/test/object.test.ts +++ b/src/test/object.test.ts @@ -1,5 +1,5 @@ import * as chai from 'chai'; -import { encodeAndDecode, makeIO } from './_mock.test'; +import { encodeAndDecode, makeIO } from './helpers/mock'; import { generic, genericEnum, object, optional } from '../describe'; import { byte, i32, string, MaxValue } from '../structure'; import { Parsed } from '../utilityTypes'; diff --git a/src/test/optional.test.ts b/src/test/optional.test.ts index 15a1093..1db957f 100644 --- a/src/test/optional.test.ts +++ b/src/test/optional.test.ts @@ -1,7 +1,7 @@ import * as chai from 'chai'; import { randIntBetween } from './random'; import { OptionalSchema, i32 } from '../structure'; -import { makeIO } from './_mock.test'; +import { makeIO } from './helpers/mock'; const expect = chai.expect; diff --git a/src/test/socket-io-usage.test.ts b/src/test/socket-io-usage.test.ts new file mode 100644 index 0000000..c21db09 --- /dev/null +++ b/src/test/socket-io-usage.test.ts @@ -0,0 +1,80 @@ +import * as chai from 'chai'; +import { createServer } from 'node:http'; +import { type AddressInfo } from 'node:net'; +import { Server, Socket as ServerSocket } from 'socket.io'; +import { Socket as ClientSocket, io as ioClient } from 'socket.io-client'; + +import { arrayOf, object } from '../describe'; +import { ISchema, byte, string, u32 } from '../structure'; +import { BufferReader, BufferWriter } from '../io'; + +const expect = chai.expect; + +function waitFor(socket: ServerSocket | ClientSocket, event = 'message') { + return new Promise((resolve) => { + socket.once(event, resolve); + }); +} + +describe('Socket IO Usage', () => { + let io: Server, serverSocket: ServerSocket, clientSocket: ClientSocket; + + before((done) => { + const httpServer = createServer(); + io = new Server(httpServer); + + httpServer.listen(() => { + const port = (httpServer.address() as AddressInfo).port; + clientSocket = ioClient(`http://localhost:${port}`); + + io.on('connection', (socket) => { + serverSocket = socket; + }); + + clientSocket.on('connect', done); + }); + }); + + async function sendAndReceive( + direction: 'server-to-client' | 'client-to-server', + schema: ISchema, + value: T, + ) { + const [from, to] = + direction === 'server-to-client' + ? [serverSocket, clientSocket] + : [clientSocket, serverSocket]; + + const buffer = Buffer.alloc(schema.measure(value).size); + schema.write(new BufferWriter(buffer), value); + from.send(buffer); + + // Receiving + + const response = schema.read(new BufferReader(await waitFor(to))); + expect(response).to.deep.equal(value); + } + + after(() => { + io.close(); + clientSocket.disconnect(); + }); + + it('should send a byte array successfully', async () => { + const schema = arrayOf(byte); + + await sendAndReceive('server-to-client', schema, [1, 2, 254, 255]); + }); + + it('should send a basic struct successfully', async () => { + const schema = object({ + name: string, + age: u32, + }); + + await sendAndReceive('server-to-client', schema, { + name: 'James Huns', + age: 15, + }); + }); +}); diff --git a/src/test/string.test.ts b/src/test/string.test.ts index cbe2414..737e955 100644 --- a/src/test/string.test.ts +++ b/src/test/string.test.ts @@ -1,7 +1,7 @@ import * as chai from 'chai'; import { randIntBetween } from './random'; import { string } from '../structure'; -import { encodeAndDecode } from './_mock.test'; +import { encodeAndDecode } from './helpers/mock'; const expect = chai.expect; describe('StringSchema', () => { diff --git a/src/utilityTypes.ts b/src/utilityTypes.ts index 5d9b10d..9e3bdb6 100644 --- a/src/utilityTypes.ts +++ b/src/utilityTypes.ts @@ -1,44 +1,45 @@ -import { ISchema, Keyed, Ref } from './structure/types'; - -export type Parsed> = - T extends Keyed ? - Parsed : - T extends Ref ? - (K extends keyof M ? Parsed : never) : - T extends ISchema ? - Parsed : - T extends Record ? - { [K in keyof T]: Parsed } : - T extends (infer E)[] ? - (Parsed)[] : - T; +import { IUnstableSchema, Keyed, Ref } from './structure/types'; +export type Parsed< + T, + M extends { [key in keyof M]: M[key] } = Record, +> = T extends Keyed + ? Parsed + : T extends Ref + ? K extends keyof M + ? Parsed + : never + : T extends IUnstableSchema + ? Parsed + : T extends Record + ? { [K in keyof T]: Parsed } + : T extends (infer E)[] + ? Parsed[] + : T; export type ValueOrProvider = T | (() => T); type UndefinedKeys = { - [P in keyof T]: undefined extends T[P] ? P : never -}[keyof T] + [P in keyof T]: undefined extends T[P] ? P : never; +}[keyof T]; -export type OptionalUndefined = Partial>> & Omit>; +export type OptionalUndefined = Partial>> & + Omit>; -export type Expand = - T extends (...args: infer A) => infer R - ? (...args: Expand) => Expand : - T extends Record & infer O // Getting rid of Record-s, which represent empty objects. - ? O : - T extends infer O - ? { [K in keyof O]: O[K] } : - never; +export type Expand = T extends (...args: infer A) => infer R + ? (...args: Expand) => Expand + : T extends Record & infer O // Getting rid of Record-s, which represent empty objects. + ? O + : T extends infer O + ? { [K in keyof O]: O[K] } + : never; -export type ExpandRecursively = - T extends (...args: infer A) => infer R - ? (...args: ExpandRecursively) => ExpandRecursively : - T extends Record & infer O // Getting rid of Record-s, which represent empty objects. - ? ExpandRecursively : - T extends object - ? (T extends infer O - ? { [K in keyof O]: ExpandRecursively } : - never - ) : - T; +export type ExpandRecursively = T extends (...args: infer A) => infer R + ? (...args: ExpandRecursively) => ExpandRecursively + : T extends Record & infer O // Getting rid of Record-s, which represent empty objects. + ? ExpandRecursively + : T extends object + ? T extends infer O + ? { [K in keyof O]: ExpandRecursively } + : never + : T; diff --git a/tsconfig.json b/tsconfig.json index 63a06dc..48e822c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,7 @@ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ /* Language and Environment */ - "target": "es5", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ // "jsx": "preserve", /* Specify what JSX code is generated. */ // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */