@vinejs/compiler
Advanced tools
Comparing version 2.1.2 to 2.2.0
@@ -1,2 +0,62 @@ | ||
export { Compiler } from './src/compiler/main.js'; | ||
export { refsBuilder } from './src/refs_builder.js'; | ||
import { RootNode, CompilerOptions, CompilerNodes, CompilerParent, CompilerField, Refs, MessagesProviderContact, ErrorReporterContract, RefsStore } from './src/types.js'; | ||
/** | ||
* Compiler buffer to collect JS fragments in memory | ||
*/ | ||
declare class CompilerBuffer { | ||
#private; | ||
/** | ||
* The character used to create a new line | ||
*/ | ||
newLine: string; | ||
/** | ||
* Write statement ot the output | ||
*/ | ||
writeStatement(statement: string): void; | ||
/** | ||
* Creates a child buffer | ||
*/ | ||
child(): CompilerBuffer; | ||
/** | ||
* Returns the buffer contents as string | ||
*/ | ||
toString(): string; | ||
/** | ||
* Flush in-memory string | ||
*/ | ||
flush(): void; | ||
} | ||
/** | ||
* Compiler is used to compile an array of schema nodes into a re-usable | ||
* JavaScript. | ||
*/ | ||
declare class Compiler { | ||
#private; | ||
/** | ||
* Variables counter is used to generate unique variable | ||
* names with a counter suffix. | ||
*/ | ||
variablesCounter: number; | ||
constructor(rootNode: RootNode, options?: CompilerOptions); | ||
/** | ||
* Converts a node to a field. Optionally accepts a parent node to create | ||
* a field for a specific parent type. | ||
*/ | ||
createFieldFor(node: CompilerNodes, parent: CompilerParent): CompilerField; | ||
/** | ||
* Compiles a given compiler node | ||
*/ | ||
compileNode(node: CompilerNodes, buffer: CompilerBuffer, parent: CompilerParent, parentField?: CompilerField): void; | ||
/** | ||
* Compile schema nodes to an async function | ||
*/ | ||
compile(): (data: any, meta: Record<string, any>, refs: Refs, messagesProvider: MessagesProviderContact, errorReporter: ErrorReporterContract) => Promise<Record<string, any>>; | ||
} | ||
/** | ||
* Creates a refs store for parsing the schema | ||
*/ | ||
declare function refsBuilder(): RefsStore; | ||
export { Compiler, refsBuilder }; |
1061
build/index.js
@@ -1,10 +0,1051 @@ | ||
/* | ||
* @vinejs/compiler | ||
* | ||
* (c) VineJS | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
export { Compiler } from './src/compiler/main.js'; | ||
export { refsBuilder } from './src/refs_builder.js'; | ||
// src/compiler/buffer.ts | ||
var CompilerBuffer = class _CompilerBuffer { | ||
#content = ""; | ||
/** | ||
* The character used to create a new line | ||
*/ | ||
newLine = "\n"; | ||
/** | ||
* Write statement ot the output | ||
*/ | ||
writeStatement(statement) { | ||
this.#content = `${this.#content}${this.newLine}${statement}`; | ||
} | ||
/** | ||
* Creates a child buffer | ||
*/ | ||
child() { | ||
return new _CompilerBuffer(); | ||
} | ||
/** | ||
* Returns the buffer contents as string | ||
*/ | ||
toString() { | ||
return this.#content; | ||
} | ||
/** | ||
* Flush in-memory string | ||
*/ | ||
flush() { | ||
this.#content = ""; | ||
} | ||
}; | ||
// src/scripts/field/variables.ts | ||
function defineFieldVariables({ | ||
parseFnRefId, | ||
variableName, | ||
wildCardPath, | ||
isArrayMember, | ||
valueExpression, | ||
fieldNameExpression, | ||
parentValueExpression | ||
}) { | ||
const inValueExpression = parseFnRefId ? `refs['${parseFnRefId}'](${valueExpression}, { | ||
data: root, | ||
meta: meta, | ||
parent: ${parentValueExpression} | ||
})` : valueExpression; | ||
return `const ${variableName} = defineValue(${inValueExpression}, { | ||
data: root, | ||
meta: meta, | ||
name: ${fieldNameExpression}, | ||
wildCardPath: '${wildCardPath}', | ||
mutate: defineValue, | ||
report: report, | ||
isValid: true, | ||
parent: ${parentValueExpression}, | ||
isArrayMember: ${isArrayMember}, | ||
});`; | ||
} | ||
// src/compiler/nodes/base.ts | ||
var BaseNode = class { | ||
#node; | ||
#parentField; | ||
field; | ||
constructor(node, compiler, parent, parentField) { | ||
this.#parentField = parentField; | ||
this.#node = node; | ||
if (this.#parentField) { | ||
this.field = this.#parentField; | ||
} else { | ||
compiler.variablesCounter++; | ||
this.field = compiler.createFieldFor(node, parent); | ||
} | ||
} | ||
defineField(buffer) { | ||
if (!this.#parentField) { | ||
buffer.writeStatement( | ||
defineFieldVariables({ | ||
fieldNameExpression: this.field.fieldNameExpression, | ||
isArrayMember: this.field.isArrayMember, | ||
parentValueExpression: this.field.parentValueExpression, | ||
valueExpression: this.field.valueExpression, | ||
variableName: this.field.variableName, | ||
wildCardPath: this.field.wildCardPath, | ||
parseFnRefId: "parseFnId" in this.#node ? this.#node.parseFnId : void 0 | ||
}) | ||
); | ||
} | ||
} | ||
}; | ||
// src/scripts/array/guard.ts | ||
function defineArrayGuard({ variableName, guardedCodeSnippet }) { | ||
return `if (ensureIsArray(${variableName})) { | ||
${guardedCodeSnippet} | ||
}`; | ||
} | ||
// src/scripts/field/is_valid_guard.ts | ||
function defineIsValidGuard({ variableName, bail, guardedCodeSnippet }) { | ||
if (!bail) { | ||
return guardedCodeSnippet; | ||
} | ||
return `if (${variableName}.isValid) { | ||
${guardedCodeSnippet} | ||
}`; | ||
} | ||
// src/scripts/field/null_output.ts | ||
function defineFieldNullOutput({ | ||
allowNull, | ||
conditional, | ||
variableName, | ||
outputExpression, | ||
transformFnRefId | ||
}) { | ||
if (!allowNull) { | ||
return ""; | ||
} | ||
return `${conditional || "if"}(${variableName}.value === null) { | ||
${outputExpression} = ${transformFnRefId ? `refs['${transformFnRefId}'](null, ${variableName});` : "null;"} | ||
}`; | ||
} | ||
// src/scripts/field/validations.ts | ||
function wrapInConditional(conditions, wrappingCode) { | ||
const [first, second] = conditions; | ||
if (first && second) { | ||
return `if (${first} && ${second}) { | ||
${wrappingCode} | ||
}`; | ||
} | ||
if (first) { | ||
return `if (${first}) { | ||
${wrappingCode} | ||
}`; | ||
} | ||
if (second) { | ||
return `if (${second}) { | ||
${wrappingCode} | ||
}`; | ||
} | ||
return wrappingCode; | ||
} | ||
function emitValidationSnippet({ isAsync, implicit, ruleFnId }, variableName, bail, dropMissingCheck) { | ||
const rule = `refs['${ruleFnId}']`; | ||
const callable = `${rule}.validator(${variableName}.value, ${rule}.options, ${variableName});`; | ||
const bailCondition = bail ? `${variableName}.isValid` : ""; | ||
const implicitCondition = implicit || dropMissingCheck ? "" : `${variableName}.isDefined`; | ||
return wrapInConditional( | ||
[bailCondition, implicitCondition], | ||
isAsync ? `await ${callable}` : `${callable}` | ||
); | ||
} | ||
function defineFieldValidations({ | ||
bail, | ||
validations, | ||
variableName, | ||
dropMissingCheck | ||
}) { | ||
return `${validations.map((one) => emitValidationSnippet(one, variableName, bail, dropMissingCheck)).join("\n")}`; | ||
} | ||
// src/scripts/array/initial_output.ts | ||
function defineArrayInitialOutput({ | ||
variableName, | ||
outputExpression, | ||
outputValueExpression | ||
}) { | ||
return `const ${variableName}_out = ${outputValueExpression}; | ||
${outputExpression} = ${variableName}_out;`; | ||
} | ||
// src/scripts/field/existence_validations.ts | ||
function defineFieldExistenceValidations({ | ||
allowNull, | ||
isOptional, | ||
variableName | ||
}) { | ||
if (isOptional === false) { | ||
if (allowNull === false) { | ||
return `ensureExists(${variableName});`; | ||
} else { | ||
return `ensureIsDefined(${variableName});`; | ||
} | ||
} | ||
return ""; | ||
} | ||
// src/compiler/nodes/tuple.ts | ||
var TupleNodeCompiler = class extends BaseNode { | ||
#node; | ||
#buffer; | ||
#compiler; | ||
constructor(node, buffer, compiler, parent, parentField) { | ||
super(node, compiler, parent, parentField); | ||
this.#node = node; | ||
this.#buffer = buffer; | ||
this.#compiler = compiler; | ||
} | ||
/** | ||
* Compiles the tuple children to a JS fragment | ||
*/ | ||
#compileTupleChildren() { | ||
const buffer = this.#buffer.child(); | ||
const parent = { | ||
type: "tuple", | ||
fieldPathExpression: this.field.fieldPathExpression, | ||
outputExpression: this.field.outputExpression, | ||
variableName: this.field.variableName, | ||
wildCardPath: this.field.wildCardPath | ||
}; | ||
this.#node.properties.forEach((child) => { | ||
this.#compiler.compileNode(child, buffer, parent); | ||
}); | ||
return buffer.toString(); | ||
} | ||
compile() { | ||
this.defineField(this.#buffer); | ||
this.#buffer.writeStatement( | ||
defineFieldExistenceValidations({ | ||
allowNull: this.#node.allowNull, | ||
isOptional: this.#node.isOptional, | ||
variableName: this.field.variableName | ||
}) | ||
); | ||
const isArrayValidBlock = defineIsValidGuard({ | ||
variableName: this.field.variableName, | ||
bail: this.#node.bail, | ||
guardedCodeSnippet: `${defineArrayInitialOutput({ | ||
variableName: this.field.variableName, | ||
outputExpression: this.field.outputExpression, | ||
outputValueExpression: this.#node.allowUnknownProperties ? `copyProperties(${this.field.variableName}.value)` : `[]` | ||
})}${this.#compileTupleChildren()}` | ||
}); | ||
const isValueAnArrayBlock = defineArrayGuard({ | ||
variableName: this.field.variableName, | ||
guardedCodeSnippet: `${defineFieldValidations({ | ||
variableName: this.field.variableName, | ||
validations: this.#node.validations, | ||
bail: this.#node.bail, | ||
dropMissingCheck: true | ||
})}${this.#buffer.newLine}${isArrayValidBlock}` | ||
}); | ||
this.#buffer.writeStatement( | ||
`${isValueAnArrayBlock}${this.#buffer.newLine}${defineFieldNullOutput({ | ||
allowNull: this.#node.allowNull, | ||
outputExpression: this.field.outputExpression, | ||
variableName: this.field.variableName, | ||
conditional: "else if" | ||
})}` | ||
); | ||
} | ||
}; | ||
// src/scripts/array/loop.ts | ||
function defineArrayLoop({ | ||
variableName, | ||
loopCodeSnippet, | ||
startingIndex | ||
}) { | ||
startingIndex = startingIndex || 0; | ||
return `const ${variableName}_items_size = ${variableName}.value.length; | ||
for (let ${variableName}_i = ${startingIndex}; ${variableName}_i < ${variableName}_items_size; ${variableName}_i++) { | ||
${loopCodeSnippet} | ||
}`; | ||
} | ||
// src/compiler/nodes/array.ts | ||
var ArrayNodeCompiler = class extends BaseNode { | ||
#node; | ||
#buffer; | ||
#compiler; | ||
constructor(node, buffer, compiler, parent, parentField) { | ||
super(node, compiler, parent, parentField); | ||
this.#node = node; | ||
this.#buffer = buffer; | ||
this.#compiler = compiler; | ||
} | ||
/** | ||
* Compiles the array elements to a JS fragment | ||
*/ | ||
#compileArrayElements() { | ||
const arrayElementsBuffer = this.#buffer.child(); | ||
this.#compiler.compileNode(this.#node.each, arrayElementsBuffer, { | ||
type: "array", | ||
fieldPathExpression: this.field.fieldPathExpression, | ||
outputExpression: this.field.outputExpression, | ||
variableName: this.field.variableName, | ||
wildCardPath: this.field.wildCardPath | ||
}); | ||
const buffer = this.#buffer.child(); | ||
buffer.writeStatement( | ||
defineArrayLoop({ | ||
variableName: this.field.variableName, | ||
startingIndex: 0, | ||
loopCodeSnippet: arrayElementsBuffer.toString() | ||
}) | ||
); | ||
arrayElementsBuffer.flush(); | ||
return buffer.toString(); | ||
} | ||
compile() { | ||
this.defineField(this.#buffer); | ||
this.#buffer.writeStatement( | ||
defineFieldExistenceValidations({ | ||
allowNull: this.#node.allowNull, | ||
isOptional: this.#node.isOptional, | ||
variableName: this.field.variableName | ||
}) | ||
); | ||
const isArrayValidBlock = defineIsValidGuard({ | ||
variableName: this.field.variableName, | ||
bail: this.#node.bail, | ||
guardedCodeSnippet: `${defineArrayInitialOutput({ | ||
variableName: this.field.variableName, | ||
outputExpression: this.field.outputExpression, | ||
outputValueExpression: `[]` | ||
})}${this.#buffer.newLine}${this.#compileArrayElements()}` | ||
}); | ||
const isValueAnArrayBlock = defineArrayGuard({ | ||
variableName: this.field.variableName, | ||
guardedCodeSnippet: `${defineFieldValidations({ | ||
variableName: this.field.variableName, | ||
validations: this.#node.validations, | ||
bail: this.#node.bail, | ||
dropMissingCheck: true | ||
})}${this.#buffer.newLine}${isArrayValidBlock}` | ||
}); | ||
this.#buffer.writeStatement( | ||
`${isValueAnArrayBlock}${this.#buffer.newLine}${defineFieldNullOutput({ | ||
allowNull: this.#node.allowNull, | ||
outputExpression: this.field.outputExpression, | ||
variableName: this.field.variableName, | ||
conditional: "else if" | ||
})}` | ||
); | ||
} | ||
}; | ||
// src/scripts/union/parse.ts | ||
function callParseFunction({ parseFnRefId, variableName }) { | ||
if (parseFnRefId) { | ||
return `${variableName}.value = refs['${parseFnRefId}'](${variableName}.value);`; | ||
} | ||
return ""; | ||
} | ||
// src/scripts/define_else_conditon.ts | ||
function defineElseCondition({ variableName, conditionalFnRefId }) { | ||
return `else { | ||
refs['${conditionalFnRefId}'](${variableName}.value, ${variableName}); | ||
}`; | ||
} | ||
// src/scripts/define_conditional_guard.ts | ||
function defineConditionalGuard({ | ||
conditional, | ||
variableName, | ||
conditionalFnRefId, | ||
guardedCodeSnippet | ||
}) { | ||
return `${conditional}(refs['${conditionalFnRefId}'](${variableName}.value, ${variableName})) { | ||
${guardedCodeSnippet} | ||
}`; | ||
} | ||
// src/compiler/nodes/union.ts | ||
var UnionNodeCompiler = class extends BaseNode { | ||
#compiler; | ||
#node; | ||
#buffer; | ||
#parent; | ||
constructor(node, buffer, compiler, parent, parentField) { | ||
super(node, compiler, parent, parentField); | ||
this.#node = node; | ||
this.#buffer = buffer; | ||
this.#parent = parent; | ||
this.#compiler = compiler; | ||
} | ||
/** | ||
* Compiles union children by wrapping each conditon inside a conditional | ||
* guard block | ||
*/ | ||
#compileUnionChildren() { | ||
const childrenBuffer = this.#buffer.child(); | ||
this.#node.conditions.forEach((child, index) => { | ||
const conditionalBuffer = this.#buffer.child(); | ||
if ("parseFnId" in child.schema) { | ||
conditionalBuffer.writeStatement( | ||
callParseFunction({ | ||
parseFnRefId: child.schema.parseFnId, | ||
variableName: this.field.variableName | ||
}) | ||
); | ||
} | ||
this.#compiler.compileNode(child.schema, conditionalBuffer, this.#parent, this.field); | ||
childrenBuffer.writeStatement( | ||
defineConditionalGuard({ | ||
conditional: index === 0 ? "if" : "else if", | ||
variableName: this.field.variableName, | ||
conditionalFnRefId: child.conditionalFnRefId, | ||
guardedCodeSnippet: conditionalBuffer.toString() | ||
}) | ||
); | ||
conditionalBuffer.flush(); | ||
}); | ||
if (this.#node.elseConditionalFnRefId && this.#node.conditions.length) { | ||
childrenBuffer.writeStatement( | ||
defineElseCondition({ | ||
variableName: this.field.variableName, | ||
conditionalFnRefId: this.#node.elseConditionalFnRefId | ||
}) | ||
); | ||
} | ||
return childrenBuffer.toString(); | ||
} | ||
compile() { | ||
this.defineField(this.#buffer); | ||
this.#buffer.writeStatement(this.#compileUnionChildren()); | ||
} | ||
}; | ||
// src/scripts/record/loop.ts | ||
function defineRecordLoop({ variableName, loopCodeSnippet }) { | ||
return `const ${variableName}_keys = Object.keys(${variableName}.value); | ||
const ${variableName}_keys_size = ${variableName}_keys.length; | ||
for (let ${variableName}_key_i = 0; ${variableName}_key_i < ${variableName}_keys_size; ${variableName}_key_i++) { | ||
const ${variableName}_i = ${variableName}_keys[${variableName}_key_i]; | ||
${loopCodeSnippet} | ||
}`; | ||
} | ||
// src/scripts/object/guard.ts | ||
function defineObjectGuard({ variableName, guardedCodeSnippet }) { | ||
return `if (ensureIsObject(${variableName})) { | ||
${guardedCodeSnippet} | ||
}`; | ||
} | ||
// src/scripts/object/initial_output.ts | ||
function defineObjectInitialOutput({ | ||
variableName, | ||
outputExpression, | ||
outputValueExpression | ||
}) { | ||
return `const ${variableName}_out = ${outputValueExpression}; | ||
${outputExpression} = ${variableName}_out;`; | ||
} | ||
// src/compiler/nodes/record.ts | ||
var RecordNodeCompiler = class extends BaseNode { | ||
#node; | ||
#buffer; | ||
#compiler; | ||
constructor(node, buffer, compiler, parent, parentField) { | ||
super(node, compiler, parent, parentField); | ||
this.#node = node; | ||
this.#buffer = buffer; | ||
this.#compiler = compiler; | ||
} | ||
/** | ||
* Compiles the record elements to a JS fragment | ||
*/ | ||
#compileRecordElements() { | ||
const buffer = this.#buffer.child(); | ||
const recordElementsBuffer = this.#buffer.child(); | ||
this.#compiler.compileNode(this.#node.each, recordElementsBuffer, { | ||
type: "record", | ||
fieldPathExpression: this.field.fieldPathExpression, | ||
outputExpression: this.field.outputExpression, | ||
variableName: this.field.variableName, | ||
wildCardPath: this.field.wildCardPath | ||
}); | ||
buffer.writeStatement( | ||
defineRecordLoop({ | ||
variableName: this.field.variableName, | ||
loopCodeSnippet: recordElementsBuffer.toString() | ||
}) | ||
); | ||
recordElementsBuffer.flush(); | ||
return buffer.toString(); | ||
} | ||
compile() { | ||
this.defineField(this.#buffer); | ||
this.#buffer.writeStatement( | ||
defineFieldExistenceValidations({ | ||
allowNull: this.#node.allowNull, | ||
isOptional: this.#node.isOptional, | ||
variableName: this.field.variableName | ||
}) | ||
); | ||
const isObjectValidBlock = defineIsValidGuard({ | ||
variableName: this.field.variableName, | ||
bail: this.#node.bail, | ||
guardedCodeSnippet: `${defineObjectInitialOutput({ | ||
variableName: this.field.variableName, | ||
outputExpression: this.field.outputExpression, | ||
outputValueExpression: `{}` | ||
})}${this.#compileRecordElements()}` | ||
}); | ||
const isValueAnObjectBlock = defineObjectGuard({ | ||
variableName: this.field.variableName, | ||
guardedCodeSnippet: `${defineFieldValidations({ | ||
variableName: this.field.variableName, | ||
validations: this.#node.validations, | ||
bail: this.#node.bail, | ||
dropMissingCheck: true | ||
})}${this.#buffer.newLine}${isObjectValidBlock}` | ||
}); | ||
this.#buffer.writeStatement( | ||
`${isValueAnObjectBlock}${this.#buffer.newLine}${defineFieldNullOutput({ | ||
allowNull: this.#node.allowNull, | ||
outputExpression: this.field.outputExpression, | ||
variableName: this.field.variableName, | ||
conditional: "else if" | ||
})}` | ||
); | ||
} | ||
}; | ||
// src/scripts/object/move_unknown_properties.ts | ||
import { inspect } from "node:util"; | ||
function defineMoveProperties({ | ||
variableName, | ||
fieldsToIgnore, | ||
allowUnknownProperties | ||
}) { | ||
if (!allowUnknownProperties) { | ||
return ""; | ||
} | ||
return `moveProperties(${variableName}.value, ${variableName}_out, ${inspect(fieldsToIgnore)});`; | ||
} | ||
// src/compiler/nodes/object.ts | ||
var ObjectNodeCompiler = class extends BaseNode { | ||
#node; | ||
#buffer; | ||
#compiler; | ||
constructor(node, buffer, compiler, parent, parentField) { | ||
super(node, compiler, parent, parentField); | ||
this.#node = node; | ||
this.#buffer = buffer; | ||
this.#compiler = compiler; | ||
} | ||
/** | ||
* Returns known field names for the object | ||
*/ | ||
#getFieldNames(node) { | ||
let fieldNames = node.properties.map((child) => child.fieldName); | ||
const groupsFieldNames = node.groups.flatMap((group) => this.#getGroupFieldNames(group)); | ||
return fieldNames.concat(groupsFieldNames); | ||
} | ||
/** | ||
* Returns field names of a group. | ||
*/ | ||
#getGroupFieldNames(group) { | ||
return group.conditions.flatMap((condition) => { | ||
return this.#getFieldNames(condition.schema); | ||
}); | ||
} | ||
/** | ||
* Compiles object children to JS output | ||
*/ | ||
#compileObjectChildren() { | ||
const buffer = this.#buffer.child(); | ||
const parent = { | ||
type: "object", | ||
fieldPathExpression: this.field.fieldPathExpression, | ||
outputExpression: this.field.outputExpression, | ||
variableName: this.field.variableName, | ||
wildCardPath: this.field.wildCardPath | ||
}; | ||
this.#node.properties.forEach((child) => this.#compiler.compileNode(child, buffer, parent)); | ||
return buffer.toString(); | ||
} | ||
/** | ||
* Compiles object groups with conditions to JS output. | ||
*/ | ||
#compileObjectGroups() { | ||
const buffer = this.#buffer.child(); | ||
const parent = { | ||
type: "object", | ||
fieldPathExpression: this.field.fieldPathExpression, | ||
outputExpression: this.field.outputExpression, | ||
variableName: this.field.variableName, | ||
wildCardPath: this.field.wildCardPath | ||
}; | ||
this.#node.groups.forEach((group) => this.#compileObjectGroup(group, buffer, parent)); | ||
return buffer.toString(); | ||
} | ||
/** | ||
* Compiles an object groups recursively | ||
*/ | ||
#compileObjectGroup(group, buffer, parent) { | ||
group.conditions.forEach((condition, index) => { | ||
const guardBuffer = buffer.child(); | ||
condition.schema.properties.forEach((child) => { | ||
this.#compiler.compileNode(child, guardBuffer, parent); | ||
}); | ||
condition.schema.groups.forEach((child) => { | ||
this.#compileObjectGroup(child, guardBuffer, parent); | ||
}); | ||
buffer.writeStatement( | ||
defineConditionalGuard({ | ||
variableName: this.field.variableName, | ||
conditional: index === 0 ? "if" : "else if", | ||
conditionalFnRefId: condition.conditionalFnRefId, | ||
guardedCodeSnippet: guardBuffer.toString() | ||
}) | ||
); | ||
}); | ||
if (group.elseConditionalFnRefId && group.conditions.length) { | ||
buffer.writeStatement( | ||
defineElseCondition({ | ||
variableName: this.field.variableName, | ||
conditionalFnRefId: group.elseConditionalFnRefId | ||
}) | ||
); | ||
} | ||
} | ||
compile() { | ||
this.defineField(this.#buffer); | ||
this.#buffer.writeStatement( | ||
defineFieldExistenceValidations({ | ||
allowNull: this.#node.allowNull, | ||
isOptional: this.#node.isOptional, | ||
variableName: this.field.variableName | ||
}) | ||
); | ||
const isObjectValidBlock = defineIsValidGuard({ | ||
variableName: this.field.variableName, | ||
bail: this.#node.bail, | ||
guardedCodeSnippet: `${defineObjectInitialOutput({ | ||
variableName: this.field.variableName, | ||
outputExpression: this.field.outputExpression, | ||
outputValueExpression: "{}" | ||
})}${this.#buffer.newLine}${this.#compileObjectChildren()}${this.#buffer.newLine}${this.#compileObjectGroups()}${this.#buffer.newLine}${defineMoveProperties({ | ||
variableName: this.field.variableName, | ||
allowUnknownProperties: this.#node.allowUnknownProperties, | ||
fieldsToIgnore: this.#node.allowUnknownProperties ? this.#getFieldNames(this.#node) : [] | ||
})}` | ||
}); | ||
const isValueAnObject = defineObjectGuard({ | ||
variableName: this.field.variableName, | ||
guardedCodeSnippet: `${defineFieldValidations({ | ||
variableName: this.field.variableName, | ||
validations: this.#node.validations, | ||
bail: this.#node.bail, | ||
dropMissingCheck: true | ||
})}${isObjectValidBlock}` | ||
}); | ||
this.#buffer.writeStatement( | ||
`${isValueAnObject}${this.#buffer.newLine}${defineFieldNullOutput({ | ||
variableName: this.field.variableName, | ||
allowNull: this.#node.allowNull, | ||
outputExpression: this.field.outputExpression, | ||
conditional: "else if" | ||
})}` | ||
); | ||
} | ||
}; | ||
// src/compiler/fields/root_field.ts | ||
function createRootField(parent) { | ||
return { | ||
parentValueExpression: parent.variableName, | ||
fieldNameExpression: `''`, | ||
fieldPathExpression: `''`, | ||
wildCardPath: "", | ||
variableName: `${parent.variableName}_item`, | ||
valueExpression: "root", | ||
outputExpression: parent.outputExpression, | ||
isArrayMember: false | ||
}; | ||
} | ||
// src/scripts/field/value_output.ts | ||
function defineFieldValueOutput({ | ||
variableName, | ||
outputExpression, | ||
transformFnRefId | ||
}) { | ||
const outputValueExpression = transformFnRefId ? `refs['${transformFnRefId}'](${variableName}.value, ${variableName})` : `${variableName}.value`; | ||
return `if (${variableName}.isDefined && ${variableName}.isValid) { | ||
${outputExpression} = ${outputValueExpression}; | ||
}`; | ||
} | ||
// src/compiler/nodes/literal.ts | ||
var LiteralNodeCompiler = class extends BaseNode { | ||
#node; | ||
#buffer; | ||
constructor(node, buffer, compiler, parent, parentField) { | ||
super(node, compiler, parent, parentField); | ||
this.#node = node; | ||
this.#buffer = buffer; | ||
} | ||
compile() { | ||
this.defineField(this.#buffer); | ||
this.#buffer.writeStatement( | ||
defineFieldExistenceValidations({ | ||
allowNull: this.#node.allowNull, | ||
isOptional: this.#node.isOptional, | ||
variableName: this.field.variableName | ||
}) | ||
); | ||
this.#buffer.writeStatement( | ||
defineFieldValidations({ | ||
variableName: this.field.variableName, | ||
validations: this.#node.validations, | ||
bail: this.#node.bail, | ||
dropMissingCheck: false | ||
}) | ||
); | ||
this.#buffer.writeStatement( | ||
`${defineFieldValueOutput({ | ||
variableName: this.field.variableName, | ||
outputExpression: this.field.outputExpression, | ||
transformFnRefId: this.#node.transformFnId | ||
})}${this.#buffer.newLine}${defineFieldNullOutput({ | ||
variableName: this.field.variableName, | ||
allowNull: this.#node.allowNull, | ||
outputExpression: this.field.outputExpression, | ||
transformFnRefId: this.#node.transformFnId, | ||
conditional: "else if" | ||
})}` | ||
); | ||
} | ||
}; | ||
// src/compiler/fields/array_field.ts | ||
function createArrayField(parent) { | ||
const wildCardPath = parent.wildCardPath !== "" ? `${parent.wildCardPath}.*` : `*`; | ||
return { | ||
parentValueExpression: `${parent.variableName}.value`, | ||
fieldNameExpression: `${parent.variableName}_i`, | ||
fieldPathExpression: wildCardPath, | ||
wildCardPath, | ||
variableName: `${parent.variableName}_item`, | ||
valueExpression: `${parent.variableName}.value[${parent.variableName}_i]`, | ||
outputExpression: `${parent.variableName}_out[${parent.variableName}_i]`, | ||
isArrayMember: true | ||
}; | ||
} | ||
// src/compiler/fields/tuple_field.ts | ||
function createTupleField(node, parent) { | ||
const wildCardPath = parent.wildCardPath !== "" ? `${parent.wildCardPath}.${node.fieldName}` : node.fieldName; | ||
return { | ||
parentValueExpression: `${parent.variableName}.value`, | ||
fieldNameExpression: `${node.fieldName}`, | ||
fieldPathExpression: wildCardPath, | ||
wildCardPath, | ||
variableName: `${parent.variableName}_item_${node.fieldName}`, | ||
valueExpression: `${parent.variableName}.value[${node.fieldName}]`, | ||
outputExpression: `${parent.variableName}_out[${node.propertyName}]`, | ||
isArrayMember: true | ||
}; | ||
} | ||
// src/scripts/report_errors.ts | ||
function reportErrors() { | ||
return `if(errorReporter.hasErrors) { | ||
throw errorReporter.createError(); | ||
}`; | ||
} | ||
// src/compiler/fields/object_field.ts | ||
function createObjectField(node, variablesCounter, parent) { | ||
const wildCardPath = parent.wildCardPath !== "" ? `${parent.wildCardPath}.${node.fieldName}` : node.fieldName; | ||
return { | ||
parentValueExpression: `${parent.variableName}.value`, | ||
fieldNameExpression: `'${node.fieldName}'`, | ||
fieldPathExpression: wildCardPath, | ||
wildCardPath, | ||
variableName: `${node.propertyName}_${variablesCounter}`, | ||
valueExpression: `${parent.variableName}.value['${node.fieldName}']`, | ||
outputExpression: `${parent.variableName}_out['${node.propertyName}']`, | ||
isArrayMember: false | ||
}; | ||
} | ||
// src/compiler/fields/record_field.ts | ||
function createRecordField(parent) { | ||
const wildCardPath = parent.wildCardPath !== "" ? `${parent.wildCardPath}.*` : `*`; | ||
return { | ||
parentValueExpression: `${parent.variableName}.value`, | ||
fieldNameExpression: `${parent.variableName}_i`, | ||
fieldPathExpression: wildCardPath, | ||
wildCardPath, | ||
variableName: `${parent.variableName}_item`, | ||
valueExpression: `${parent.variableName}.value[${parent.variableName}_i]`, | ||
outputExpression: `${parent.variableName}_out[${parent.variableName}_i]`, | ||
isArrayMember: false | ||
}; | ||
} | ||
// src/scripts/define_inline_functions.ts | ||
function defineInlineFunctions(options) { | ||
return `function report(message, rule, field, args) { | ||
field.isValid = false; | ||
errorReporter.report(messagesProvider.getMessage(message, rule, field, args), rule, field, args); | ||
}; | ||
function defineValue(value, field) { | ||
${options.convertEmptyStringsToNull ? `if (value === '') { value = null; }` : ""} | ||
field.value = value; | ||
field.isDefined = value !== undefined && value !== null; | ||
return field; | ||
}; | ||
function ensureExists(field) { | ||
if (field.value === undefined || field.value === null) { | ||
field.report(REQUIRED, 'required', field); | ||
return false; | ||
} | ||
return true; | ||
}; | ||
function ensureIsDefined(field) { | ||
if (field.value === undefined) { | ||
field.report(REQUIRED, 'required', field); | ||
return false; | ||
} | ||
return true; | ||
}; | ||
function ensureIsObject(field) { | ||
if (!field.isDefined) { | ||
return false; | ||
} | ||
if (typeof field.value == 'object' && !Array.isArray(field.value)) { | ||
return true; | ||
} | ||
field.report(NOT_AN_OBJECT, 'object', field); | ||
return false; | ||
}; | ||
function ensureIsArray(field) { | ||
if (!field.isDefined) { | ||
return false; | ||
} | ||
if (Array.isArray(field.value)) { | ||
return true; | ||
} | ||
field.report(NOT_AN_ARRAY, 'array', field); | ||
return false; | ||
}; | ||
function copyProperties(val) { | ||
let k, out, tmp; | ||
if (Array.isArray(val)) { | ||
out = Array((k = val.length)) | ||
while (k--) out[k] = (tmp = val[k]) && typeof tmp == 'object' ? copyProperties(tmp) : tmp | ||
return out | ||
} | ||
if (Object.prototype.toString.call(val) === '[object Object]') { | ||
out = {} // null | ||
for (k in val) { | ||
out[k] = (tmp = val[k]) && typeof tmp == 'object' ? copyProperties(tmp) : tmp | ||
} | ||
return out | ||
} | ||
return val | ||
}; | ||
function moveProperties(source, destination, ignoreKeys) { | ||
for (let key in source) { | ||
if (!ignoreKeys.includes(key)) { | ||
const value = source[key] | ||
destination[key] = copyProperties(value) | ||
} | ||
} | ||
};`; | ||
} | ||
// src/scripts/define_error_messages.ts | ||
function defineInlineErrorMessages(messages) { | ||
return `const REQUIRED = '${messages.required}'; | ||
const NOT_AN_OBJECT = '${messages.object}'; | ||
const NOT_AN_ARRAY = '${messages.array}';`; | ||
} | ||
// src/compiler/main.ts | ||
var AsyncFunction = Object.getPrototypeOf(async function() { | ||
}).constructor; | ||
var Compiler = class { | ||
/** | ||
* Variables counter is used to generate unique variable | ||
* names with a counter suffix. | ||
*/ | ||
variablesCounter = 0; | ||
/** | ||
* An array of nodes to process | ||
*/ | ||
#rootNode; | ||
/** | ||
* Options to configure the compiler behavior | ||
*/ | ||
#options; | ||
/** | ||
* Buffer for collection the JS output string | ||
*/ | ||
#buffer = new CompilerBuffer(); | ||
constructor(rootNode, options) { | ||
this.#rootNode = rootNode; | ||
this.#options = options || { convertEmptyStringsToNull: false }; | ||
} | ||
/** | ||
* Initiates the JS output | ||
*/ | ||
#initiateJSOutput() { | ||
this.#buffer.writeStatement( | ||
defineInlineErrorMessages({ | ||
required: "value is required", | ||
object: "value is not a valid object", | ||
array: "value is not a valid array", | ||
...this.#options.messages | ||
}) | ||
); | ||
this.#buffer.writeStatement(defineInlineFunctions(this.#options)); | ||
this.#buffer.writeStatement("let out;"); | ||
} | ||
/** | ||
* Finished the JS output | ||
*/ | ||
#finishJSOutput() { | ||
this.#buffer.writeStatement(reportErrors()); | ||
this.#buffer.writeStatement("return out;"); | ||
} | ||
/** | ||
* Compiles all the nodes | ||
*/ | ||
#compileNodes() { | ||
this.compileNode(this.#rootNode.schema, this.#buffer, { | ||
type: "root", | ||
variableName: "root", | ||
outputExpression: "out", | ||
fieldPathExpression: "out", | ||
wildCardPath: "" | ||
}); | ||
} | ||
/** | ||
* Returns compiled output as a function | ||
*/ | ||
#toAsyncFunction() { | ||
return new AsyncFunction( | ||
"root", | ||
"meta", | ||
"refs", | ||
"messagesProvider", | ||
"errorReporter", | ||
this.#buffer.toString() | ||
); | ||
} | ||
/** | ||
* Converts a node to a field. Optionally accepts a parent node to create | ||
* a field for a specific parent type. | ||
*/ | ||
createFieldFor(node, parent) { | ||
switch (parent.type) { | ||
case "array": | ||
return createArrayField(parent); | ||
case "root": | ||
return createRootField(parent); | ||
case "object": | ||
return createObjectField(node, this.variablesCounter, parent); | ||
case "tuple": | ||
return createTupleField(node, parent); | ||
case "record": | ||
return createRecordField(parent); | ||
} | ||
} | ||
/** | ||
* Compiles a given compiler node | ||
*/ | ||
compileNode(node, buffer, parent, parentField) { | ||
switch (node.type) { | ||
case "literal": | ||
return new LiteralNodeCompiler(node, buffer, this, parent, parentField).compile(); | ||
case "array": | ||
return new ArrayNodeCompiler(node, buffer, this, parent, parentField).compile(); | ||
case "record": | ||
return new RecordNodeCompiler(node, buffer, this, parent, parentField).compile(); | ||
case "object": | ||
return new ObjectNodeCompiler(node, buffer, this, parent, parentField).compile(); | ||
case "tuple": | ||
return new TupleNodeCompiler(node, buffer, this, parent, parentField).compile(); | ||
case "union": | ||
return new UnionNodeCompiler(node, buffer, this, parent, parentField).compile(); | ||
} | ||
} | ||
/** | ||
* Compile schema nodes to an async function | ||
*/ | ||
compile() { | ||
this.#initiateJSOutput(); | ||
this.#compileNodes(); | ||
this.#finishJSOutput(); | ||
const outputFunction = this.#toAsyncFunction(); | ||
this.variablesCounter = 0; | ||
this.#buffer.flush(); | ||
return outputFunction; | ||
} | ||
}; | ||
// src/refs_builder.ts | ||
function refsBuilder() { | ||
let counter = 0; | ||
const refs = {}; | ||
return { | ||
toJSON() { | ||
return refs; | ||
}, | ||
/** | ||
* Track a value inside refs | ||
*/ | ||
track(value) { | ||
counter++; | ||
const ref = `ref://${counter}`; | ||
refs[ref] = value; | ||
return ref; | ||
}, | ||
/** | ||
* Track a validation inside refs | ||
*/ | ||
trackValidation(validation) { | ||
return this.track(validation); | ||
}, | ||
/** | ||
* Track input value parser inside refs | ||
*/ | ||
trackParser(fn) { | ||
return this.track(fn); | ||
}, | ||
/** | ||
* Track output value transformer inside refs | ||
*/ | ||
trackTransformer(fn) { | ||
return this.track(fn); | ||
}, | ||
/** | ||
* Track a conditional inside refs | ||
*/ | ||
trackConditional(fn) { | ||
return this.track(fn); | ||
} | ||
}; | ||
} | ||
export { | ||
Compiler, | ||
refsBuilder | ||
}; |
/** | ||
* Represenation of a ref id | ||
*/ | ||
export type RefIdentifier = `ref://${number}`; | ||
type RefIdentifier = `ref://${number}`; | ||
/** | ||
* Allowed values for refs | ||
*/ | ||
export type Refs = Record<RefIdentifier, ValidationRule | TransformFn<any, any> | ParseFn | ConditionalFn<any>>; | ||
type Refs = Record<RefIdentifier, ValidationRule | TransformFn<any, any> | ParseFn | ConditionalFn<any>>; | ||
/** | ||
@@ -13,3 +13,3 @@ * Refs store to track runtime values as refs with | ||
*/ | ||
export type RefsStore = { | ||
type RefsStore = { | ||
toJSON(): Refs; | ||
@@ -41,3 +41,3 @@ /** | ||
*/ | ||
export type FieldContext = { | ||
type FieldContext = { | ||
/** | ||
@@ -98,3 +98,3 @@ * Field value | ||
*/ | ||
export type ValidationRule = { | ||
type ValidationRule = { | ||
/** | ||
@@ -112,15 +112,15 @@ * Performs validation | ||
*/ | ||
export type ParseFn = (value: unknown, ctx: Pick<FieldContext, 'data' | 'parent' | 'meta'>) => any; | ||
type ParseFn = (value: unknown, ctx: Pick<FieldContext, 'data' | 'parent' | 'meta'>) => any; | ||
/** | ||
* The shape of transform function picked from the refs | ||
*/ | ||
export type TransformFn<Input, Output> = (value: Input, field: FieldContext) => Output; | ||
type TransformFn<Input, Output> = (value: Input, field: FieldContext) => Output; | ||
/** | ||
* The shape of conditional function used for narrowing down unions. | ||
*/ | ||
export type ConditionalFn<Input> = (value: Input, field: FieldContext) => boolean; | ||
type ConditionalFn<Input> = (value: Input, field: FieldContext) => boolean; | ||
/** | ||
* Shape of a validation rule accepted by the compiler | ||
*/ | ||
export type ValidationNode = { | ||
type ValidationNode = { | ||
/** | ||
@@ -143,3 +143,3 @@ * Rule implementation function id. | ||
*/ | ||
export type FieldNode = { | ||
type FieldNode = { | ||
/** | ||
@@ -184,3 +184,3 @@ * Should the validation cycle stop after the first error. | ||
*/ | ||
export type LiteralNode = FieldNode & { | ||
type LiteralNode = FieldNode & { | ||
type: 'literal'; | ||
@@ -197,3 +197,3 @@ /** | ||
*/ | ||
export type ObjectNode = FieldNode & { | ||
type ObjectNode = FieldNode & { | ||
type: 'object'; | ||
@@ -222,3 +222,3 @@ /** | ||
*/ | ||
export type ObjectGroupNode = { | ||
type ObjectGroupNode = { | ||
type: 'group'; | ||
@@ -259,3 +259,3 @@ /** | ||
*/ | ||
export type TupleNode = FieldNode & { | ||
type TupleNode = FieldNode & { | ||
type: 'tuple'; | ||
@@ -277,3 +277,3 @@ /** | ||
*/ | ||
export type RecordNode = FieldNode & { | ||
type RecordNode = FieldNode & { | ||
type: 'record'; | ||
@@ -288,3 +288,3 @@ /** | ||
*/ | ||
export type ArrayNode = FieldNode & { | ||
type ArrayNode = FieldNode & { | ||
type: 'array'; | ||
@@ -300,3 +300,3 @@ /** | ||
*/ | ||
export type UnionNode = { | ||
type UnionNode = { | ||
type: 'union'; | ||
@@ -335,3 +335,3 @@ /** | ||
*/ | ||
export type RootNode = { | ||
type RootNode = { | ||
type: 'root'; | ||
@@ -346,3 +346,3 @@ /** | ||
*/ | ||
export type CompilerNodes = LiteralNode | ObjectNode | ArrayNode | UnionNode | RecordNode | TupleNode; | ||
type CompilerNodes = LiteralNode | ObjectNode | ArrayNode | UnionNode | RecordNode | TupleNode; | ||
/** | ||
@@ -352,3 +352,3 @@ * Properties of a parent node as the compiler loops through the | ||
*/ | ||
export type CompilerParent = { | ||
type CompilerParent = { | ||
type: 'array' | 'object' | 'tuple' | 'record' | 'root'; | ||
@@ -378,3 +378,3 @@ /** | ||
*/ | ||
export type CompilerField = { | ||
type CompilerField = { | ||
parentValueExpression: string; | ||
@@ -393,3 +393,3 @@ fieldNameExpression: string; | ||
*/ | ||
export interface ErrorReporterContract { | ||
interface ErrorReporterContract { | ||
/** | ||
@@ -413,3 +413,3 @@ * A boolean to known if there are one or more | ||
*/ | ||
export interface MessagesProviderContact { | ||
interface MessagesProviderContact { | ||
/** | ||
@@ -424,3 +424,3 @@ * Returns a validation message for a given field + rule. The args | ||
*/ | ||
export type CompilerOptions = { | ||
type CompilerOptions = { | ||
/** | ||
@@ -441,1 +441,3 @@ * Convert empty string values to null for sake of | ||
}; | ||
export { ArrayNode, CompilerField, CompilerNodes, CompilerOptions, CompilerParent, ConditionalFn, ErrorReporterContract, FieldContext, FieldNode, LiteralNode, MessagesProviderContact, ObjectGroupNode, ObjectNode, ParseFn, RecordNode, RefIdentifier, Refs, RefsStore, RootNode, TransformFn, TupleNode, UnionNode, ValidationNode, ValidationRule }; |
{ | ||
"name": "@vinejs/compiler", | ||
"version": "2.1.2", | ||
"version": "2.2.0", | ||
"description": "Low level compiler for VineJS validator", | ||
@@ -8,8 +8,3 @@ "type": "module", | ||
"files": [ | ||
"src", | ||
"index.ts", | ||
"build/src", | ||
"build/index.d.ts", | ||
"build/index.d.ts.map", | ||
"build/index.js" | ||
"build" | ||
], | ||
@@ -22,3 +17,2 @@ "exports": { | ||
"clean": "del-cli build", | ||
"copy:templates": "copyfiles \"stubs/**/*.stub\" build", | ||
"typecheck": "tsc --noEmit", | ||
@@ -31,4 +25,3 @@ "lint": "eslint . --ext=.ts", | ||
"prebuild": "npm run lint && npm run clean", | ||
"build": "tsc", | ||
"postbuild": "npm run copy:templates", | ||
"build": "tsup-node", | ||
"release": "np", | ||
@@ -50,17 +43,17 @@ "version": "npm run build", | ||
"@japa/assert": "^2.0.0-1", | ||
"@japa/runner": "^3.0.0-5", | ||
"@swc/core": "^1.3.70", | ||
"@types/node": "^20.4.2", | ||
"@japa/runner": "^3.0.0-6", | ||
"@swc/core": "^1.3.71", | ||
"@types/node": "^20.4.5", | ||
"acorn": "^8.10.0", | ||
"ajv": "^8.12.0", | ||
"benchmark": "^2.1.4", | ||
"c8": "^8.0.0", | ||
"copyfiles": "^2.4.1", | ||
"c8": "^8.0.1", | ||
"del-cli": "^5.0.0", | ||
"eslint": "^8.45.0", | ||
"js-beautify": "^1.14.8", | ||
"js-beautify": "^1.14.9", | ||
"np": "^8.0.3", | ||
"prettier": "^3.0.0", | ||
"tinybench": "^2.4.0", | ||
"tinybench": "^2.5.0", | ||
"ts-node": "^10.9.1", | ||
"tsup": "^7.1.0", | ||
"typescript": "^5.1.6", | ||
@@ -104,3 +97,14 @@ "zod": "^3.21.4" | ||
}, | ||
"prettier": "@adonisjs/prettier-config" | ||
"prettier": "@adonisjs/prettier-config", | ||
"tsup": { | ||
"entry": [ | ||
"./index.ts", | ||
"./src/types.ts" | ||
], | ||
"outDir": "./build", | ||
"clean": true, | ||
"format": "esm", | ||
"dts": true, | ||
"target": "esnext" | ||
} | ||
} |
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
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
54437
7
1497
2