## @strpc 기존 RPC를 package화 ### @strpc/express express의 middleware + router 결함 ## @sammo 게임 전체 - server, client - gateway_server, gateway_client
263 lines
8.2 KiB
TypeScript
263 lines
8.2 KiB
TypeScript
import { every, isString } from "lodash-es";
|
|
import type { JsonifiableClassObj, PlainJson, PlainJsonObj } from "./jsonify.js";
|
|
|
|
type BsonifiableLite = string | number | boolean | bigint | null | undefined;
|
|
|
|
export type PlainBsonItem =
|
|
string | number | boolean | null | undefined |
|
|
Date | ArrayBuffer | SharedArrayBuffer | ArrayBufferView |
|
|
PlainBsonItem[] |
|
|
{ [key: string]: PlainBsonItem };
|
|
|
|
export type PlainBson =
|
|
{ [key: string]: PlainBsonItem };
|
|
|
|
export type BsonifiableItem =
|
|
BsonifiableLite |
|
|
Date | ArrayBuffer | SharedArrayBuffer | ArrayBufferView |
|
|
Array<BsonifiableItem> |
|
|
Map<BsonifiableLite, BsonifiableItem> |
|
|
Set<BsonifiableLite> |
|
|
BsonifiableClassObj<PlainBson> |
|
|
JsonifiableClassObj<PlainJson> |
|
|
{ [key: string]: BsonifiableItem } |
|
|
ReadonlyArray<BsonifiableItem>;
|
|
|
|
export type Bsonifiable =
|
|
Map<string, BsonifiableItem> |
|
|
BsonifiableClassObj<PlainBson> |
|
|
JsonifiableClassObj<PlainJsonObj> |
|
|
{ [key: string]: BsonifiableItem };
|
|
|
|
|
|
export interface BsonifiableClassObj<T extends PlainBson> {
|
|
bsonify(): T;
|
|
}
|
|
|
|
type MaybeBsonifiedItem<T> = T extends BsonifiableItem ? BsonifiedItem<T> : never;
|
|
type MaybeBsonified<T> = T extends Bsonifiable ? Bsonified<T> : never;
|
|
|
|
export type BsonifiedItem<T extends BsonifiableItem | unknown> =
|
|
T extends string ? T :
|
|
T extends number ? T :
|
|
T extends boolean ? T :
|
|
T extends bigint ? ReturnType<T['toString']> :
|
|
T extends null ? T :
|
|
T extends undefined ? T :
|
|
T extends Date ? T :
|
|
T extends ArrayBuffer ? T :
|
|
T extends SharedArrayBuffer ? T :
|
|
T extends ArrayBufferView ? T :
|
|
T extends Map<infer K extends string, infer V> ? { [key in K]: MaybeBsonified<V> } :
|
|
T extends Map<infer K, infer V> ? [MaybeBsonified<K>, MaybeBsonified<V>][] :
|
|
T extends Set<infer V> ? MaybeBsonified<V>[] :
|
|
T extends BsonifiableClassObj<PlainBson> ? ReturnType<T['bsonify']> :
|
|
T extends JsonifiableClassObj<PlainJson> ? ReturnType<T['jsonify']> :
|
|
T extends object ? { [key in keyof T]: MaybeBsonified<T[key]> } :
|
|
T extends unknown ? unknown :
|
|
never;
|
|
|
|
export type Bsonified<T extends Bsonifiable | unknown> =
|
|
T extends Map<infer K extends string, infer V> ? { [key in K]: MaybeBsonifiedItem<V> } :
|
|
T extends BsonifiableClassObj<PlainBson> ? ReturnType<T['bsonify']> :
|
|
T extends JsonifiableClassObj<PlainJsonObj> ? ReturnType<T['jsonify']> :
|
|
T extends object ? { [key in keyof T]: MaybeBsonifiedItem<T[key]> } :
|
|
T extends unknown ? unknown :
|
|
never;
|
|
|
|
/**
|
|
* Convert any object to BSON-safe object
|
|
*/
|
|
export function bsonifyItem<T extends BsonifiableItem>(item: T): BsonifiedItem<T> {
|
|
if (typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean') {
|
|
return item as BsonifiedItem<T>;
|
|
}
|
|
|
|
if (typeof item === 'bigint') {
|
|
return item.toString() as BsonifiedItem<T>;
|
|
}
|
|
|
|
if (typeof item === 'undefined') {
|
|
return item as BsonifiedItem<typeof item>;
|
|
}
|
|
|
|
if (item === null) {
|
|
return item as BsonifiedItem<typeof item>;
|
|
}
|
|
|
|
if (item instanceof Date) {
|
|
return item as BsonifiedItem<typeof item>;
|
|
}
|
|
|
|
if (item instanceof ArrayBuffer) {
|
|
return item as BsonifiedItem<typeof item>;
|
|
}
|
|
|
|
if ('SharedArrayBuffer' in globalThis && item instanceof SharedArrayBuffer) {
|
|
return item as BsonifiedItem<typeof item>;
|
|
}
|
|
|
|
if (ArrayBuffer.isView(item)) {
|
|
return item as BsonifiedItem<typeof item>;
|
|
}
|
|
|
|
//HACK: depth hack, escape ts(2589)
|
|
if (item instanceof Map) {
|
|
const onlyStringKey = every(item.keys, isString);
|
|
if (onlyStringKey) {
|
|
const result: { [key: string]: string } = {};
|
|
for (const [k, v] of item.entries()) {
|
|
result[k as string] = bsonifyItem(v as string);
|
|
}
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
return result as any;
|
|
}
|
|
|
|
const result: [string, string][] = [];
|
|
for (const [k, v] of item.entries()) {
|
|
result.push([bsonifyItem(k as string), bsonifyItem(v as string)]);
|
|
}
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
return result as any;
|
|
}
|
|
|
|
if (item instanceof Set) {
|
|
const result: string[] = [];
|
|
for (const v of item.values()) {
|
|
result.push(bsonifyItem(v as string));
|
|
}
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
return result as any;
|
|
}
|
|
|
|
if (Array.isArray(item)) {
|
|
const result: string[] = [];
|
|
for (const v of item) {
|
|
result.push(bsonifyItem(v as string));
|
|
}
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
return result as any;
|
|
}
|
|
|
|
if (typeof item !== 'object') {
|
|
throw new Error(`jsonify: invalid type ${typeof item}`);
|
|
}
|
|
|
|
if ('bsonify' in item && typeof item.bsonify === 'function') {
|
|
return item.bsonify() as BsonifiedItem<typeof item>;
|
|
}
|
|
|
|
if ('jsonify' in item && typeof item.jsonify === 'function') {
|
|
return item.jsonify() as BsonifiedItem<typeof item>;
|
|
}
|
|
|
|
const result: { [key: string]: unknown } = {};
|
|
for (const [k, v] of Object.entries(item)) {
|
|
if (typeof k !== 'string') {
|
|
continue;
|
|
}
|
|
if (k === 'prototype') {
|
|
continue;
|
|
}
|
|
if (k === '__proto__ ') {
|
|
continue;
|
|
}
|
|
if (typeof v === 'function') {
|
|
continue;
|
|
}
|
|
result[k] = bsonifyItem(v as BsonifiableItem);
|
|
}
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
return result as any;
|
|
}
|
|
|
|
/**
|
|
* Convert any object to BSON-document object
|
|
*/
|
|
export function bsonify<T extends Bsonifiable>(item: T): Bsonified<T> {
|
|
if (typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean') {
|
|
throw new Error(`bsonify: invalid type ${typeof item}`);
|
|
}
|
|
|
|
if (typeof item === 'bigint') {
|
|
throw new Error(`bsonify: invalid type ${typeof item}`);
|
|
}
|
|
|
|
if (typeof item === 'undefined') {
|
|
throw new Error(`bsonify: invalid type ${typeof item}`);
|
|
}
|
|
|
|
if (item === null) {
|
|
throw new Error(`bsonify: invalid type ${typeof item}`);
|
|
}
|
|
|
|
if (item instanceof Date) {
|
|
throw new Error(`bsonify: invalid type ${typeof item}`);
|
|
}
|
|
|
|
if (item instanceof ArrayBuffer) {
|
|
throw new Error(`bsonify: invalid type ${typeof item}`);
|
|
}
|
|
|
|
if ('SharedArrayBuffer' in globalThis && item instanceof SharedArrayBuffer) {
|
|
throw new Error(`bsonify: invalid type ${typeof item}`);
|
|
}
|
|
|
|
if (ArrayBuffer.isView(item)) {
|
|
throw new Error(`bsonify: invalid type ${typeof item}`);
|
|
}
|
|
|
|
//HACK: depth hack, escape ts(2589)
|
|
if (item instanceof Map) {
|
|
const onlyStringKey = every(item.keys, isString);
|
|
if (onlyStringKey) {
|
|
const result: { [key: string]: unknown } = {};
|
|
for (const [k, v] of item.entries()) {
|
|
result[k as string] = bsonifyItem(v as string);
|
|
}
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
return result as any;
|
|
}
|
|
|
|
throw new Error(`bsonify: invalid type ${typeof item}`);
|
|
}
|
|
|
|
if (item instanceof Set) {
|
|
throw new Error(`bsonify: invalid type ${typeof item}`);
|
|
}
|
|
|
|
if (Array.isArray(item)) {
|
|
throw new Error(`bsonify: invalid type ${typeof item}`);
|
|
}
|
|
|
|
if (typeof item !== 'object') {
|
|
throw new Error(`jsonify: invalid type ${typeof item}`);
|
|
}
|
|
|
|
if ('bsonify' in item && typeof item.bsonify === 'function') {
|
|
return item.bsonify() as Bsonified<typeof item>;
|
|
}
|
|
|
|
if ('jsonify' in item && typeof item.jsonify === 'function') {
|
|
return item.jsonify() as Bsonified<typeof item>;
|
|
}
|
|
|
|
const result: { [key: string]: unknown } = {};
|
|
for (const [k, v] of Object.entries(item)) {
|
|
if (typeof k !== 'string') {
|
|
continue;
|
|
}
|
|
if (k === 'prototype') {
|
|
continue;
|
|
}
|
|
if (k === '__proto__ ') {
|
|
continue;
|
|
}
|
|
if (typeof v === 'function') {
|
|
continue;
|
|
}
|
|
result[k] = bsonifyItem(v);
|
|
}
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
return result as any;
|
|
} |