@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 +7 -0
- package/README.md +9 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/binary.js +25 -0
- package/dist/lib/binary.js.map +1 -0
- package/dist/lib/blob.js +42 -0
- package/dist/lib/blob.js.map +1 -0
- package/dist/lib/enums.js +15 -0
- package/dist/lib/enums.js.map +1 -0
- package/dist/lib/format.js +4 -0
- package/dist/lib/format.js.map +1 -0
- package/dist/lib/protocol.js +27 -0
- package/dist/lib/protocol.js.map +1 -0
- package/dist/lib/types.js +1 -0
- package/dist/lib/types.js.map +1 -0
- package/index.ts +6 -0
- package/lib/binary.ts +60 -0
- package/lib/blob.ts +67 -0
- package/lib/enums.ts +14 -0
- package/lib/format.ts +60 -0
- package/lib/protocol.ts +40 -0
- package/lib/types.ts +29 -0
- package/package.json +24 -0
- package/tsconfig.json +3 -0
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 @@
|
|
|
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"}
|
package/dist/lib/blob.js
ADDED
|
@@ -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 @@
|
|
|
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
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
|
+
}
|
package/lib/protocol.ts
ADDED
|
@@ -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