Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

gubu

Package Overview
Dependencies
Maintainers
1
Versions
27
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

gubu - npm Package Compare versions

Comparing version 0.0.1 to 0.0.2

118

gubu.d.ts

@@ -1,2 +0,116 @@

declare function gubu(spec?: any): <T>(src?: T | undefined) => T;
export { gubu };
declare const GUBU: {
gubu$: symbol;
v$: string;
};
declare type Options = {
name?: string;
};
declare type Context = Record<string, any> & {
err?: ErrDesc[];
};
declare type ValType = 'any' | 'none' | 'node' | 'custom' | 'null' | 'list' | 'string' | 'number' | 'boolean' | 'object' | 'array' | 'bigint' | 'symbol' | 'function' | 'instance' | 'nan';
declare type ValSpec = {
$: typeof GUBU;
t: ValType;
d: number;
v: any;
r: boolean;
k: string;
u?: any;
b?: Validate;
a?: Validate;
};
declare type Builder = (opts?: any, ...specs: any[]) => ValSpec & {
[name: string]: Builder | any;
};
declare type Validate = (val: any, update: Update, state: State) => boolean;
declare type State = {
key: string;
node: ValSpec;
src: any;
dI: number;
nI: number;
sI: number;
pI: number;
cN: number;
nodes: (ValSpec | number)[];
srcs: any[];
path: string[];
terr: any[];
err: any[];
ctx: any;
};
declare type Update = {
pass: boolean;
done?: boolean;
val?: any;
node?: ValSpec;
type?: ValType;
nI?: number;
sI?: number;
pI?: number;
cN?: number;
err?: boolean | ErrDesc | ErrDesc[];
why?: string;
};
declare type ErrDesc = {
n: ValSpec;
s: any;
p: string;
w: string;
m: number;
t: string;
};
declare function norm(spec?: any): ValSpec;
declare function make(inspec?: any, inopts?: Options): GubuShape;
declare const Required: Builder;
declare const Optional: Builder;
declare const Any: Builder;
declare const None: Builder;
declare const One: Builder;
declare const Some: Builder;
declare const All: Builder;
declare const Before: Builder;
declare const After: Builder;
declare const Closed: Builder;
declare const Define: Builder;
declare const Refer: Builder;
declare const Rename: Builder;
declare function buildize(invs?: any): ValSpec;
declare function gubuError(val: any, state: State, text?: string, why?: string): ErrDesc;
declare type GubuShape = (<T>(inroot?: T, inctx?: any) => T) & {
spec: () => any;
};
declare type Gubu = typeof make & {
desc: () => any;
After: typeof After;
All: typeof All;
Any: typeof Any;
Before: typeof Before;
Closed: typeof Closed;
Define: typeof Define;
None: typeof None;
One: typeof One;
Optional: typeof Optional;
Refer: typeof Refer;
Rename: typeof Rename;
Required: typeof Required;
Some: typeof Some;
};
declare const G$: (spec: any) => ValSpec;
declare const gubu: Gubu;
declare const GAfter: Builder;
declare const GAll: Builder;
declare const GAny: Builder;
declare const GBefore: Builder;
declare const GClosed: Builder;
declare const GDefine: Builder;
declare const GNone: Builder;
declare const GOne: Builder;
declare const GOptional: Builder;
declare const GRefer: Builder;
declare const GRename: Builder;
declare const GRequired: Builder;
declare const GSome: Builder;
export type { Validate, Update, Context, Builder, ValSpec, State, };
export { gubu, G$, norm, buildize, gubuError, After, All, Any, Before, Closed, Define, None, One, Optional, Refer, Rename, Required, Some, GAfter, GAll, GAny, GBefore, GClosed, GDefine, GNone, GOne, GOptional, GRefer, GRename, GRequired, GSome, };
"use strict";
/* Copyright (c) 2021 Richard Rodger, MIT License */
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.gubu = void 0;
function gubu(spec) {
return function gubu(src) {
return { ...spec, ...src };
exports.GSome = exports.GRequired = exports.GRename = exports.GRefer = exports.GOptional = exports.GOne = exports.GNone = exports.GDefine = exports.GClosed = exports.GBefore = exports.GAny = exports.GAll = exports.GAfter = exports.Some = exports.Required = exports.Rename = exports.Refer = exports.Optional = exports.One = exports.None = exports.Define = exports.Closed = exports.Before = exports.Any = exports.All = exports.After = exports.gubuError = exports.buildize = exports.norm = exports.G$ = exports.gubu = void 0;
/*
* NOTE: `undefined` is not considered a value or type, and thus means 'any'.
*/
// TODO: function deref?
// TODO: test Some, or drop?
// TODO: BigInt spec roundtrip test
// TODO: Only - builder, exact values
// TODO: Min,Max - builder, depends on value
const package_json_1 = __importDefault(require("./package.json"));
const GUBU$ = Symbol.for('gubu$');
const GUBU = { gubu$: GUBU$, v$: package_json_1.default.version };
class GubuError extends TypeError {
constructor(code, err, ctx) {
let message = err.map((e) => e.t).join('\n');
super(message);
let name = 'GubuError';
let ge = this;
ge.gubu = true;
ge.name = name;
ge.code = code;
ge.desc = () => ({ name, code, err, ctx, });
}
toJSON() {
return {
...this,
err: this.desc().err,
name: this.name,
message: this.message,
};
}
}
const IS_TYPE = {
String: true,
Number: true,
Boolean: true,
Object: true,
Array: true,
Function: true,
Symbol: true,
BigInt: true,
};
const EMPTY_VAL = {
string: '',
number: 0,
boolean: false,
object: {},
array: [],
function: () => undefined,
symbol: Symbol(''),
bigint: BigInt(0),
null: null,
};
function norm(spec) {
var _a, _b, _c, _d, _e, _f;
// Is this a (possibly incomplete) ValSpec?
if (null != spec && ((_a = spec.$) === null || _a === void 0 ? void 0 : _a.gubu$)) {
// Assume complete if gubu$ has special internal reference.
if (GUBU$ === spec.$.gubu$) {
return spec;
}
// Normalize an incomplete ValSpec, avoiding any recursive calls to norm.
else if (true === spec.$.gubu$) {
let vs = { ...spec };
vs.$ = { v$: package_json_1.default.version, ...vs.$, gubu$: GUBU$ };
vs.v = (null != vs.v && 'object' === typeof (vs.v)) ? { ...vs.v } : vs.v;
vs.t = vs.t || typeof (vs.v);
if ('function' === vs.t && IS_TYPE[vs.v.name]) {
vs.t = vs.v.name.toLowerCase();
vs.v = clone(EMPTY_VAL[vs.t]);
}
vs.k = null == vs.k ? '' : vs.k;
vs.r = !!vs.r;
vs.d = null == vs.d ? -1 : vs.d;
vs.u = vs.u || {};
if ((_b = vs.u.list) === null || _b === void 0 ? void 0 : _b.specs) {
vs.u.list.specs = [...vs.u.list.specs];
}
return vs;
}
}
// Not a ValSpec, so build one based on value and its type.
let t = (null === spec ? 'null' : typeof (spec));
t = (undefined === t ? 'any' : t);
// console.log('Nt', t)
let v = spec;
let r = false; // Optional by default
let b = undefined;
let u = {};
if ('object' === t) {
if (Array.isArray(spec)) {
t = 'array';
}
else if (null != v &&
Function !== v.constructor &&
Object !== v.constructor &&
null != v.constructor) {
t = 'instance';
u.n = (_c = v.constructor) === null || _c === void 0 ? void 0 : _c.name;
u.i = v.constructor;
}
}
else if ('function' === t) {
if (IS_TYPE[spec.name]) {
t = spec.name.toLowerCase();
r = true;
v = clone(EMPTY_VAL[t]);
}
else if ((undefined === spec.prototype && Function === spec.constructor) ||
Function === ((_d = spec.prototype) === null || _d === void 0 ? void 0 : _d.constructor)) {
t = 'custom';
b = v;
}
else {
t = 'instance';
r = true;
u.n = (_f = (_e = v.prototype) === null || _e === void 0 ? void 0 : _e.constructor) === null || _f === void 0 ? void 0 : _f.name;
u.i = v;
}
}
else if ('number' === t && isNaN(v)) {
t = 'nan';
}
// console.log('Nv', v)
let vs = {
$: GUBU,
t,
// v: (null != v && 'object' === typeof (v)) ? { ...v } : v,
v: (null != v && ('object' === t || 'array' === t)) ? { ...v } : v,
r,
k: '',
d: -1,
u,
};
if (b) {
vs.b = b;
}
// console.log('N', vs)
return vs;
}
exports.norm = norm;
function make(inspec, inopts) {
const opts = null == inopts ? {} : inopts;
opts.name = null == opts.name ? ('' + Math.random()).substring(2) : '' + opts.name;
let top = { '': inspec };
// let spec: ValSpec = norm(inspec) // Tree of validation nodes.
let spec = norm(top); // Tree of validation nodes.
// console.log(spec)
let gubuShape = function GubuShape(inroot, inctx) {
var _a, _b, _c, _d;
const ctx = inctx || {};
const root = {
// '': (undefined === inroot && null != spec.v['']) ?
// clone(EMPTY_VAL[spec.v[''].t]) :
// inroot
'': inroot
};
const nodes = [spec, -1];
const srcs = [root, -1];
const path = []; // Key path to current node.
// let dI: number = 0 // Node depth.
let dI = -1; // Node depth.
let nI = 2; // Next free slot in nodes.
let pI = 0; // Pointer to current node.
let sI = -1; // Pointer to next sibling node.
let cN = 0; // Number of children of current node.
let err = []; // Errors collected.
let node; // Current node.
let src; // Current source value to validate.
// Iterative depth-first traversal of the spec.
while (true) {
// Dereference the back pointers to ancestor siblings.
// Only objects|arrays can be nodes, so a number is a back pointer.
// NOTE: terminates because (+{...} -> NaN, +[] -> 0) -> false (JS wat FTW!)
node = nodes[pI];
while (+node) {
pI = node;
node = nodes[pI];
dI--;
}
sI = pI + 1;
src = srcs[pI];
if (!node) {
break;
}
if (-1 < dI) {
path[dI] = node.k;
}
dI++;
cN = 0;
pI = nI;
let keys = Object.keys(node.v);
// Treat array indexes as keys.
// Inject missing indexes if present in ValSpec.
if ('array' === node.t) {
keys = Object.keys(src);
for (let vk in node.v) {
if ('0' !== vk && !keys.includes(vk)) {
keys.splice(parseInt(vk) - 1, 0, '' + (parseInt(vk) - 1));
}
}
}
// console.log('KEYS', keys)
for (let key of keys) {
path[dI] = key;
let sval = src[key];
let stype = typeof (sval);
if ('number' === stype && isNaN(sval)) {
stype = 'nan';
}
let n = node.v[key];
let tvs = null;
// NOTE: special case handling for arrays keys.
if ('array' === node.t) {
// First array entry is general type spec.
// Following are special case elements offset by +1.
// Use these if src has no corresponding element.
let akey = '' + (parseInt(key) + 1);
n = node.v[akey];
if (undefined !== n) {
tvs = n = GUBU$ === ((_a = n.$) === null || _a === void 0 ? void 0 : _a.gubu$) ? n : (n = node.v[akey] = norm(n));
}
if (undefined === n) {
n = node.v[0];
key = '' + 0;
// No first element defining element type spec, so use Any.
if (undefined === n) {
n = node.v[0] = Any();
}
tvs = n = GUBU$ === ((_b = n.$) === null || _b === void 0 ? void 0 : _b.gubu$) ? n : (n = node.v[key] = norm(n));
}
}
else {
tvs = (null != n && GUBU$ === ((_c = n.$) === null || _c === void 0 ? void 0 : _c.gubu$)) ? n : (n = node.v[key] = norm(n));
}
tvs.k = key;
tvs.d = dI;
let t = tvs.t;
let vss;
let listkind = '';
let failN = 0;
if ('list' === t) {
vss = tvs.u.list.specs;
listkind = tvs.u.list.kind;
}
else {
vss = [tvs];
}
let terr = [];
for (let vsI = 0; vsI < vss.length; vsI++) {
let vs = vss[vsI];
// console.log('Ta', vs)
vs = GUBU$ === ((_d = vs.$) === null || _d === void 0 ? void 0 : _d.gubu$) ? vs : (vss[vsI] = norm(vs));
// console.log('Tb', vs)
let t = vs.t;
let pass = true;
let done = false;
// udpate can set t
if (vs.b) {
let update = handleValidate(vs.b, sval, {
dI, nI, sI, pI, cN,
key, node: vs, src, nodes, srcs, path, terr, err, ctx
});
// console.log('BU', update)
pass = update.pass;
if (undefined !== update.val) {
sval = src[key] = update.val;
}
if (undefined !== update.node) {
vs = update.node;
}
if (undefined !== update.type) {
t = update.type;
}
if (undefined !== update.done) {
done = update.done;
}
nI = undefined === update.nI ? nI : update.nI;
sI = undefined === update.sI ? sI : update.sI;
pI = undefined === update.pI ? pI : update.pI;
cN = undefined === update.cN ? cN : update.cN;
}
// console.log('M', t, stype, sval, vs.v)
// // sval instanceof vs.v,
// 'C',
// 'any' !== t,
// 'custom' !== t,
// undefined !== sval,
// t !== stype,
// !('object' === stype && 'instance' !== t && null != sval),
// !('instance' === t && sval instanceof vs.v),
// !('null' === t && null === sval)
// )
if (!done) {
if ('none' === t) {
terr.push(makeErr('none', sval, path, dI, vs, 1070));
}
else if ('object' === t) {
// console.log('SVAL', sval, null === sval)
// if (vs.r && null == sval) {
if (vs.r && undefined === sval) {
terr.push(makeErr('required', sval, path, dI, vs, 1010));
}
else if (
//(null != sval && ('object' !== stype || Array.isArray(sval)))
undefined !== sval && (null === sval ||
'object' !== stype ||
Array.isArray(sval))) {
// console.log('SVAL Q')
terr.push(makeErr('type', sval, path, dI, vs, 1020));
}
else {
nodes[nI] = vs;
srcs[nI] = src[key] = (src[key] || {});
nI++;
cN++;
}
}
else if ('array' === t) {
// if (vs.r && null == sval) {
if (vs.r && undefined === sval) {
terr.push(makeErr('required', sval, path, dI, vs, 1030));
}
// else if (null != sval && !Array.isArray(sval)) {
else if (undefined !== sval && !Array.isArray(sval)) {
terr.push(makeErr('type', sval, path, dI, vs, 1040));
}
else {
nodes[nI] = vs;
srcs[nI] = src[key] = (src[key] || []);
nI++;
cN++;
}
}
// Invalid type.
else if (!('any' === t ||
'custom' === t ||
undefined === sval ||
t === stype ||
('instance' === t && vs.u.i && sval instanceof vs.u.i) ||
// ('instance' !== t && 'object' === stype && null != sval) ||
('null' === t && null === sval)))
// 'any' !== t &&
// 'custom' !== t &&
// undefined !== sval &&
// t !== stype &&
// !('instance' !== t && 'object' === stype && null != sval) &&
// !('instance' === t && vs.u.i && sval instanceof vs.u.i) &&
// !('null' === t && null === sval)
{
terr.push(makeErr('type', sval, path, dI, vs, 1050));
pass = false;
}
// Value itself, or default.
else if (undefined === sval) {
if (vs.r) {
terr.push(makeErr('required', sval, path, dI, vs, 1060));
pass = false;
}
// NOTE: `undefined` is special and cannot be set
else if (undefined !== vs.v) {
src[key] = vs.v;
}
}
}
if (vs.a) {
let update = handleValidate(vs.a, sval, {
dI, nI, sI, pI, cN,
key, node: vs, src, nodes, srcs, path, terr, err, ctx
});
pass = update.pass;
if (undefined !== update.val) {
sval = src[key] = update.val;
}
nI = undefined === update.nI ? nI : update.nI;
sI = undefined === update.sI ? sI : update.sI;
pI = undefined === update.pI ? pI : update.pI;
cN = undefined === update.cN ? cN : update.cN;
}
if (!pass) {
failN++;
if ('all' === listkind) {
break;
}
}
else if ('one' === listkind) {
break;
}
}
if (0 < terr.length &&
!(('one' === listkind || 'some' === listkind) && failN < vss.length)) {
err.push(...terr);
}
}
if (0 < cN) {
// Follow pointer back to next parent sibling.
nodes[nI++] = sI;
}
else {
// Next sibling.
pI = sI;
dI--;
}
// console.log('***')
// for (let i = 0; i < nodeStack.length; i++) {
// console.log(
// ('' + i).padStart(4),
// J(nodeStack[i]).substring(0, 111).padEnd(112),
// '/',
// J(curStack[i]).substring(0, 33).padEnd(34),
// )
// }
// console.log('END', 'c=' + cN, 's=' + sI, 'p=' + pI, 'n=' + nI)
}
if (0 < err.length) {
if (ctx.err) {
ctx.err.push(...err);
}
else {
throw new GubuError('shape', err, ctx);
}
}
return root[''];
};
// TODO: test Number, String, etc also in arrays
gubuShape.spec = () => {
// Normalize spec, discard errors.
gubuShape(undefined, { err: [] });
// return JSON.parse(JSON.stringify(spec, (_key, val) => {
return JSON.parse(stringify(spec.v[''], (_key, val) => {
if (GUBU$ === val) {
return true;
}
return val;
}));
};
gubuShape.toString = () => {
return `[Gubu ${opts.name}]`;
};
return gubuShape;
}
// function J(x: any) {
// return null == x ? '' : JSON.stringify(x).replace(/"/g, '')
// }
function handleValidate(vf, sval, state) {
let update = { pass: true, done: false };
if (undefined !== sval || state.node.r) {
let valid = vf(sval, update, state);
if (!valid || update.err) {
let w = update.why || 'custom';
let p = pathstr(state.path, state.dI);
if ('object' === typeof (update.err)) {
// Assumes makeErr already called
state.terr.push(...[update.err].flat().map(e => {
e.p = null == e.p ? p : e.p;
e.m = null == e.m ? 2010 : e.m;
return e;
}));
}
else {
state.terr.push(makeErr(w, sval, state.path, state.dI, state.node, 1040));
}
update.pass = false;
}
}
// console.log('HV', update)
return update;
}
function pathstr(path, dI) {
return path.slice(1, dI + 1).filter(s => null != s).join('.');
}
const Required = function (spec) {
let vs = buildize(this || spec);
vs.r = true;
return vs;
};
exports.Required = Required;
const Optional = function (spec) {
let vs = buildize(this || spec);
vs.r = false;
return vs;
};
exports.Optional = Optional;
// Optional value provides default.
const Any = function (spec) {
let vs = buildize(this || spec);
vs.t = 'any';
if (undefined !== spec) {
vs.v = spec;
}
return vs;
};
exports.Any = Any;
const None = function (spec) {
let vs = buildize(this || spec);
vs.t = 'none';
return vs;
};
exports.None = None;
const makeListBuilder = function (kind) {
return function (...specs) {
let vs = buildize();
vs.t = 'list';
vs.u.list = {
specs: specs.map(s => buildize(s)).map(s => (s.u.list = {
kind
},
s)),
kind
};
return vs;
};
};
// Pass on first match. Short circuits.
const One = makeListBuilder('one');
exports.One = One;
// Pass if some match, but always check each one - does *not* short circuit.
const Some = makeListBuilder('some');
exports.Some = Some;
// Pass only if all match. Short circuits.
const All = makeListBuilder('all');
exports.All = All;
const Before = function (validate, spec) {
let vs = buildize(this || spec);
vs.b = validate;
return vs;
};
exports.Before = Before;
const After = function (validate, spec) {
let vs = buildize(this || spec);
// vs.t = vs.t || 'custom'
vs.a = validate;
return vs;
};
exports.After = After;
// TODO: array needs special handling as first entry is type spec
const Closed = function (spec) {
let vs = buildize(this || spec);
vs.b = (val, update, state) => {
if (null != val && 'object' === typeof (val)) {
let vkeys = Object.keys(val);
let allowed = vs.v;
// For arrays, handle non-index properties, and special element offset.
if ('array' === state.node.t) {
allowed = Object.keys(vs.v).slice(1)
.map((x) => {
let i = parseInt(x);
if (isNaN(i)) {
return x;
}
else {
return i - 1;
}
})
.reduce((a, i) => (a[i] = true, a), {});
}
for (let k of vkeys) {
if (undefined === allowed[k]) {
update.err =
makeErr('closed', val, state.path, state.dI, vs, 3010, '', { k });
return false;
}
}
}
return true;
};
return vs;
};
exports.Closed = Closed;
const Define = function (inopts, spec) {
let vs = buildize(this || spec);
let opts = 'object' === typeof inopts ? inopts || {} : {};
let name = 'string' === typeof inopts ? inopts : opts.name;
if (null != name && '' != name) {
vs.b = (_val, _update, state) => {
let ref = state.ctx.ref = state.ctx.ref || {};
ref[name] = state.node;
return true;
};
}
return vs;
};
exports.Define = Define;
const Refer = function (inopts, spec) {
let vs = buildize(this || spec);
let opts = 'object' === typeof inopts ? inopts || {} : {};
let name = 'string' === typeof inopts ? inopts : opts.name;
// Fill should be false (the default) if used recursively, to prevent loops.
let fill = !!opts.fill;
// console.log('R0', opts, name, fill)
if (null != name && '' != name) {
vs.b = (val, update, state) => {
if (undefined !== val || fill) {
let ref = state.ctx.ref = state.ctx.ref || {};
if (undefined !== ref[name]) {
// console.log('R1', ref[name])
let node = { ...ref[name] };
node.k = state.node.k;
node.t = node.t || 'none';
update.node = node;
update.type = node.t;
// console.log('R3', update)
}
}
// TODO: option to fail if ref not found?
return true;
};
}
return vs;
};
exports.Refer = Refer;
const Rename = function (inopts, spec) {
let vs = buildize(this || spec);
let opts = 'object' === typeof inopts ? inopts || {} : {};
let name = 'string' === typeof inopts ? inopts : opts.name;
if (null != name && '' != name) {
vs.a = (val, _update, state) => {
state.src[name] = val;
if (!opts.keep) {
delete state.src[state.key];
}
return true;
};
}
return vs;
};
exports.Rename = Rename;
function buildize(invs) {
let vs = norm(invs);
return Object.assign(vs, {
Required,
Optional,
Any,
Closed,
Before,
After,
});
}
exports.buildize = buildize;
function gubuError(val, state, text, why) {
return makeErr(why || 'custom', val, state.path, state.dI, state.node, 4000, text);
}
exports.gubuError = gubuError;
function makeErr(why, sval, path, dI, node, mark, text, user, fname) {
let err = {
n: node,
s: sval,
p: pathstr(path, dI),
w: why,
m: mark,
t: '',
};
let jstr = undefined === sval ? '' : stringify(sval);
let valstr = jstr.replace(/"/g, '');
valstr = valstr.substring(0, 77) + (77 < valstr.length ? '...' : '');
if (null == text || '' === text) {
err.t = `Validation failed for path "${err.p}" ` +
`with value "${valstr}" because ` +
('type' === why ? ('instance' === node.t ? `the value is not an instance of ${node.u.n}` :
`the value is not of type ${node.t}`) :
'required' === why ? `the value is required` :
'closed' === why ? `the property "${user === null || user === void 0 ? void 0 : user.k}" is not allowed` :
'none' === why ? 'no value is allowed' :
`check "${why + (fname ? ': ' + fname : '')}" failed`) +
'.';
}
else {
err.t = text
.replace(/\$VALUE/g, valstr)
.replace(/\$PATH/g, err.p);
}
return err;
}
function stringify(x, r) {
try {
return JSON.stringify(x, (key, val) => {
if (r) {
val = r(key, val);
}
if ('bigint' === typeof (val)) {
val = val.toString();
}
return val;
});
}
catch (e) {
return JSON.stringify(String(x));
}
}
function clone(x) {
return null == x ? x : 'object' !== typeof (x) ? x : JSON.parse(JSON.stringify(x));
}
Object.assign(make, {
After,
All,
Any,
Before,
Closed,
Define,
None,
One,
Optional,
Rename,
Required,
Some,
});
Object.defineProperty(make, 'name', { value: 'gubu' });
const G$ = (spec) => norm({ ...spec, $: { gubu$: true } });
exports.G$ = G$;
const gubu = make;
exports.gubu = gubu;
const GAfter = After;
exports.GAfter = GAfter;
const GAll = All;
exports.GAll = GAll;
const GAny = Any;
exports.GAny = GAny;
const GBefore = Before;
exports.GBefore = GBefore;
const GClosed = Closed;
exports.GClosed = GClosed;
const GDefine = Define;
exports.GDefine = GDefine;
const GNone = None;
exports.GNone = GNone;
const GOne = One;
exports.GOne = GOne;
const GOptional = Optional;
exports.GOptional = GOptional;
const GRefer = Refer;
exports.GRefer = GRefer;
const GRename = Rename;
exports.GRename = GRename;
const GRequired = Required;
exports.GRequired = GRequired;
const GSome = Some;
exports.GSome = GSome;
//# sourceMappingURL=gubu.js.map

@@ -1,8 +0,133 @@

import { Jsonic } from 'jsonic'
/* Copyright (c) 2021 Richard Rodger, MIT License */
/*
* NOTE: `undefined` is not considered a value or type, and thus means 'any'.
*/
function gubu(spec?: any) {
return function gubu<T>(src?: T): T {
return { ...spec, ...src }
// TODO: function deref?
// TODO: test Some, or drop?
// TODO: BigInt spec roundtrip test
// TODO: Only - builder, exact values
// TODO: Min,Max - builder, depends on value
import Pkg from './package.json'
const GUBU$ = Symbol.for('gubu$')
const GUBU = { gubu$: GUBU$, v$: Pkg.version }
type Options = {
name?: string // Name this Gubu shape.
}
type Context = Record<string, any> & {
err?: ErrDesc[] // Provide an array to collect errors, instead of throwing.
}
type ValType =
'any' |
'none' |
'node' |
'custom' |
'null' |
'list' |
'string' |
'number' |
'boolean' |
'object' |
'array' |
'bigint' |
'symbol' |
'function' |
'instance' |
'nan'
type ValSpec = {
$: typeof GUBU
t: ValType
d: number // Depth.
v: any
r: boolean // Value is required.
k: string // Key of this node.
u?: any // Custom meta data
b?: Validate // Custom before validation function.
a?: Validate // Custom after vaidation function.
}
type Builder = (opts?: any, ...specs: any[]) =>
ValSpec & { [name: string]: Builder | any }
type Validate = (val: any, update: Update, state: State) => boolean
type State = {
key: string
node: ValSpec
src: any
dI: number
nI: number
sI: number
pI: number
cN: number
nodes: (ValSpec | number)[]
srcs: any[]
path: string[]
terr: any[] // Term errors (for One,Some,All).
err: any[]
ctx: any
}
type Update = {
pass: boolean
done?: boolean
val?: any
node?: ValSpec
type?: ValType
nI?: number
sI?: number
pI?: number
cN?: number
err?: boolean | ErrDesc | ErrDesc[]
why?: string
}
type ErrDesc = {
n: ValSpec // Failing spec node.
s: any // Failing src value.
p: string // Key path to src value.
w: string // Error code ("why").
m: number // Error mark for debugging.
t: string // Error message text.
}
class GubuError extends TypeError {
constructor(
code: string,
err: ErrDesc[],
ctx: any,
) {
let message = err.map((e: ErrDesc) => e.t).join('\n')
super(message)
let name = 'GubuError'
let ge = this as unknown as any
ge.gubu = true
ge.name = name
ge.code = code
ge.desc = () => ({ name, code, err, ctx, })
}
toJSON() {
return {
...this,
err: (this as any).desc().err,
name: this.name,
message: this.message,
}
}
}

@@ -12,6 +137,943 @@

const IS_TYPE: { [name: string]: boolean } = {
String: true,
Number: true,
Boolean: true,
Object: true,
Array: true,
Function: true,
Symbol: true,
BigInt: true,
}
export { gubu }
const EMPTY_VAL: { [name: string]: any } = {
string: '',
number: 0,
boolean: false,
object: {},
array: [],
function: () => undefined,
symbol: Symbol(''),
bigint: BigInt(0),
null: null,
}
function norm(spec?: any): ValSpec {
// Is this a (possibly incomplete) ValSpec?
if (null != spec && spec.$?.gubu$) {
// Assume complete if gubu$ has special internal reference.
if (GUBU$ === spec.$.gubu$) {
return spec
}
// Normalize an incomplete ValSpec, avoiding any recursive calls to norm.
else if (true === spec.$.gubu$) {
let vs = { ...spec }
vs.$ = { v$: Pkg.version, ...vs.$, gubu$: GUBU$ }
vs.v = (null != vs.v && 'object' === typeof (vs.v)) ? { ...vs.v } : vs.v
vs.t = vs.t || typeof (vs.v)
if ('function' === vs.t && IS_TYPE[vs.v.name]) {
vs.t = (vs.v.name.toLowerCase() as ValType)
vs.v = clone(EMPTY_VAL[vs.t])
}
vs.k = null == vs.k ? '' : vs.k
vs.r = !!vs.r
vs.d = null == vs.d ? -1 : vs.d
vs.u = vs.u || {}
if (vs.u.list?.specs) {
vs.u.list.specs = [...vs.u.list.specs]
}
return vs
}
}
// Not a ValSpec, so build one based on value and its type.
let t: ValType | 'undefined' = (null === spec ? 'null' : typeof (spec))
t = (undefined === t ? 'any' : t) as ValType
// console.log('Nt', t)
let v = spec
let r = false // Optional by default
let b = undefined
let u: any = {}
if ('object' === t) {
if (Array.isArray(spec)) {
t = 'array'
}
else if (
null != v &&
Function !== v.constructor &&
Object !== v.constructor &&
null != v.constructor
) {
t = 'instance'
u.n = v.constructor?.name
u.i = v.constructor
}
}
else if ('function' === t) {
if (IS_TYPE[spec.name]) {
t = (spec.name.toLowerCase() as ValType)
r = true
v = clone(EMPTY_VAL[t])
}
else if (
(undefined === spec.prototype && Function === spec.constructor) ||
Function === spec.prototype?.constructor
) {
t = 'custom'
b = v
}
else {
t = 'instance'
r = true
u.n = v.prototype?.constructor?.name
u.i = v
}
}
else if ('number' === t && isNaN(v)) {
t = 'nan'
}
// console.log('Nv', v)
let vs: ValSpec = {
$: GUBU,
t,
// v: (null != v && 'object' === typeof (v)) ? { ...v } : v,
v: (null != v && ('object' === t || 'array' === t)) ? { ...v } : v,
r,
k: '',
d: -1,
u,
}
if (b) {
vs.b = b
}
// console.log('N', vs)
return vs
}
function make(inspec?: any, inopts?: Options): GubuShape {
const opts = null == inopts ? {} : inopts
opts.name = null == opts.name ? ('' + Math.random()).substring(2) : '' + opts.name
let top = { '': inspec }
// let spec: ValSpec = norm(inspec) // Tree of validation nodes.
let spec: ValSpec = norm(top) // Tree of validation nodes.
// console.log(spec)
let gubuShape = function GubuShape<T>(inroot?: T, inctx?: Context): T {
const ctx: any = inctx || {}
const root: any =
{
// '': (undefined === inroot && null != spec.v['']) ?
// clone(EMPTY_VAL[spec.v[''].t]) :
// inroot
'': inroot
}
const nodes: (ValSpec | number)[] = [spec, -1]
const srcs: any[] = [root, -1]
const path: string[] = [] // Key path to current node.
// let dI: number = 0 // Node depth.
let dI: number = -1 // Node depth.
let nI: number = 2 // Next free slot in nodes.
let pI: number = 0 // Pointer to current node.
let sI: number = -1 // Pointer to next sibling node.
let cN: number = 0 // Number of children of current node.
let err: any = [] // Errors collected.
let node: any // Current node.
let src: any // Current source value to validate.
// Iterative depth-first traversal of the spec.
while (true) {
// Dereference the back pointers to ancestor siblings.
// Only objects|arrays can be nodes, so a number is a back pointer.
// NOTE: terminates because (+{...} -> NaN, +[] -> 0) -> false (JS wat FTW!)
node = nodes[pI]
while (+node) {
pI = node
node = nodes[pI]
dI--
}
sI = pI + 1
src = srcs[pI]
if (!node) {
break
}
if (-1 < dI) {
path[dI] = node.k
}
dI++
cN = 0
pI = nI
let keys = Object.keys(node.v)
// Treat array indexes as keys.
// Inject missing indexes if present in ValSpec.
if ('array' === node.t) {
keys = Object.keys(src)
for (let vk in node.v) {
if ('0' !== vk && !keys.includes(vk)) {
keys.splice(parseInt(vk) - 1, 0, '' + (parseInt(vk) - 1))
}
}
}
// console.log('KEYS', keys)
for (let key of keys) {
path[dI] = key
let sval = src[key]
let stype: string = typeof (sval)
if ('number' === stype && isNaN(sval)) {
stype = 'nan'
}
let n = node.v[key]
let tvs: ValSpec = null as any
// NOTE: special case handling for arrays keys.
if ('array' === node.t) {
// First array entry is general type spec.
// Following are special case elements offset by +1.
// Use these if src has no corresponding element.
let akey = '' + (parseInt(key) + 1)
n = node.v[akey]
if (undefined !== n) {
tvs = n = GUBU$ === n.$?.gubu$ ? n : (n = node.v[akey] = norm(n))
}
if (undefined === n) {
n = node.v[0]
key = '' + 0
// No first element defining element type spec, so use Any.
if (undefined === n) {
n = node.v[0] = Any()
}
tvs = n = GUBU$ === n.$?.gubu$ ? n : (n = node.v[key] = norm(n))
}
}
else {
tvs = (null != n && GUBU$ === n.$?.gubu$) ? n : (n = node.v[key] = norm(n))
}
tvs.k = key
tvs.d = dI
let t = tvs.t
let vss: ValSpec[]
let listkind: string = ''
let failN = 0
if ('list' === t) {
vss = tvs.u.list.specs
listkind = tvs.u.list.kind
}
else {
vss = [tvs]
}
let terr: any[] = []
for (let vsI = 0; vsI < vss.length; vsI++) {
let vs = vss[vsI]
// console.log('Ta', vs)
vs = GUBU$ === vs.$?.gubu$ ? vs : (vss[vsI] = norm(vs))
// console.log('Tb', vs)
let t = vs.t
let pass = true
let done = false
// udpate can set t
if (vs.b) {
let update = handleValidate(vs.b, sval, {
dI, nI, sI, pI, cN,
key, node: vs, src, nodes, srcs, path, terr, err, ctx
})
// console.log('BU', update)
pass = update.pass
if (undefined !== update.val) {
sval = src[key] = update.val
}
if (undefined !== update.node) {
vs = update.node
}
if (undefined !== update.type) {
t = update.type
}
if (undefined !== update.done) {
done = update.done
}
nI = undefined === update.nI ? nI : update.nI
sI = undefined === update.sI ? sI : update.sI
pI = undefined === update.pI ? pI : update.pI
cN = undefined === update.cN ? cN : update.cN
}
// console.log('M', t, stype, sval, vs.v)
// // sval instanceof vs.v,
// 'C',
// 'any' !== t,
// 'custom' !== t,
// undefined !== sval,
// t !== stype,
// !('object' === stype && 'instance' !== t && null != sval),
// !('instance' === t && sval instanceof vs.v),
// !('null' === t && null === sval)
// )
if (!done) {
if ('none' === t) {
terr.push(makeErr('none', sval, path, dI, vs, 1070))
}
else if ('object' === t) {
// console.log('SVAL', sval, null === sval)
// if (vs.r && null == sval) {
if (vs.r && undefined === sval) {
terr.push(makeErr('required', sval, path, dI, vs, 1010))
}
else if (
//(null != sval && ('object' !== stype || Array.isArray(sval)))
undefined !== sval && (
null === sval ||
'object' !== stype ||
Array.isArray(sval)
)
) {
// console.log('SVAL Q')
terr.push(makeErr('type', sval, path, dI, vs, 1020))
}
else {
nodes[nI] = vs
srcs[nI] = src[key] = (src[key] || {})
nI++
cN++
}
}
else if ('array' === t) {
// if (vs.r && null == sval) {
if (vs.r && undefined === sval) {
terr.push(makeErr('required', sval, path, dI, vs, 1030))
}
// else if (null != sval && !Array.isArray(sval)) {
else if (undefined !== sval && !Array.isArray(sval)) {
terr.push(makeErr('type', sval, path, dI, vs, 1040))
}
else {
nodes[nI] = vs
srcs[nI] = src[key] = (src[key] || [])
nI++
cN++
}
}
// Invalid type.
else if (!(
'any' === t ||
'custom' === t ||
undefined === sval ||
t === stype ||
('instance' === t && vs.u.i && sval instanceof vs.u.i) ||
// ('instance' !== t && 'object' === stype && null != sval) ||
('null' === t && null === sval)
))
// 'any' !== t &&
// 'custom' !== t &&
// undefined !== sval &&
// t !== stype &&
// !('instance' !== t && 'object' === stype && null != sval) &&
// !('instance' === t && vs.u.i && sval instanceof vs.u.i) &&
// !('null' === t && null === sval)
{
terr.push(makeErr('type', sval, path, dI, vs, 1050))
pass = false
}
// Value itself, or default.
else if (undefined === sval) {
if (vs.r) {
terr.push(makeErr('required', sval, path, dI, vs, 1060))
pass = false
}
// NOTE: `undefined` is special and cannot be set
else if (undefined !== vs.v) {
src[key] = vs.v
}
}
}
if (vs.a) {
let update = handleValidate(vs.a, sval, {
dI, nI, sI, pI, cN,
key, node: vs, src, nodes, srcs, path, terr, err, ctx
})
pass = update.pass
if (undefined !== update.val) {
sval = src[key] = update.val
}
nI = undefined === update.nI ? nI : update.nI
sI = undefined === update.sI ? sI : update.sI
pI = undefined === update.pI ? pI : update.pI
cN = undefined === update.cN ? cN : update.cN
}
if (!pass) {
failN++
if ('all' === listkind) {
break
}
}
else if ('one' === listkind) {
break
}
}
if (0 < terr.length &&
!(('one' === listkind || 'some' === listkind) && failN < vss.length)) {
err.push(...terr)
}
}
if (0 < cN) {
// Follow pointer back to next parent sibling.
nodes[nI++] = sI
}
else {
// Next sibling.
pI = sI
dI--
}
// console.log('***')
// for (let i = 0; i < nodeStack.length; i++) {
// console.log(
// ('' + i).padStart(4),
// J(nodeStack[i]).substring(0, 111).padEnd(112),
// '/',
// J(curStack[i]).substring(0, 33).padEnd(34),
// )
// }
// console.log('END', 'c=' + cN, 's=' + sI, 'p=' + pI, 'n=' + nI)
}
if (0 < err.length) {
if (ctx.err) {
ctx.err.push(...err)
}
else {
throw new GubuError('shape', err, ctx)
}
}
return root['']
} as GubuShape
// TODO: test Number, String, etc also in arrays
gubuShape.spec = () => {
// Normalize spec, discard errors.
gubuShape(undefined, { err: [] })
// return JSON.parse(JSON.stringify(spec, (_key, val) => {
return JSON.parse(stringify(spec.v[''], (_key: string, val: any) => {
if (GUBU$ === val) {
return true
}
return val
}))
}
gubuShape.toString = () => {
return `[Gubu ${opts.name}]`
}
return gubuShape
}
// function J(x: any) {
// return null == x ? '' : JSON.stringify(x).replace(/"/g, '')
// }
function handleValidate(vf: Validate, sval: any, state: State): Update {
let update: Update = { pass: true, done: false }
if (undefined !== sval || state.node.r) {
let valid = vf(sval, update, state)
if (!valid || update.err) {
let w = update.why || 'custom'
let p = pathstr(state.path, state.dI)
if ('object' === typeof (update.err)) {
// Assumes makeErr already called
state.terr.push(...[update.err].flat().map(e => {
e.p = null == e.p ? p : e.p
e.m = null == e.m ? 2010 : e.m
return e
}))
}
else {
state.terr.push(makeErr(
w, sval, state.path, state.dI, state.node, 1040))
}
update.pass = false
}
}
// console.log('HV', update)
return update
}
function pathstr(path: string[], dI: number) {
return path.slice(1, dI + 1).filter(s => null != s).join('.')
}
const Required: Builder = function(this: ValSpec, spec?: any) {
let vs = buildize(this || spec)
vs.r = true
return vs
}
const Optional: Builder = function(this: ValSpec, spec?: any) {
let vs = buildize(this || spec)
vs.r = false
return vs
}
// Optional value provides default.
const Any: Builder = function(this: ValSpec, spec?: any) {
let vs = buildize(this || spec)
vs.t = 'any'
if (undefined !== spec) {
vs.v = spec
}
return vs
}
const None: Builder = function(this: ValSpec, spec?: any) {
let vs = buildize(this || spec)
vs.t = 'none'
return vs
}
const makeListBuilder = function(kind: string) {
return function(this: ValSpec, ...specs: any[]) {
let vs = buildize()
vs.t = 'list'
vs.u.list = {
specs: specs.map(s =>
buildize(s)).map(s => (
s.u.list = {
kind
},
s)),
kind
}
return vs
}
}
// Pass on first match. Short circuits.
const One: Builder = makeListBuilder('one')
// Pass if some match, but always check each one - does *not* short circuit.
const Some: Builder = makeListBuilder('some')
// Pass only if all match. Short circuits.
const All: Builder = makeListBuilder('all')
const Before: Builder = function(this: ValSpec, validate: Validate, spec?: any) {
let vs = buildize(this || spec)
vs.b = validate
return vs
}
const After: Builder = function(this: ValSpec, validate: Validate, spec?: any) {
let vs = buildize(this || spec)
// vs.t = vs.t || 'custom'
vs.a = validate
return vs
}
// TODO: array needs special handling as first entry is type spec
const Closed: Builder = function(this: ValSpec, spec?: any) {
let vs = buildize(this || spec)
vs.b = (val: any, update: Update, state: State) => {
if (null != val && 'object' === typeof (val)) {
let vkeys = Object.keys(val)
let allowed = vs.v
// For arrays, handle non-index properties, and special element offset.
if ('array' === state.node.t) {
allowed = Object.keys(vs.v).slice(1)
.map((x: any) => {
let i = parseInt(x)
if (isNaN(i)) {
return x
}
else {
return i - 1
}
})
.reduce((a: any, i: any) => (a[i] = true, a), {})
}
for (let k of vkeys) {
if (undefined === allowed[k]) {
update.err =
makeErr('closed', val, state.path, state.dI, vs, 3010, '', { k })
return false
}
}
}
return true
}
return vs
}
const Define: Builder = function(this: ValSpec, inopts: any, spec?: any): ValSpec {
let vs = buildize(this || spec)
let opts = 'object' === typeof inopts ? inopts || {} : {}
let name = 'string' === typeof inopts ? inopts : opts.name
if (null != name && '' != name) {
vs.b = (_val: any, _update: Update, state: State) => {
let ref = state.ctx.ref = state.ctx.ref || {}
ref[name] = state.node
return true
}
}
return vs
}
const Refer: Builder = function(this: ValSpec, inopts: any, spec?: any): ValSpec {
let vs = buildize(this || spec)
let opts = 'object' === typeof inopts ? inopts || {} : {}
let name = 'string' === typeof inopts ? inopts : opts.name
// Fill should be false (the default) if used recursively, to prevent loops.
let fill = !!opts.fill
// console.log('R0', opts, name, fill)
if (null != name && '' != name) {
vs.b = (val: any, update: Update, state: State) => {
if (undefined !== val || fill) {
let ref = state.ctx.ref = state.ctx.ref || {}
if (undefined !== ref[name]) {
// console.log('R1', ref[name])
let node = { ...ref[name] }
node.k = state.node.k
node.t = node.t || 'none'
update.node = node
update.type = node.t
// console.log('R3', update)
}
}
// TODO: option to fail if ref not found?
return true
}
}
return vs
}
const Rename: Builder = function(this: ValSpec, inopts: any, spec?: any): ValSpec {
let vs = buildize(this || spec)
let opts = 'object' === typeof inopts ? inopts || {} : {}
let name = 'string' === typeof inopts ? inopts : opts.name
if (null != name && '' != name) {
vs.a = (val: any, _update: Update, state: State) => {
state.src[name] = val
if (!opts.keep) {
delete state.src[state.key]
}
return true
}
}
return vs
}
function buildize(invs?: any): ValSpec {
let vs = norm(invs)
return Object.assign(vs, {
Required,
Optional,
Any,
Closed,
Before,
After,
})
}
function gubuError(val: any, state: State, text?: string, why?: string) {
return makeErr(
why || 'custom',
val,
state.path,
state.dI,
state.node,
4000,
text,
)
}
function makeErr(
why: string,
sval: any,
path: string[],
dI: number,
node: ValSpec,
mark: number,
text?: string,
user?: any,
fname?: string,
): ErrDesc {
let err: ErrDesc = {
n: node,
s: sval,
p: pathstr(path, dI),
w: why,
m: mark,
t: '',
}
let jstr = undefined === sval ? '' : stringify(sval)
let valstr = jstr.replace(/"/g, '')
valstr = valstr.substring(0, 77) + (77 < valstr.length ? '...' : '')
if (null == text || '' === text) {
err.t = `Validation failed for path "${err.p}" ` +
`with value "${valstr}" because ` +
('type' === why ? (
'instance' === node.t ? `the value is not an instance of ${node.u.n}` :
`the value is not of type ${node.t}`) :
'required' === why ? `the value is required` :
'closed' === why ? `the property "${user?.k}" is not allowed` :
'none' === why ? 'no value is allowed' :
`check "${why + (fname ? ': ' + fname : '')}" failed`) +
'.'
}
else {
err.t = text
.replace(/\$VALUE/g, valstr)
.replace(/\$PATH/g, err.p)
}
return err
}
function stringify(x: any, r?: any) {
try {
return JSON.stringify(x, (key: any, val: any) => {
if (r) {
val = r(key, val)
}
if ('bigint' === typeof (val)) {
val = val.toString()
}
return val
})
}
catch (e: any) {
return JSON.stringify(String(x))
}
}
function clone(x: any) {
return null == x ? x : 'object' !== typeof (x) ? x : JSON.parse(JSON.stringify(x))
}
type GubuShape =
(<T>(inroot?: T, inctx?: any) => T) &
{ spec: () => any }
Object.assign(make, {
After,
All,
Any,
Before,
Closed,
Define,
None,
One,
Optional,
Rename,
Required,
Some,
})
type Gubu = typeof make & {
desc: () => any
After: typeof After
All: typeof All
Any: typeof Any
Before: typeof Before
Closed: typeof Closed
Define: typeof Define
None: typeof None
One: typeof One
Optional: typeof Optional
Refer: typeof Refer
Rename: typeof Rename
Required: typeof Required
Some: typeof Some
}
Object.defineProperty(make, 'name', { value: 'gubu' })
const G$ = (spec: any): ValSpec => norm({ ...spec, $: { gubu$: true } })
const gubu: Gubu = (make as Gubu)
const GAfter = After
const GAll = All
const GAny = Any
const GBefore = Before
const GClosed = Closed
const GDefine = Define
const GNone = None
const GOne = One
const GOptional = Optional
const GRefer = Refer
const GRename = Rename
const GRequired = Required
const GSome = Some
export type {
Validate,
Update,
Context,
Builder,
ValSpec,
State,
}
export {
gubu,
G$,
norm,
buildize,
gubuError,
After,
All,
Any,
Before,
Closed,
Define,
None,
One,
Optional,
Refer,
Rename,
Required,
Some,
GAfter,
GAll,
GAny,
GBefore,
GClosed,
GDefine,
GNone,
GOne,
GOptional,
GRefer,
GRename,
GRequired,
GSome,
}

6

package.json
{
"name": "gubu",
"version": "0.0.1",
"version": "0.0.2",
"description": "gubu",

@@ -38,4 +38,4 @@ "main": "gubu.js",

"@types/jest": "^27.0.3",
"jest": "^27.3.1",
"ts-jest": "^27.0.7",
"jest": "^27.4.3",
"ts-jest": "^27.1.1",
"typescript": "^4.5.2",

@@ -42,0 +42,0 @@ "jsonic": "github:jsonicjs/jsonic#nextgen"

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc