From 0c9b2778c50ceb84df695dd24a790a58e66e69ad Mon Sep 17 00:00:00 2001 From: rowan Date: Sun, 18 May 2025 20:05:52 -0500 Subject: [PATCH] package js/d.ts --- dist/case.d.ts | 12 ++++ dist/case.js | 56 +++++++++++++++ dist/de/generic.d.ts | 15 ++++ dist/de/generic.js | 60 ++++++++++++++++ dist/de/impl.d.ts | 7 ++ dist/de/impl.js | 21 ++++++ dist/de/index.d.ts | 3 + dist/de/index.js | 19 +++++ dist/de/interface.d.ts | 52 ++++++++++++++ dist/de/interface.js | 32 +++++++++ dist/de/mixin.d.ts | 9 +++ dist/de/mixin.js | 31 ++++++++ dist/decorator.d.ts | 6 ++ dist/decorator.js | 54 ++++++++++++++ dist/index.d.ts | 6 ++ dist/index.js | 45 ++++++++++++ dist/options.d.ts | 63 ++++++++++++++++ dist/options.js | 156 ++++++++++++++++++++++++++++++++++++++++ dist/registry.d.ts | 7 ++ dist/registry.js | 21 ++++++ dist/ser/impl.d.ts | 4 ++ dist/ser/impl.js | 64 +++++++++++++++++ dist/ser/index.d.ts | 3 + dist/ser/index.js | 19 +++++ dist/ser/interface.d.ts | 31 ++++++++ dist/ser/interface.js | 21 ++++++ dist/ser/mixin.d.ts | 9 +++ dist/ser/mixin.js | 27 +++++++ dist/utils.d.ts | 28 ++++++++ dist/utils.js | 58 +++++++++++++++ package.json | 14 ++++ tsconfig.json | 4 +- 32 files changed, 955 insertions(+), 2 deletions(-) create mode 100644 dist/case.d.ts create mode 100644 dist/case.js create mode 100644 dist/de/generic.d.ts create mode 100644 dist/de/generic.js create mode 100644 dist/de/impl.d.ts create mode 100644 dist/de/impl.js create mode 100644 dist/de/index.d.ts create mode 100644 dist/de/index.js create mode 100644 dist/de/interface.d.ts create mode 100644 dist/de/interface.js create mode 100644 dist/de/mixin.d.ts create mode 100644 dist/de/mixin.js create mode 100644 dist/decorator.d.ts create mode 100644 dist/decorator.js create mode 100644 dist/index.d.ts create mode 100644 dist/index.js create mode 100644 dist/options.d.ts create mode 100644 dist/options.js create mode 100644 dist/registry.d.ts create mode 100644 dist/registry.js create mode 100644 dist/ser/impl.d.ts create mode 100644 dist/ser/impl.js create mode 100644 dist/ser/index.d.ts create mode 100644 dist/ser/index.js create mode 100644 dist/ser/interface.d.ts create mode 100644 dist/ser/interface.js create mode 100644 dist/ser/mixin.d.ts create mode 100644 dist/ser/mixin.js create mode 100644 dist/utils.d.ts create mode 100644 dist/utils.js diff --git a/dist/case.d.ts b/dist/case.d.ts new file mode 100644 index 0000000..a53b05a --- /dev/null +++ b/dist/case.d.ts @@ -0,0 +1,12 @@ +export declare const CaseConvention: Readonly<{ + readonly Lowercase: 0; + readonly Uppercase: 1; + readonly PascalCase: 2; + readonly CamelCase: 3; + readonly SnakeCase: 4; + readonly ScreamingSnakeCase: 5; + readonly KebabCase: 6; + readonly ScreamingKebabCase: 7; +}>; +export type CaseConvention = typeof CaseConvention[keyof typeof CaseConvention]; +export declare function convertCase(value: string, convention: CaseConvention): string; diff --git a/dist/case.js b/dist/case.js new file mode 100644 index 0000000..3e64fdc --- /dev/null +++ b/dist/case.js @@ -0,0 +1,56 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.CaseConvention = void 0; +exports.convertCase = convertCase; +exports.CaseConvention = Object.freeze({ + Lowercase: 0, + Uppercase: 1, + PascalCase: 2, + CamelCase: 3, + SnakeCase: 4, + ScreamingSnakeCase: 5, + KebabCase: 6, + ScreamingKebabCase: 7 +}); +const wordBoundaryRegex = /[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g; +function identifyWords(value) { + return value && value.match(wordBoundaryRegex); +} +const lower = (ch) => ch.toLowerCase(); +const upper = (ch) => ch.toUpperCase(); +const first = (xs) => xs && xs[0]; +const tail = (xs) => xs && xs.slice(1); +function upperFirst(xs) { + return upper(first(xs)) + tail(xs); +} +function toPascalCase(words) { + return words.map(lower).map(upperFirst).join(''); +} +const joinMap = (fn, delim, xs) => { + return xs.map(fn).join(delim); +}; +function convertCase(value, convention) { + const words = identifyWords(value); + if (!words || words.length <= 0) { + return ''; + } + switch (convention) { + case exports.CaseConvention.Lowercase: + return words.join('').toLowerCase(); + case exports.CaseConvention.Uppercase: + return words.join('').toUpperCase(); + case exports.CaseConvention.PascalCase: + return toPascalCase(words); + case exports.CaseConvention.CamelCase: + const pascal = toPascalCase(words); + return first(pascal).toLowerCase() + tail(pascal); + case exports.CaseConvention.SnakeCase: + return joinMap(lower, '_', words); + case exports.CaseConvention.ScreamingSnakeCase: + return joinMap(upper, '_', words); + case exports.CaseConvention.KebabCase: + return joinMap(lower, '-', words); + case exports.CaseConvention.ScreamingKebabCase: + return joinMap(upper, '-', words); + } +} diff --git a/dist/de/generic.d.ts b/dist/de/generic.d.ts new file mode 100644 index 0000000..3db19cd --- /dev/null +++ b/dist/de/generic.d.ts @@ -0,0 +1,15 @@ +import { Deserialize, Deserializer, IterableAccess, MapAccess, Visitor } from './interface'; +export declare class GenericSeed implements Deserialize { + static deserialize(deserializer: D): T; + deserialize(deserializer: D): T; +} +export declare class GenericVisitor implements Visitor { + visitString(value: string): T; + visitNumber(value: number): T; + visitBigInt(value: bigint): T; + visitBoolean(value: boolean): T; + visitSymbol(value: symbol): T; + visitNull(): T; + visitObject(access: MapAccess): T; + visitIterable(access: IterableAccess): T; +} diff --git a/dist/de/generic.js b/dist/de/generic.js new file mode 100644 index 0000000..f28db74 --- /dev/null +++ b/dist/de/generic.js @@ -0,0 +1,60 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var GenericSeed_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.GenericVisitor = exports.GenericSeed = void 0; +const utils_1 = require("../utils"); +let GenericSeed = GenericSeed_1 = class GenericSeed { + static deserialize(deserializer) { + return deserializer.deserializeAny(new GenericVisitor()); + } + deserialize(deserializer) { + return GenericSeed_1.deserialize(deserializer); + } +}; +exports.GenericSeed = GenericSeed; +exports.GenericSeed = GenericSeed = GenericSeed_1 = __decorate([ + (0, utils_1.staticImplements)() +], GenericSeed); +class GenericVisitor { + visitString(value) { + return value; + } + visitNumber(value) { + return value; + } + visitBigInt(value) { + return value; + } + visitBoolean(value) { + return value; + } + visitSymbol(value) { + return value; + } + visitNull() { + return null; + } + visitObject(access) { + const result = {}; + let entry; + while ((entry = access.nextEntry())) { + result[entry[0]] = entry[1]; + } + return result; + } + visitIterable(access) { + const result = new Array(access.sizeHint()); + let element; + while ((element = access.nextElement())) { + result.push(element); + } + return result; + } +} +exports.GenericVisitor = GenericVisitor; diff --git a/dist/de/impl.d.ts b/dist/de/impl.d.ts new file mode 100644 index 0000000..0a6bbac --- /dev/null +++ b/dist/de/impl.d.ts @@ -0,0 +1,7 @@ +import { SerdeOptions } from '../options'; +import { Deserialize, Deserializer } from './interface'; +type DeserializeConstructor = Deserialize & { + new (): Deserialize; +}; +export declare function deserializeWith>(deserializer: D, into: E, options: SerdeOptions): T; +export {}; diff --git a/dist/de/impl.js b/dist/de/impl.js new file mode 100644 index 0000000..0fbae3e --- /dev/null +++ b/dist/de/impl.js @@ -0,0 +1,21 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.deserializeWith = deserializeWith; +const options_1 = require("../options"); +const generic_1 = require("./generic"); +function deserializeWith(deserializer, into, options) { + const visitor = new generic_1.GenericVisitor(); + const obj = deserializer.deserializeObject(visitor); + const target = new into(); + const newObject = {}; + for (const property in target) { + const name = options.getPropertyName(property, options_1.Stage.Deserialize); + const value = obj[name] || options.getDefault(property); + newObject[property] = value; + delete obj[name]; + } + if (options.options.denyUnknownFields && Object.keys(obj).length > 0) { + throw new TypeError(`Unexpected fields: ${Object.keys(obj).join(', ')}`); + } + return Object.assign(target, newObject); +} diff --git a/dist/de/index.d.ts b/dist/de/index.d.ts new file mode 100644 index 0000000..2be11ba --- /dev/null +++ b/dist/de/index.d.ts @@ -0,0 +1,3 @@ +export * from './interface'; +export * from './generic'; +export * from './mixin'; diff --git a/dist/de/index.js b/dist/de/index.js new file mode 100644 index 0000000..d0ef348 --- /dev/null +++ b/dist/de/index.js @@ -0,0 +1,19 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +__exportStar(require("./interface"), exports); +__exportStar(require("./generic"), exports); +__exportStar(require("./mixin"), exports); diff --git a/dist/de/interface.d.ts b/dist/de/interface.d.ts new file mode 100644 index 0000000..bb0403d --- /dev/null +++ b/dist/de/interface.d.ts @@ -0,0 +1,52 @@ +import { Nullable } from '../utils'; +export interface MapAccess { + nextKeySeed>(seed: K): Nullable; + nextValueSeed>(seed: V): Nullable; + nextEntrySeed, V extends Deserialize>(kseed: K, vseed: V): Nullable<[TK, TV]>; + nextKey(): Nullable; + nextValue(): Nullable; + nextEntry(): Nullable<[K, V]>; +} +export declare abstract class DefaultMapAccessImpl implements MapAccess { + abstract nextKeySeed>(seed: K): Nullable; + abstract nextValueSeed>(seed: V): Nullable; + nextEntrySeed, V extends Deserialize>(kseed: K, vseed: V): Nullable<[TK, TV]>; + nextKey(): Nullable; + nextValue(): Nullable; + nextEntry(): Nullable<[K, V]>; +} +export interface IterableAccess { + nextElementSeed>(seed: I): Nullable; + nextElement(): Nullable; + sizeHint(): number; +} +export declare abstract class DefaultIterableAccessImpl implements IterableAccess { + abstract nextElementSeed>(seed: I): Nullable; + nextElement(): Nullable; + sizeHint(): number; +} +export interface Visitor { + visitString(value: string): T; + visitNumber(value: number): T; + visitBigInt(value: bigint): T; + visitBoolean(value: boolean): T; + visitSymbol(value: symbol): T; + visitNull(): T; + visitObject(value: MapAccess): T; + visitIterable?(value: IterableAccess): T; +} +export interface Deserializer { + deserializeAny>(visitor: V): T; + deserializeString>(visitor: V): T; + deserializeNumber>(visitor: V): T; + deserializeBigInt>(visitor: V): T; + deserializeBoolean>(visitor: V): T; + deserializeSymbol>(visitor: V): T; + deserializeNull>(visitor: V): T; + deserializeObject>(visitor: V): T; + deserializeClass>(name: string, fields: string[], visitor: V): T; + deserializeIterable>(visitor: V): T; +} +export interface Deserialize { + deserialize(deserializer: D): T; +} diff --git a/dist/de/interface.js b/dist/de/interface.js new file mode 100644 index 0000000..df7dee3 --- /dev/null +++ b/dist/de/interface.js @@ -0,0 +1,32 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.DefaultIterableAccessImpl = exports.DefaultMapAccessImpl = void 0; +const generic_1 = require("./generic"); +class DefaultMapAccessImpl { + nextEntrySeed(kseed, vseed) { + const key = this.nextKeySeed(kseed); + if (key !== undefined) { + const value = this.nextValueSeed(vseed); + if (value !== undefined) { + return [key, value]; + } + } + } + nextKey() { + return this.nextValueSeed((generic_1.GenericSeed)); + } + nextValue() { + return this.nextValueSeed((generic_1.GenericSeed)); + } + nextEntry() { + return this.nextEntrySeed((generic_1.GenericSeed), (generic_1.GenericSeed)); + } +} +exports.DefaultMapAccessImpl = DefaultMapAccessImpl; +class DefaultIterableAccessImpl { + nextElement() { + return this.nextElementSeed((generic_1.GenericSeed)); + } + sizeHint() { return 0; } +} +exports.DefaultIterableAccessImpl = DefaultIterableAccessImpl; diff --git a/dist/de/mixin.d.ts b/dist/de/mixin.d.ts new file mode 100644 index 0000000..4b3eaaa --- /dev/null +++ b/dist/de/mixin.d.ts @@ -0,0 +1,9 @@ +import { Constructor } from '../utils'; +import { Deserializer } from './interface'; +export declare function deserialize(constructor: C): { + new (...args: any[]): { + [x: string]: any; + }; + name: string; + deserialize(deserializer: D): T; +} & C; diff --git a/dist/de/mixin.js b/dist/de/mixin.js new file mode 100644 index 0000000..c6bda1f --- /dev/null +++ b/dist/de/mixin.js @@ -0,0 +1,31 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.deserialize = deserialize; +const decorator_1 = require("../decorator"); +const utils_1 = require("../utils"); +const impl_1 = require("./impl"); +function deserialize(constructor) { + let Deserializable = class Deserializable extends constructor { + static deserialize(deserializer) { + return (0, impl_1.deserializeWith)(deserializer, this, (0, decorator_1.getMetadata)(this)); + } + }; + Object.defineProperty(Deserializable, "name", { + enumerable: true, + configurable: true, + writable: true, + value: constructor.name + }); + Deserializable = __decorate([ + (0, utils_1.staticImplements)() + ], Deserializable); + // @ts-ignore + Deserializable[decorator_1.Serde] = constructor[decorator_1.Serde]; + return Deserializable; +} diff --git a/dist/decorator.d.ts b/dist/decorator.d.ts new file mode 100644 index 0000000..ee4e9d9 --- /dev/null +++ b/dist/decorator.d.ts @@ -0,0 +1,6 @@ +import { ContainerOptions, PropertyOptions } from './options'; +import { Registry } from './registry'; +export declare const Serde: unique symbol; +export declare function serde(options: ContainerOptions | PropertyOptions): (target: any, property?: PropertyKey) => any; +export declare function getMetadata(value: any): any; +export declare function register(registry?: Registry): (target: any) => any; diff --git a/dist/decorator.js b/dist/decorator.js new file mode 100644 index 0000000..2e7f278 --- /dev/null +++ b/dist/decorator.js @@ -0,0 +1,54 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Serde = void 0; +exports.serde = serde; +exports.getMetadata = getMetadata; +exports.register = register; +const options_1 = require("./options"); +const registry_1 = require("./registry"); +exports.Serde = Symbol('Serde'); +function decorateContainer(options, constructor) { + if (constructor[exports.Serde] == null) { + constructor[exports.Serde] = new options_1.SerdeOptions(constructor, options); + } + else { + constructor[exports.Serde].options = options; + } + return constructor; +} +function decorateProperty(options, target, property) { + let constructor; + if (typeof target === 'function') { + constructor = target; + } + else { + constructor = target.constructor; + } + if (constructor[exports.Serde] == null) { + constructor[exports.Serde] = options_1.SerdeOptions.from(target); + } + constructor[exports.Serde].properties.set(property, options); + return target; +} +function serde(options) { + return function (target, property) { + if (property != null) { + return decorateProperty(options, target, property); + } + else { + return decorateContainer(options, target); + } + }; +} +function getMetadata(value) { + return value[exports.Serde]; +} +function register(registry = registry_1.GlobalRegistry) { + return function (target) { + if (target[exports.Serde] == null) { + target[exports.Serde] = options_1.SerdeOptions.from(target); + } + registry.add(target); + return target; + }; +} diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 0000000..183a464 --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1,6 @@ +export * as ser from './ser/index'; +export * as de from './de/index'; +export * from './case'; +export * from './registry'; +export * from './decorator'; +export * from './options'; diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..d435624 --- /dev/null +++ b/dist/index.js @@ -0,0 +1,45 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.de = exports.ser = void 0; +exports.ser = __importStar(require("./ser/index")); +exports.de = __importStar(require("./de/index")); +__exportStar(require("./case"), exports); +__exportStar(require("./registry"), exports); +__exportStar(require("./decorator"), exports); +__exportStar(require("./options"), exports); diff --git a/dist/options.d.ts b/dist/options.d.ts new file mode 100644 index 0000000..ca91c62 --- /dev/null +++ b/dist/options.d.ts @@ -0,0 +1,63 @@ +import { CaseConvention } from './case'; +import { Deserializer } from './de'; +import { Serializer } from './ser'; +import { Registry } from './registry'; +export interface RenameOptions { + serialize?: string; + deserialize?: string; +} +export interface RenameAllOptions { + serialize?: CaseConvention; + deserialize?: CaseConvention; +} +export interface ContainerOptions { + default?: () => any; + denyUnknownFields?: boolean; + expecting?: string; + rename?: RenameOptions | string; + renameAll?: RenameAllOptions | CaseConvention; + registry?: Registry; + tag?: string; + content?: string; + untagged?: boolean; +} +export interface ConditionalSkipOptions { + if: (value: any) => boolean; +} +export interface SkipOptions { + serializing?: ConditionalSkipOptions | boolean; + deserializing?: ConditionalSkipOptions | boolean; +} +export type CustomSerializer = >(value: V, serializer: S) => T; +export type CustomDeserializer = (deserializer: D) => T; +export interface PropertyOptions { + alias?: string; + default?: () => any; + flatten?: boolean; + rename?: RenameOptions | string; + skip?: SkipOptions | ConditionalSkipOptions | boolean; +} +export declare const Stage: Readonly<{ + readonly Serialize: 0; + readonly Deserialize: 1; +}>; +export type Stage = typeof Stage[keyof typeof Stage]; +export declare class SerdeOptions { + private readonly target; + readonly options: ContainerOptions; + readonly properties: Map; + get registry(): Registry; + constructor(target: any, options?: ContainerOptions, properties?: Map); + static from(target: any): SerdeOptions; + getClassName(stage: Stage): any; + private getPropertyRename; + private getPropertyCase; + getPropertyName(property: string, stage: Stage): string; + getSerializationName(property: string): string; + getDeserializationName(property: string): string; + getDefault(property: string): any; + private isConditionalSkip; + shouldSkip(property: string, value: any, stage: Stage): boolean; + shouldSkipSerializing(property: string, value: any): boolean; + shouldSkipDeserializing(property: string, value: any): boolean; +} diff --git a/dist/options.js b/dist/options.js new file mode 100644 index 0000000..a396ef4 --- /dev/null +++ b/dist/options.js @@ -0,0 +1,156 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SerdeOptions = exports.Stage = void 0; +const case_1 = require("./case"); +const utils_1 = require("./utils"); +const registry_1 = require("./registry"); +exports.Stage = Object.freeze({ + Serialize: 0, + Deserialize: 1 +}); +class SerdeOptions { + get registry() { + return this.options.registry || registry_1.GlobalRegistry; + } + constructor(target, options = {}, properties = new Map()) { + Object.defineProperty(this, "target", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "options", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "properties", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + this.target = target; + this.options = options; + this.properties = properties; + } + static from(target) { + return new this(target); + } + getClassName(stage) { + var _a, _b; + if ((0, utils_1.isString)(this.options.rename)) { + return this.options.rename; + } + else if (stage === exports.Stage.Serialize && (0, utils_1.isString)((_a = this.options.rename) === null || _a === void 0 ? void 0 : _a.serialize)) { + return this.options.rename.serialize; + } + else if (stage === exports.Stage.Deserialize && (0, utils_1.isString)((_b = this.options.rename) === null || _b === void 0 ? void 0 : _b.deserialize)) { + return this.options.rename.deserialize; + } + else { + return this.target.constructor.name; + } + } + getPropertyRename(property, stage, options) { + var _a, _b; + if (options != null) { + if ((0, utils_1.isString)(options.rename)) { + return options.rename; + } + else if (stage === exports.Stage.Serialize && (0, utils_1.isString)((_a = options.rename) === null || _a === void 0 ? void 0 : _a.serialize)) { + return options.rename.serialize; + } + else if (stage === exports.Stage.Deserialize && (0, utils_1.isString)((_b = options.rename) === null || _b === void 0 ? void 0 : _b.deserialize)) { + return options.rename.deserialize; + } + } + return property; + } + getPropertyCase(name, stage) { + var _a, _b; + if ((0, utils_1.isNumber)(this.options.renameAll)) { + return (0, case_1.convertCase)(name, this.options.renameAll); + } + else if (stage === exports.Stage.Serialize && (0, utils_1.isNumber)((_a = this.options.renameAll) === null || _a === void 0 ? void 0 : _a.serialize)) { + return (0, case_1.convertCase)(name, this.options.renameAll.serialize); + } + else if (stage === exports.Stage.Deserialize && (0, utils_1.isNumber)((_b = this.options.renameAll) === null || _b === void 0 ? void 0 : _b.deserialize)) { + return (0, case_1.convertCase)(name, this.options.renameAll.deserialize); + } + else { + return name; + } + } + getPropertyName(property, stage) { + const options = this.properties.get(property); + const name = options != null ? this.getPropertyRename(property, stage, options) : property; + return this.getPropertyCase(name, stage); + } + getSerializationName(property) { + return this.getPropertyName(property, exports.Stage.Serialize); + } + getDeserializationName(property) { + return this.getPropertyName(property, exports.Stage.Deserialize); + } + getDefault(property) { + const options = this.properties.get(property); + if (options && (0, utils_1.isFunction)(options.default)) { + return options.default(); + } + else if ((0, utils_1.isFunction)(this.options.default)) { + return this.options.default(); + } + } + //getCustomImpl(property: string, stage: Stage) { + // const options = this.properties.get(property) + // if (options != null) { + // if (stage === Stage.Serialize && isFunction(options.serializeWith)) { + // return options.serializeWith + // } else if (stage === Stage.Deserialize && isFunction(options.deserializeWith)) { + // return options.deserializeWith + // } + // } + //} + //getSerializer(property: string): Nullable { + // return this.getCustomImpl(property, Stage.Serialize) as CustomSerializer + //} + //getDeserializer(property: string): Nullable { + // return this.getCustomImpl(property, Stage.Deserialize) as CustomDeserializer + //} + isConditionalSkip(skip) { + return 'if' in skip && (0, utils_1.isFunction)(skip.if); + } + shouldSkip(property, value, stage) { + const options = this.properties.get(property); + if (options != null && options.skip != null) { + if (typeof options.skip === 'boolean') { + return options.skip; + } + else if (this.isConditionalSkip(options.skip)) { + return options.skip.if(value); + } + else if (stage === exports.Stage.Serialize && typeof options.skip.serializing === 'boolean') { + return options.skip.serializing; + } + else if (stage === exports.Stage.Serialize && this.isConditionalSkip(options.skip.serializing)) { + return options.skip.serializing.if(value); + } + else if (stage === exports.Stage.Deserialize && typeof options.skip.deserializing === 'boolean') { + return options.skip.deserializing; + } + else if (stage === exports.Stage.Deserialize && this.isConditionalSkip(options.skip.deserializing)) { + return options.skip.deserializing.if(value); + } + } + return false; + } + shouldSkipSerializing(property, value) { + return this.shouldSkip(property, value, exports.Stage.Serialize); + } + shouldSkipDeserializing(property, value) { + return this.shouldSkip(property, value, exports.Stage.Deserialize); + } +} +exports.SerdeOptions = SerdeOptions; diff --git a/dist/registry.d.ts b/dist/registry.d.ts new file mode 100644 index 0000000..4075841 --- /dev/null +++ b/dist/registry.d.ts @@ -0,0 +1,7 @@ +import { Constructor } from './utils'; +export declare class Registry { + registeredClasses: Map; + add(ctor: Constructor, key?: string): void; + get(name: string): Constructor | undefined; +} +export declare const GlobalRegistry: Registry; diff --git a/dist/registry.js b/dist/registry.js new file mode 100644 index 0000000..aeca347 --- /dev/null +++ b/dist/registry.js @@ -0,0 +1,21 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.GlobalRegistry = exports.Registry = void 0; +class Registry { + constructor() { + Object.defineProperty(this, "registeredClasses", { + enumerable: true, + configurable: true, + writable: true, + value: new Map() + }); + } + add(ctor, key = ctor.name) { + this.registeredClasses.set(key, ctor); + } + get(name) { + return this.registeredClasses.get(name); + } +} +exports.Registry = Registry; +exports.GlobalRegistry = new Registry(); diff --git a/dist/ser/impl.d.ts b/dist/ser/impl.d.ts new file mode 100644 index 0000000..2ad7cda --- /dev/null +++ b/dist/ser/impl.d.ts @@ -0,0 +1,4 @@ +import { Serializable, Serializer } from './interface'; +import { SerdeOptions } from '../options'; +import { Nullable } from '../utils'; +export declare function serializeWith(serializer: Serializer, value: Serializable, optionsGetter?: (value: any) => Nullable): Nullable; diff --git a/dist/ser/impl.js b/dist/ser/impl.js new file mode 100644 index 0000000..32248dc --- /dev/null +++ b/dist/ser/impl.js @@ -0,0 +1,64 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.serializeWith = serializeWith; +const decorator_1 = require("../decorator"); +const options_1 = require("../options"); +const utils_1 = require("../utils"); +const unhandledType = (serializer, value) => new TypeError(`'${serializer.constructor.name}' has no method for value type '${typeof value}'`); +function serializeEntries(serializer, value, options) { + var _a; + let state; + for (const [key, val] of value) { + if (options === null || options === void 0 ? void 0 : options.shouldSkipSerializing(key, val)) { + continue; + } + const name = (_a = options === null || options === void 0 ? void 0 : options.getPropertyName(key, options_1.Stage.Serialize)) !== null && _a !== void 0 ? _a : key; + console.log('prop name for', key, name); + state = serializer.serializeKey(name); + state = serializer.serializeValue(val); + } + return serializer.end(); +} +function serializeClass(serializer, value, options) { + const classSerializer = serializer.serializeClass(value.constructor.name); + return serializeEntries(classSerializer, Object.entries(value), options); +} +function serializeObject(serializer, value, options) { + return serializeEntries(serializer.serializeObject(), Object.entries(value), options); +} +function serializeIter(serializer, value, options) { + let state; + for (const val of value) { + state = serializer.serializeElement(val); + } + return serializer.end(); +} +function defaultOptions(value) { + return value.constructor[decorator_1.Serde]; +} +// dispatches in the order of serializeType -> serializeAny -> throw TypeError +function serializeWith(serializer, value, optionsGetter = defaultOptions) { + // prepare fallback methods + const serializeAny = (0, utils_1.orElse)(serializer, serializer.serializeAny, (value) => unhandledType(serializer, value)); + const serialize = (0, utils_1.ifNull)(serializer, serializeAny, value); + switch (typeof value) { + case 'string': return serialize(serializer.serializeString); + case 'number': return serialize(serializer.serializeNumber); + case 'bigint': return serialize(serializer.serializeBigInt); + case 'boolean': return serialize(serializer.serializeBoolean); + case 'symbol': return serialize(serializer.serializeSymbol); + case 'undefined': return serialize(serializer.serializeNull); + case 'object': + const options = optionsGetter(value); + if ((0, utils_1.isIterable)(value) && (0, utils_1.isFunction)(serializer.serializeIterable)) { + return serializeIter(serializer.serializeIterable(), value, options); + } + if (!(0, utils_1.isPlainObject)(value)) { + return serializeClass(serializer, value, options); + } + else if ((0, utils_1.isFunction)(serializer.serializeObject)) { + return serializeObject(serializer, value, options); + } // deliberate fallthrough when the above fail + default: return serializeAny(value); + } +} diff --git a/dist/ser/index.d.ts b/dist/ser/index.d.ts new file mode 100644 index 0000000..aad71f9 --- /dev/null +++ b/dist/ser/index.d.ts @@ -0,0 +1,3 @@ +export * from './interface'; +export * from './mixin'; +export * from './impl'; diff --git a/dist/ser/index.js b/dist/ser/index.js new file mode 100644 index 0000000..a4219d9 --- /dev/null +++ b/dist/ser/index.js @@ -0,0 +1,19 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +__exportStar(require("./interface"), exports); +__exportStar(require("./mixin"), exports); +__exportStar(require("./impl"), exports); diff --git a/dist/ser/interface.d.ts b/dist/ser/interface.d.ts new file mode 100644 index 0000000..af8741b --- /dev/null +++ b/dist/ser/interface.d.ts @@ -0,0 +1,31 @@ +import { Primitive, ToString } from "../utils"; +export interface ObjectSerializer { + serializeKey(key: U): T; + serializeValue(value: U): T; + end(): T; +} +export interface IterableSerializer { + serializeElement(element: U): T; + end(): T; +} +interface TypeSerializer { + serializeString(value: string): T; + serializeNumber(value: number): T; + serializeBigInt(value: bigint): T; + serializeBoolean(value: boolean): T; + serializeSymbol(value: Symbol): T; + serializeNull(): T; + serializeObject(): ObjectSerializer; + serializeIterable?(): IterableSerializer; + serializeClass(name: PropertyKey): ObjectSerializer; +} +interface AnySerializer { + serializeAny?(value?: any): T; +} +export declare function isGenericSerializer(value: any): boolean; +export type Serializer = Partial> & Partial>; +export type Serializable = Primitive | ToString | Serialize; +export interface Serialize { + serialize>(serializer: S): T; +} +export {}; diff --git a/dist/ser/interface.js b/dist/ser/interface.js new file mode 100644 index 0000000..d0a2e57 --- /dev/null +++ b/dist/ser/interface.js @@ -0,0 +1,21 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.isGenericSerializer = isGenericSerializer; +const utils_1 = require("../utils"); +const TypeSerializerMethods = [ + 'serializeString', + 'serializeNumber', + 'serializeBigInt', + 'serializeBoolean', + 'serializeSymbol', + //'serializeMap', + 'serializeIterable', + 'serializeNull', + 'serializeObject', + 'serializeClass', +]; +const AnySerializerMethods = ['serializeAny']; +function isGenericSerializer(value) { + return AnySerializerMethods.every(k => (0, utils_1.isFunction)(value[k])) && + TypeSerializerMethods.every(k => !(0, utils_1.isFunction)(value[k])); +} diff --git a/dist/ser/mixin.d.ts b/dist/ser/mixin.d.ts new file mode 100644 index 0000000..55d14bb --- /dev/null +++ b/dist/ser/mixin.d.ts @@ -0,0 +1,9 @@ +import { Constructor } from '../utils'; +import { Serializer } from './interface'; +export declare function serialize(constructor: T): { + new (...args: any[]): { + [x: string]: any; + serialize(serializer: Serializer): U; + }; + name: string; +} & T; diff --git a/dist/ser/mixin.js b/dist/ser/mixin.js new file mode 100644 index 0000000..d36bb80 --- /dev/null +++ b/dist/ser/mixin.js @@ -0,0 +1,27 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.serialize = serialize; +const decorator_1 = require("../decorator"); +const impl_1 = require("./impl"); +const interface_1 = require("./interface"); +function serialize(constructor) { + class Serializable extends constructor { + serialize(serializer) { + // shortcut for serializers with only the serializeAny method + if ((0, interface_1.isGenericSerializer)(serializer)) { + return serializer.serializeAny(this); + } + else { + return (0, impl_1.serializeWith)(serializer, this); + } + } + } + Object.defineProperty(Serializable, "name", { + enumerable: true, + configurable: true, + writable: true, + value: constructor.name + }); + Serializable[decorator_1.Serde] = constructor[decorator_1.Serde]; + return Serializable; +} diff --git a/dist/utils.d.ts b/dist/utils.d.ts new file mode 100644 index 0000000..7c8688a --- /dev/null +++ b/dist/utils.d.ts @@ -0,0 +1,28 @@ +export type Nullable = T | undefined; +export interface Morphism { + (value: T): U; +} +export type Primitive = string | number | boolean | symbol | bigint | null | undefined; +export interface ToString { + toString(): string; +} +export declare function staticImplements(): (constructor: U) => void; +export declare function isPlainObject(value: any): boolean; +export declare function isFunction(value: any): value is Function; +export declare function isIterable(value: any): value is Iterable; +export declare function isString(value: any): value is string; +export declare function isNumber(value: any): value is number; +export type Constructor = new (...args: any[]) => T; +export declare function orElse(thisArg: any, a: Nullable, b: Function): (...args: any) => any; +export declare function ifNull(thisArg: any, b: Function, ...args: any): (a: Nullable) => any; +export declare function mixin(impl: Function): >(constructor: TBase) => TBase; +type AnyFunc = (...arg: any) => any; +type PipeArgs = F extends [ + (...args: infer A) => infer B +] ? [...Acc, (...args: A) => B] : F extends [(...args: infer A) => any, ...infer Tail] ? Tail extends [(arg: infer B) => any, ...any[]] ? PipeArgs B]> : Acc : Acc; +type LastFnReturnType, Else = never> = F extends [ + ...any[], + (...arg: any) => infer R +] ? R : Else; +export declare function pipe(arg: Parameters[0], firstFn: FirstFn, ...fns: PipeArgs extends F ? F : PipeArgs): LastFnReturnType>; +export {}; diff --git a/dist/utils.js b/dist/utils.js new file mode 100644 index 0000000..3403781 --- /dev/null +++ b/dist/utils.js @@ -0,0 +1,58 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.staticImplements = staticImplements; +exports.isPlainObject = isPlainObject; +exports.isFunction = isFunction; +exports.isIterable = isIterable; +exports.isString = isString; +exports.isNumber = isNumber; +exports.orElse = orElse; +exports.ifNull = ifNull; +exports.mixin = mixin; +exports.pipe = pipe; +function staticImplements() { + return (constructor) => { constructor; }; +} +function isPlainObject(value) { + return Object.getPrototypeOf(value) === Object.prototype; +} +function isFunction(value) { + return value != null && typeof value === 'function'; +} +function isIterable(value) { + return isFunction(value[Symbol.iterator]); +} +function isString(value) { + return typeof value === 'string'; +} +function isNumber(value) { + return !isNaN(value); +} +function orElse(thisArg, a, b) { + return function (...args) { + const fn = a != null ? a : b; + return fn.apply(thisArg, args); + }; +} +function ifNull(thisArg, b, ...args) { + return function (a) { + return orElse(thisArg, a, b).apply(thisArg, args); + }; +} +function applyMixins(derivedCtor, constructors) { + constructors.forEach((baseCtor) => { + Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => { + Object.defineProperty(derivedCtor.prototype, name, Object.getOwnPropertyDescriptor(baseCtor.prototype, name) || + Object.create(null)); + }); + }); +} +function mixin(impl) { + return function (constructor) { + applyMixins(constructor, [impl]); + return constructor; + }; +} +function pipe(arg, firstFn, ...fns) { + return fns.reduce((acc, fn) => fn(acc), firstFn(arg)); +} diff --git a/package.json b/package.json index d4196c6..dfcc2bc 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,20 @@ "version": "1.0.0", "description": "", "main": "src/index.ts", + "exports": { + ".": { + "types": "dist/index.d.ts", + "default": "src/index.ts" + }, + "./ser": { + "types": "dist/ser/index.d.ts", + "default": "dist/ser/index.js" + }, + "./de": { + "types": "dist/de/index.d.ts", + "default": "dist/de/index.js" + } + }, "scripts": { "build": "tsc", "test": "echo \"Error: no test specified\" && exit 1" diff --git a/tsconfig.json b/tsconfig.json index c073d43..1ad5817 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -52,14 +52,14 @@ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ /* Emit */ - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ // "declarationMap": true, /* Create sourcemaps for d.ts files. */ // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ // "noEmit": true, /* Disable emitting files from a compilation. */ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - // "outDir": "./", /* Specify an output folder for all emitted files. */ + "outDir": "./dist/", /* Specify an output folder for all emitted files. */ // "removeComments": true, /* Disable emitting comments. */ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */