@nmtjs/common 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/LICENSE.md ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2024 Denis Ilchyshyn
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # NeemataJS - RPC application server for real-time applications (proof of concept)
2
+
3
+ ### Built with following in mind:
4
+ - transport-agnostic (like WebSockets, WebTransport, .etc)
5
+ - format-agnostic (like JSON, MessagePack, BSON, .etc)
6
+ - binary data streaming and event subscriptions
7
+ - contract-based API
8
+ - end-to-end type safety
9
+ - CPU-intensive task execution on separate workers
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export * from "./lib/binary.js";
2
+ export * from "./lib/enums.js";
3
+ export * from "./lib/format.js";
4
+ export * from "./lib/protocol.js";
5
+ export * from "./lib/types.js";
6
+ export * from "./lib/blob.js";
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../index.ts"],"sourcesContent":["export * from './lib/binary.ts'\nexport * from './lib/enums.ts'\nexport * from './lib/format.ts'\nexport * from './lib/protocol.ts'\nexport * from './lib/types.ts'\nexport * from './lib/blob.ts'\n"],"names":[],"mappings":"AAAA,cAAc,kBAAiB;AAC/B,cAAc,iBAAgB;AAC9B,cAAc,kBAAiB;AAC/B,cAAc,oBAAmB;AACjC,cAAc,iBAAgB;AAC9B,cAAc,gBAAe"}
@@ -0,0 +1,25 @@
1
+ const utf8decoder = new TextDecoder();
2
+ const utf8encoder = new TextEncoder();
3
+ export const encodeNumber = (value, type, littleEndian = false)=>{
4
+ const bytesNeeded = globalThis[`${type}Array`].BYTES_PER_ELEMENT;
5
+ const ab = new ArrayBuffer(bytesNeeded);
6
+ const dv = new DataView(ab);
7
+ dv[`set${type}`](0, value, littleEndian);
8
+ return ab;
9
+ };
10
+ export const decodeNumber = (buffer, type, offset = 0, littleEndian = false)=>{
11
+ const view = new DataView(buffer);
12
+ return view[`get${type}`](offset, littleEndian);
13
+ };
14
+ export const encodeText = (text)=>new Uint8Array(utf8encoder.encode(text)).buffer;
15
+ export const decodeText = (buffer)=>utf8decoder.decode(buffer);
16
+ export const concat = (...buffers)=>{
17
+ const totalLength = buffers.reduce((acc, buffer)=>acc + buffer.byteLength, 0);
18
+ const view = new Uint8Array(totalLength);
19
+ let offset = 0;
20
+ for (const buffer of buffers){
21
+ view.set(new Uint8Array(buffer), offset);
22
+ offset += buffer.byteLength;
23
+ }
24
+ return view.buffer;
25
+ };
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../lib/binary.ts"],"sourcesContent":["// TODO: get rid of lib DOM somehow...\n/// <reference lib=\"dom\" />\n\nconst utf8decoder = new TextDecoder()\nconst utf8encoder = new TextEncoder()\n\nexport type BinaryTypes = {\n Int8: number\n Int16: number\n Int32: number\n Uint8: number\n Uint16: number\n Uint32: number\n Float32: number\n Float64: number\n BigInt64: bigint\n BigUint64: bigint\n}\n\nexport const encodeNumber = <T extends keyof BinaryTypes>(\n value: BinaryTypes[T],\n type: T,\n littleEndian = false,\n) => {\n const bytesNeeded = globalThis[`${type}Array`].BYTES_PER_ELEMENT\n const ab = new ArrayBuffer(bytesNeeded)\n const dv = new DataView(ab)\n dv[`set${type}`](0, value as never, littleEndian)\n return ab\n}\n\nexport const decodeNumber = <T extends keyof BinaryTypes>(\n buffer: ArrayBuffer,\n type: T,\n offset = 0,\n littleEndian = false,\n): BinaryTypes[T] => {\n const view = new DataView(buffer)\n return view[`get${type}`](offset, littleEndian) as BinaryTypes[T]\n}\n\nexport const encodeText = (text: string) =>\n new Uint8Array(utf8encoder.encode(text)).buffer as ArrayBuffer\n\nexport const decodeText = (buffer: Parameters<typeof utf8decoder.decode>[0]) =>\n utf8decoder.decode(buffer)\n\nexport const concat = (...buffers: ArrayBuffer[]) => {\n const totalLength = buffers.reduce(\n (acc, buffer) => acc + buffer.byteLength,\n 0,\n )\n const view = new Uint8Array(totalLength)\n let offset = 0\n for (const buffer of buffers) {\n view.set(new Uint8Array(buffer), offset)\n offset += buffer.byteLength\n }\n return view.buffer as ArrayBuffer\n}\n"],"names":["utf8decoder","TextDecoder","utf8encoder","TextEncoder","encodeNumber","value","type","littleEndian","bytesNeeded","globalThis","BYTES_PER_ELEMENT","ab","ArrayBuffer","dv","DataView","decodeNumber","buffer","offset","view","encodeText","text","Uint8Array","encode","decodeText","decode","concat","buffers","totalLength","reduce","acc","byteLength","set"],"mappings":"AAGA,MAAMA,cAAc,IAAIC;AACxB,MAAMC,cAAc,IAAIC;AAexB,OAAO,MAAMC,eAAe,CAC1BC,OACAC,MACAC,eAAe,KAAK;IAEpB,MAAMC,cAAcC,UAAU,CAAC,CAAC,EAAEH,KAAK,KAAK,CAAC,CAAC,CAACI,iBAAiB;IAChE,MAAMC,KAAK,IAAIC,YAAYJ;IAC3B,MAAMK,KAAK,IAAIC,SAASH;IACxBE,EAAE,CAAC,CAAC,GAAG,EAAEP,KAAK,CAAC,CAAC,CAAC,GAAGD,OAAgBE;IACpC,OAAOI;AACT,EAAC;AAED,OAAO,MAAMI,eAAe,CAC1BC,QACAV,MACAW,SAAS,CAAC,EACVV,eAAe,KAAK;IAEpB,MAAMW,OAAO,IAAIJ,SAASE;IAC1B,OAAOE,IAAI,CAAC,CAAC,GAAG,EAAEZ,KAAK,CAAC,CAAC,CAACW,QAAQV;AACpC,EAAC;AAED,OAAO,MAAMY,aAAa,CAACC,OACzB,IAAIC,WAAWnB,YAAYoB,MAAM,CAACF,OAAOJ,MAAM,CAAe;AAEhE,OAAO,MAAMO,aAAa,CAACP,SACzBhB,YAAYwB,MAAM,CAACR,QAAO;AAE5B,OAAO,MAAMS,SAAS,CAAC,GAAGC;IACxB,MAAMC,cAAcD,QAAQE,MAAM,CAChC,CAACC,KAAKb,SAAWa,MAAMb,OAAOc,UAAU,EACxC;IAEF,MAAMZ,OAAO,IAAIG,WAAWM;IAC5B,IAAIV,SAAS;IACb,KAAK,MAAMD,UAAUU,QAAS;QAC5BR,KAAKa,GAAG,CAAC,IAAIV,WAAWL,SAASC;QACjCA,UAAUD,OAAOc,UAAU;IAC7B;IACA,OAAOZ,KAAKF,MAAM;AACpB,EAAC"}
@@ -0,0 +1,42 @@
1
+ export class ApiBlob {
2
+ metadata;
3
+ source;
4
+ constructor(source, size = -1, type = 'application/octet-stream', filename){
5
+ if (size < -1 || size === 0) throw new Error('Blob size is invalid');
6
+ this.source = source;
7
+ this.metadata = {
8
+ size,
9
+ type,
10
+ filename
11
+ };
12
+ }
13
+ static from(source, metadata = {}) {
14
+ let _source = undefined;
15
+ if (source instanceof ReadableStream) {
16
+ _source = source;
17
+ } else if ('File' in globalThis && source instanceof globalThis.File) {
18
+ _source = source.stream();
19
+ metadata.size = source.size;
20
+ metadata.filename = source.name;
21
+ } else if (source instanceof Blob) {
22
+ _source = source.stream();
23
+ metadata.size = source.size;
24
+ } else if (typeof source === 'string') {
25
+ const blob = new Blob([
26
+ source
27
+ ]);
28
+ _source = blob.stream();
29
+ metadata.size = blob.size;
30
+ metadata.type = metadata.type || 'text/plain';
31
+ } else if (source instanceof ArrayBuffer) {
32
+ const blob = new Blob([
33
+ source
34
+ ]);
35
+ _source = blob.stream();
36
+ metadata.size = blob.size;
37
+ } else {
38
+ _source = source;
39
+ }
40
+ return new ApiBlob(_source, metadata.size, metadata.type, metadata.filename);
41
+ }
42
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../lib/blob.ts"],"sourcesContent":["import type { ApiBlobMetadata } from './types.ts'\n\nexport interface ApiBlobInterface {\n readonly metadata: ApiBlobMetadata\n}\n\nexport type Exact<A, B> = [A] extends [B]\n ? [B] extends [A]\n ? true\n : false\n : false\n\nexport class ApiBlob implements ApiBlobInterface {\n public readonly metadata: ApiBlobMetadata\n public readonly source: any\n\n constructor(\n source: any,\n size = -1,\n type = 'application/octet-stream',\n filename?: string,\n ) {\n if (size < -1 || size === 0) throw new Error('Blob size is invalid')\n\n this.source = source\n this.metadata = {\n size,\n type,\n filename,\n }\n }\n\n static from(\n source: any,\n metadata: {\n size?: number\n type?: string\n filename?: string\n } = {},\n ) {\n let _source: any = undefined\n\n if (source instanceof ReadableStream) {\n _source = source\n } else if ('File' in globalThis && source instanceof globalThis.File) {\n _source = source.stream()\n metadata.size = source.size\n metadata.filename = source.name\n } else if (source instanceof Blob) {\n _source = source.stream()\n metadata.size = source.size\n } else if (typeof source === 'string') {\n const blob = new Blob([source])\n _source = blob.stream()\n metadata.size = blob.size\n metadata.type = metadata.type || 'text/plain'\n } else if (source instanceof ArrayBuffer) {\n const blob = new Blob([source])\n _source = blob.stream()\n metadata.size = blob.size\n } else {\n _source = source\n }\n\n return new ApiBlob(_source, metadata.size, metadata.type, metadata.filename)\n }\n}\n"],"names":["ApiBlob","metadata","source","constructor","size","type","filename","Error","from","_source","undefined","ReadableStream","globalThis","File","stream","name","Blob","blob","ArrayBuffer"],"mappings":"AAYA,OAAO,MAAMA;IACKC,SAAyB;IACzBC,OAAW;IAE3BC,YACED,MAAW,EACXE,OAAO,CAAC,CAAC,EACTC,OAAO,0BAA0B,EACjCC,QAAiB,CACjB;QACA,IAAIF,OAAO,CAAC,KAAKA,SAAS,GAAG,MAAM,IAAIG,MAAM;QAE7C,IAAI,CAACL,MAAM,GAAGA;QACd,IAAI,CAACD,QAAQ,GAAG;YACdG;YACAC;YACAC;QACF;IACF;IAEA,OAAOE,KACLN,MAAW,EACXD,WAII,CAAC,CAAC,EACN;QACA,IAAIQ,UAAeC;QAEnB,IAAIR,kBAAkBS,gBAAgB;YACpCF,UAAUP;QACZ,OAAO,IAAI,UAAUU,cAAcV,kBAAkBU,WAAWC,IAAI,EAAE;YACpEJ,UAAUP,OAAOY,MAAM;YACvBb,SAASG,IAAI,GAAGF,OAAOE,IAAI;YAC3BH,SAASK,QAAQ,GAAGJ,OAAOa,IAAI;QACjC,OAAO,IAAIb,kBAAkBc,MAAM;YACjCP,UAAUP,OAAOY,MAAM;YACvBb,SAASG,IAAI,GAAGF,OAAOE,IAAI;QAC7B,OAAO,IAAI,OAAOF,WAAW,UAAU;YACrC,MAAMe,OAAO,IAAID,KAAK;gBAACd;aAAO;YAC9BO,UAAUQ,KAAKH,MAAM;YACrBb,SAASG,IAAI,GAAGa,KAAKb,IAAI;YACzBH,SAASI,IAAI,GAAGJ,SAASI,IAAI,IAAI;QACnC,OAAO,IAAIH,kBAAkBgB,aAAa;YACxC,MAAMD,OAAO,IAAID,KAAK;gBAACd;aAAO;YAC9BO,UAAUQ,KAAKH,MAAM;YACrBb,SAASG,IAAI,GAAGa,KAAKb,IAAI;QAC3B,OAAO;YACLK,UAAUP;QACZ;QAEA,OAAO,IAAIF,QAAQS,SAASR,SAASG,IAAI,EAAEH,SAASI,IAAI,EAAEJ,SAASK,QAAQ;IAC7E;AACF"}
@@ -0,0 +1,15 @@
1
+ export var ErrorCode;
2
+ (function(ErrorCode) {
3
+ ErrorCode["ValidationError"] = "ValidationError";
4
+ ErrorCode["BadRequest"] = "BadRequest";
5
+ ErrorCode["NotFound"] = "NotFound";
6
+ ErrorCode["Forbidden"] = "Forbidden";
7
+ ErrorCode["Unauthorized"] = "Unauthorized";
8
+ ErrorCode["InternalServerError"] = "InternalServerError";
9
+ ErrorCode["NotAcceptable"] = "NotAcceptable";
10
+ ErrorCode["RequestTimeout"] = "RequestTimeout";
11
+ ErrorCode["GatewayTimeout"] = "GatewayTimeout";
12
+ ErrorCode["ServiceUnavailable"] = "ServiceUnavailable";
13
+ ErrorCode["ClientRequestError"] = "ClientRequestError";
14
+ ErrorCode["ConnectionError"] = "ConnectionError";
15
+ })(ErrorCode || (ErrorCode = {}));
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../lib/enums.ts"],"sourcesContent":["export enum ErrorCode {\n ValidationError = 'ValidationError',\n BadRequest = 'BadRequest',\n NotFound = 'NotFound',\n Forbidden = 'Forbidden',\n Unauthorized = 'Unauthorized',\n InternalServerError = 'InternalServerError',\n NotAcceptable = 'NotAcceptable',\n RequestTimeout = 'RequestTimeout',\n GatewayTimeout = 'GatewayTimeout',\n ServiceUnavailable = 'ServiceUnavailable',\n ClientRequestError = 'ClientRequestError',\n ConnectionError = 'ConnectionError',\n}\n"],"names":["ErrorCode"],"mappings":";UAAYA;;;;;;;;;;;;;GAAAA,cAAAA"}
@@ -0,0 +1,4 @@
1
+ export class BaseClientFormat {
2
+ }
3
+ export class BaseServerFormat {
4
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../lib/format.ts"],"sourcesContent":["import type { ApiBlob } from './blob.ts'\nimport type { ApiBlobMetadata, Pattern, Rpc, RpcResponse } from './types.ts'\n\nexport interface EncodeRpcContext {\n getStream: (id: number) => any\n addStream: (blob: ApiBlob) => { id: number; metadata: ApiBlobMetadata }\n}\n\nexport interface DecodeRpcContext {\n getStream: (id: number) => any\n addStream: (id: number, metadata: ApiBlobMetadata) => any\n}\n\nexport interface BaseClientDecoder {\n decode(buffer: ArrayBuffer): any\n decodeRpc(buffer: ArrayBuffer, context: DecodeRpcContext): RpcResponse\n}\n\nexport interface BaseClientEncoder {\n encode(data: any): ArrayBuffer\n encodeRpc(rpc: Rpc, context: EncodeRpcContext): ArrayBuffer\n}\n\nexport abstract class BaseClientFormat\n implements BaseClientDecoder, BaseClientEncoder\n{\n abstract contentType: string\n\n abstract encode(data: any): ArrayBuffer\n abstract encodeRpc(rpc: Rpc, context: EncodeRpcContext): ArrayBuffer\n abstract decode(buffer: ArrayBuffer): any\n abstract decodeRpc(\n buffer: ArrayBuffer,\n context: DecodeRpcContext,\n ): RpcResponse\n}\n\nexport interface BaseServerDecoder {\n accept: Pattern[]\n decode(buffer: ArrayBuffer): any\n decodeRpc(buffer: ArrayBuffer, context: DecodeRpcContext): Rpc\n}\n\nexport interface BaseServerEncoder {\n contentType: string\n encode(data: any): ArrayBuffer\n encodeRpc(rpc: RpcResponse, context: EncodeRpcContext): ArrayBuffer\n}\n\nexport abstract class BaseServerFormat\n implements BaseServerDecoder, BaseServerEncoder\n{\n abstract accept: Pattern[]\n abstract contentType: string\n\n abstract encode(data: any): ArrayBuffer\n abstract encodeRpc(rpc: RpcResponse, context: EncodeRpcContext): ArrayBuffer\n abstract decode(buffer: ArrayBuffer): any\n abstract decodeRpc(buffer: ArrayBuffer, context: DecodeRpcContext): Rpc\n}\n"],"names":["BaseClientFormat","BaseServerFormat"],"mappings":"AAuBA,OAAO,MAAeA;AAYtB;AAcA,OAAO,MAAeC;AAUtB"}
@@ -0,0 +1,27 @@
1
+ export const MessageType = Object.freeze({
2
+ Event: 10,
3
+ Rpc: 11,
4
+ RpcBatch: 12,
5
+ RpcAbort: 14,
6
+ RpcSubscription: 15,
7
+ UpStreamAbort: 30,
8
+ UpStreamPush: 31,
9
+ UpStreamPull: 32,
10
+ UpStreamEnd: 33,
11
+ ClientUnsubscribe: 34,
12
+ DownStreamAbort: 50,
13
+ DownStreamPull: 51,
14
+ DownStreamPush: 52,
15
+ DownStreamEnd: 53,
16
+ ServerUnsubscribe: 54,
17
+ ServerSubscriptionEvent: 55
18
+ });
19
+ export const MessageTypeName = Object.fromEntries(Object.entries(MessageType).map(([k, v])=>[
20
+ v,
21
+ k
22
+ ]));
23
+ export var TransportType;
24
+ (function(TransportType) {
25
+ TransportType["WS"] = "WS";
26
+ TransportType["HTTP"] = "HTTP";
27
+ })(TransportType || (TransportType = {}));
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../lib/protocol.ts"],"sourcesContent":["export const MessageType = Object.freeze({\n // Common\n Event: 10,\n Rpc: 11,\n RpcBatch: 12,\n RpcAbort: 14,\n RpcSubscription: 15,\n\n // Client streams\n UpStreamAbort: 30,\n UpStreamPush: 31,\n UpStreamPull: 32,\n UpStreamEnd: 33,\n\n // Client subsctiption\n ClientUnsubscribe: 34,\n\n // Server streams\n DownStreamAbort: 50,\n DownStreamPull: 51,\n DownStreamPush: 52,\n DownStreamEnd: 53,\n\n // Server subsctiption\n ServerUnsubscribe: 54,\n ServerSubscriptionEvent: 55,\n} as const)\n\nexport type MessageType = (typeof MessageType)[keyof typeof MessageType]\n\nexport const MessageTypeName = Object.fromEntries(\n Object.entries(MessageType).map(([k, v]) => [v, k]),\n)\nexport type MessageTypeName = keyof typeof MessageType\n\n// TODO: Should it be hardcoded ??\nexport enum TransportType {\n WS = 'WS',\n HTTP = 'HTTP',\n}\n"],"names":["MessageType","Object","freeze","Event","Rpc","RpcBatch","RpcAbort","RpcSubscription","UpStreamAbort","UpStreamPush","UpStreamPull","UpStreamEnd","ClientUnsubscribe","DownStreamAbort","DownStreamPull","DownStreamPush","DownStreamEnd","ServerUnsubscribe","ServerSubscriptionEvent","MessageTypeName","fromEntries","entries","map","k","v","TransportType"],"mappings":"AAAA,OAAO,MAAMA,cAAcC,OAAOC,MAAM,CAAC;IAEvCC,OAAO;IACPC,KAAK;IACLC,UAAU;IACVC,UAAU;IACVC,iBAAiB;IAGjBC,eAAe;IACfC,cAAc;IACdC,cAAc;IACdC,aAAa;IAGbC,mBAAmB;IAGnBC,iBAAiB;IACjBC,gBAAgB;IAChBC,gBAAgB;IAChBC,eAAe;IAGfC,mBAAmB;IACnBC,yBAAyB;AAC3B,GAAW;AAIX,OAAO,MAAMC,kBAAkBlB,OAAOmB,WAAW,CAC/CnB,OAAOoB,OAAO,CAACrB,aAAasB,GAAG,CAAC,CAAC,CAACC,GAAGC,EAAE,GAAK;QAACA;QAAGD;KAAE,GACnD;;UAIWE;;;GAAAA,kBAAAA"}
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../lib/types.ts"],"sourcesContent":["export type ApiBlobMetadata = {\n type: string\n size: number\n filename?: string\n}\n\nexport type Rpc = {\n callId: number\n service: string\n procedure: string\n payload: any\n}\n\nexport type RpcResponse = {\n callId: number\n error?: any\n payload?: any\n}\n\nexport interface TypeProvider {\n readonly input: unknown\n readonly output: unknown\n}\n\nexport type CallTypeProvider<T extends TypeProvider, V> = (T & {\n input: V\n})['output']\n\nexport type Pattern = RegExp | string | ((value: string) => boolean)\n"],"names":[],"mappings":"AA4BA,WAAoE"}
package/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ export * from './lib/binary.ts'
2
+ export * from './lib/enums.ts'
3
+ export * from './lib/format.ts'
4
+ export * from './lib/protocol.ts'
5
+ export * from './lib/types.ts'
6
+ export * from './lib/blob.ts'
package/lib/binary.ts ADDED
@@ -0,0 +1,60 @@
1
+ // TODO: get rid of lib DOM somehow...
2
+ /// <reference lib="dom" />
3
+
4
+ const utf8decoder = new TextDecoder()
5
+ const utf8encoder = new TextEncoder()
6
+
7
+ export type BinaryTypes = {
8
+ Int8: number
9
+ Int16: number
10
+ Int32: number
11
+ Uint8: number
12
+ Uint16: number
13
+ Uint32: number
14
+ Float32: number
15
+ Float64: number
16
+ BigInt64: bigint
17
+ BigUint64: bigint
18
+ }
19
+
20
+ export const encodeNumber = <T extends keyof BinaryTypes>(
21
+ value: BinaryTypes[T],
22
+ type: T,
23
+ littleEndian = false,
24
+ ) => {
25
+ const bytesNeeded = globalThis[`${type}Array`].BYTES_PER_ELEMENT
26
+ const ab = new ArrayBuffer(bytesNeeded)
27
+ const dv = new DataView(ab)
28
+ dv[`set${type}`](0, value as never, littleEndian)
29
+ return ab
30
+ }
31
+
32
+ export const decodeNumber = <T extends keyof BinaryTypes>(
33
+ buffer: ArrayBuffer,
34
+ type: T,
35
+ offset = 0,
36
+ littleEndian = false,
37
+ ): BinaryTypes[T] => {
38
+ const view = new DataView(buffer)
39
+ return view[`get${type}`](offset, littleEndian) as BinaryTypes[T]
40
+ }
41
+
42
+ export const encodeText = (text: string) =>
43
+ new Uint8Array(utf8encoder.encode(text)).buffer as ArrayBuffer
44
+
45
+ export const decodeText = (buffer: Parameters<typeof utf8decoder.decode>[0]) =>
46
+ utf8decoder.decode(buffer)
47
+
48
+ export const concat = (...buffers: ArrayBuffer[]) => {
49
+ const totalLength = buffers.reduce(
50
+ (acc, buffer) => acc + buffer.byteLength,
51
+ 0,
52
+ )
53
+ const view = new Uint8Array(totalLength)
54
+ let offset = 0
55
+ for (const buffer of buffers) {
56
+ view.set(new Uint8Array(buffer), offset)
57
+ offset += buffer.byteLength
58
+ }
59
+ return view.buffer as ArrayBuffer
60
+ }
package/lib/blob.ts ADDED
@@ -0,0 +1,67 @@
1
+ import type { ApiBlobMetadata } from './types.ts'
2
+
3
+ export interface ApiBlobInterface {
4
+ readonly metadata: ApiBlobMetadata
5
+ }
6
+
7
+ export type Exact<A, B> = [A] extends [B]
8
+ ? [B] extends [A]
9
+ ? true
10
+ : false
11
+ : false
12
+
13
+ export class ApiBlob implements ApiBlobInterface {
14
+ public readonly metadata: ApiBlobMetadata
15
+ public readonly source: any
16
+
17
+ constructor(
18
+ source: any,
19
+ size = -1,
20
+ type = 'application/octet-stream',
21
+ filename?: string,
22
+ ) {
23
+ if (size < -1 || size === 0) throw new Error('Blob size is invalid')
24
+
25
+ this.source = source
26
+ this.metadata = {
27
+ size,
28
+ type,
29
+ filename,
30
+ }
31
+ }
32
+
33
+ static from(
34
+ source: any,
35
+ metadata: {
36
+ size?: number
37
+ type?: string
38
+ filename?: string
39
+ } = {},
40
+ ) {
41
+ let _source: any = undefined
42
+
43
+ if (source instanceof ReadableStream) {
44
+ _source = source
45
+ } else if ('File' in globalThis && source instanceof globalThis.File) {
46
+ _source = source.stream()
47
+ metadata.size = source.size
48
+ metadata.filename = source.name
49
+ } else if (source instanceof Blob) {
50
+ _source = source.stream()
51
+ metadata.size = source.size
52
+ } else if (typeof source === 'string') {
53
+ const blob = new Blob([source])
54
+ _source = blob.stream()
55
+ metadata.size = blob.size
56
+ metadata.type = metadata.type || 'text/plain'
57
+ } else if (source instanceof ArrayBuffer) {
58
+ const blob = new Blob([source])
59
+ _source = blob.stream()
60
+ metadata.size = blob.size
61
+ } else {
62
+ _source = source
63
+ }
64
+
65
+ return new ApiBlob(_source, metadata.size, metadata.type, metadata.filename)
66
+ }
67
+ }
package/lib/enums.ts ADDED
@@ -0,0 +1,14 @@
1
+ export enum ErrorCode {
2
+ ValidationError = 'ValidationError',
3
+ BadRequest = 'BadRequest',
4
+ NotFound = 'NotFound',
5
+ Forbidden = 'Forbidden',
6
+ Unauthorized = 'Unauthorized',
7
+ InternalServerError = 'InternalServerError',
8
+ NotAcceptable = 'NotAcceptable',
9
+ RequestTimeout = 'RequestTimeout',
10
+ GatewayTimeout = 'GatewayTimeout',
11
+ ServiceUnavailable = 'ServiceUnavailable',
12
+ ClientRequestError = 'ClientRequestError',
13
+ ConnectionError = 'ConnectionError',
14
+ }
package/lib/format.ts ADDED
@@ -0,0 +1,60 @@
1
+ import type { ApiBlob } from './blob.ts'
2
+ import type { ApiBlobMetadata, Pattern, Rpc, RpcResponse } from './types.ts'
3
+
4
+ export interface EncodeRpcContext {
5
+ getStream: (id: number) => any
6
+ addStream: (blob: ApiBlob) => { id: number; metadata: ApiBlobMetadata }
7
+ }
8
+
9
+ export interface DecodeRpcContext {
10
+ getStream: (id: number) => any
11
+ addStream: (id: number, metadata: ApiBlobMetadata) => any
12
+ }
13
+
14
+ export interface BaseClientDecoder {
15
+ decode(buffer: ArrayBuffer): any
16
+ decodeRpc(buffer: ArrayBuffer, context: DecodeRpcContext): RpcResponse
17
+ }
18
+
19
+ export interface BaseClientEncoder {
20
+ encode(data: any): ArrayBuffer
21
+ encodeRpc(rpc: Rpc, context: EncodeRpcContext): ArrayBuffer
22
+ }
23
+
24
+ export abstract class BaseClientFormat
25
+ implements BaseClientDecoder, BaseClientEncoder
26
+ {
27
+ abstract contentType: string
28
+
29
+ abstract encode(data: any): ArrayBuffer
30
+ abstract encodeRpc(rpc: Rpc, context: EncodeRpcContext): ArrayBuffer
31
+ abstract decode(buffer: ArrayBuffer): any
32
+ abstract decodeRpc(
33
+ buffer: ArrayBuffer,
34
+ context: DecodeRpcContext,
35
+ ): RpcResponse
36
+ }
37
+
38
+ export interface BaseServerDecoder {
39
+ accept: Pattern[]
40
+ decode(buffer: ArrayBuffer): any
41
+ decodeRpc(buffer: ArrayBuffer, context: DecodeRpcContext): Rpc
42
+ }
43
+
44
+ export interface BaseServerEncoder {
45
+ contentType: string
46
+ encode(data: any): ArrayBuffer
47
+ encodeRpc(rpc: RpcResponse, context: EncodeRpcContext): ArrayBuffer
48
+ }
49
+
50
+ export abstract class BaseServerFormat
51
+ implements BaseServerDecoder, BaseServerEncoder
52
+ {
53
+ abstract accept: Pattern[]
54
+ abstract contentType: string
55
+
56
+ abstract encode(data: any): ArrayBuffer
57
+ abstract encodeRpc(rpc: RpcResponse, context: EncodeRpcContext): ArrayBuffer
58
+ abstract decode(buffer: ArrayBuffer): any
59
+ abstract decodeRpc(buffer: ArrayBuffer, context: DecodeRpcContext): Rpc
60
+ }
@@ -0,0 +1,40 @@
1
+ export const MessageType = Object.freeze({
2
+ // Common
3
+ Event: 10,
4
+ Rpc: 11,
5
+ RpcBatch: 12,
6
+ RpcAbort: 14,
7
+ RpcSubscription: 15,
8
+
9
+ // Client streams
10
+ UpStreamAbort: 30,
11
+ UpStreamPush: 31,
12
+ UpStreamPull: 32,
13
+ UpStreamEnd: 33,
14
+
15
+ // Client subsctiption
16
+ ClientUnsubscribe: 34,
17
+
18
+ // Server streams
19
+ DownStreamAbort: 50,
20
+ DownStreamPull: 51,
21
+ DownStreamPush: 52,
22
+ DownStreamEnd: 53,
23
+
24
+ // Server subsctiption
25
+ ServerUnsubscribe: 54,
26
+ ServerSubscriptionEvent: 55,
27
+ } as const)
28
+
29
+ export type MessageType = (typeof MessageType)[keyof typeof MessageType]
30
+
31
+ export const MessageTypeName = Object.fromEntries(
32
+ Object.entries(MessageType).map(([k, v]) => [v, k]),
33
+ )
34
+ export type MessageTypeName = keyof typeof MessageType
35
+
36
+ // TODO: Should it be hardcoded ??
37
+ export enum TransportType {
38
+ WS = 'WS',
39
+ HTTP = 'HTTP',
40
+ }
package/lib/types.ts ADDED
@@ -0,0 +1,29 @@
1
+ export type ApiBlobMetadata = {
2
+ type: string
3
+ size: number
4
+ filename?: string
5
+ }
6
+
7
+ export type Rpc = {
8
+ callId: number
9
+ service: string
10
+ procedure: string
11
+ payload: any
12
+ }
13
+
14
+ export type RpcResponse = {
15
+ callId: number
16
+ error?: any
17
+ payload?: any
18
+ }
19
+
20
+ export interface TypeProvider {
21
+ readonly input: unknown
22
+ readonly output: unknown
23
+ }
24
+
25
+ export type CallTypeProvider<T extends TypeProvider, V> = (T & {
26
+ input: V
27
+ })['output']
28
+
29
+ export type Pattern = RegExp | string | ((value: string) => boolean)
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@nmtjs/common",
3
+ "type": "module",
4
+ "exports": {
5
+ ".": {
6
+ "bun": "./index.ts",
7
+ "default": "./dist/index.js",
8
+ "types": "./index.ts"
9
+ }
10
+ },
11
+ "files": [
12
+ "index.ts",
13
+ "lib",
14
+ "dist",
15
+ "tsconfig.json",
16
+ "LICENSE.md",
17
+ "README.md"
18
+ ],
19
+ "version": "0.0.1",
20
+ "scripts": {
21
+ "build": "neemata-build -p neutral ./index.ts './lib/**/*.ts'",
22
+ "type-check": "tsc --noEmit"
23
+ }
24
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "../../tsconfig.json"
3
+ }