mirror of
https://github.com/appy-one/acebase-core.git
synced 2026-06-30 06:02:04 -06:00
Adding new serialization method for client/server
This commit is contained in:
parent
c2338d9a38
commit
e89fef1a0f
2 changed files with 204 additions and 4 deletions
196
src/transport.ts
196
src/transport.ts
|
|
@ -8,6 +8,47 @@ export type SerializedDataType = 'date'|'binary'|'reference'|'regexp'|'array';
|
|||
export type SerializedDataMap = { [path: string]: SerializedDataType };
|
||||
export type SerializedValue = { map?: SerializedDataType | SerializedDataMap, val: any };
|
||||
|
||||
export type V2SerializedPrimitive = string|number|boolean;
|
||||
export type V2SerializedDate = { '.type': 'date'; '.val': string }
|
||||
export type V2SerializedBinary = { '.type': 'binary'; '.val': string };
|
||||
export type V2SerializedReference = { '.type': 'reference'; '.val': string };
|
||||
export type V2SerializedRegExp = { '.type': 'regexp'; '.val': { pattern: string; flags: string } };
|
||||
export type V2SerializedPartialArray = { '.type': 'array'; [index: string]: any };
|
||||
export type V2SerializedObject = { [key: string]: V2SerializedValue };
|
||||
export type V2SerializedArray = V2SerializedValue[];
|
||||
export type V2SerializedValue = V2SerializedPrimitive|V2SerializedDate|V2SerializedBinary|V2SerializedReference|V2SerializedRegExp|V2SerializedPartialArray|V2SerializedObject|V2SerializedArray;
|
||||
|
||||
/**
|
||||
* There are now 2 different serialization methods for transporting values.
|
||||
*
|
||||
* v1:
|
||||
* The original version (v1) created an object with "map" and "val" properties.
|
||||
* The "map" property was made optional in v1.14.1 so they won't be present for values needing no serializing
|
||||
*
|
||||
* v2:
|
||||
* The new version replaces serialized values inline by objects containing ".type" and ".val" properties.
|
||||
* This serializing method was introduced by `export` and `import` methods because they use streaming and
|
||||
* are unable to prepare type mappings up-front. This format is smaller in transmission (in many cases),
|
||||
* and easier to read and process.
|
||||
*
|
||||
* original: { "date": (some date) }
|
||||
* v1 serialized: { "map": { "date": "date" }, "val": { date: "2022-04-22T07:49:23Z" } }
|
||||
* v2 serialized: { "date": { ".type": "date", ".val": "2022-04-22T07:49:23Z" } }
|
||||
*
|
||||
* original: (some date)
|
||||
* v1 serialized: { "map": "date", "val": "2022-04-22T07:49:23Z" }
|
||||
* v2 serialized: { ".type": "date", ".val": "2022-04-22T07:49:23Z" }
|
||||
* comment: top level value that need serializing is wrapped in an object with ".type" and ".val". v1 is smaller in this case
|
||||
*
|
||||
* original: 'some string'
|
||||
* v1 serialized: { "map": {}, "val": "some string" }
|
||||
* v2 serialized: "some string"
|
||||
* comment: primitive types such as strings don't need serializing and are returned as is in v2
|
||||
*
|
||||
* original: { "date": (some date), "text": "Some string" }
|
||||
* v1 serialized: { "map": { "date": "date" }, "val": { date: "2022-04-22T07:49:23Z", "text": "Some string" } }
|
||||
* v2 serialized: { "date": { ".type": "date", ".val": "2022-04-22T07:49:23Z" }, "text": "Some string" }
|
||||
*/
|
||||
export const Transport = {
|
||||
deserialize(data: SerializedValue) {
|
||||
if (data.map === null || typeof data.map === 'undefined') {
|
||||
|
|
@ -55,6 +96,22 @@ export const Transport = {
|
|||
return data.val;
|
||||
},
|
||||
|
||||
detectSerializeVersion(data: any) {
|
||||
if (typeof data !== 'object' || data === null) {
|
||||
// This can only be v2, which allows primitive types to bypass serializing
|
||||
return 2;
|
||||
}
|
||||
if ('map' in data && 'val' in data) {
|
||||
return 1;
|
||||
}
|
||||
else if ('val' in data) {
|
||||
// If it's v1, 'val' will be the only key in the object because serialize2 adds ".version": 2 to the object to prevent confusion.
|
||||
if (Object.keys(data).length > 1) { return 2; }
|
||||
return 1;
|
||||
}
|
||||
return 2;
|
||||
},
|
||||
|
||||
serialize(obj: any): SerializedValue {
|
||||
// Recursively find dates and binary data
|
||||
if (obj === null || typeof obj !== 'object' || obj instanceof Date || obj instanceof ArrayBuffer || obj instanceof PathReference) {
|
||||
|
|
@ -99,9 +156,140 @@ export const Transport = {
|
|||
};
|
||||
const mappings: SerializedDataMap = {};
|
||||
process(obj, mappings, '');
|
||||
return {
|
||||
map: Object.keys(mappings).length > 0 ? mappings : undefined,
|
||||
val: obj
|
||||
};
|
||||
|
||||
const serialized: SerializedValue = { val: obj };
|
||||
if (Object.keys(mappings).length > 0) {
|
||||
serialized.map = mappings;
|
||||
}
|
||||
return serialized;
|
||||
},
|
||||
|
||||
serialize2(obj: any): any {
|
||||
// Recursively find data that needs serializing
|
||||
const getSerializedValue = (val: any): V2SerializedValue => {
|
||||
if (val instanceof Date) {
|
||||
// serialize date to UTC string
|
||||
return <V2SerializedDate> {
|
||||
'.type': 'date',
|
||||
'.val': val.toISOString()
|
||||
};
|
||||
}
|
||||
else if (val instanceof ArrayBuffer) {
|
||||
// Serialize binary data with ascii85
|
||||
return <V2SerializedBinary> {
|
||||
'.type': 'binary',
|
||||
'.val': ascii85.encode(val)
|
||||
};
|
||||
}
|
||||
else if (val instanceof PathReference) {
|
||||
return <V2SerializedReference> {
|
||||
'.type': 'reference',
|
||||
'.val': val.path
|
||||
};
|
||||
}
|
||||
else if (val instanceof RegExp) {
|
||||
// Queries using the 'matches' filter with a regular expression can now also be used on remote db's
|
||||
return <V2SerializedRegExp> {
|
||||
'.type': 'regexp',
|
||||
'.val': {
|
||||
pattern: val.source,
|
||||
flags: val.flags
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (typeof val === 'object' && val !== null) {
|
||||
if (val instanceof Array) {
|
||||
const copy: V2SerializedArray = [];
|
||||
for (let i = 0; i < val.length; i++) {
|
||||
copy[i] = getSerializedValue(val[i]);
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
else {
|
||||
const copy: V2SerializedObject = {}; //val instanceof Array ? [] : {} as SerializedValueV2;
|
||||
if (val instanceof PartialArray) {
|
||||
// Mark the object as partial ("sparse") array
|
||||
(copy as V2SerializedPartialArray)['.type'] = 'array';
|
||||
}
|
||||
for (const prop in val) {
|
||||
copy[prop] = getSerializedValue(val[prop]);
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Primitive value. Don't serialize
|
||||
return <V2SerializedPrimitive>val;
|
||||
}
|
||||
}
|
||||
|
||||
const serialized = getSerializedValue(obj);
|
||||
if (typeof serialized === 'object' && 'val' in serialized && Object.keys(serialized).length === 1) {
|
||||
// acebase-core v1.14.1 made the 'map' property optional.
|
||||
// This v2 serialized object might be confused with a v1 without mappings, because it only has a "val" property
|
||||
// To prevent this, mark the serialized object with version 2
|
||||
serialized['.version'] = 2;
|
||||
}
|
||||
return serialized;
|
||||
},
|
||||
|
||||
deserialize2(data: V2SerializedValue): any {
|
||||
if (typeof data !== 'object' || data === null) {
|
||||
// primitive value, not serialized
|
||||
return data;
|
||||
}
|
||||
switch (data['.type']) {
|
||||
case undefined: {
|
||||
// No type given: this is a plain object or array
|
||||
|
||||
if (data instanceof Array) {
|
||||
// Plain array, deserialize items into a copy
|
||||
const copy = [];
|
||||
const arr = data as V2SerializedArray;
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
copy.push(Transport.deserialize2(arr[i]));
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
else {
|
||||
// Plain object, deserialize properties into a copy
|
||||
const copy = {};
|
||||
const obj = data as V2SerializedObject;
|
||||
for (const prop in obj) {
|
||||
copy[prop] = Transport.deserialize2(obj[prop]);
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
case 'array': {
|
||||
// partial ("sparse") array, deserialize children into a copy
|
||||
const copy = {};
|
||||
for (const index in data as V2SerializedPartialArray) {
|
||||
copy[index] = Transport.deserialize2(data[index]);
|
||||
}
|
||||
delete copy['.type'];
|
||||
return new PartialArray(copy);
|
||||
}
|
||||
case 'date': {
|
||||
// Date was serialized as a string (UTC)
|
||||
const val = (data as V2SerializedDate)['.val'];
|
||||
return new Date(val);
|
||||
}
|
||||
case 'binary': {
|
||||
// ascii85 encoded binary data
|
||||
const val = (data as V2SerializedBinary)['.val'];
|
||||
return ascii85.decode(val);
|
||||
}
|
||||
case 'reference': {
|
||||
const val = (data as V2SerializedReference)['.val'];
|
||||
return new PathReference(val);
|
||||
}
|
||||
case 'regexp': {
|
||||
const val = (data as V2SerializedRegExp)['.val'];
|
||||
return new RegExp(val.pattern, val.flags);
|
||||
}
|
||||
}
|
||||
throw new Error(`Unknown data type "${data['.type']}" in serialized value`);
|
||||
}
|
||||
|
||||
};
|
||||
12
types/transport.d.ts
vendored
12
types/transport.d.ts
vendored
|
|
@ -1,7 +1,19 @@
|
|||
export type SerializedDataType = 'date'|'binary'|'reference'|'regexp'|'array';
|
||||
export type SerializedDataMap = { [path: string]: SerializedDataType };
|
||||
export type SerializedValue = { map?: SerializedDataType | SerializedDataMap, val: any };
|
||||
|
||||
export type V2SerializedPrimitive = string|number|boolean;
|
||||
export type V2SerializedDate = { '.type': 'date'; '.val': string }
|
||||
export type V2SerializedBinary = { '.type': 'binary'; '.val': string };
|
||||
export type V2SerializedReference = { '.type': 'reference'; '.val': string };
|
||||
export type V2SerializedRegExp = { '.type': 'regexp'; '.val': { pattern: string; flags: string } };
|
||||
export type V2SerializedPartialArray = { '.type': 'array'; [index: string]: any };
|
||||
export type V2SerializedObject = { [key: string]: V2SerializedValue };
|
||||
export type V2SerializedArray = V2SerializedValue[];
|
||||
export type V2SerializedValue = V2SerializedPrimitive|V2SerializedDate|V2SerializedBinary|V2SerializedReference|V2SerializedRegExp|V2SerializedPartialArray|V2SerializedObject|V2SerializedArray;
|
||||
export abstract class Transport {
|
||||
static serialize(obj: any): any
|
||||
static deserialize(obj: any): any
|
||||
static serialize2(obj: any): any
|
||||
static deserialize2(obj: any): any
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue