Comparing version 0.1.1 to 1.0.0
@@ -94,3 +94,3 @@ import { JSONSchema7 } from 'json-schema'; | ||
*/ | ||
serialize<Type = any>(schemaType: string, data: unknown, context?: ContextObject, options?: ScrubbrOptions): Promise<Type>; | ||
serialize<Type = any>(schemaType: string, data: unknown, context?: ContextObject, options?: ScrubbrOptions): Type; | ||
/** | ||
@@ -97,0 +97,0 @@ * Traverse into a node of data on an object to serialize. |
@@ -32,38 +32,2 @@ "use strict"; | ||
}; | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
var __generator = (this && this.__generator) || function (thisArg, body) { | ||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; | ||
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; | ||
function verb(n) { return function (v) { return step([n, v]); }; } | ||
function step(op) { | ||
if (f) throw new TypeError("Generator is already executing."); | ||
while (_) try { | ||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; | ||
if (y = 0, t) op = [op[0] & 2, t.value]; | ||
switch (op[0]) { | ||
case 0: case 1: t = op; break; | ||
case 4: _.label++; return { value: op[1], done: false }; | ||
case 5: _.label++; y = op[1]; op = [0]; continue; | ||
case 7: op = _.ops.pop(); _.trys.pop(); continue; | ||
default: | ||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } | ||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } | ||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } | ||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } | ||
if (t[2]) _.ops.pop(); | ||
_.trys.pop(); continue; | ||
} | ||
op = body.call(thisArg, _); | ||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } | ||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; | ||
} | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -244,29 +208,19 @@ var fs = __importStar(require("fs")); | ||
if (options === void 0) { options = {}; } | ||
return __awaiter(this, void 0, void 0, function () { | ||
var schema, state, clonedData, serialized; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
schema = this.getSchemaFor(schemaType); | ||
if (!schema) { | ||
throw this.error("Could not find the type: " + schemaType); | ||
} | ||
this.logger.info("Serializing data with TS type: '" + schemaType + "'"); | ||
// Merge contexts | ||
context = __assign(__assign({}, this.globalContext), context); | ||
this.logger.debug("Using context: '" + JSON.stringify(context, null, ' ') + "'"); | ||
// Create state | ||
options = __assign(__assign({}, this.options), options); | ||
state = new ScrubbrState_1.ScrubbrState(data, schema, options, context); | ||
// Serialize | ||
state.rootSchemaType = schemaType; | ||
state.schemaType = schemaType; | ||
clonedData = JSON.parse(JSON.stringify(data)); | ||
return [4 /*yield*/, this.walkData(clonedData, state)]; | ||
case 1: | ||
serialized = (_a.sent()); | ||
return [2 /*return*/, serialized]; | ||
} | ||
}); | ||
}); | ||
var schema = this.getSchemaFor(schemaType); | ||
if (!schema) { | ||
throw this.error("Could not find the type: " + schemaType); | ||
} | ||
this.logger.info("Serializing data with TS type: '" + schemaType + "'"); | ||
// Merge contexts | ||
context = __assign(__assign({}, this.globalContext), context); | ||
this.logger.debug("Using context: '" + JSON.stringify(context, null, ' ') + "'"); | ||
// Create state | ||
options = __assign(__assign({}, this.options), options); | ||
var state = new ScrubbrState_1.ScrubbrState(data, schema, options, context); | ||
// Serialize | ||
state.rootSchemaType = schemaType; | ||
state.schemaType = schemaType; | ||
var clonedData = JSON.parse(JSON.stringify(data)); | ||
var serialized = this.walkData(clonedData, state); | ||
return serialized; | ||
}; | ||
@@ -279,23 +233,13 @@ /** | ||
Scrubbr.prototype.walkData = function (node, state) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var serializedNode; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: return [4 /*yield*/, this.serializeNode(node, state)]; | ||
case 1: | ||
serializedNode = _a.sent(); | ||
if (!(serializedNode === null)) return [3 /*break*/, 2]; | ||
return [2 /*return*/, serializedNode]; | ||
case 2: | ||
if (!Array.isArray(serializedNode)) return [3 /*break*/, 4]; | ||
return [4 /*yield*/, this.walkArrayNode(serializedNode, state)]; | ||
case 3: return [2 /*return*/, _a.sent()]; | ||
case 4: | ||
if (!(typeof serializedNode === 'object')) return [3 /*break*/, 6]; | ||
return [4 /*yield*/, this.walkObjectNode(serializedNode, state)]; | ||
case 5: return [2 /*return*/, _a.sent()]; | ||
case 6: return [2 /*return*/, serializedNode]; | ||
} | ||
}); | ||
}); | ||
var serializedNode = this.serializeNode(node, state); | ||
if (serializedNode === null) { | ||
return serializedNode; | ||
} | ||
else if (Array.isArray(serializedNode)) { | ||
return this.walkArrayNode(serializedNode, state); | ||
} | ||
else if (typeof serializedNode === 'object') { | ||
return this.walkObjectNode(serializedNode, state); | ||
} | ||
return serializedNode; | ||
}; | ||
@@ -308,38 +252,20 @@ /** | ||
Scrubbr.prototype.walkObjectNode = function (node, state) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var nodeProps, schemaProps, filteredNode, pathPrefix, i, _a, name_1, value, propSchema, propPath, propState, _b, _c; | ||
return __generator(this, function (_d) { | ||
switch (_d.label) { | ||
case 0: | ||
nodeProps = Object.entries(node); | ||
schemaProps = state.schemaDef.properties || {}; | ||
filteredNode = {}; | ||
pathPrefix = state.path ? state.path + "." : ''; | ||
i = 0; | ||
_d.label = 1; | ||
case 1: | ||
if (!(i < nodeProps.length)) return [3 /*break*/, 4]; | ||
_a = nodeProps[i], name_1 = _a[0], value = _a[1]; | ||
propSchema = schemaProps[name_1]; | ||
propPath = "" + pathPrefix + name_1; | ||
state.logger.debug("[PATH] " + propPath); | ||
propState = state.createNodeState(value, name_1, propPath, propSchema); | ||
// Property not defined in the schema, do not serialize property | ||
if (!propSchema) { | ||
state.logger.debug("Property '" + name_1 + "' not defined in " + state.schemaType + "."); | ||
return [3 /*break*/, 3]; | ||
} | ||
_b = filteredNode; | ||
_c = name_1; | ||
return [4 /*yield*/, this.walkData(value, propState)]; | ||
case 2: | ||
_b[_c] = _d.sent(); | ||
_d.label = 3; | ||
case 3: | ||
i++; | ||
return [3 /*break*/, 1]; | ||
case 4: return [2 /*return*/, filteredNode]; | ||
} | ||
}); | ||
}); | ||
var nodeProps = Object.entries(node); | ||
var schemaProps = state.schemaDef.properties || {}; | ||
var filteredNode = {}; | ||
var pathPrefix = state.path ? state.path + "." : ''; | ||
for (var i = 0; i < nodeProps.length; i++) { | ||
var _a = nodeProps[i], name_1 = _a[0], value = _a[1]; | ||
var propSchema = schemaProps[name_1]; | ||
var propPath = "" + pathPrefix + name_1; | ||
state.logger.debug("[PATH] " + propPath); | ||
var propState = state.createNodeState(value, name_1, propPath, propSchema); | ||
// Property not defined in the schema, do not serialize property | ||
if (!propSchema) { | ||
state.logger.debug("Property '" + name_1 + "' not defined in " + state.schemaType + "."); | ||
continue; | ||
} | ||
filteredNode[name_1] = this.walkData(value, propState); | ||
} | ||
return filteredNode; | ||
}; | ||
@@ -352,42 +278,24 @@ /** | ||
Scrubbr.prototype.walkArrayNode = function (node, state) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var schema, listSchema, tupleSchema, isTuple, filteredNode, i, value, itemSchema, itemPath, itemState, _a, _b; | ||
return __generator(this, function (_c) { | ||
switch (_c.label) { | ||
case 0: | ||
schema = state.schemaDef; | ||
listSchema = schema.items; | ||
tupleSchema = []; | ||
isTuple = Array.isArray(schema.items); | ||
if (isTuple) { | ||
tupleSchema = schema.items; | ||
} | ||
filteredNode = []; | ||
i = 0; | ||
_c.label = 1; | ||
case 1: | ||
if (!(i < node.length)) return [3 /*break*/, 4]; | ||
value = node[i]; | ||
itemSchema = isTuple ? tupleSchema[i] : listSchema; | ||
itemPath = state.path + "[" + i + "]"; | ||
state.logger.debug("[PATH] " + itemPath); | ||
itemState = state.createListState(value, i, itemPath, itemSchema); | ||
// Skip items past the tuple length | ||
if (isTuple && i >= tupleSchema.length) { | ||
state.logger.debug("Index " + i + " not defined in tuple"); | ||
return [3 /*break*/, 4]; | ||
} | ||
_a = filteredNode; | ||
_b = i; | ||
return [4 /*yield*/, this.walkData(value, itemState)]; | ||
case 2: | ||
_a[_b] = _c.sent(); | ||
_c.label = 3; | ||
case 3: | ||
i++; | ||
return [3 /*break*/, 1]; | ||
case 4: return [2 /*return*/, filteredNode]; | ||
} | ||
}); | ||
}); | ||
var schema = state.schemaDef; | ||
var listSchema = schema.items; | ||
var tupleSchema = []; | ||
var isTuple = Array.isArray(schema.items); | ||
if (isTuple) { | ||
tupleSchema = schema.items; | ||
} | ||
var filteredNode = []; | ||
for (var i = 0; i < node.length; i++) { | ||
var value = node[i]; | ||
var itemSchema = isTuple ? tupleSchema[i] : listSchema; | ||
var itemPath = state.path + "[" + i + "]"; | ||
state.logger.debug("[PATH] " + itemPath); | ||
var itemState = state.createListState(value, i, itemPath, itemSchema); | ||
// Skip items past the tuple length | ||
if (isTuple && i >= tupleSchema.length) { | ||
state.logger.debug("Index " + i + " not defined in tuple"); | ||
break; | ||
} | ||
filteredNode[i] = this.walkData(value, itemState); | ||
} | ||
return filteredNode; | ||
}; | ||
@@ -400,36 +308,26 @@ /** | ||
Scrubbr.prototype.serializeNode = function (data, state) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var originalDef, schemaType, circularRef, schemaDef; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
originalDef = state.schemaDef; | ||
schemaType = this.getTypeName(state.schemaDef, state); | ||
if (schemaType) { | ||
circularRef = this.isCircularReference(schemaType, state); | ||
if (circularRef) { | ||
return [2 /*return*/, data]; | ||
} | ||
state = this.setStateSchemaDefinition(schemaType, state); | ||
} | ||
return [4 /*yield*/, this.runGenericSerializers(data, state)]; | ||
case 1: | ||
// Run serializers | ||
data = _a.sent(); | ||
return [4 /*yield*/, this.runTypeSerializers(data, state)]; | ||
case 2: | ||
data = _a.sent(); | ||
schemaDef = state.schemaDef; | ||
if (schemaDef && | ||
schemaType && | ||
originalDef !== schemaDef && | ||
!schemaDef.properties && | ||
(schemaDef.allOf || schemaDef.anyOf || schemaDef.oneOf)) { | ||
state.logger.debug("'" + state.schemaType + "' appears to be an union type."); | ||
return [2 /*return*/, this.serializeNode(data, state)]; | ||
} | ||
return [2 /*return*/, data]; | ||
} | ||
}); | ||
}); | ||
var originalDef = state.schemaDef; | ||
// Get typescript type for the schema definition | ||
var schemaType = this.getTypeName(state.schemaDef, state); | ||
if (schemaType) { | ||
var circularRef = this.isCircularReference(schemaType, state); | ||
if (circularRef) { | ||
return data; | ||
} | ||
state = this.setStateSchemaDefinition(schemaType, state); | ||
} | ||
// Run serializers | ||
data = this.runGenericSerializers(data, state); | ||
data = this.runTypeSerializers(data, state); | ||
// If the type definition is an alias to a union, walk one level deeper | ||
var schemaDef = state.schemaDef; | ||
if (schemaDef && | ||
schemaType && | ||
originalDef !== schemaDef && | ||
!schemaDef.properties && | ||
(schemaDef.allOf || schemaDef.anyOf || schemaDef.oneOf)) { | ||
state.logger.debug("'" + state.schemaType + "' appears to be an union type."); | ||
return this.serializeNode(data, state); | ||
} | ||
return data; | ||
}; | ||
@@ -532,44 +430,28 @@ /** | ||
Scrubbr.prototype.runGenericSerializers = function (dataNode, state) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var serialized, i, serializerFn, result; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
if (!this.genericSerializers.length) { | ||
return [2 /*return*/, dataNode]; | ||
} | ||
state.logger.debug("Running " + this.genericSerializers.length + " generic serializers"); | ||
serialized = dataNode; | ||
i = 0; | ||
_a.label = 1; | ||
case 1: | ||
if (!(i < this.genericSerializers.length)) return [3 /*break*/, 4]; | ||
serializerFn = this.genericSerializers[i]; | ||
return [4 /*yield*/, serializerFn.call(null, serialized, state)]; | ||
case 2: | ||
result = _a.sent(); | ||
if (typeof result === 'undefined') { | ||
state.logger.warn("Generic serializer returned 'undefined' at object path: " + state.path); | ||
} | ||
if (result instanceof helpers_1.UseType) { | ||
state.logger.debug("Overriding type: '" + result.typeName + "'"); | ||
if (result.typeName === state.schemaType) { | ||
state.logger.warn("Trying to override type with the same type ('" + result.typeName + "') at object path: " + state.path); | ||
} | ||
if (typeof result.data !== 'undefined') { | ||
serialized = result.data; | ||
} | ||
state = this.setStateSchemaDefinition(result.typeName, state); | ||
} | ||
else { | ||
serialized = result; | ||
} | ||
_a.label = 3; | ||
case 3: | ||
i++; | ||
return [3 /*break*/, 1]; | ||
case 4: return [2 /*return*/, serialized]; | ||
if (!this.genericSerializers.length) { | ||
return dataNode; | ||
} | ||
state.logger.debug("Running " + this.genericSerializers.length + " generic serializers"); | ||
var serialized = dataNode; | ||
for (var i = 0; i < this.genericSerializers.length; i++) { | ||
var serializerFn = this.genericSerializers[i]; | ||
var result = serializerFn.call(null, serialized, state); | ||
if (typeof result === 'undefined') { | ||
state.logger.warn("Generic serializer returned 'undefined' at object path: " + state.path); | ||
} | ||
if (result instanceof helpers_1.UseType) { | ||
state.logger.debug("Overriding type: '" + result.typeName + "'"); | ||
if (result.typeName === state.schemaType) { | ||
state.logger.warn("Trying to override type with the same type ('" + result.typeName + "') at object path: " + state.path); | ||
} | ||
}); | ||
}); | ||
if (typeof result.data !== 'undefined') { | ||
serialized = result.data; | ||
} | ||
state = this.setStateSchemaDefinition(result.typeName, state); | ||
} | ||
else { | ||
serialized = result; | ||
} | ||
} | ||
return serialized; | ||
}; | ||
@@ -580,57 +462,46 @@ /** | ||
Scrubbr.prototype.runTypeSerializers = function (dataNode, state) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var typeName, serializerFns, serialized, i, result, circularRef; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
typeName = state.schemaType; | ||
if (!typeName) { | ||
return [2 /*return*/, dataNode]; | ||
} | ||
if (!this.getSchemaFor(typeName)) { | ||
this.error("No type named '" + typeName + "'.", state); | ||
return [2 /*return*/, dataNode]; | ||
} | ||
serializerFns = this.typeSerializers.get(typeName) || []; | ||
if (!serializerFns.length) { | ||
return [2 /*return*/, dataNode]; | ||
} | ||
state.logger.debug("Running " + serializerFns.length + " serializers for type '" + typeName + "'"); | ||
serialized = dataNode; | ||
i = 0; | ||
_a.label = 1; | ||
case 1: | ||
if (!(i < serializerFns.length)) return [3 /*break*/, 8]; | ||
return [4 /*yield*/, serializerFns[i].call(null, dataNode, state)]; | ||
case 2: | ||
result = _a.sent(); | ||
if (typeof result === 'undefined') { | ||
state.logger.warn("Serializer for type '" + typeName + "' returned 'undefined' at object path: " + state.path); | ||
} | ||
if (!(result instanceof helpers_1.UseType)) return [3 /*break*/, 6]; | ||
state.logger.debug("Overriding type: '" + typeName + "'"); | ||
// Serialized data | ||
if (typeof result.data !== 'undefined') { | ||
serialized = result.data; | ||
} | ||
if (!(result.typeName === state.schemaType)) return [3 /*break*/, 3]; | ||
state.logger.warn("Trying to override type with the same type ('" + result.typeName + "') at object path: " + state.path); | ||
return [3 /*break*/, 5]; | ||
case 3: | ||
circularRef = this.isCircularReference(result.typeName, state); | ||
if (!!circularRef) return [3 /*break*/, 5]; | ||
var typeName = state.schemaType; | ||
if (!typeName) { | ||
return dataNode; | ||
} | ||
if (!this.getSchemaFor(typeName)) { | ||
this.error("No type named '" + typeName + "'.", state); | ||
return dataNode; | ||
} | ||
var serializerFns = this.typeSerializers.get(typeName) || []; | ||
if (!serializerFns.length) { | ||
return dataNode; | ||
} | ||
state.logger.debug("Running " + serializerFns.length + " serializers for type '" + typeName + "'"); | ||
var serialized = dataNode; | ||
for (var i = 0; i < serializerFns.length; i++) { | ||
var result = serializerFns[i].call(null, dataNode, state); | ||
if (typeof result === 'undefined') { | ||
state.logger.warn("Serializer for type '" + typeName + "' returned 'undefined' at object path: " + state.path); | ||
} | ||
// Change type | ||
if (result instanceof helpers_1.UseType) { | ||
state.logger.debug("Overriding type: '" + typeName + "'"); | ||
// Serialized data | ||
if (typeof result.data !== 'undefined') { | ||
serialized = result.data; | ||
} | ||
// No type change | ||
if (result.typeName === state.schemaType) { | ||
state.logger.warn("Trying to override type with the same type ('" + result.typeName + "') at object path: " + state.path); | ||
} | ||
else { | ||
// Stop serializing this type and switch to the new type, if it's not a circular reference | ||
var circularRef = this.isCircularReference(result.typeName, state); | ||
if (!circularRef) { | ||
state = this.setStateSchemaDefinition(result.typeName, state); | ||
return [4 /*yield*/, this.runTypeSerializers(serialized, state)]; | ||
case 4: return [2 /*return*/, _a.sent()]; | ||
case 5: return [3 /*break*/, 7]; | ||
case 6: | ||
serialized = result; | ||
_a.label = 7; | ||
case 7: | ||
i++; | ||
return [3 /*break*/, 1]; | ||
case 8: return [2 /*return*/, serialized]; | ||
return this.runTypeSerializers(serialized, state); | ||
} | ||
} | ||
}); | ||
}); | ||
} | ||
else { | ||
serialized = result; | ||
} | ||
} | ||
return serialized; | ||
}; | ||
@@ -637,0 +508,0 @@ /** |
@@ -8,3 +8,3 @@ import { JSONSchema7 } from 'json-schema'; | ||
*/ | ||
export declare type TypeSerializer = (data: unknown, state: ScrubbrState) => unknown | Promise<unknown>; | ||
export declare type TypeSerializer = (data: unknown, state: ScrubbrState) => unknown; | ||
/** | ||
@@ -14,3 +14,3 @@ * Serializer called for each node within the data object that is being serialized. | ||
*/ | ||
export declare type GenericSerializer = (data: unknown, state: ScrubbrState) => unknown | Promise<unknown>; | ||
export declare type GenericSerializer = (data: unknown, state: ScrubbrState) => unknown; | ||
/** | ||
@@ -17,0 +17,0 @@ * Options passed to the Scrubbr constructor |
@@ -68,3 +68,3 @@ # Scrubbr | ||
// Serialize the data based on the UserList type defined in schema.ts | ||
return await scrubbr.serialize('UserList', data); | ||
return scrubbr.serialize('UserList', data); | ||
} | ||
@@ -71,0 +71,0 @@ ``` |
@@ -9,3 +9,2 @@ # Custom Serializers | ||
```typescript | ||
@@ -18,3 +17,2 @@ scrubbr.addTypeSerializer('User', (data, state) => { | ||
## Context | ||
@@ -28,6 +26,6 @@ | ||
const context = { | ||
userId: 5 | ||
} | ||
userId: 5, | ||
}; | ||
scrubbr.addTypeSerializer('User', serializeUser); | ||
const serialized = await scrubbr.serialize('MemberList', data, context); | ||
const serialized = scrubbr.serialize('MemberList', data, context); | ||
@@ -49,3 +47,2 @@ // Only return the logged-in user | ||
```typescript | ||
function userLoggedIn(user) { | ||
@@ -59,5 +56,4 @@ scrubbr.setGlobalContext({ loggedInUserId: user.id }); | ||
}; | ||
const serialized = await scrubbr.serialize('MemberList', data, context); | ||
const serialized = scrubbr.serialize('MemberList', data, context); | ||
} | ||
``` | ||
@@ -79,3 +75,3 @@ | ||
password: string; | ||
} | ||
}; | ||
@@ -85,3 +81,3 @@ type PublicUser = { | ||
name: string; | ||
} | ||
}; | ||
``` | ||
@@ -93,6 +89,6 @@ | ||
const context = { | ||
userId: 5 | ||
} | ||
userId: 5, | ||
}; | ||
scrubbr.addTypeSerializer('User', serializeUser); | ||
const serialized = await scrubbr.serialize('MemberList', data, context); | ||
const serialized = scrubbr.serialize('MemberList', data, context); | ||
@@ -122,8 +118,8 @@ // Convert User to PublicUser for everyone but the logged-in user | ||
* `state.path` - The object path to where you are (i.e. `blog.posts[1].author.name`) | ||
* `state.name` - The name of the property that is being serialized. | ||
* `state.index` - If the node being serialized is an item in an array, this is the index in that array. | ||
- `state.path` - The object path to where you are (i.e. `blog.posts[1].author.name`) | ||
- `state.name` - The name of the property that is being serialized. | ||
- `state.index` - If the node being serialized is an item in an array, this is the index in that array. | ||
!!! note | ||
In most cases Type Serializers provide a cleaner and more elegant way to serialize your data than Generic Serializers. | ||
In most cases Type Serializers provide a cleaner and more elegant way to serialize your data than Generic Serializers. | ||
@@ -136,3 +132,3 @@ Let's say we want to convert every `startTime` date value to the local timezone. | ||
startTime: Date; | ||
} | ||
}; | ||
type Meeting = { | ||
@@ -142,7 +138,7 @@ name: string; | ||
recurring: string; | ||
} | ||
}; | ||
type AppointmentList = { | ||
events: Event[]; | ||
meeting: Meeting[]; | ||
} | ||
}; | ||
``` | ||
@@ -152,3 +148,3 @@ | ||
import moment from 'moment-timezone'; | ||
import Scrubbr, {ScrubbrState} from 'scrubbr'; | ||
import Scrubbr, { ScrubbrState } from 'scrubbr'; | ||
@@ -174,3 +170,3 @@ const scrubbr = new Scrubbr('./schema.ts'); | ||
scrubbr.addGenericSerializer(serializeStartTime); | ||
const serialized = await scrubbr.serialize('AppointmentList', data, context); | ||
const serialized = scrubbr.serialize('AppointmentList', data, context); | ||
} | ||
@@ -184,3 +180,3 @@ | ||
startTime: '2021-06-26T19:00:00.000Z', | ||
} | ||
}, | ||
], | ||
@@ -192,7 +188,6 @@ meeting: [ | ||
recurring: 'daily', | ||
} | ||
] | ||
} | ||
}, | ||
], | ||
}; | ||
} | ||
``` | ||
@@ -211,7 +206,7 @@ | ||
startTime: StartTime; | ||
} | ||
}; | ||
type Meeting = { | ||
name: string; | ||
startTime: StartTime; | ||
} | ||
}; | ||
type AppointmentList = { | ||
@@ -221,3 +216,3 @@ events: Event[]; | ||
recurring: string; | ||
} | ||
}; | ||
``` | ||
@@ -227,3 +222,3 @@ | ||
import moment from 'moment-timezone'; | ||
import Scrubbr, {ScrubbrState} from 'scrubbr'; | ||
import Scrubbr, { ScrubbrState } from 'scrubbr'; | ||
@@ -245,3 +240,3 @@ const scrubbr = new Scrubbr('./schema.ts'); | ||
scrubbr.addTypeSerializer('StartTime', serializeStartTime); | ||
const serialized = await scrubbr.serialize('AppointmentList', data, context); | ||
const serialized = scrubbr.serialize('AppointmentList', data, context); | ||
} | ||
@@ -255,3 +250,3 @@ | ||
startTime: '2021-06-26T19:00:00.000Z', | ||
} | ||
}, | ||
], | ||
@@ -263,7 +258,6 @@ meeting: [ | ||
recurring: 'daily', | ||
} | ||
] | ||
} | ||
}, | ||
], | ||
}; | ||
} | ||
``` |
@@ -20,3 +20,3 @@ # Tips & Tricks | ||
!!! warning | ||
You cannot load just any JSON schema into scrubbr, it needs to follow the output conventions of [ts-json-schema-generator](https://www.npmjs.com/package/ts-json-schema-generator): | ||
You cannot load just any JSON schema into scrubbr, it needs to follow the output conventions of [ts-json-schema-generator](https://www.npmjs.com/package/ts-json-schema-generator): | ||
@@ -54,3 +54,2 @@ * All types are defined in the root `definitions` object. | ||
```typescript | ||
// Global config | ||
@@ -67,3 +66,2 @@ const scrubbr = new Scrubbr('./schema.ts'); | ||
} | ||
``` | ||
@@ -83,3 +81,3 @@ | ||
// Serialize | ||
const output = await scrubbr.serialize('UserList', data); | ||
const output = scrubbr.serialize('UserList', data); | ||
const jsonSchema = scrubbr.getSchemaFor('UserList'); | ||
@@ -86,0 +84,0 @@ |
{ | ||
"name": "scrubbr", | ||
"version": "0.1.1", | ||
"version": "1.0.0", | ||
"description": "Serialize and sanitize JSON data using TypeScript.", | ||
@@ -5,0 +5,0 @@ "repository": "https://github.com/jgillick/scrubbr", |
@@ -210,3 +210,3 @@ import * as fs from 'fs'; | ||
*/ | ||
async serialize<Type = any>( | ||
serialize<Type = any>( | ||
schemaType: string, | ||
@@ -216,3 +216,3 @@ data: unknown, | ||
options: ScrubbrOptions = {} | ||
): Promise<Type> { | ||
): Type { | ||
const schema = this.getSchemaFor(schemaType); | ||
@@ -249,3 +249,3 @@ if (!schema) { | ||
const clonedData = JSON.parse(JSON.stringify(data)); | ||
const serialized = (await this.walkData(clonedData, state)) as Type; | ||
const serialized = this.walkData(clonedData, state) as Type; | ||
return serialized; | ||
@@ -259,4 +259,4 @@ } | ||
*/ | ||
private async walkData(node: unknown, state: ScrubbrState): Promise<unknown> { | ||
const serializedNode = await this.serializeNode(node, state); | ||
private walkData(node: unknown, state: ScrubbrState): unknown { | ||
const serializedNode = this.serializeNode(node, state); | ||
@@ -266,5 +266,5 @@ if (serializedNode === null) { | ||
} else if (Array.isArray(serializedNode)) { | ||
return await this.walkArrayNode(serializedNode, state); | ||
return this.walkArrayNode(serializedNode, state); | ||
} else if (typeof serializedNode === 'object') { | ||
return await this.walkObjectNode(serializedNode as ObjectNode, state); | ||
return this.walkObjectNode(serializedNode as ObjectNode, state); | ||
} | ||
@@ -279,6 +279,3 @@ return serializedNode; | ||
*/ | ||
private async walkObjectNode( | ||
node: ObjectNode, | ||
state: ScrubbrState | ||
): Promise<unknown> { | ||
private walkObjectNode(node: ObjectNode, state: ScrubbrState): unknown { | ||
const nodeProps = Object.entries(node); | ||
@@ -310,3 +307,3 @@ const schemaProps = state.schemaDef.properties || {}; | ||
filteredNode[name] = await this.walkData(value, propState); | ||
filteredNode[name] = this.walkData(value, propState); | ||
} | ||
@@ -321,6 +318,3 @@ return filteredNode; | ||
*/ | ||
private async walkArrayNode( | ||
node: unknown[], | ||
state: ScrubbrState | ||
): Promise<unknown> { | ||
private walkArrayNode(node: unknown[], state: ScrubbrState): unknown { | ||
const schema = state.schemaDef; | ||
@@ -355,3 +349,3 @@ const listSchema = schema.items as JSONSchema7 | JSONSchema7[]; | ||
filteredNode[i] = await this.walkData(value, itemState); | ||
filteredNode[i] = this.walkData(value, itemState); | ||
} | ||
@@ -367,6 +361,3 @@ | ||
*/ | ||
private async serializeNode( | ||
data: unknown, | ||
state: ScrubbrState | ||
): Promise<unknown> { | ||
private serializeNode(data: unknown, state: ScrubbrState): unknown { | ||
const originalDef = state.schemaDef; | ||
@@ -385,4 +376,4 @@ | ||
// Run serializers | ||
data = await this.runGenericSerializers(data, state); | ||
data = await this.runTypeSerializers(data, state); | ||
data = this.runGenericSerializers(data, state); | ||
data = this.runTypeSerializers(data, state); | ||
@@ -518,6 +509,6 @@ // If the type definition is an alias to a union, walk one level deeper | ||
*/ | ||
private async runGenericSerializers( | ||
private runGenericSerializers( | ||
dataNode: unknown, | ||
state: ScrubbrState | ||
): Promise<unknown> { | ||
): unknown { | ||
if (!this.genericSerializers.length) { | ||
@@ -534,3 +525,3 @@ return dataNode; | ||
const serializerFn = this.genericSerializers[i]; | ||
const result = await serializerFn.call(null, serialized, state); | ||
const result = serializerFn.call(null, serialized, state); | ||
@@ -566,6 +557,3 @@ if (typeof result === 'undefined') { | ||
*/ | ||
private async runTypeSerializers( | ||
dataNode: unknown, | ||
state: ScrubbrState | ||
): Promise<unknown> { | ||
private runTypeSerializers(dataNode: unknown, state: ScrubbrState): unknown { | ||
const typeName = state.schemaType; | ||
@@ -592,3 +580,3 @@ if (!typeName) { | ||
for (let i = 0; i < serializerFns.length; i++) { | ||
const result = await serializerFns[i].call(null, dataNode, state); | ||
const result = serializerFns[i].call(null, dataNode, state); | ||
@@ -620,3 +608,3 @@ if (typeof result === 'undefined') { | ||
state = this.setStateSchemaDefinition(result.typeName, state); | ||
return await this.runTypeSerializers(serialized, state); | ||
return this.runTypeSerializers(serialized, state); | ||
} | ||
@@ -623,0 +611,0 @@ } |
@@ -9,6 +9,3 @@ import { JSONSchema7 } from 'json-schema'; | ||
*/ | ||
export type TypeSerializer = ( | ||
data: unknown, | ||
state: ScrubbrState | ||
) => unknown | Promise<unknown>; | ||
export type TypeSerializer = (data: unknown, state: ScrubbrState) => unknown; | ||
@@ -19,6 +16,3 @@ /** | ||
*/ | ||
export type GenericSerializer = ( | ||
data: unknown, | ||
state: ScrubbrState | ||
) => unknown | Promise<unknown>; | ||
export type GenericSerializer = (data: unknown, state: ScrubbrState) => unknown; | ||
@@ -25,0 +19,0 @@ /** |
@@ -27,5 +27,6 @@ import 'jest'; | ||
const data = { value: 'test' }; | ||
const scrub = scrubbr.serialize('CircularReferenceTest1', data); | ||
return expect(scrub).rejects.toThrowError(); | ||
return expect(() => | ||
scrubbr.serialize('CircularReferenceTest1', data) | ||
).toThrowError(); | ||
}, 500); | ||
}); |
@@ -19,10 +19,10 @@ import 'jest'; | ||
test('pass context to serializers', async () => { | ||
test('pass context to serializers', () => { | ||
const testContext = { hello: 'world' }; | ||
await scrubbr.serialize('ContextTest', data, testContext); | ||
scrubbr.serialize('ContextTest', data, testContext); | ||
expect(receivedContext.hello).toBe('world'); | ||
}); | ||
test('no context passes empty object', async () => { | ||
await scrubbr.serialize('ContextTest', data); | ||
test('no context passes empty object', () => { | ||
scrubbr.serialize('ContextTest', data); | ||
expect(typeof receivedContext).toBe('object'); | ||
@@ -33,3 +33,3 @@ expect(Object.keys(receivedContext).length).toBe(0); | ||
describe('global context', () => { | ||
test('merge global context with passed context object', async () => { | ||
test('merge global context with passed context object', () => { | ||
const globalContext = { foo: 'bar' }; | ||
@@ -39,7 +39,7 @@ const passedContext = { hello: 'world' }; | ||
scrubbr.setGlobalContext(globalContext); | ||
await scrubbr.serialize('ContextTest', data, passedContext); | ||
scrubbr.serialize('ContextTest', data, passedContext); | ||
expect(Object.keys(receivedContext)).toEqual(['foo', 'hello']); | ||
}); | ||
test('passed context overrides global context', async () => { | ||
test('passed context overrides global context', () => { | ||
const globalContext = { foo: 'bar' }; | ||
@@ -49,3 +49,3 @@ const passedContext = { foo: 'boo' }; | ||
scrubbr.setGlobalContext(globalContext); | ||
await scrubbr.serialize('ContextTest', data, passedContext); | ||
scrubbr.serialize('ContextTest', data, passedContext); | ||
expect(Object.keys(receivedContext)).toEqual(['foo']); | ||
@@ -55,7 +55,7 @@ expect(receivedContext.foo).toEqual('boo'); | ||
test('global context still used if no context is passed', async () => { | ||
test('global context still used if no context is passed', () => { | ||
const globalContext = { foo: 'bar' }; | ||
scrubbr.setGlobalContext(globalContext); | ||
await scrubbr.serialize('ContextTest', data); | ||
scrubbr.serialize('ContextTest', data); | ||
expect(receivedContext.foo).toEqual('bar'); | ||
@@ -62,0 +62,0 @@ }); |
@@ -7,3 +7,3 @@ import 'jest'; | ||
describe('do not serialize properties not in schema', () => { | ||
test('root object', async () => { | ||
test('root object', () => { | ||
const data = { | ||
@@ -14,3 +14,3 @@ value1: 1, | ||
}; | ||
const serialized = await scrubbr.serialize('ExtraPropsTest', data); | ||
const serialized = scrubbr.serialize('ExtraPropsTest', data); | ||
expect(serialized).toEqual( | ||
@@ -22,3 +22,3 @@ expect.objectContaining({ value1: 1, value2: 'foo' }) | ||
test('child object', async () => { | ||
test('child object', () => { | ||
const data = { | ||
@@ -31,3 +31,3 @@ child: { | ||
}; | ||
const serialized = await scrubbr.serialize('ExtraPropsChildTest', data); | ||
const serialized = scrubbr.serialize('ExtraPropsChildTest', data); | ||
expect(serialized.child).toEqual( | ||
@@ -39,3 +39,3 @@ expect.objectContaining({ value1: 1, value2: 'foo' }) | ||
test('as array item', async () => { | ||
test('as array item', () => { | ||
const data = { | ||
@@ -55,3 +55,3 @@ list: [ | ||
}; | ||
const serialized = await scrubbr.serialize('ExtraPropsArrayTest', data); | ||
const serialized = scrubbr.serialize('ExtraPropsArrayTest', data); | ||
expect(serialized.list[0]).toEqual( | ||
@@ -58,0 +58,0 @@ expect.objectContaining({ value1: 1, value2: 'foo' }) |
@@ -22,3 +22,3 @@ import 'jest'; | ||
test('call serializer for every node of the object', async () => { | ||
test('call serializer for every node of the object', () => { | ||
const paths: string[] = []; | ||
@@ -30,3 +30,3 @@ serializerFn.mockImplementation((data, state) => { | ||
const serialized = await scrubbr.serialize('GenericSerializerTest', data); | ||
const serialized = scrubbr.serialize('GenericSerializerTest', data); | ||
@@ -47,3 +47,3 @@ expect(serializerFn).toBeCalled(); | ||
test('pass context to generic serializers', async () => { | ||
test('pass context to generic serializers', () => { | ||
const context = { foo: 'bar' }; | ||
@@ -67,6 +67,6 @@ | ||
await scrubbr.serialize('GenericSerializerTest', data, context); | ||
scrubbr.serialize('GenericSerializerTest', data, context); | ||
}); | ||
test('modify node', async () => { | ||
test('modify node', () => { | ||
serializerFn.mockImplementation((data, state) => { | ||
@@ -79,7 +79,7 @@ if (state.path == 'child.node') { | ||
const serialized = await scrubbr.serialize('GenericSerializerTest', data); | ||
const serialized = scrubbr.serialize('GenericSerializerTest', data); | ||
expect(serialized.child.node).toBe('Changed!'); | ||
}); | ||
test('remove part of the tree', async () => { | ||
test('remove part of the tree', () => { | ||
const paths: string[] = []; | ||
@@ -94,7 +94,7 @@ serializerFn.mockImplementation((data, state) => { | ||
await scrubbr.serialize('GenericSerializerTest', data); | ||
scrubbr.serialize('GenericSerializerTest', data); | ||
expect(paths).toEqual(['', 'child', 'child.node', 'children']); | ||
}); | ||
test('override node type', async () => { | ||
test('override node type', () => { | ||
serializerFn.mockImplementation((data, state) => { | ||
@@ -107,7 +107,7 @@ if (state.path == 'child') { | ||
const serialized = await scrubbr.serialize('GenericSerializerTest', data); | ||
const serialized = scrubbr.serialize('GenericSerializerTest', data); | ||
expect(serialized.child.extra).toBe('bar'); | ||
}); | ||
test('override type and transform node', async () => { | ||
test('override type and transform node', () => { | ||
serializerFn.mockImplementation((data, state) => { | ||
@@ -124,3 +124,3 @@ if (state.path == 'child') { | ||
const serialized = await scrubbr.serialize('GenericSerializerTest', { | ||
const serialized = scrubbr.serialize('GenericSerializerTest', { | ||
child: { | ||
@@ -127,0 +127,0 @@ node: 'foo', |
@@ -9,3 +9,3 @@ import 'jest'; | ||
describe('<Record>', () => { | ||
test('return all matching properties', async () => { | ||
test('return all matching properties', () => { | ||
const data = { | ||
@@ -18,3 +18,3 @@ value: { | ||
}; | ||
const serialized = await scrubbr.serialize('RecordTest', data); | ||
const serialized = scrubbr.serialize('RecordTest', data); | ||
expect(serialized.value).toBeDefined(); | ||
@@ -21,0 +21,0 @@ expect(serialized.value).toEqual( |
@@ -7,7 +7,7 @@ import 'jest'; | ||
describe('Tuples', () => { | ||
test('convert tuple to array', async () => { | ||
test('convert tuple to array', () => { | ||
const data = { | ||
value: ['string', 123, false], | ||
}; | ||
const serialized = await scrubbr.serialize('TupleTest', data); | ||
const serialized = scrubbr.serialize('TupleTest', data); | ||
expect(serialized.value.length).toBe(3); | ||
@@ -17,7 +17,7 @@ expect(serialized.value).toEqual(['string', 123, false]); | ||
test('enforce max length', async () => { | ||
test('enforce max length', () => { | ||
const data = { | ||
value: ['string', 123, false, 'extra item'], | ||
}; | ||
const serialized = await scrubbr.serialize('TupleTest', data); | ||
const serialized = scrubbr.serialize('TupleTest', data); | ||
expect(serialized.value.length).toEqual(3); | ||
@@ -27,3 +27,3 @@ expect(serialized.value).toEqual(['string', 123, false]); | ||
test('follow type references', async () => { | ||
test('follow type references', () => { | ||
const data = { | ||
@@ -37,3 +37,3 @@ value: [ | ||
}; | ||
const serialized = await scrubbr.serialize('TypleTypeReference', data); | ||
const serialized = scrubbr.serialize('TypleTypeReference', data); | ||
expect(serialized.value.length).toEqual(1); | ||
@@ -40,0 +40,0 @@ expect(serialized.value[0].name).toBe('foo'); |
@@ -11,3 +11,3 @@ import 'jest'; | ||
test('call serializer for each matching type', async () => { | ||
test('call serializer for each matching type', () => { | ||
const values: string[] = []; | ||
@@ -25,3 +25,3 @@ const serializerFn = jest.fn((data, _state) => { | ||
}; | ||
await scrubbr.serialize('TypeSerializerTest', data); | ||
scrubbr.serialize('TypeSerializerTest', data); | ||
@@ -32,3 +32,3 @@ expect(serializerFn).toHaveBeenCalledTimes(3); | ||
test('override type', async () => { | ||
test('override type', () => { | ||
const typeBSerializerFn = jest.fn((data, _state) => data); | ||
@@ -44,7 +44,7 @@ | ||
}; | ||
await scrubbr.serialize('TypeSerializerTest', data); | ||
scrubbr.serialize('TypeSerializerTest', data); | ||
expect(typeBSerializerFn).toHaveBeenCalledTimes(4); | ||
}); | ||
test('override type and transform node', async () => { | ||
test('override type and transform node', () => { | ||
scrubbr.addTypeSerializer('TargetTypeA', () => { | ||
@@ -60,7 +60,7 @@ const newData = { | ||
}; | ||
const serialized = await scrubbr.serialize('TypeSerializerTest', data); | ||
const serialized = scrubbr.serialize('TypeSerializerTest', data); | ||
expect(serialized.node.value).toBe('boo'); | ||
}); | ||
test('union type', async () => { | ||
test('union type', () => { | ||
const typeASerializerFn = jest.fn((data, _state) => data); | ||
@@ -75,3 +75,3 @@ const typeBSerializerFn = jest.fn((data, _state) => data); | ||
}; | ||
await scrubbr.serialize('TypeSerializerUnionTest', data); | ||
scrubbr.serialize('TypeSerializerUnionTest', data); | ||
expect(typeASerializerFn).toHaveBeenCalledTimes(0); | ||
@@ -81,3 +81,3 @@ expect(typeBSerializerFn).toHaveBeenCalledTimes(1); | ||
test('add multiple types to a single serializer', async () => { | ||
test('add multiple types to a single serializer', () => { | ||
let receivedTypes = new Set<string>(); | ||
@@ -99,3 +99,3 @@ | ||
scrubbr.addTypeSerializer(['TargetTypeA', 'TargetTypeB'], serializerFn); | ||
await scrubbr.serialize('TypeSerializerTest', data, {}); | ||
scrubbr.serialize('TypeSerializerTest', data, {}); | ||
@@ -102,0 +102,0 @@ expect(serializerFn).toHaveBeenCalledTimes(2); |
@@ -23,4 +23,4 @@ import 'jest'; | ||
test('automatically choose between two types', async () => { | ||
await scrubbr.serialize('UnionTypeTestSimple', { | ||
test('automatically choose between two types', () => { | ||
scrubbr.serialize('UnionTypeTestSimple', { | ||
value: { | ||
@@ -36,14 +36,14 @@ nodeA: 'foo', | ||
test('choose between primitive types', async () => { | ||
await scrubbr.serialize('UnionTypeTestPrimitive', { value: 'test' }); | ||
test('choose between primitive types', () => { | ||
scrubbr.serialize('UnionTypeTestPrimitive', { value: 'test' }); | ||
expect(pathTypes.get('value')).toBe(null); | ||
}); | ||
test('union between TypeScript type and primitive', async () => { | ||
await scrubbr.serialize('UnionTypeTestMixed', { value: 'test' }); | ||
test('union between TypeScript type and primitive', () => { | ||
scrubbr.serialize('UnionTypeTestMixed', { value: 'test' }); | ||
expect(pathTypes.get('value')).toBe('UnionTypeTestType1'); | ||
}); | ||
test('Aliased union type', async () => { | ||
await scrubbr.serialize('UnionTypeTestAlias', { | ||
test('Aliased union type', () => { | ||
scrubbr.serialize('UnionTypeTestAlias', { | ||
value: { | ||
@@ -57,3 +57,3 @@ nodeA: 'foo', | ||
test('Override type', async () => { | ||
test('Override type', () => { | ||
firstSerializerFn.mockImplementation((data, state) => { | ||
@@ -66,3 +66,3 @@ if (state.path == 'value') { | ||
const output = await scrubbr.serialize('UnionTypeTestSimple', { | ||
const output = scrubbr.serialize('UnionTypeTestSimple', { | ||
value: { | ||
@@ -78,3 +78,3 @@ nodeA: 'foo', | ||
// test('change type and override value', async () => { | ||
// test('change type and override value', () => { | ||
// firstSerializerFn.mockImplementation((data, state) => { | ||
@@ -91,3 +91,3 @@ // if (state.path == 'value') { | ||
// const output = await scrubbr.serialize('UnionTypeTestSimple', { | ||
// const output = scrubbr.serialize('UnionTypeTestSimple', { | ||
// value: { | ||
@@ -94,0 +94,0 @@ // nodeA: 'foo', |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
0
1073699
2946