parent
9167a16641
commit
02f97e34f4
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,3 @@ |
||||
{ |
||||
"extends": ["../../.eslintrc"] |
||||
} |
@ -0,0 +1,879 @@ |
||||
# ShexJ Traverser |
||||
|
||||
Traverse a ShexJ schema with custom functionality. |
||||
|
||||
## Installation |
||||
```bash |
||||
npm i shexj-traverser |
||||
``` |
||||
|
||||
## Tutorial |
||||
This library uses `type-traverser`. To learn more about how type traverser can be used, go to the [Type Traverser Tutorial](https://github.com/o-development/type-traverser/blob/master/README.md#using-a-traverser). |
||||
|
||||
## Usage |
||||
There are two ways you can use the ShexJ-Traverser: |
||||
- A `visitor` |
||||
- A `transformer` |
||||
|
||||
### Visitor |
||||
This visitor shows all the possible visitor functions you can use. Note that you do not need to define every visitor function, only the ones you care about. |
||||
|
||||
Run `npm run start` to see this code in action. |
||||
|
||||
```typescript |
||||
const shexJVisitor = shexJTraverser.createVisitor<undefined>({ |
||||
Schema: { |
||||
visitor: async (_item, _context) => { |
||||
console.log("Schema"); |
||||
}, |
||||
properties: { |
||||
startActs: async (_item, _context) => { |
||||
console.log("Schema.startActs"); |
||||
}, |
||||
start: async (_item, _context) => { |
||||
console.log("Schema.start"); |
||||
}, |
||||
imports: async (_item, _context) => { |
||||
console.log("Schema.imports"); |
||||
}, |
||||
shapes: async (_item, _context) => { |
||||
console.log("Schema.shape"); |
||||
}, |
||||
}, |
||||
}, |
||||
shapeExpr: async (_item, _context) => { |
||||
console.log("shapeExpr"); |
||||
}, |
||||
ShapeOr: { |
||||
visitor: async (_item, _context) => { |
||||
console.log("ShapeOr"); |
||||
}, |
||||
properties: { |
||||
id: async (_item, _context) => { |
||||
console.log("ShapeOr.id"); |
||||
}, |
||||
shapeExprs: async (_item, _context) => { |
||||
console.log("ShapeOr.shapeExprs"); |
||||
}, |
||||
}, |
||||
}, |
||||
ShapeAnd: { |
||||
visitor: async (_item, _context) => { |
||||
console.log("ShapeAnd"); |
||||
}, |
||||
properties: { |
||||
id: async (_item, _context) => { |
||||
console.log("ShapeAnd.id"); |
||||
}, |
||||
shapeExprs: async (_item, _context) => { |
||||
console.log("ShapeAnd.shapeExprs"); |
||||
}, |
||||
}, |
||||
}, |
||||
ShapeNot: { |
||||
visitor: async (_item, _context) => { |
||||
console.log("ShapeNot"); |
||||
}, |
||||
properties: { |
||||
id: async (_item, _context) => { |
||||
console.log("ShapeNot.id"); |
||||
}, |
||||
shapeExpr: async (_item, _context) => { |
||||
console.log("ShapeNot.shapeExpr"); |
||||
}, |
||||
}, |
||||
}, |
||||
ShapeExternal: { |
||||
visitor: async (_item, _context) => { |
||||
console.log("ShapeExternal"); |
||||
}, |
||||
properties: { |
||||
id: async (_item, _context) => { |
||||
console.log("ShapeExternal.id"); |
||||
}, |
||||
}, |
||||
}, |
||||
shapeExprRef: async (_item, _context) => { |
||||
console.log("shapeExprRef"); |
||||
}, |
||||
NodeConstraint: { |
||||
visitor: async (_item, _context) => { |
||||
console.log("NodeConstraint"); |
||||
}, |
||||
properties: { |
||||
id: async (_item, _context) => { |
||||
console.log("NodeConstraint.id"); |
||||
}, |
||||
datatype: async (_item, _context) => { |
||||
console.log("NodeConstraint.datatype"); |
||||
}, |
||||
values: async (_item, _context) => { |
||||
console.log("NodeConstraint.values"); |
||||
}, |
||||
length: async (_item, _context) => { |
||||
console.log("NodeConstraint.length"); |
||||
}, |
||||
minlength: async (_item, _context) => { |
||||
console.log("NodeConstraint.minlength"); |
||||
}, |
||||
maxlength: async (_item, _context) => { |
||||
console.log("NodeConstraint.maxlength"); |
||||
}, |
||||
pattern: async (_item, _context) => { |
||||
console.log("NodeConstraint.pattern"); |
||||
}, |
||||
flags: async (_item, _context) => { |
||||
console.log("NodeConstraint.flags"); |
||||
}, |
||||
mininclusive: async (_item, _context) => { |
||||
console.log("NodeConstraint.mininclusive"); |
||||
}, |
||||
minexclusive: async (_item, _context) => { |
||||
console.log("NodeConstraint.minexclusive"); |
||||
}, |
||||
maxinclusive: async (_item, _context) => { |
||||
console.log("NodeConstraint.maxinclusive"); |
||||
}, |
||||
maxexclusive: async (_item, _context) => { |
||||
console.log("NodeConstraint.maxexclusive"); |
||||
}, |
||||
totaldigits: async (_item, _context) => { |
||||
console.log("NodeConstraint.totaldigits"); |
||||
}, |
||||
fractiondigits: async (_item, _context) => { |
||||
console.log("NodeConstraints.fractiondigits"); |
||||
}, |
||||
}, |
||||
}, |
||||
numericLiteral: async (_item, _context) => { |
||||
console.log("numericLiteral"); |
||||
}, |
||||
valueSetValue: async (_item, _context) => { |
||||
console.log("valueSetValue"); |
||||
}, |
||||
objectValue: async (_item, _context) => { |
||||
console.log("objectValue"); |
||||
}, |
||||
ObjectLiteral: { |
||||
visitor: async (_item, _context) => { |
||||
console.log("ObjectLiteral"); |
||||
}, |
||||
properties: { |
||||
value: async (_item, _context) => { |
||||
console.log("ObjectLiteral.value"); |
||||
}, |
||||
language: async (_item, _context) => { |
||||
console.log("ObjectLiteral.language"); |
||||
}, |
||||
type: async (_item, _context) => { |
||||
console.log("ObjectLiteral.type"); |
||||
}, |
||||
}, |
||||
}, |
||||
IriStem: { |
||||
visitor: async (_item, _context) => { |
||||
console.log("IriStem"); |
||||
}, |
||||
properties: { |
||||
stem: async (_item, _context) => { |
||||
console.log("IriStem.stem"); |
||||
}, |
||||
}, |
||||
}, |
||||
IriStemRange: { |
||||
visitor: async (_item, _context) => { |
||||
console.log("IriStemRange"); |
||||
}, |
||||
properties: { |
||||
stem: async (_item, _context) => { |
||||
console.log("IriStemRange.stem"); |
||||
}, |
||||
exclusions: async (_item, _context) => { |
||||
console.log("IriStemRange.exclusions"); |
||||
}, |
||||
}, |
||||
}, |
||||
IriStemRangeStem: async (_item, _context) => { |
||||
console.log("IriStemRangeStem"); |
||||
}, |
||||
IriStemRangeExclusions: async (_item, _context) => { |
||||
console.log("IriStemRangeExclusions"); |
||||
}, |
||||
LiteralStem: { |
||||
visitor: async (_item, _context) => { |
||||
console.log("LiteralStem"); |
||||
}, |
||||
properties: { |
||||
stem: async (_item, _context) => { |
||||
console.log("LiteralStem.stem"); |
||||
}, |
||||
}, |
||||
}, |
||||
LiteralStemRange: { |
||||
visitor: async (_item, _context) => { |
||||
console.log("LiteralStemRange"); |
||||
}, |
||||
properties: { |
||||
stem: async (_item, _context) => { |
||||
console.log("LiteralStemRange.stem"); |
||||
}, |
||||
exclusions: async (_item, _context) => { |
||||
console.log("LiteralStemRange.exclusions"); |
||||
}, |
||||
}, |
||||
}, |
||||
LiteralStemRangeStem: async (_item, _context) => { |
||||
console.log("LiteralStemRangeStem"); |
||||
}, |
||||
LiteralStemRangeExclusions: async (_item, _context) => { |
||||
console.log("LiteralStemRangeExclusions"); |
||||
}, |
||||
Language: { |
||||
visitor: async (_item, _context) => { |
||||
console.log("Language"); |
||||
}, |
||||
properties: { |
||||
languageTag: async (_item, _context) => { |
||||
console.log("Language.languageTag"); |
||||
}, |
||||
}, |
||||
}, |
||||
LanguageStem: { |
||||
visitor: async (_item, _context) => { |
||||
console.log("LanguageStem"); |
||||
}, |
||||
properties: { |
||||
stem: async (_item, _context) => { |
||||
console.log("LanguageStem.stem"); |
||||
}, |
||||
}, |
||||
}, |
||||
LanguageStemRange: { |
||||
visitor: async (_item, _context) => { |
||||
console.log("LanguageStemRange"); |
||||
}, |
||||
properties: { |
||||
stem: async (_item, _context) => { |
||||
console.log("LanguageStemRange.stem"); |
||||
}, |
||||
exclusions: async (_item, _context) => { |
||||
console.log("LanguageStemRange.exclusions"); |
||||
}, |
||||
}, |
||||
}, |
||||
LanguageStemRangeStem: async (_item, _context) => { |
||||
console.log("LanguageStemRangeStem"); |
||||
}, |
||||
LanguageStemRangeExclusions: async (_item, _context) => { |
||||
console.log("LanguageStemRangeExclusions"); |
||||
}, |
||||
Wildcard: { |
||||
visitor: async (_item, _context) => { |
||||
console.log("Wildcard"); |
||||
}, |
||||
}, |
||||
Shape: { |
||||
visitor: async (_item, _context) => { |
||||
console.log("Shape"); |
||||
}, |
||||
properties: { |
||||
id: async (_item, _context) => { |
||||
console.log("Shape.id"); |
||||
}, |
||||
closed: async (_item, _context) => { |
||||
console.log("Shape.closed"); |
||||
}, |
||||
extra: async (_item, _context) => { |
||||
console.log("Shape.extra"); |
||||
}, |
||||
expression: async (_item, _context) => { |
||||
console.log("Shape.expression"); |
||||
}, |
||||
semActs: async (_item, _context) => { |
||||
console.log("Shape.semActs"); |
||||
}, |
||||
annotations: async (_item, _context) => { |
||||
console.log("Shape.annotations"); |
||||
}, |
||||
}, |
||||
}, |
||||
tripleExpr: async (_item, _context) => { |
||||
console.log("tripleExpr"); |
||||
}, |
||||
EachOf: { |
||||
visitor: async (_item, _context) => { |
||||
console.log("EachOf"); |
||||
}, |
||||
properties: { |
||||
expressions: async (_item, _context) => { |
||||
console.log("EachOf.expressions"); |
||||
}, |
||||
id: async (_item, _context) => { |
||||
console.log("EachOf.id"); |
||||
}, |
||||
min: async (_item, _context) => { |
||||
console.log("EachOf.min"); |
||||
}, |
||||
max: async (_item, _context) => { |
||||
console.log("EachOf.max"); |
||||
}, |
||||
semActs: async (_item, _context) => { |
||||
console.log("EachOf.semActs"); |
||||
}, |
||||
annotations: async (_item, _context) => { |
||||
console.log("EachOf.annotations"); |
||||
}, |
||||
}, |
||||
}, |
||||
OneOf: { |
||||
visitor: async (_item, _context) => { |
||||
console.log("OneOf"); |
||||
}, |
||||
properties: { |
||||
expressions: async (_item, _context) => { |
||||
console.log("OneOf.expressions"); |
||||
}, |
||||
id: async (_item, _context) => { |
||||
console.log("OneOf.id"); |
||||
}, |
||||
min: async (_item, _context) => { |
||||
console.log("OneOf.min"); |
||||
}, |
||||
max: async (_item, _context) => { |
||||
console.log("OneOf.max"); |
||||
}, |
||||
semActs: async (_item, _context) => { |
||||
console.log("OneOf.semActs"); |
||||
}, |
||||
annotations: async (_item, _context) => { |
||||
console.log("OneOf.annotations"); |
||||
}, |
||||
}, |
||||
}, |
||||
TripleConstraint: { |
||||
visitor: async (_item, _context) => { |
||||
console.log("TripleConstraint"); |
||||
}, |
||||
properties: { |
||||
inverse: async (_item, _context) => { |
||||
console.log("TripleConstraint.inverse"); |
||||
}, |
||||
predicate: async (_item, _context) => { |
||||
console.log("TripleConstraint.predicate"); |
||||
}, |
||||
valueExpr: async (_item, _context) => { |
||||
console.log("TripleConstraint.valueExpr"); |
||||
}, |
||||
id: async (_item, _context) => { |
||||
console.log("TripleConstraint.id"); |
||||
}, |
||||
min: async (_item, _context) => { |
||||
console.log("TripleConstraint.min"); |
||||
}, |
||||
max: async (_item, _context) => { |
||||
console.log("TripleConstraint.max"); |
||||
}, |
||||
semActs: async (_item, _context) => { |
||||
console.log("TripleConstraint.semActs"); |
||||
}, |
||||
annotations: async (_item, _context) => { |
||||
console.log("TripleConstraint.annotations"); |
||||
}, |
||||
}, |
||||
}, |
||||
tripleExprRef: async (_item, _context) => { |
||||
console.log("tripleExprRef"); |
||||
}, |
||||
SemAct: { |
||||
visitor: async (_item, _context) => { |
||||
console.log("SemAct"); |
||||
}, |
||||
properties: { |
||||
name: async (_item, _context) => { |
||||
console.log("SemAct.name"); |
||||
}, |
||||
code: async (_item, _context) => { |
||||
console.log("SemAct.code"); |
||||
}, |
||||
}, |
||||
}, |
||||
Annotation: { |
||||
visitor: async (_item, _context) => { |
||||
console.log("Annotation"); |
||||
}, |
||||
properties: { |
||||
predicate: async (_item, _context) => { |
||||
console.log("Annotation.predicate"); |
||||
}, |
||||
object: async (_item, _context) => { |
||||
console.log("Annotation.object"); |
||||
}, |
||||
}, |
||||
}, |
||||
IRIREF: async (_item, _context) => { |
||||
console.log("IRIREF"); |
||||
}, |
||||
STRING: async (_item, _context) => { |
||||
console.log("STRING"); |
||||
}, |
||||
LANGTAG: async (_item, _context) => { |
||||
console.log("LANGTAG"); |
||||
}, |
||||
INTEGER: async (_item, _context) => { |
||||
console.log("INTEGER"); |
||||
}, |
||||
BOOL: async (_item, _context) => { |
||||
console.log("BOOL"); |
||||
}, |
||||
IRI: async (_item, _context) => { |
||||
console.log("IRI"); |
||||
}, |
||||
}); |
||||
|
||||
/** |
||||
* Sample Data |
||||
*/ |
||||
const simpleSchema: Schema = { |
||||
type: "Schema", |
||||
shapes: [ |
||||
{ |
||||
type: "Shape", |
||||
id: "http://shex.io/webapps/shex.js/doc/EmployeeShape", |
||||
expression: { |
||||
type: "EachOf", |
||||
expressions: [ |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://xmlns.com/foaf/0.1/givenName", |
||||
valueExpr: { |
||||
type: "NodeConstraint", |
||||
datatype: "http://www.w3.org/2001/XMLSchema#string", |
||||
}, |
||||
min: 1, |
||||
max: -1, |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://xmlns.com/foaf/0.1/familyName", |
||||
valueExpr: { |
||||
type: "NodeConstraint", |
||||
datatype: "http://www.w3.org/2001/XMLSchema#string", |
||||
}, |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://xmlns.com/foaf/0.1/phone", |
||||
valueExpr: { |
||||
type: "NodeConstraint", |
||||
nodeKind: "iri", |
||||
}, |
||||
min: 0, |
||||
max: -1, |
||||
}, |
||||
{ |
||||
type: "TripleConstraint", |
||||
predicate: "http://xmlns.com/foaf/0.1/mbox", |
||||
valueExpr: { |
||||
type: "NodeConstraint", |
||||
nodeKind: "iri", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
}, |
||||
], |
||||
"@context": "http://www.w3.org/ns/shex.jsonld", |
||||
}; |
||||
|
||||
await shexJVisitor.visit(simpleSchema, "Schema", undefined); |
||||
// Logs: |
||||
// Schema |
||||
// Schema.startActs |
||||
// Schema.start |
||||
// Schema.imports |
||||
// Schema.shape |
||||
// shapeExpr |
||||
// Shape |
||||
// Shape.id |
||||
// shapeExprRef |
||||
// Shape.closed |
||||
// Shape.extra |
||||
// Shape.expression |
||||
// tripleExpr |
||||
// EachOf |
||||
// EachOf.expressions |
||||
// tripleExpr |
||||
// TripleConstraint |
||||
// TripleConstraint.inverse |
||||
// TripleConstraint.predicate |
||||
// IRIREF |
||||
// TripleConstraint.valueExpr |
||||
// shapeExpr |
||||
// NodeConstraint |
||||
// NodeConstraint.id |
||||
// NodeConstraint.datatype |
||||
// IRIREF |
||||
// NodeConstraint.values |
||||
// NodeConstraint.length |
||||
// NodeConstraint.minlength |
||||
// NodeConstraint.maxlength |
||||
// NodeConstraint.pattern |
||||
// NodeConstraint.flags |
||||
// NodeConstraint.mininclusive |
||||
// NodeConstraint.minexclusive |
||||
// NodeConstraint.maxinclusive |
||||
// NodeConstraint.maxexclusive |
||||
// NodeConstraint.totaldigits |
||||
// NodeConstraints.fractiondigits |
||||
// TripleConstraint.id |
||||
// TripleConstraint.min |
||||
// INTEGER |
||||
// TripleConstraint.max |
||||
// INTEGER |
||||
// TripleConstraint.semActs |
||||
// TripleConstraint.annotations |
||||
// tripleExpr |
||||
// TripleConstraint |
||||
// TripleConstraint.inverse |
||||
// TripleConstraint.predicate |
||||
// IRIREF |
||||
// TripleConstraint.valueExpr |
||||
// shapeExpr |
||||
// NodeConstraint |
||||
// NodeConstraint.id |
||||
// NodeConstraint.datatype |
||||
// NodeConstraint.values |
||||
// NodeConstraint.length |
||||
// NodeConstraint.minlength |
||||
// NodeConstraint.maxlength |
||||
// NodeConstraint.pattern |
||||
// NodeConstraint.flags |
||||
// NodeConstraint.mininclusive |
||||
// NodeConstraint.minexclusive |
||||
// NodeConstraint.maxinclusive |
||||
// NodeConstraint.maxexclusive |
||||
// NodeConstraint.totaldigits |
||||
// NodeConstraints.fractiondigits |
||||
// TripleConstraint.id |
||||
// TripleConstraint.min |
||||
// TripleConstraint.max |
||||
// TripleConstraint.semActs |
||||
// TripleConstraint.annotations |
||||
// tripleExpr |
||||
// TripleConstraint |
||||
// TripleConstraint.inverse |
||||
// TripleConstraint.predicate |
||||
// IRIREF |
||||
// TripleConstraint.valueExpr |
||||
// shapeExpr |
||||
// NodeConstraint |
||||
// NodeConstraint.id |
||||
// NodeConstraint.datatype |
||||
// NodeConstraint.values |
||||
// NodeConstraint.length |
||||
// NodeConstraint.minlength |
||||
// NodeConstraint.maxlength |
||||
// NodeConstraint.pattern |
||||
// NodeConstraint.flags |
||||
// NodeConstraint.mininclusive |
||||
// NodeConstraint.minexclusive |
||||
// NodeConstraint.maxinclusive |
||||
// NodeConstraint.maxexclusive |
||||
// NodeConstraint.totaldigits |
||||
// NodeConstraints.fractiondigits |
||||
// TripleConstraint.id |
||||
// TripleConstraint.min |
||||
// INTEGER |
||||
// TripleConstraint.max |
||||
// TripleConstraint.semActs |
||||
// TripleConstraint.annotations |
||||
// tripleExpr |
||||
// TripleConstraint |
||||
// TripleConstraint.inverse |
||||
// TripleConstraint.predicate |
||||
// IRIREF |
||||
// TripleConstraint.valueExpr |
||||
// shapeExpr |
||||
// NodeConstraint |
||||
// NodeConstraint.id |
||||
// NodeConstraint.datatype |
||||
// NodeConstraint.values |
||||
// NodeConstraint.length |
||||
// NodeConstraint.minlength |
||||
// NodeConstraint.maxlength |
||||
// NodeConstraint.pattern |
||||
// NodeConstraint.flags |
||||
// NodeConstraint.mininclusive |
||||
// NodeConstraint.minexclusive |
||||
// NodeConstraint.maxinclusive |
||||
// NodeConstraint.maxexclusive |
||||
// NodeConstraint.totaldigits |
||||
// NodeConstraints.fractiondigits |
||||
// TripleConstraint.id |
||||
// TripleConstraint.min |
||||
// TripleConstraint.max |
||||
// TripleConstraint.semActs |
||||
// TripleConstraint.annotations |
||||
// EachOf.id |
||||
// EachOf.min |
||||
// EachOf.max |
||||
// EachOf.semActs |
||||
// EachOf.annotations |
||||
// Shape.semActs |
||||
// Shape.annotations |
||||
``` |
||||
|
||||
### Transformer |
||||
This transformer shows all the possible return types. Note that you do not need to define every return type yourself. This library will automatically deduce return types. |
||||
|
||||
```typescript |
||||
const shexJTransformer = shexJTraverser.createTransformer< |
||||
{ |
||||
Schema: { |
||||
return: string; |
||||
properties: { |
||||
startActs: string; |
||||
start: string; |
||||
imports: string; |
||||
shapes: string; |
||||
}; |
||||
}; |
||||
shapeExpr: { |
||||
return: string; |
||||
}; |
||||
ShapeOr: { |
||||
return: string; |
||||
properties: { |
||||
id: string; |
||||
shapeExprs: string; |
||||
}; |
||||
}; |
||||
ShapeAnd: { |
||||
return: string; |
||||
properties: { |
||||
id: string; |
||||
shapeExprs: string; |
||||
}; |
||||
}; |
||||
ShapeNot: { |
||||
return: string; |
||||
properties: { |
||||
id: string; |
||||
shapeExpr: string; |
||||
}; |
||||
}; |
||||
ShapeExternal: { |
||||
return: string; |
||||
properties: { |
||||
id: string; |
||||
}; |
||||
}; |
||||
shapeExprRef: { |
||||
return: string; |
||||
}; |
||||
NodeConstraint: { |
||||
return: string; |
||||
properties: { |
||||
id: string; |
||||
datatype: string; |
||||
values: string; |
||||
length: string; |
||||
minlength: string; |
||||
maxlength: string; |
||||
pattern: string; |
||||
flags: string; |
||||
mininclusive: string; |
||||
minexclusive: string; |
||||
maxinclusive: string; |
||||
maxexclusive: string; |
||||
totaldigits: string; |
||||
fractiondigits: string; |
||||
}; |
||||
}; |
||||
numericLiteral: { |
||||
return: string; |
||||
}; |
||||
valueSetValue: { |
||||
return: string; |
||||
}; |
||||
objectValue: { |
||||
return: string; |
||||
}; |
||||
ObjectLiteral: { |
||||
return: string; |
||||
properties: { |
||||
value: string; |
||||
language: string; |
||||
type: string; |
||||
}; |
||||
}; |
||||
IriStem: { |
||||
return: string; |
||||
properties: { |
||||
stem: string; |
||||
}; |
||||
}; |
||||
IriStemRange: { |
||||
return: string; |
||||
properties: { |
||||
stem: string; |
||||
exclusions: string; |
||||
}; |
||||
}; |
||||
IriStemRangeStem: { |
||||
return: string; |
||||
}; |
||||
IriStemRangeExclusions: { |
||||
return: string; |
||||
}; |
||||
LiteralStem: { |
||||
return: string; |
||||
properties: { |
||||
stem: string; |
||||
}; |
||||
}; |
||||
LiteralStemRange: { |
||||
return: string; |
||||
properties: { |
||||
stem: string; |
||||
exclusions: string; |
||||
}; |
||||
}; |
||||
LiteralStemRangeStem: { |
||||
return: string; |
||||
}; |
||||
LiteralStemRangeExclusions: { |
||||
return: string; |
||||
}; |
||||
Language: { |
||||
return: string; |
||||
properties: { |
||||
languageTag: string; |
||||
}; |
||||
}; |
||||
LanguageStem: { |
||||
return: string; |
||||
properties: { |
||||
stem: string; |
||||
}; |
||||
}; |
||||
LanguageStemRangeStem: { |
||||
return: string; |
||||
}; |
||||
LanguageStemRangeExclusions: { |
||||
return: string; |
||||
}; |
||||
Wildcard: { |
||||
return: string; |
||||
}; |
||||
Shape: { |
||||
return: string; |
||||
properties: { |
||||
id: string; |
||||
closed: string; |
||||
extra: string; |
||||
expression: string; |
||||
semActs: string; |
||||
annotations: string; |
||||
}; |
||||
}; |
||||
tripleExpr: { |
||||
return: string; |
||||
}; |
||||
EachOf: { |
||||
return: string; |
||||
properties: { |
||||
expressions: string; |
||||
id: string; |
||||
min: string; |
||||
max: string; |
||||
semActs: string; |
||||
annotations: string; |
||||
}; |
||||
}; |
||||
OneOf: { |
||||
return: string; |
||||
properties: { |
||||
expressions: string; |
||||
id: string; |
||||
min: string; |
||||
max: string; |
||||
semActs: string; |
||||
annotations: string; |
||||
}; |
||||
}; |
||||
TripleConstraint: { |
||||
return: string; |
||||
properties: { |
||||
inverse: string; |
||||
predicate: string; |
||||
valueExpr: string; |
||||
id: string; |
||||
min: string; |
||||
max: string; |
||||
semActs: string; |
||||
annotations: string; |
||||
}; |
||||
}; |
||||
tripleExprRef: { |
||||
return: string; |
||||
}; |
||||
SemAct: { |
||||
return: string; |
||||
properties: { |
||||
name: string; |
||||
code: string; |
||||
}; |
||||
}; |
||||
Annotation: { |
||||
return: string; |
||||
properties: { |
||||
predicate: string; |
||||
object: string; |
||||
}; |
||||
}; |
||||
IRIREF: { |
||||
return: string; |
||||
}; |
||||
STRING: { |
||||
return: string; |
||||
}; |
||||
LANGTAG: { |
||||
return: string; |
||||
}; |
||||
INTEGER: { |
||||
return: string; |
||||
}; |
||||
BOOL: { |
||||
return: string; |
||||
}; |
||||
IRI: { |
||||
return: string; |
||||
}; |
||||
}, |
||||
undefined |
||||
>({ |
||||
Schema: { |
||||
transformer: async ( |
||||
item, |
||||
getTransformedChildren, |
||||
_setReturnPointer, |
||||
_context |
||||
) => { |
||||
const children: { |
||||
startActs: string; |
||||
start: string; |
||||
imports: string; |
||||
shapes: string; |
||||
} = await getTransformedChildren(); |
||||
return `Transformer(startActs:${children.startActs},start:${children.start},imports:${children.imports},shapes:${children.shapes})`; |
||||
}, |
||||
}, |
||||
// ... |
||||
}); |
||||
``` |
||||
|
||||
|
||||
|
||||
## Liscense |
||||
MIT |
@ -0,0 +1,6 @@ |
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const sharedConfig = require("../../jest.config.js"); |
||||
module.exports = { |
||||
...sharedConfig, |
||||
rootDir: "./", |
||||
}; |
@ -0,0 +1,34 @@ |
||||
{ |
||||
"name": "@ldo/traverser-shexj", |
||||
"version": "0.0.0", |
||||
"description": "A type-traverser for ShexJ", |
||||
"main": "dist/index.js", |
||||
"scripts": { |
||||
"build": "tsc --project tsconfig.build.json", |
||||
"test": "jest --coverage", |
||||
"prepublishOnly": "npm run test && npm run build", |
||||
"lint": "eslint src/** --fix --no-error-on-unmatched-pattern" |
||||
}, |
||||
"repository": { |
||||
"type": "git", |
||||
"url": "git+https://github.com/o-development/json-ld-shex-mapper.git" |
||||
}, |
||||
"author": "", |
||||
"license": "MIT", |
||||
"bugs": { |
||||
"url": "https://github.com/o-development/json-ld-shex-mapper/issues" |
||||
}, |
||||
"homepage": "https://github.com/o-development/json-ld-shex-mapper#readme", |
||||
"devDependencies": { |
||||
"@types/shexj": "^2.1.3", |
||||
"@types/jest": "^29.0.3", |
||||
"jest": "^29.0.3", |
||||
"ts-jest": "^29.0.2" |
||||
}, |
||||
"files": [ |
||||
"dist" |
||||
], |
||||
"dependencies": { |
||||
"@ldo/type-traverser": "^0.0.0" |
||||
} |
||||
} |
@ -0,0 +1,7 @@ |
||||
import { ShexJTraverserDefinition } from "./ShexJTraverserDefinition"; |
||||
import type { ShexJTraverserTypes } from "./ShexJTraverserTypes"; |
||||
import { Traverser } from "@ldo/type-traverser"; |
||||
|
||||
export const ShexJTraverser = new Traverser<ShexJTraverserTypes>( |
||||
ShexJTraverserDefinition, |
||||
); |
@ -0,0 +1,303 @@ |
||||
import type { ShexJTraverserTypes } from "."; |
||||
import type { TraverserDefinition } from "@ldo/type-traverser"; |
||||
import type { shapeExpr, valueSetValue } from "shexj"; |
||||
|
||||
export const ShexJTraverserDefinition: TraverserDefinition<ShexJTraverserTypes> = |
||||
{ |
||||
Schema: { |
||||
kind: "interface", |
||||
properties: { |
||||
startActs: "SemAct", |
||||
start: "shapeExprOrRef", |
||||
imports: "IRIREF", |
||||
shapes: "ShapeDecl", |
||||
}, |
||||
}, |
||||
ShapeDecl: { |
||||
kind: "interface", |
||||
properties: { |
||||
id: "shapeDeclLabel", |
||||
abstract: "BOOL", |
||||
restricts: "shapeExprOrRef", |
||||
shapeExpr: "shapeExpr", |
||||
}, |
||||
}, |
||||
shapeExpr: { |
||||
kind: "union", |
||||
selector: (item: shapeExpr) => item.type, |
||||
}, |
||||
shapeExprOrRef: { |
||||
kind: "union", |
||||
selector: (item) => |
||||
typeof item === "string" ? "shapeDeclRef" : "shapeExpr", |
||||
}, |
||||
ShapeOr: { |
||||
kind: "interface", |
||||
properties: { |
||||
shapeExprs: "shapeExprOrRef", |
||||
}, |
||||
}, |
||||
ShapeAnd: { |
||||
kind: "interface", |
||||
properties: { |
||||
shapeExprs: "shapeExprOrRef", |
||||
}, |
||||
}, |
||||
ShapeNot: { |
||||
kind: "interface", |
||||
properties: { |
||||
shapeExpr: "shapeExprOrRef", |
||||
}, |
||||
}, |
||||
ShapeExternal: { |
||||
kind: "interface", |
||||
properties: {}, |
||||
}, |
||||
shapeDeclRef: { |
||||
kind: "union", |
||||
selector: () => "shapeDeclLabel", |
||||
}, |
||||
shapeDeclLabel: { |
||||
kind: "union", |
||||
selector: () => "IRIREF", |
||||
}, |
||||
NodeConstraint: { |
||||
kind: "interface", |
||||
properties: { |
||||
datatype: "IRIREF", |
||||
values: "valueSetValue", |
||||
length: "INTEGER", |
||||
minlength: "INTEGER", |
||||
maxlength: "INTEGER", |
||||
pattern: "STRING", |
||||
flags: "STRING", |
||||
mininclusive: "numericLiteral", |
||||
minexclusive: "numericLiteral", |
||||
totaldigits: "INTEGER", |
||||
fractiondigits: "INTEGER", |
||||
semActs: "SemAct", |
||||
annotations: "Annotation", |
||||
}, |
||||
}, |
||||
numericLiteral: { |
||||
kind: "union", |
||||
selector: () => "DOUBLE", |
||||
}, |
||||
valueSetValue: { |
||||
kind: "union", |
||||
selector: (item: valueSetValue) => { |
||||
if (typeof item === "string") { |
||||
return "objectValue"; |
||||
} else if ( |
||||
item.type && |
||||
[ |
||||
"IriStem", |
||||
"IriStemRange", |
||||
"LiteralStem", |
||||
"LiteralStemRange", |
||||
"Language", |
||||
"LanguageStem", |
||||
"LanguageStemRange", |
||||
].includes(item.type) |
||||
) { |
||||
return item.type as |
||||
| "IriStem" |
||||
| "IriStemRange" |
||||
| "LiteralStem" |
||||
| "LiteralStemRange" |
||||
| "Language" |
||||
| "LanguageStem" |
||||
| "LanguageStemRange"; |
||||
} else { |
||||
return "objectValue"; |
||||
} |
||||
}, |
||||
}, |
||||
objectValue: { |
||||
kind: "union", |
||||
selector: (item) => |
||||
typeof item === "string" ? "IRIREF" : "ObjectLiteral", |
||||
}, |
||||
ObjectLiteral: { |
||||
kind: "interface", |
||||
properties: { |
||||
value: "STRING", |
||||
language: "STRING", |
||||
type: "STRING", |
||||
}, |
||||
}, |
||||
IriStem: { |
||||
kind: "interface", |
||||
properties: { |
||||
stem: "IRIREF", |
||||
}, |
||||
}, |
||||
IriStemRange: { |
||||
kind: "interface", |
||||
properties: { |
||||
stem: "IRIREF", |
||||
exclusions: "IriStemRangeExclusions", |
||||
}, |
||||
}, |
||||
IriStemRangeExclusions: { |
||||
kind: "union", |
||||
selector: (item) => (typeof item === "string" ? "IRIREF" : "IriStem"), |
||||
}, |
||||
LiteralStem: { |
||||
kind: "interface", |
||||
properties: { |
||||
stem: "STRING", |
||||
}, |
||||
}, |
||||
LiteralStemRange: { |
||||
kind: "interface", |
||||
properties: { |
||||
stem: "LiteralStemRangeStem", |
||||
exclusions: "LiteralStemRangeExclusions", |
||||
}, |
||||
}, |
||||
LiteralStemRangeStem: { |
||||
kind: "union", |
||||
selector: (item) => (typeof item === "string" ? "STRING" : "Wildcard"), |
||||
}, |
||||
LiteralStemRangeExclusions: { |
||||
kind: "union", |
||||
selector: (item) => (typeof item === "string" ? "STRING" : "LiteralStem"), |
||||
}, |
||||
Language: { |
||||
kind: "interface", |
||||
properties: { |
||||
languageTag: "LANGTAG", |
||||
}, |
||||
}, |
||||
LanguageStem: { |
||||
kind: "interface", |
||||
properties: { |
||||
stem: "LANGTAG", |
||||
}, |
||||
}, |
||||
LanguageStemRange: { |
||||
kind: "interface", |
||||
properties: { |
||||
stem: "LanguageStemRangeStem", |
||||
exclusions: "LanguageStemRangeExclusions", |
||||
}, |
||||
}, |
||||
LanguageStemRangeStem: { |
||||
kind: "union", |
||||
selector: (item) => (typeof item === "string" ? "LANGTAG" : "Wildcard"), |
||||
}, |
||||
LanguageStemRangeExclusions: { |
||||
kind: "union", |
||||
selector: (item) => |
||||
typeof item === "string" ? "LANGTAG" : "LanguageStem", |
||||
}, |
||||
Wildcard: { |
||||
kind: "interface", |
||||
properties: {}, |
||||
}, |
||||
Shape: { |
||||
kind: "interface", |
||||
properties: { |
||||
closed: "BOOL", |
||||
extra: "IRIREF", |
||||
extends: "shapeExprOrRef", |
||||
expression: "tripleExprOrRef", |
||||
semActs: "SemAct", |
||||
annotations: "Annotation", |
||||
}, |
||||
}, |
||||
tripleExpr: { |
||||
kind: "union", |
||||
selector: (item) => item.type, |
||||
}, |
||||
tripleExprOrRef: { |
||||
kind: "union", |
||||
selector: (item) => |
||||
typeof item === "string" ? "tripleExprRef" : "tripleExpr", |
||||
}, |
||||
EachOf: { |
||||
kind: "interface", |
||||
properties: { |
||||
id: "tripleExprLabel", |
||||
min: "INTEGER", |
||||
max: "INTEGER", |
||||
expressions: "tripleExprOrRef", |
||||
semActs: "SemAct", |
||||
annotations: "Annotation", |
||||
}, |
||||
}, |
||||
OneOf: { |
||||
kind: "interface", |
||||
properties: { |
||||
id: "tripleExprLabel", |
||||
min: "INTEGER", |
||||
max: "INTEGER", |
||||
expressions: "tripleExprOrRef", |
||||
semActs: "SemAct", |
||||
annotations: "Annotation", |
||||
}, |
||||
}, |
||||
TripleConstraint: { |
||||
kind: "interface", |
||||
properties: { |
||||
id: "tripleExprLabel", |
||||
min: "INTEGER", |
||||
max: "INTEGER", |
||||
inverse: "BOOL", |
||||
predicate: "IRIREF", |
||||
valueExpr: "shapeExprOrRef", |
||||
semActs: "SemAct", |
||||
annotations: "Annotation", |
||||
}, |
||||
}, |
||||
tripleExprRef: { |
||||
kind: "union", |
||||
selector: () => "tripleExprLabel", |
||||
}, |
||||
tripleExprLabel: { |
||||
kind: "union", |
||||
selector: () => "IRIREF", |
||||
}, |
||||
SemAct: { |
||||
kind: "interface", |
||||
properties: { |
||||
name: "IRIREF", |
||||
code: "STRING", |
||||
}, |
||||
}, |
||||
Annotation: { |
||||
kind: "interface", |
||||
properties: { |
||||
predicate: "IRI", |
||||
object: "objectValue", |
||||
}, |
||||
}, |
||||
IRIREF: { |
||||
kind: "primitive", |
||||
}, |
||||
BNODE: { |
||||
kind: "primitive", |
||||
}, |
||||
INTEGER: { |
||||
kind: "primitive", |
||||
}, |
||||
STRING: { |
||||
kind: "primitive", |
||||
}, |
||||
DECIMAL: { |
||||
kind: "primitive", |
||||
}, |
||||
DOUBLE: { |
||||
kind: "primitive", |
||||
}, |
||||
LANGTAG: { |
||||
kind: "primitive", |
||||
}, |
||||
BOOL: { |
||||
kind: "primitive", |
||||
}, |
||||
IRI: { |
||||
kind: "primitive", |
||||
}, |
||||
}; |
@ -0,0 +1,375 @@ |
||||
import type { |
||||
Annotation, |
||||
BNODE, |
||||
BOOL, |
||||
DECIMAL, |
||||
DOUBLE, |
||||
EachOf, |
||||
INTEGER, |
||||
IRI, |
||||
IRIREF, |
||||
IriStem, |
||||
IriStemRange, |
||||
LANGTAG, |
||||
Language, |
||||
LanguageStem, |
||||
LanguageStemRange, |
||||
LiteralStem, |
||||
LiteralStemRange, |
||||
NodeConstraint, |
||||
numericLiteral, |
||||
ObjectLiteral, |
||||
objectValue, |
||||
OneOf, |
||||
Schema, |
||||
SemAct, |
||||
Shape, |
||||
ShapeAnd, |
||||
ShapeDecl, |
||||
shapeDeclLabel, |
||||
shapeDeclRef, |
||||
shapeExpr, |
||||
shapeExprOrRef, |
||||
ShapeExternal, |
||||
ShapeNot, |
||||
ShapeOr, |
||||
STRING, |
||||
TripleConstraint, |
||||
tripleExpr, |
||||
tripleExprLabel, |
||||
tripleExprOrRef, |
||||
tripleExprRef, |
||||
valueSetValue, |
||||
Wildcard, |
||||
} from "shexj"; |
||||
import type { ValidateTraverserTypes } from "@ldo/type-traverser"; |
||||
|
||||
export type ShexJTraverserTypes = ValidateTraverserTypes<{ |
||||
Schema: { |
||||
kind: "interface"; |
||||
type: Schema; |
||||
properties: { |
||||
startActs: "SemAct"; |
||||
start: "shapeExprOrRef"; |
||||
imports: "IRIREF"; |
||||
shapes: "ShapeDecl"; |
||||
}; |
||||
}; |
||||
ShapeDecl: { |
||||
kind: "interface"; |
||||
type: ShapeDecl; |
||||
properties: { |
||||
id: "shapeDeclLabel"; |
||||
abstract: "BOOL"; |
||||
restricts: "shapeExprOrRef"; |
||||
shapeExpr: "shapeExpr"; |
||||
}; |
||||
}; |
||||
shapeExpr: { |
||||
kind: "union"; |
||||
type: shapeExpr; |
||||
typeNames: |
||||
| "ShapeOr" |
||||
| "ShapeAnd" |
||||
| "ShapeNot" |
||||
| "NodeConstraint" |
||||
| "Shape" |
||||
| "ShapeExternal"; |
||||
}; |
||||
shapeExprOrRef: { |
||||
kind: "union"; |
||||
type: shapeExprOrRef; |
||||
typeNames: "shapeExpr" | "shapeDeclRef"; |
||||
}; |
||||
ShapeOr: { |
||||
kind: "interface"; |
||||
type: ShapeOr; |
||||
properties: { |
||||
shapeExprs: "shapeExprOrRef"; |
||||
}; |
||||
}; |
||||
ShapeAnd: { |
||||
kind: "interface"; |
||||
type: ShapeAnd; |
||||
properties: { |
||||
shapeExprs: "shapeExprOrRef"; |
||||
}; |
||||
}; |
||||
ShapeNot: { |
||||
kind: "interface"; |
||||
type: ShapeNot; |
||||
properties: { |
||||
shapeExpr: "shapeExprOrRef"; |
||||
}; |
||||
}; |
||||
ShapeExternal: { |
||||
kind: "interface"; |
||||
type: ShapeExternal; |
||||
properties: Record<string, never>; |
||||
}; |
||||
shapeDeclRef: { |
||||
kind: "union"; |
||||
type: shapeDeclRef; |
||||
typeNames: "shapeDeclLabel"; |
||||
}; |
||||
shapeDeclLabel: { |
||||
kind: "union"; |
||||
type: shapeDeclLabel; |
||||
typeNames: "IRIREF" | "BNODE"; |
||||
}; |
||||
NodeConstraint: { |
||||
kind: "interface"; |
||||
type: NodeConstraint; |
||||
properties: { |
||||
datatype: "IRIREF"; |
||||
values: "valueSetValue"; |
||||
length: "INTEGER"; |
||||
minlength: "INTEGER"; |
||||
maxlength: "INTEGER"; |
||||
pattern: "STRING"; |
||||
flags: "STRING"; |
||||
mininclusive: "numericLiteral"; |
||||
minexclusive: "numericLiteral"; |
||||
totaldigits: "INTEGER"; |
||||
fractiondigits: "INTEGER"; |
||||
semActs: "SemAct"; |
||||
annotations: "Annotation"; |
||||
}; |
||||
}; |
||||
numericLiteral: { |
||||
kind: "union"; |
||||
type: numericLiteral; |
||||
typeNames: "INTEGER" | "DECIMAL" | "DOUBLE"; |
||||
}; |
||||
valueSetValue: { |
||||
kind: "union"; |
||||
type: valueSetValue; |
||||
typeNames: |
||||
| "objectValue" |
||||
| "IriStem" |
||||
| "IriStemRange" |
||||
| "LiteralStem" |
||||
| "LiteralStemRange" |
||||
| "Language" |
||||
| "LanguageStem" |
||||
| "LanguageStemRange"; |
||||
}; |
||||
objectValue: { |
||||
kind: "union"; |
||||
type: objectValue; |
||||
typeNames: "IRIREF" | "ObjectLiteral"; |
||||
}; |
||||
ObjectLiteral: { |
||||
kind: "interface"; |
||||
type: ObjectLiteral; |
||||
properties: { |
||||
value: "STRING"; |
||||
language: "STRING"; |
||||
type: "STRING"; |
||||
}; |
||||
}; |
||||
IriStem: { |
||||
kind: "interface"; |
||||
type: IriStem; |
||||
properties: { |
||||
stem: "IRIREF"; |
||||
}; |
||||
}; |
||||
IriStemRange: { |
||||
kind: "interface"; |
||||
type: IriStemRange; |
||||
properties: { |
||||
stem: "IRIREF"; |
||||
exclusions: "IriStemRangeExclusions"; |
||||
}; |
||||
}; |
||||
IriStemRangeExclusions: { |
||||
kind: "union"; |
||||
type: IRIREF | IriStem; |
||||
typeNames: "IRIREF" | "IriStem"; |
||||
}; |
||||
LiteralStem: { |
||||
kind: "interface"; |
||||
type: LiteralStem; |
||||
properties: { |
||||
stem: "STRING"; |
||||
}; |
||||
}; |
||||
LiteralStemRange: { |
||||
kind: "interface"; |
||||
type: LiteralStemRange; |
||||
properties: { |
||||
stem: "LiteralStemRangeStem"; |
||||
exclusions: "LiteralStemRangeExclusions"; |
||||
}; |
||||
}; |
||||
LiteralStemRangeStem: { |
||||
kind: "union"; |
||||
type: STRING | Wildcard; |
||||
typeNames: "STRING" | "Wildcard"; |
||||
}; |
||||
LiteralStemRangeExclusions: { |
||||
kind: "union"; |
||||
type: STRING | LiteralStem; |
||||
typeNames: "STRING" | "LiteralStem"; |
||||
}; |
||||
Language: { |
||||
kind: "interface"; |
||||
type: Language; |
||||
properties: { |
||||
languageTag: "LANGTAG"; |
||||
}; |
||||
}; |
||||
LanguageStem: { |
||||
kind: "interface"; |
||||
type: LanguageStem; |
||||
properties: { |
||||
stem: "LANGTAG"; |
||||
}; |
||||
}; |
||||
LanguageStemRange: { |
||||
kind: "interface"; |
||||
type: LanguageStemRange; |
||||
properties: { |
||||
stem: "LanguageStemRangeStem"; |
||||
exclusions: "LanguageStemRangeExclusions"; |
||||
}; |
||||
}; |
||||
LanguageStemRangeStem: { |
||||
kind: "union"; |
||||
type: LANGTAG | Wildcard; |
||||
typeNames: "LANGTAG" | "Wildcard"; |
||||
}; |
||||
LanguageStemRangeExclusions: { |
||||
kind: "union"; |
||||
type: LANGTAG | LanguageStem; |
||||
typeNames: "LANGTAG" | "LanguageStem"; |
||||
}; |
||||
Wildcard: { |
||||
kind: "interface"; |
||||
type: Wildcard; |
||||
properties: Record<string, never>; |
||||
}; |
||||
Shape: { |
||||
kind: "interface"; |
||||
type: Shape; |
||||
properties: { |
||||
closed: "BOOL"; |
||||
extra: "IRIREF"; |
||||
extends: "shapeExprOrRef"; |
||||
expression: "tripleExprOrRef"; |
||||
semActs: "SemAct"; |
||||
annotations: "Annotation"; |
||||
}; |
||||
}; |
||||
tripleExpr: { |
||||
kind: "union"; |
||||
type: tripleExpr; |
||||
typeNames: "EachOf" | "OneOf" | "TripleConstraint"; |
||||
}; |
||||
tripleExprOrRef: { |
||||
kind: "union"; |
||||
type: tripleExprOrRef; |
||||
typeNames: "tripleExpr" | "tripleExprRef"; |
||||
}; |
||||
EachOf: { |
||||
kind: "interface"; |
||||
type: EachOf; |
||||
properties: { |
||||
id: "tripleExprLabel"; |
||||
min: "INTEGER"; |
||||
max: "INTEGER"; |
||||
expressions: "tripleExprOrRef"; |
||||
semActs: "SemAct"; |
||||
annotations: "Annotation"; |
||||
}; |
||||
}; |
||||
OneOf: { |
||||
kind: "interface"; |
||||
type: OneOf; |
||||
properties: { |
||||
id: "tripleExprLabel"; |
||||
min: "INTEGER"; |
||||
max: "INTEGER"; |
||||
expressions: "tripleExprOrRef"; |
||||
semActs: "SemAct"; |
||||
annotations: "Annotation"; |
||||
}; |
||||
}; |
||||
TripleConstraint: { |
||||
kind: "interface"; |
||||
type: TripleConstraint; |
||||
properties: { |
||||
id: "tripleExprLabel"; |
||||
min: "INTEGER"; |
||||
max: "INTEGER"; |
||||
inverse: "BOOL"; |
||||
predicate: "IRIREF"; |
||||
valueExpr: "shapeExprOrRef"; |
||||
semActs: "SemAct"; |
||||
annotations: "Annotation"; |
||||
}; |
||||
}; |
||||
tripleExprRef: { |
||||
kind: "union"; |
||||
type: tripleExprRef; |
||||
typeNames: "tripleExprLabel"; |
||||
}; |
||||
tripleExprLabel: { |
||||
kind: "union"; |
||||
type: tripleExprLabel; |
||||
typeNames: "IRIREF" | "BNODE"; |
||||
}; |
||||
SemAct: { |
||||
kind: "interface"; |
||||
type: SemAct; |
||||
properties: { |
||||
name: "IRIREF"; |
||||
code: "STRING"; |
||||
}; |
||||
}; |
||||
Annotation: { |
||||
kind: "interface"; |
||||
type: Annotation; |
||||
properties: { |
||||
predicate: "IRI"; |
||||
object: "objectValue"; |
||||
}; |
||||
}; |
||||
IRIREF: { |
||||
kind: "primitive"; |
||||
type: IRIREF; |
||||
}; |
||||
BNODE: { |
||||
kind: "primitive"; |
||||
type: BNODE; |
||||
}; |
||||
INTEGER: { |
||||
kind: "primitive"; |
||||
type: INTEGER; |
||||
}; |
||||
STRING: { |
||||
kind: "primitive"; |
||||
type: STRING; |
||||
}; |
||||
DECIMAL: { |
||||
kind: "primitive"; |
||||
type: DECIMAL; |
||||
}; |
||||
DOUBLE: { |
||||
kind: "primitive"; |
||||
type: DOUBLE; |
||||
}; |
||||
LANGTAG: { |
||||
kind: "primitive"; |
||||
type: LANGTAG; |
||||
}; |
||||
BOOL: { |
||||
kind: "primitive"; |
||||
type: BOOL; |
||||
}; |
||||
IRI: { |
||||
kind: "primitive"; |
||||
type: IRI; |
||||
}; |
||||
}>; |
@ -0,0 +1,5 @@ |
||||
import { ShexJTraverser } from "./ShexJTraverser"; |
||||
|
||||
export * from "./ShexJTraverserDefinition"; |
||||
export * from "./ShexJTraverserTypes"; |
||||
export default ShexJTraverser; |
@ -0,0 +1,5 @@ |
||||
describe("traverseSchema", () => { |
||||
it("trivial", () => { |
||||
expect(true).toBe(true); |
||||
}); |
||||
}); |
@ -0,0 +1,7 @@ |
||||
{ |
||||
"extends": "../../tsconfig.base.json", |
||||
"compilerOptions": { |
||||
"outDir": "./dist" |
||||
}, |
||||
"include": ["./src"] |
||||
} |
@ -0,0 +1,3 @@ |
||||
{ |
||||
"extends": ["../../.eslintrc"] |
||||
} |
@ -0,0 +1,674 @@ |
||||
# Type Traverser |
||||
|
||||
An organized way to traverse objects using typescript. |
||||
|
||||
## Installation |
||||
``` |
||||
npm i type-traverser |
||||
``` |
||||
|
||||
## Why use this library? |
||||
Have you ever needed to traverse large, complex objects? Chances are you built traversers to recognize when something is an array or a certain kind of record. |
||||
|
||||
Building traversers is an arduous process that involves a lot of repeated concepts and repeated code. Type-Traverser minimizes the repetition. |
||||
|
||||
## Tutorial |
||||
|
||||
This tutorial walks you through how to set up a traverser. You can see the full runnable example at [`example/example.ts`](./example/example.ts), or you can run the example by running `npm run start`. |
||||
|
||||
### Defining the type |
||||
|
||||
The first step is defining what your type looks like. Let's say, for example, the following typescript typings represent what you need to traverse. These types represent characters from Avatar: The Last Airbender where there are special people called "Benders" who can manipulate the elements. |
||||
|
||||
```typescript |
||||
type Element = "Water" | "Earth" | "Fire" | "Air"; |
||||
interface Bender { |
||||
name: string; |
||||
element: Element; |
||||
friends: Person[]; |
||||
} |
||||
interface NonBender { |
||||
name: string; |
||||
friends: Person[]; |
||||
} |
||||
type Person = Bender | NonBender; |
||||
``` |
||||
|
||||
Some sample data that follows this type looks like this: |
||||
|
||||
```typescript |
||||
const aang: Bender = { |
||||
name: "Aang", |
||||
element: "Air", |
||||
friends: [], |
||||
}; |
||||
const sokka: NonBender = { |
||||
name: "Sokka", |
||||
friends: [], |
||||
}; |
||||
const katara: Bender = { |
||||
name: "Katara", |
||||
element: "Water", |
||||
friends: [], |
||||
}; |
||||
aang.friends.push(sokka, katara); |
||||
sokka.friends.push(aang, katara); |
||||
katara.friends.push(aang, sokka); |
||||
``` |
||||
|
||||
### Defining Traverser Types |
||||
|
||||
Next, we need to define a traverser. A traverser has data that defines what your object looks like. |
||||
|
||||
To define a traverser, you first need to set up a `TraverserTypes` type. You can validate the `TraverserTypes` type by using the `ValidateTraverserTypes` type and passing in your `TraverserTypes` as a generic. |
||||
|
||||
```typescript |
||||
type AvatarTraverserTypes = ValidateTraverserTypes<{ |
||||
Element: { |
||||
kind: "primitive"; |
||||
type: Element; |
||||
}; |
||||
Bender: { |
||||
kind: "interface"; |
||||
type: Bender; |
||||
properties: { |
||||
element: "Element"; |
||||
friends: "Person"; |
||||
}; |
||||
}; |
||||
NonBender: { |
||||
kind: "interface"; |
||||
type: Bender; |
||||
properties: { |
||||
friends: "Person"; |
||||
}; |
||||
}; |
||||
Person: { |
||||
kind: "union"; |
||||
type: Person; |
||||
typeNames: "Bender" | "NonBender"; |
||||
}; |
||||
}>; |
||||
``` |
||||
|
||||
Let's break down what's happening here. We're telling typescript the kinds of "Sub-Traversers" each type uses. |
||||
|
||||
Each sub-traverser has a name called a "TypeName". In this example we have four TypeNames: `Element`, `Bender`, `NonBender`, and `Person`. TypeNames can be any name you want, but it's best practice to match your TypeNames to the name of it's corresponding type. For example, the type `Element` is associated with the TypeName `Element`. Some sub-traversers will also require you to provide TypeNames as strings. |
||||
|
||||
`ValidateTraverserTypes` is convenient way to check if you have made a valid `TraverserTypes`. If you make a mistake - for example, you provide an invalid TypeName as seen below - the resulting type will be `never`; |
||||
|
||||
 |
||||
|
||||
At the moment, there are three sub-traverser types: |
||||
|
||||
#### Union |
||||
You should use a union sub-traverser for typescript union types - types that usually follow the form `a | b | c` where any element from the list is valid. |
||||
|
||||
In the example, `Person` is a "Union" sub-traverser because a `Person` is a union of `Bender` and `NonBender` (`Bender | NonBender`). |
||||
|
||||
```typescript |
||||
Person: { |
||||
kind: "union"; |
||||
type: Person; |
||||
typeNames: "Bender" | "NonBender"; |
||||
}; |
||||
``` |
||||
|
||||
There are three fields for the union sub-traverser: |
||||
- `kind`: Must be `"union"` to denote that this defines a union sub-traverser. |
||||
- `type`: The typescript type corresponding to this union. |
||||
- `typeNames`: Defines a list of TypeNames that correspond to the actual type. |
||||
|
||||
#### Interface |
||||
You should use a interface sub-traverser for interface or object types - any type that maps discrete keys to values. |
||||
|
||||
In the example, `Bender` is an "Interface" sub-traverser because a `Bender` type is a map of discrete keys (like `element` and `friends`) to type values (like `Element` and `Person[]`). |
||||
|
||||
```typescript |
||||
Bender: { |
||||
kind: "interface"; |
||||
type: Bender; |
||||
properties: { |
||||
element: "Element"; |
||||
friends: "Person"; |
||||
}; |
||||
}; |
||||
``` |
||||
|
||||
There are three fields for the interface sub-traverser: |
||||
- `kind`: Must be `"interface"` to denote that this defines a interface sub-traverser. |
||||
- `type`: The typescript type corresponding to this inteface or object type. |
||||
- `properties`: Defines the properties that should be traversed by mapping the property name to its corresponding TypeName. Not all interface properties need to be listed here, only the ones that should be traversed. If no properties from this interface need to be traversed, you either provide `properties: Record<string, never>` or use a "Primitive" sub-traverser. |
||||
|
||||
#### Primitive |
||||
You should use a primitive sub-traverser for "leaf-types" - types that do not have any children. |
||||
|
||||
In the example, `Element` is a "Primitive" sub-traverser because an `Element` type is just a collection of strings (`"Water" | "Earth" | "Fire" | "Air"`). |
||||
|
||||
```typescript |
||||
Element: { |
||||
kind: "primitive"; |
||||
type: Element; |
||||
}; |
||||
``` |
||||
|
||||
Note: while it is common to use a primitive sub traverser for primitive types like `string`, `boolean`, `number`, etc. you may also use it for more complex types like interfaces and arrays if you do not care about traversing the children of those types. |
||||
|
||||
There are two fields for the primitive sub-traverser: |
||||
- `kind`: Must be `"primitive"` to denote that this defines a primitive sub-traverser. |
||||
- `type`: The typescript type corresponding to this primitive. |
||||
|
||||
### Creating a traverser definition |
||||
Typescript typings aren't available at runtime, so the next step is to translate the `TraverserTypes` that we made into a standard JSON object called a "TraverserDefinition". But, don't worry! This will be easy. If you define a variable as a `TraverserDefinition<TraverserType>`, your IDE's IntelliSense will be able to direct you through exactly what to fill out, as seen below. |
||||
|
||||
 |
||||
|
||||
In our example, the TraverserDefinition looks like: |
||||
|
||||
```typescript |
||||
const avatarTraverserDefinition: TraverserDefinition<AvatarTraverserTypes> = { |
||||
Element: { |
||||
kind: "primitive", |
||||
}, |
||||
Bender: { |
||||
kind: "interface", |
||||
properties: { |
||||
element: "Element", |
||||
friends: "Person", |
||||
}, |
||||
}, |
||||
NonBender: { |
||||
kind: "interface", |
||||
properties: { |
||||
friends: "Person", |
||||
}, |
||||
}, |
||||
Person: { |
||||
kind: "union", |
||||
selector: (item) => { |
||||
return (item as Bender).element ? "Bender" : "NonBender"; |
||||
}, |
||||
}, |
||||
}; |
||||
``` |
||||
|
||||
#### Defining a Union Selector |
||||
The only part of the TraverserDefinition that isn't just blindly following IntelliSense is the `selector` on a Union sub-traverser. A `selector` is given the item and should return the TypeName corresponding to the item. |
||||
|
||||
In the above example, `"Bender"` is returned if the given item has a `"element"` property because a `"NonBender"` does not include an `"element"` property. |
||||
|
||||
### Instantiating a Traverser |
||||
Now that we've defined the traverser types, we're ready to instantiate the traverser itself. A `Traverser` is a class that lets you traverse the object you defined. |
||||
|
||||
In our example, this is how we instantiate the traverser |
||||
|
||||
```typescript |
||||
const avatarTraverser = new Traverser<AvatarTraverserTypes>( |
||||
avatarTraverserDefinition |
||||
); |
||||
``` |
||||
|
||||
### Using a Traverser |
||||
At this point I'd like to welcome everyone who was linked to this section from another library. If this is you, someone else has already defined a traverser for you. You don't need to know how to create a traverser, but if you want to know, you can read the proceeding sections in this document. |
||||
|
||||
From now on, we will detail how to use an already defined traverser using sample data about the fictional universe of "Avatar: The Last Airbender." If you came here from another library, your traverser will be different, but the concepts are the same. So far we have the following: |
||||
|
||||
```typescript |
||||
type Element = "Water" | "Earth" | "Fire" | "Air"; |
||||
interface Bender { |
||||
name: string; |
||||
element: Element; |
||||
friends: Person[]; |
||||
} |
||||
interface NonBender { |
||||
name: string; |
||||
friends: Person[]; |
||||
} |
||||
type Person = Bender | NonBender; |
||||
``` |
||||
|
||||
Some sample data that follows this type looks like this: |
||||
|
||||
```typescript |
||||
/** |
||||
* The Type Definitions |
||||
*/ |
||||
type Element = "Water" | "Earth" | "Fire" | "Air"; |
||||
interface Bender { |
||||
name: string; |
||||
element: Element; |
||||
friends: Person[]; |
||||
} |
||||
interface NonBender { |
||||
name: string; |
||||
friends: Person[]; |
||||
} |
||||
type Person = Bender | NonBender; |
||||
|
||||
/** |
||||
* Some Raw Sample Data that follows our Types |
||||
*/ |
||||
const aang: Bender = { |
||||
name: "Aang", |
||||
element: "Air", |
||||
friends: [], |
||||
}; |
||||
const sokka: NonBender = { |
||||
name: "Sokka", |
||||
friends: [], |
||||
}; |
||||
const katara: Bender = { |
||||
name: "Katara", |
||||
element: "Water", |
||||
friends: [], |
||||
}; |
||||
aang.friends.push(sokka, katara); |
||||
sokka.friends.push(aang, katara); |
||||
katara.friends.push(aang, sokka); |
||||
|
||||
/** |
||||
* A Traverser to Traverse these type definitions |
||||
*/ |
||||
avatarTraverser |
||||
``` |
||||
|
||||
### Using a Visitor |
||||
The simplest way to traverse your object is using a `Visitor`. A visitor will trigger a given function when it finds an object that matches the corresponding TypeName. |
||||
|
||||
In our Avatar example, our visitor could look like: |
||||
|
||||
```typescript |
||||
const avatarVisitor = avatarTraverser.createVisitor<undefined>({ |
||||
Element: async (item) => { |
||||
console.log(`Element: ${item}`); |
||||
}, |
||||
Bender: { |
||||
visitor: async (item) => { |
||||
console.log(`Bender: ${item.name}`); |
||||
}, |
||||
properties: { |
||||
element: async (item) => { |
||||
console.log(`Bender.element: ${item}`); |
||||
}, |
||||
}, |
||||
}, |
||||
NonBender: { |
||||
visitor: async (item) => { |
||||
console.log(`NonBender: ${item.name}`); |
||||
}, |
||||
}, |
||||
Person: async (item) => { |
||||
console.log(`Person: ${item.name}`); |
||||
}, |
||||
}); |
||||
|
||||
await avatarVisitor.visit(aang, "Bender", undefined); |
||||
``` |
||||
|
||||
Running this code will result in this log: |
||||
```bash |
||||
Bender: Aang |
||||
Bender.element: Air |
||||
Element: Air |
||||
Person: Sokka |
||||
NonBender: Sokka |
||||
Person: Aang |
||||
Person: Katara |
||||
Bender: Katara |
||||
Bender.element: Water |
||||
Element: Water |
||||
``` |
||||
|
||||
Let's break down what's happening here. |
||||
|
||||
"Primitive" and "Union" sub-traverser types (Like `Element` and `Person` respectively) require only a function. That function is crafted for the specific item. For example: |
||||
|
||||
- Element's visitor function: `(item: Element, context: undefined) => Promise<void>`; |
||||
- Person's visitor function: `(item: Person, context: undefined) => Promise<void>`; |
||||
|
||||
"Interface" sub-traversers allow you to define a visitor function for the whole object AND each of its properties. Each of these, including the visitor function for the whole object is optional. For example, an interface visitor for "Bender" follows the following typing: |
||||
|
||||
```typescript |
||||
{ |
||||
visitor?: (item: Bender, context: undefined) => Promise<void>; |
||||
properties?: { |
||||
element?: (item: Element, context: undefined) => Promise<void>; |
||||
friends?: (item: Person[], context: undefined) => Promise<void>; |
||||
} |
||||
} |
||||
``` |
||||
|
||||
When we want to run the visitor we use the `visit` method. |
||||
|
||||
```typescript |
||||
await avatarVisitor.visit(aang, "Bender", undefined); |
||||
``` |
||||
|
||||
The `visit` method takes in three arguments: |
||||
- The data that should be traversed. In this example the "aang" object. |
||||
- The TypeName of the data. In this example `"Bender"` because the "aang" object is a `Bender`. `"Person"` would have also been acceptable. |
||||
- The context (described in the next section) |
||||
|
||||
Note: You DO NOT need to define a visitor function for every TypeName, only the ones you care about. |
||||
|
||||
Note: Notice that we see the same person's name appear in two logs. For example we see both "Person: Sokka" and "NonBender: Sokka". That's because we visit the same object as a `Person` type and as a `NonBender` type. |
||||
|
||||
### Using Context |
||||
Sometimes you want to pass a shared object that's accessible from all traversers. That's where "context" comes in. Here's an example that uses context with our AvatarTraverser: |
||||
|
||||
```typescript |
||||
interface AvatarCountingVisitorContext { |
||||
numberOfBenders: number; |
||||
} |
||||
const avatarCountingVisitor = |
||||
avatarTraverser.createVisitor<AvatarCountingVisitorContext>({ |
||||
Bender: { |
||||
visitor: async (item, context) => { |
||||
context.numberOfBenders++; |
||||
}, |
||||
}, |
||||
}); |
||||
const countContext: AvatarCountingVisitorContext = { numberOfBenders: 0 }; |
||||
await avatarCountingVisitor.visit(aang, "Bender", countContext); |
||||
console.log(countContext.numberOfBenders); // Logs 2 |
||||
``` |
||||
|
||||
In this example, we want to count the number of "Benders" we encounter while traversing our object. |
||||
|
||||
`traverser.createVisitor` accepts one generic defining the type of the context. |
||||
|
||||
### Using a Transformer |
||||
Sometimes you don't want to just visit an object, you want to transform it into something else. A Transformer is the best way to do that. |
||||
|
||||
In the following example, we want to transform our data into a graph of `ActionablePerson` as defined by this interface: |
||||
|
||||
```typescript |
||||
interface ActionablePerson { |
||||
doAction(): void; |
||||
friends: ActionablePerson[]; |
||||
} |
||||
``` |
||||
|
||||
We still want to maintain the friendship connections, but the transformed object is completely different. |
||||
|
||||
Let's first look at the full code, then we'll break it down into sections: |
||||
|
||||
```typescript |
||||
const avatarTransformer = avatarTraverser.createTransformer< |
||||
{ |
||||
Element: { |
||||
return: string; |
||||
}; |
||||
Bender: { |
||||
return: ActionablePerson; |
||||
properties: { |
||||
element: string; |
||||
}; |
||||
}; |
||||
NonBender: { |
||||
return: ActionablePerson; |
||||
}; |
||||
}, |
||||
undefined |
||||
>({ |
||||
Element: async (item) => { |
||||
return item.toUpperCase(); |
||||
}, |
||||
Bender: { |
||||
transformer: async (item, getTransformedChildren) => { |
||||
const transformedChildren = await getTransformedChildren(); |
||||
return { |
||||
doAction: () => { |
||||
console.log(`I can bend ${transformedChildren.element}`); |
||||
}, |
||||
friends: transformedChildren.friends, |
||||
}; |
||||
}, |
||||
properties: { |
||||
element: async (item, getTransformedChildren) => { |
||||
const transformedChildren = await getTransformedChildren(); |
||||
return `the element of ${transformedChildren}`; |
||||
}, |
||||
}, |
||||
}, |
||||
NonBender: { |
||||
transformer: async (item, getTransformedChildren) => { |
||||
const transformedChildren = await getTransformedChildren(); |
||||
return { |
||||
doAction: () => { |
||||
console.log(`I can't bend.`); |
||||
}, |
||||
friends: transformedChildren.friends, |
||||
}; |
||||
}, |
||||
}, |
||||
Person: async ( |
||||
item, |
||||
getTransformedChildren, |
||||
setReturnPointer, |
||||
_context |
||||
) => { |
||||
const personToReturn: ActionablePerson = {} as ActionablePerson; |
||||
setReturnPointer(personToReturn); |
||||
const transformedChildren = await getTransformedChildren(); |
||||
personToReturn.doAction = transformedChildren.doAction; |
||||
personToReturn.friends = transformedChildren.friends; |
||||
return personToReturn; |
||||
}, |
||||
}); |
||||
|
||||
const result = await avatarTransformer.transform(aang, "Bender", undefined); |
||||
result.doAction(); |
||||
result.friends[0].doAction(); |
||||
result.friends[1].doAction(); |
||||
// Logs: |
||||
// I can bend the element of AIR |
||||
// I can't bend. |
||||
// I can bend the element of WATER |
||||
``` |
||||
|
||||
#### Defining Transformer Return Types |
||||
The first step to working with transformers is defining the return types for each transformer. This is done by providing a generic to the `createTransformer` function: |
||||
|
||||
```typescript |
||||
const avatarTransformer = avatarTraverser.createTransformer< |
||||
{ |
||||
Element: { |
||||
return: string; |
||||
}; |
||||
Bender: { |
||||
return: ActionablePerson; |
||||
properties: { |
||||
element: string; |
||||
}; |
||||
}; |
||||
NonBender: { |
||||
return: ActionablePerson; |
||||
}; |
||||
}, |
||||
undefined |
||||
>({ |
||||
// ... |
||||
}); |
||||
``` |
||||
|
||||
For each TypeName you wish to transform, you can provide return type. In this example, the `Element` transformer must return a `string`, and the `NonBender` transformer must return an `Actionable Person`. |
||||
|
||||
Interface types may also provide the return types for their individual properties, as seen in the `Bender` type. Though, this is not required. |
||||
|
||||
Note: You don't need to define the return type for every TypeName and property. This library's typings will recursively search for the correct return type based on the input you have given and display that in the tooltip. In the example below, the "Bender" translator knows that the `friends` property is an `ActionablePerson[]` not because the `friends` property was defined in the return types, but because it has deduced that the `friends` field is linked the the `Person` type which itself is made up of the `Bender` and `NonBender` types, and both those types have a return type of `ActionablePerson`. |
||||
|
||||
 |
||||
|
||||
Note: the `undefined` at the end defines the context and is not associated with the return types. To learn how to use context see the "Using Context" section. |
||||
|
||||
#### Primitive Transformer |
||||
A primitive transformer is the simplest of the transformers. In this example `Element` is a primitive: |
||||
|
||||
```typescript |
||||
const avatarTransformer = avatarTraverser.createTransformer< |
||||
// ... |
||||
>({ |
||||
Element: async (item: Element, context: undefined): Promise<string> => { |
||||
return item.toUpperCase(); |
||||
}, |
||||
// ... |
||||
}); |
||||
``` |
||||
|
||||
A primitive transformer receives an "item" corresponding to its type and a context and must return its defined return type. |
||||
|
||||
#### Interface Transformer |
||||
An interface transformer allows you to transform not only the base interface object, but the properties of that object as well. In this example, `Bender` is an interface: |
||||
|
||||
```typescript |
||||
const avatarTransformer = avatarTraverser.createTransformer< |
||||
// ... |
||||
>({ |
||||
// ... |
||||
Bender: { |
||||
transformer: async ( |
||||
item: Bender, |
||||
getTransformedChildren: GetTransformedChildrenFunction, |
||||
setReturnPointer: SetReturnPointerFunction, |
||||
context: undefined |
||||
): Promise<ActionablePerson> => { |
||||
const transformedChildren: { |
||||
element: string; |
||||
friends: ActionablePerson[]; |
||||
} = await getTransformedChildren(); |
||||
return { |
||||
doAction: () => { |
||||
console.log(`I can bend ${transformedChildren.element}`); |
||||
}, |
||||
friends: transformedChildren.friends, |
||||
}; |
||||
}, |
||||
properties: { |
||||
element: async (item, getTransformedChildren, setReturnProperty, context) => { |
||||
const transformedChildren: string = await getTransformedChildren(); |
||||
return `the element of ${transformedChildren}`; |
||||
}, |
||||
}, |
||||
}, |
||||
// ... |
||||
}); |
||||
``` |
||||
|
||||
The base transformer (located at the `transformer` field) and the property transformers are functions with four arguments: |
||||
|
||||
- item: The original item. In this example, the base transformer would receive a `Bender` and the "element" transformer would receive an `Element`. |
||||
- getTransformedChildren: runs the transformers corresponding to the children of this object and returns their result. In this example, the base transformer's `getTransformedChildren` function returns `{ element: string; friends: ActionablePerson[] }` because the element property transformer returns a `string` and the transformers that eventually feed into the friend field (`Person`, `Bender`, and `NonBender`) return ActionablePerson. In the elements transformer, the `getTransformedChildren` function returns string because the `Elements` transformer returns string. |
||||
- setReturnProperty: A function used to prevent infinite recursion for circular data. See the "Preventing Circular Recursion" section for more. |
||||
- context: The context variable. See the "Using Context" section for more. |
||||
|
||||
A transformer must return its defined return type. For example, the base transformer must return `ActionablePerson` because it was defined that way when creating the return types. |
||||
|
||||
#### Union Transformer |
||||
A union transformer lets you transform a Union type. In this example "Person" is a union type. |
||||
|
||||
```typescript |
||||
const avatarTransformer = avatarTraverser.createTransformer< |
||||
// ... |
||||
>({ |
||||
// ... |
||||
Person: async ( |
||||
item: Person, |
||||
getTransformedChildren: GetTransformedChildrenFunction, |
||||
setReturnPointer: SetReturnPointerFunction, |
||||
context: undefined |
||||
) => { |
||||
const personToReturn: ActionablePerson = {} as ActionablePerson; |
||||
const transformedChildren: ActionablePerson = await getTransformedChildren(); |
||||
personToReturn.doAction = transformedChildren.doAction; |
||||
personToReturn.friends = transformedChildren.friends; |
||||
return personToReturn; |
||||
}, |
||||
}); |
||||
``` |
||||
|
||||
The transformer function has four arguments: |
||||
|
||||
- item: The original item. In this example, the transformer would receive a `Person`. |
||||
- getTransformedChildren: runs the transformers corresponding to the indivual elements that make up the union and returns their result. In this example, the transformer's `getTransformedChildren` function returns `ActionablePerson` because this union is made up of `Bender` and `NonBender` and both are set to return an `ActionablePerson`. |
||||
- setReturnProperty: A function used to prevent infinite recursion for circular data. See the "Preventing Circular Recursion" section for more. |
||||
- context: The context variable. See the "Using Context" section for more. |
||||
|
||||
A transformer must return its defined return type. For example, the transformer must return `ActionablePerson` because it was defined that way when creating the return types. |
||||
|
||||
Note: The above example transformer is actually not needed as it simply returns a duplicate of the value provided by `getTransformedChildren`. If a transformer is not provided, by default, this library will pass on the values from `getTransformedChildren`. |
||||
|
||||
#### Preventing Circular Recursion |
||||
Our example data is circular - it loops back on itself. If I were to call `aang.friends[0].friends[0]`, I would once again be at the aang object. This is very useful when visiting the objects, but it poses a problem when we want to transform them. The aang transformer needs to wait for the sokka and katara objects to be transformed in order for it to transform itself. But the sokka object needs to wait for the aang and katara object to be transformed. The same is true for the katara object. So, if this is the case where do you begin? |
||||
|
||||
Recall the way we defined this data in the first place: |
||||
|
||||
```typescript |
||||
const aang: Bender = { |
||||
name: "Aang", |
||||
element: "Air", |
||||
friends: [], |
||||
}; |
||||
const sokka: NonBender = { |
||||
name: "Sokka", |
||||
friends: [], |
||||
}; |
||||
const katara: Bender = { |
||||
name: "Katara", |
||||
element: "Water", |
||||
friends: [], |
||||
}; |
||||
aang.friends.push(sokka, katara); |
||||
sokka.friends.push(aang, katara); |
||||
katara.friends.push(aang, sokka); |
||||
``` |
||||
|
||||
Note that we first defined an incomplete object, then once all of our objects were defined, we modified it to link them all together. |
||||
|
||||
You can do the same thing using the `setRetunPointer` function: |
||||
|
||||
```typescript |
||||
const avatarTransformer = avatarTraverser.createTransformer< |
||||
// ... |
||||
>({ |
||||
// ... |
||||
Person: async ( |
||||
item: Person, |
||||
getTransformedChildren: GetTransformedChildrenFunction, |
||||
setReturnProinter: SetReturnPointerFunction, |
||||
context: undefined |
||||
) => { |
||||
const personToReturn: ActionablePerson = {} as ActionablePerson; |
||||
setReturnPointer(personToReturn); // <------ HERE |
||||
const transformedChildren: ActionablePerson = await getTransformedChildren(); |
||||
personToReturn.doAction = transformedChildren.doAction; |
||||
personToReturn.friends = transformedChildren.friends; |
||||
return personToReturn; |
||||
}, |
||||
}); |
||||
``` |
||||
|
||||
In this example we call `setReturnPointer` and pass in an incomplete object. After that, we can safely call `getTransformedChildren` without worrying about hitting an infinite loop. Once we have the transformedChildren, we can attach them to the original object we passed to `setReturnPointer`; |
||||
|
||||
`setReturnPointer` should be called by at least one of the transformers involved in a potential loop. |
||||
|
||||
#### Running the Transform Function |
||||
Once you have defined all of your transformers, you can run the transform function: |
||||
|
||||
```typescript |
||||
const result = await avatarTransformer.transform(aang, "Bender", undefined); |
||||
result.doAction(); |
||||
result.friends[0].doAction(); |
||||
result.friends[1].doAction(); |
||||
// Logs: |
||||
// I can bend the element of AIR |
||||
// I can't bend. |
||||
// I can bend the element of WATER |
||||
``` |
||||
|
||||
The `transform` function takes in three arguments: |
||||
- The data that should be traversed. In this example the aang object. |
||||
- The TypeName of the data. In this example `"Bender"` because the aang object is a `Bender`. `"Person"` would have also been acceptable. |
||||
- The context (see the "Using Context" section for more) |
||||
|
||||
The transform method returns the return type corresponding to the TypeName. In this case, it returns `ActionablePerson` because that is the return type for `Bender`. |
@ -0,0 +1,246 @@ |
||||
import { Traverser, TraverserDefinition, ValidateTraverserTypes } from "../src"; |
||||
|
||||
async function run() { |
||||
/** |
||||
* Original Type Definition |
||||
*/ |
||||
type Element = "Water" | "Earth" | "Fire" | "Air"; |
||||
interface Bender { |
||||
name: string; |
||||
element: Element; |
||||
friends: Person[]; |
||||
} |
||||
interface NonBender { |
||||
name: string; |
||||
friends: Person[]; |
||||
} |
||||
type Person = Bender | NonBender; |
||||
|
||||
/** |
||||
* Raw Data to Traverse |
||||
*/ |
||||
const aang: Bender = { |
||||
name: "Aang", |
||||
element: "Air", |
||||
friends: [], |
||||
}; |
||||
const sokka: NonBender = { |
||||
name: "Sokka", |
||||
friends: [], |
||||
}; |
||||
const katara: Bender = { |
||||
name: "Katara", |
||||
element: "Water", |
||||
friends: [], |
||||
}; |
||||
aang.friends.push(sokka, katara); |
||||
sokka.friends.push(aang, katara); |
||||
katara.friends.push(aang, sokka); |
||||
|
||||
/** |
||||
* Traverser Types |
||||
*/ |
||||
type AvatarTraverserTypes = ValidateTraverserTypes<{ |
||||
Element: { |
||||
kind: "primitive"; |
||||
type: Element; |
||||
}; |
||||
Bender: { |
||||
kind: "interface"; |
||||
type: Bender; |
||||
properties: { |
||||
element: "Element"; |
||||
friends: "Person"; |
||||
}; |
||||
}; |
||||
NonBender: { |
||||
kind: "interface"; |
||||
type: Bender; |
||||
properties: { |
||||
friends: "Person"; |
||||
}; |
||||
}; |
||||
Person: { |
||||
kind: "union"; |
||||
type: Person; |
||||
typeNames: "Bender" | "NonBender"; |
||||
}; |
||||
}>; |
||||
|
||||
/** |
||||
* Create the traverser definition |
||||
*/ |
||||
const avatarTraverserDefinition: TraverserDefinition<AvatarTraverserTypes> = { |
||||
Element: { |
||||
kind: "primitive", |
||||
}, |
||||
Bender: { |
||||
kind: "interface", |
||||
properties: { |
||||
element: "Element", |
||||
friends: "Person", |
||||
}, |
||||
}, |
||||
NonBender: { |
||||
kind: "interface", |
||||
properties: { |
||||
friends: "Person", |
||||
}, |
||||
}, |
||||
Person: { |
||||
kind: "union", |
||||
selector: (item) => { |
||||
return (item as Bender).element ? "Bender" : "NonBender"; |
||||
}, |
||||
}, |
||||
}; |
||||
|
||||
/** |
||||
* Instantiate the Traverser |
||||
*/ |
||||
const avatarTraverser = new Traverser<AvatarTraverserTypes>( |
||||
avatarTraverserDefinition |
||||
); |
||||
|
||||
/** |
||||
* Create a visitor |
||||
*/ |
||||
const avatarVisitor = avatarTraverser.createVisitor<undefined>({ |
||||
Element: async (item) => { |
||||
console.log(`Element: ${item}`); |
||||
}, |
||||
Bender: { |
||||
visitor: async (item) => { |
||||
console.log(`Bender: ${item.name}`); |
||||
}, |
||||
properties: { |
||||
element: async (item) => { |
||||
console.log(`Bender.element: ${item}`); |
||||
}, |
||||
}, |
||||
}, |
||||
NonBender: { |
||||
visitor: async (item) => { |
||||
console.log(`NonBender: ${item.name}`); |
||||
}, |
||||
}, |
||||
Person: async (item) => { |
||||
console.log(`Person: ${item.name}`); |
||||
}, |
||||
}); |
||||
|
||||
/** |
||||
* Run the visitor on data |
||||
*/ |
||||
console.log( |
||||
"############################## Visitor Logs ##############################" |
||||
); |
||||
await avatarVisitor.visit(aang, "Bender", undefined); |
||||
|
||||
/** |
||||
* Create a visitor that uses context |
||||
*/ |
||||
interface AvatarCountingVisitorContext { |
||||
numberOfBenders: number; |
||||
} |
||||
const avatarCountingVisitor = |
||||
avatarTraverser.createVisitor<AvatarCountingVisitorContext>({ |
||||
Bender: { |
||||
visitor: async (item, context) => { |
||||
context.numberOfBenders++; |
||||
}, |
||||
}, |
||||
}); |
||||
|
||||
/** |
||||
* Run the counting visitor |
||||
*/ |
||||
console.log( |
||||
"############################## Found Number of Benders Using Visitor ##############################" |
||||
); |
||||
const countContext: AvatarCountingVisitorContext = { numberOfBenders: 0 }; |
||||
await avatarCountingVisitor.visit(aang, "Bender", countContext); |
||||
console.log(countContext.numberOfBenders); |
||||
|
||||
/** |
||||
* Set up a transformer |
||||
*/ |
||||
interface ActionablePerson { |
||||
doAction(): void; |
||||
friends: ActionablePerson[]; |
||||
} |
||||
const avatarTransformer = avatarTraverser.createTransformer< |
||||
{ |
||||
Element: { |
||||
return: string; |
||||
}; |
||||
Bender: { |
||||
return: ActionablePerson; |
||||
properties: { |
||||
element: string; |
||||
}; |
||||
}; |
||||
NonBender: { |
||||
return: ActionablePerson; |
||||
}; |
||||
}, |
||||
undefined |
||||
>({ |
||||
Element: async (item) => { |
||||
return item.toUpperCase(); |
||||
}, |
||||
Bender: { |
||||
transformer: async (item, getTransformedChildren) => { |
||||
const transformedChildren = await getTransformedChildren(); |
||||
return { |
||||
doAction: () => { |
||||
console.log(`I can bend ${transformedChildren.element}`); |
||||
}, |
||||
friends: transformedChildren.friends, |
||||
}; |
||||
}, |
||||
properties: { |
||||
element: async (item, getTransformedChildren) => { |
||||
const transformedChildren = await getTransformedChildren(); |
||||
return `the element of ${transformedChildren}`; |
||||
}, |
||||
}, |
||||
}, |
||||
NonBender: { |
||||
transformer: async (item, getTransformedChildren) => { |
||||
const transformedChildren = await getTransformedChildren(); |
||||
return { |
||||
doAction: () => { |
||||
console.log(`I can't bend.`); |
||||
}, |
||||
friends: transformedChildren.friends, |
||||
}; |
||||
}, |
||||
}, |
||||
Person: async ( |
||||
item, |
||||
getTransformedChildren, |
||||
setReturnPointer, |
||||
_context |
||||
) => { |
||||
const personToReturn: ActionablePerson = {} as ActionablePerson; |
||||
setReturnPointer(personToReturn); |
||||
const transformedChildren = await getTransformedChildren(); |
||||
personToReturn.doAction = transformedChildren.doAction; |
||||
personToReturn.friends = transformedChildren.friends; |
||||
return personToReturn; |
||||
}, |
||||
}); |
||||
|
||||
/** |
||||
* Run the Transformer |
||||
*/ |
||||
console.log( |
||||
"############################## AvatarTraverser DoAction ##############################" |
||||
); |
||||
const result = await avatarTransformer.transform(aang, "Bender", undefined); |
||||
result.doAction(); |
||||
result.friends[0].doAction(); |
||||
result.friends[1].doAction(); |
||||
} |
||||
run(); |
@ -0,0 +1,6 @@ |
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const sharedConfig = require("../../jest.config.js"); |
||||
module.exports = { |
||||
...sharedConfig, |
||||
rootDir: "./", |
||||
}; |
@ -0,0 +1,35 @@ |
||||
{ |
||||
"name": "@ldo/type-traverser", |
||||
"version": "0.0.0", |
||||
"description": "An organized way to traverse over objects using typescript", |
||||
"main": "dist/index.js", |
||||
"scripts": { |
||||
"build": "tsc --project tsconfig.build.json", |
||||
"test": "jest --coverage", |
||||
"start": "ts-node ./example/example", |
||||
"prepublishOnly": "npm run test && npm run build", |
||||
"lint": "eslint src/** --fix --no-error-on-unmatched-pattern" |
||||
}, |
||||
"repository": { |
||||
"type": "git", |
||||
"url": "git+https://github.com/o-development/type-traverser.git" |
||||
}, |
||||
"author": "Jackson Morgan", |
||||
"license": "MIT", |
||||
"bugs": { |
||||
"url": "https://github.com/o-development/type-traverser/issues" |
||||
}, |
||||
"homepage": "https://github.com/o-development/type-traverser#readme", |
||||
"devDependencies": { |
||||
"@types/jest": "^27.4.0", |
||||
"@types/shexj": "^2.1.1", |
||||
"@types/uuid": "^8.3.4", |
||||
"jest": "^27.4.7", |
||||
"jsonld2graphobject": "^0.0.3", |
||||
"ts-jest": "^27.1.2", |
||||
"ts-node": "^10.4.0" |
||||
}, |
||||
"dependencies": { |
||||
"uuid": "^8.3.2" |
||||
} |
||||
} |
@ -0,0 +1,243 @@ |
||||
/* eslint-disable @typescript-eslint/no-explicit-any */ |
||||
import type { |
||||
ApplyTransformerReturnTypesDefaults, |
||||
InterfaceReturnType, |
||||
InterfaceTraverserDefinition, |
||||
InterfaceType, |
||||
KeyTypes, |
||||
PrimitiveReturnType, |
||||
PrimitiveType, |
||||
TransformerInputReturnTypes, |
||||
TraverserDefinition, |
||||
TraverserTypes, |
||||
UnionReturnType, |
||||
UnionType, |
||||
} from "."; |
||||
import { transformerParentSubTraverser } from "./transformerSubTraversers/TransformerParentSubTraverser"; |
||||
import { CircularDepenedencyAwaiter } from "./transformerSubTraversers/util/CircularDependencyAwaiter"; |
||||
import { MultiMap } from "./transformerSubTraversers/util/MultiMap"; |
||||
import { SuperPromise } from "./transformerSubTraversers/util/SuperPromise"; |
||||
import type { |
||||
GetTransformedChildrenFunction, |
||||
InterfaceTransformerDefinition, |
||||
InterfaceTransformerInputDefinition, |
||||
PrimitiveTransformerDefinition, |
||||
PrimitiveTransformerInputDefinition, |
||||
Transformers, |
||||
TransformersInput, |
||||
UnionTransformerDefinition, |
||||
UnionTransformerInputDefinition, |
||||
} from "./Transformers"; |
||||
|
||||
// TODO: Lots of "any" in this file. I'm just done with fancy typescript,
|
||||
// but if I ever feel so inclined, I should fix this in the future.
|
||||
|
||||
export class Transformer< |
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
Types extends TraverserTypes<any>, |
||||
InputReturnTypes extends TransformerInputReturnTypes<Types>, |
||||
Context = undefined, |
||||
> { |
||||
private traverserDefinition: TraverserDefinition<Types>; |
||||
private transformers: Transformers< |
||||
Types, |
||||
ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>, |
||||
Context |
||||
>; |
||||
|
||||
constructor( |
||||
traverserDefinition: TraverserDefinition<Types>, |
||||
transformers: TransformersInput<Types, InputReturnTypes, Context>, |
||||
) { |
||||
this.traverserDefinition = traverserDefinition; |
||||
this.transformers = this.applyDefaultTransformers(transformers); |
||||
} |
||||
|
||||
private applyDefaultInterfaceTransformerProperties< |
||||
Type extends InterfaceType<keyof Types>, |
||||
ReturnType extends InterfaceReturnType<Type>, |
||||
>( |
||||
typeName: keyof Types, |
||||
typePropertiesInput: InterfaceTransformerInputDefinition< |
||||
Types, |
||||
Type, |
||||
ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>, |
||||
ReturnType, |
||||
Context |
||||
>["properties"], |
||||
): InterfaceTransformerDefinition< |
||||
Types, |
||||
Type, |
||||
ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>, |
||||
ReturnType, |
||||
Context |
||||
>["properties"] { |
||||
return Object.keys( |
||||
(this.traverserDefinition[typeName] as InterfaceTraverserDefinition<Type>) |
||||
.properties, |
||||
).reduce<Record<KeyTypes, any>>((agg, key: keyof Type["properties"]) => { |
||||
if (typePropertiesInput && typePropertiesInput[key]) { |
||||
agg[key] = typePropertiesInput[key]; |
||||
} else { |
||||
agg[key] = ( |
||||
originalData: any, |
||||
getTransformedChildren: GetTransformedChildrenFunction<any>, |
||||
) => { |
||||
return getTransformedChildren(); |
||||
}; |
||||
} |
||||
return agg; |
||||
}, {}) as InterfaceTransformerDefinition< |
||||
Types, |
||||
Type, |
||||
ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>, |
||||
ReturnType, |
||||
Context |
||||
>["properties"]; |
||||
} |
||||
|
||||
private applyDefaultInterfaceTransformer< |
||||
Type extends InterfaceType<keyof Types>, |
||||
ReturnType extends InterfaceReturnType<Type>, |
||||
>( |
||||
typeName: keyof Types, |
||||
typeInput?: InterfaceTransformerInputDefinition< |
||||
Types, |
||||
Type, |
||||
ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>, |
||||
ReturnType, |
||||
Context |
||||
>, |
||||
): InterfaceTransformerDefinition< |
||||
Types, |
||||
Type, |
||||
ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>, |
||||
ReturnType, |
||||
Context |
||||
> { |
||||
if (!typeInput) { |
||||
return { |
||||
transformer: async ( |
||||
originalData, |
||||
getTransformedChildren: GetTransformedChildrenFunction<any>, |
||||
) => { |
||||
return getTransformedChildren(); |
||||
}, |
||||
properties: this.applyDefaultInterfaceTransformerProperties( |
||||
typeName, |
||||
{}, |
||||
), |
||||
}; |
||||
} |
||||
return { |
||||
transformer: typeInput.transformer, |
||||
properties: this.applyDefaultInterfaceTransformerProperties( |
||||
typeName, |
||||
typeInput.properties, |
||||
), |
||||
}; |
||||
} |
||||
|
||||
private applyDefaultUnionTransformer< |
||||
Type extends UnionType<keyof Types>, |
||||
ReturnType extends UnionReturnType, |
||||
>( |
||||
typeInput?: UnionTransformerInputDefinition< |
||||
Types, |
||||
Type, |
||||
ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>, |
||||
ReturnType, |
||||
Context |
||||
>, |
||||
): UnionTransformerDefinition< |
||||
Types, |
||||
Type, |
||||
ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>, |
||||
ReturnType, |
||||
Context |
||||
> { |
||||
if (!typeInput) { |
||||
return async ( |
||||
originalData, |
||||
getTransformedChildren: GetTransformedChildrenFunction<any>, |
||||
) => { |
||||
return getTransformedChildren(); |
||||
}; |
||||
} |
||||
return typeInput; |
||||
} |
||||
|
||||
private applyDefaultPrimitiveTransformer< |
||||
Type extends PrimitiveType, |
||||
ReturnType extends PrimitiveReturnType, |
||||
>( |
||||
typeInput?: PrimitiveTransformerInputDefinition<Type, ReturnType, Context>, |
||||
): PrimitiveTransformerDefinition<Type, ReturnType, Context> { |
||||
if (!typeInput) { |
||||
return async (originalData) => { |
||||
return originalData; |
||||
}; |
||||
} |
||||
return typeInput; |
||||
} |
||||
|
||||
private applyDefaultTransformers( |
||||
inputTransformers: TransformersInput<Types, InputReturnTypes, Context>, |
||||
): Transformers< |
||||
Types, |
||||
ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>, |
||||
Context |
||||
> { |
||||
const finalTansformers: Partial< |
||||
Transformers< |
||||
Types, |
||||
ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>, |
||||
Context |
||||
> |
||||
> = {}; |
||||
Object.keys(this.traverserDefinition).forEach((typeName: keyof Types) => { |
||||
if (this.traverserDefinition[typeName].kind === "interface") { |
||||
finalTansformers[typeName] = this.applyDefaultInterfaceTransformer( |
||||
typeName, |
||||
inputTransformers[typeName] as any, |
||||
) as any; |
||||
} else if (this.traverserDefinition[typeName].kind === "union") { |
||||
finalTansformers[typeName] = this.applyDefaultUnionTransformer( |
||||
inputTransformers[typeName] as any, |
||||
) as any; |
||||
} else if (this.traverserDefinition[typeName].kind === "primitive") { |
||||
finalTansformers[typeName] = this.applyDefaultPrimitiveTransformer( |
||||
inputTransformers[typeName] as any, |
||||
) as any; |
||||
} |
||||
}); |
||||
return finalTansformers as Transformers< |
||||
Types, |
||||
ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>, |
||||
Context |
||||
>; |
||||
} |
||||
|
||||
public async transform<TypeName extends keyof Types>( |
||||
item: Types[TypeName]["type"], |
||||
itemTypeName: TypeName, |
||||
context: Context, |
||||
): Promise< |
||||
ApplyTransformerReturnTypesDefaults< |
||||
Types, |
||||
InputReturnTypes |
||||
>[TypeName]["return"] |
||||
> { |
||||
const superPromise = new SuperPromise(); |
||||
const toReturn = await transformerParentSubTraverser(item, itemTypeName, { |
||||
traverserDefinition: this.traverserDefinition, |
||||
transformers: this.transformers, |
||||
executingPromises: new MultiMap(), |
||||
circularDependencyAwaiter: new CircularDepenedencyAwaiter(), |
||||
superPromise, |
||||
context, |
||||
}); |
||||
await superPromise.wait(); |
||||
return toReturn; |
||||
} |
||||
} |
@ -0,0 +1,75 @@ |
||||
import type { |
||||
InterfaceType, |
||||
PrimitiveType, |
||||
TraverserTypes, |
||||
UnionType, |
||||
} from "."; |
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */ |
||||
export type InterfaceReturnType<Type extends InterfaceType<any>> = { |
||||
return: any; |
||||
properties: { |
||||
[PropertyName in keyof Type["properties"]]: any; |
||||
}; |
||||
}; |
||||
|
||||
export type UnionReturnType = { |
||||
return: any; |
||||
}; |
||||
|
||||
export type PrimitiveReturnType = { |
||||
return: any; |
||||
}; |
||||
|
||||
export type BaseReturnType< |
||||
Types extends TraverserTypes<any>, |
||||
TypeName extends keyof Types, |
||||
> = Types[TypeName] extends InterfaceType<keyof Types> |
||||
? InterfaceReturnType<Types[TypeName]> |
||||
: Types[TypeName] extends UnionType<keyof Types> |
||||
? UnionReturnType |
||||
: Types[TypeName] extends PrimitiveType |
||||
? PrimitiveReturnType |
||||
: never; |
||||
|
||||
export type TransformerReturnTypes<Types extends TraverserTypes<any>> = { |
||||
[TypeName in keyof Types]: BaseReturnType<Types, TypeName>; |
||||
}; |
||||
|
||||
/** |
||||
* Input |
||||
*/ |
||||
export type InterfacePropertiesInputReturnType< |
||||
Type extends InterfaceType<any>, |
||||
> = Partial<{ |
||||
[PropertyName in keyof Type["properties"]]: any; |
||||
}>; |
||||
|
||||
export type InterfaceInputReturnType<Type extends InterfaceType<any>> = { |
||||
return: any; |
||||
properties?: InterfacePropertiesInputReturnType<Type>; |
||||
}; |
||||
|
||||
export type UnionInputReturnType = { |
||||
return: any; |
||||
}; |
||||
|
||||
export type PrimitiveInputReturnType = { |
||||
return: any; |
||||
}; |
||||
|
||||
export type BaseInputReturnType< |
||||
Types extends TraverserTypes<any>, |
||||
TypeName extends keyof Types, |
||||
> = Types[TypeName] extends InterfaceType<keyof Types> |
||||
? InterfaceInputReturnType<Types[TypeName]> |
||||
: Types[TypeName] extends UnionType<keyof Types> |
||||
? UnionInputReturnType |
||||
: Types[TypeName] extends PrimitiveType |
||||
? PrimitiveInputReturnType |
||||
: never; |
||||
|
||||
export type TransformerInputReturnTypes<Types extends TraverserTypes<any>> = |
||||
Partial<{ |
||||
[TypeName in keyof Types]: BaseInputReturnType<Types, TypeName>; |
||||
}>; |
@ -0,0 +1,215 @@ |
||||
/* eslint-disable @typescript-eslint/no-explicit-any */ |
||||
import type { |
||||
BaseInputReturnType, |
||||
InterfaceInputReturnType, |
||||
InterfacePropertiesInputReturnType, |
||||
InterfaceType, |
||||
PrimitiveReturnType, |
||||
PrimitiveType, |
||||
TransformerInputReturnTypes, |
||||
TraverserTypes, |
||||
UnionInputReturnType, |
||||
UnionType, |
||||
} from "."; |
||||
|
||||
export type RecursivelyFindReturnType< |
||||
Types extends TraverserTypes<any>, |
||||
InputReturnTypes extends TransformerInputReturnTypes<Types>, |
||||
TypeNameToFind extends keyof Types, |
||||
VisitedTypeNames extends keyof Types, |
||||
> = TypeNameToFind extends VisitedTypeNames |
||||
? Types[TypeNameToFind]["type"] |
||||
: InputReturnTypes[TypeNameToFind] extends BaseInputReturnType< |
||||
Types, |
||||
Types[TypeNameToFind] |
||||
> |
||||
? InputReturnTypes[TypeNameToFind]["return"] |
||||
: ApplyTransformerReturnTypeDefault< |
||||
Types, |
||||
InputReturnTypes, |
||||
TypeNameToFind, |
||||
VisitedTypeNames | TypeNameToFind |
||||
>["return"]; |
||||
|
||||
export type ApplyUndefined<OriginalType, ReturnType> = |
||||
undefined extends OriginalType ? ReturnType | undefined : ReturnType; |
||||
|
||||
export type ApplyArrayAndUndefined<OriginalType, ReturnType> = ApplyUndefined< |
||||
OriginalType, |
||||
NonNullable<OriginalType> extends Array<any> ? Array<ReturnType> : ReturnType |
||||
>; |
||||
|
||||
export type HackilyApplyConditionalPropertyDefaults< |
||||
Types extends TraverserTypes<any>, |
||||
InputReturnTypes extends TransformerInputReturnTypes<Types>, |
||||
VisitedTypeNames extends keyof Types, |
||||
Type extends InterfaceType<keyof Types>, |
||||
PropertiesInputRetunType extends InterfacePropertiesInputReturnType<Type>, |
||||
PropertyName extends keyof Type["type"], |
||||
FallbackKey extends keyof Types, |
||||
> = unknown extends PropertiesInputRetunType[PropertyName] |
||||
? ApplyArrayAndUndefined< |
||||
Type["type"][PropertyName], |
||||
RecursivelyFindReturnType< |
||||
Types, |
||||
InputReturnTypes, |
||||
FallbackKey, |
||||
VisitedTypeNames |
||||
> |
||||
> |
||||
: ApplyUndefined< |
||||
Type["type"][PropertyName], |
||||
PropertiesInputRetunType[PropertyName] |
||||
>; |
||||
|
||||
export type HackilyApplyConditionalPropertiesDefaults< |
||||
Types extends TraverserTypes<any>, |
||||
InputReturnTypes extends TransformerInputReturnTypes<Types>, |
||||
VisitedTypeNames extends keyof Types, |
||||
Type extends InterfaceType<keyof Types>, |
||||
InputReturnType extends InterfaceInputReturnType<any>, |
||||
PropertyName extends keyof Type["type"], |
||||
FallbackKey extends keyof Types, |
||||
> = InputReturnType["properties"] extends InterfacePropertiesInputReturnType<Type> |
||||
? HackilyApplyConditionalPropertyDefaults< |
||||
Types, |
||||
InputReturnTypes, |
||||
VisitedTypeNames, |
||||
Type, |
||||
InputReturnType["properties"], |
||||
PropertyName, |
||||
FallbackKey |
||||
> |
||||
: ApplyArrayAndUndefined< |
||||
Type["type"][PropertyName], |
||||
RecursivelyFindReturnType< |
||||
Types, |
||||
InputReturnTypes, |
||||
FallbackKey, |
||||
VisitedTypeNames |
||||
> |
||||
>; |
||||
|
||||
export type ApplyTransformerInterfacePropertiesReturnTypeDefault< |
||||
Types extends TraverserTypes<any>, |
||||
InputReturnTypes extends TransformerInputReturnTypes<Types>, |
||||
TypeName extends keyof Types, |
||||
VisitedTypeNames extends keyof Types, |
||||
> = Types[TypeName] extends InterfaceType<keyof Types> |
||||
? { |
||||
[PropertyName in keyof Types[TypeName]["properties"]]: InputReturnTypes[TypeName] extends InterfaceInputReturnType< |
||||
Types[TypeName] |
||||
> |
||||
? HackilyApplyConditionalPropertiesDefaults< |
||||
Types, |
||||
InputReturnTypes, |
||||
VisitedTypeNames, |
||||
Types[TypeName], |
||||
InputReturnTypes[TypeName], |
||||
PropertyName, |
||||
Types[TypeName]["properties"][PropertyName] |
||||
> |
||||
: ApplyArrayAndUndefined< |
||||
Types[TypeName]["type"][PropertyName], |
||||
RecursivelyFindReturnType< |
||||
Types, |
||||
InputReturnTypes, |
||||
Types[TypeName]["properties"][PropertyName], |
||||
VisitedTypeNames |
||||
> |
||||
>; |
||||
} |
||||
: never; |
||||
|
||||
export type ApplyTransformerInterfaceReturnTypeDefault< |
||||
Types extends TraverserTypes<any>, |
||||
InputReturnTypes extends TransformerInputReturnTypes<Types>, |
||||
TypeName extends keyof Types, |
||||
Type extends InterfaceType<keyof Types>, |
||||
VisitedTypeNames extends keyof Types, |
||||
> = { |
||||
return: InputReturnTypes[TypeName] extends InterfaceInputReturnType<Type> |
||||
? InputReturnTypes[TypeName]["return"] |
||||
: ApplyTransformerInterfacePropertiesReturnTypeDefault< |
||||
Types, |
||||
InputReturnTypes, |
||||
TypeName, |
||||
VisitedTypeNames |
||||
>; |
||||
properties: ApplyTransformerInterfacePropertiesReturnTypeDefault< |
||||
Types, |
||||
InputReturnTypes, |
||||
TypeName, |
||||
VisitedTypeNames |
||||
>; |
||||
}; |
||||
|
||||
export type ApplyTransformerUnionReturnTypeDefault< |
||||
Types extends TraverserTypes<any>, |
||||
InputReturnTypes extends TransformerInputReturnTypes<Types>, |
||||
TypeName extends keyof Types, |
||||
Type extends UnionType<keyof Types>, |
||||
VisitedTypeNames extends keyof Types, |
||||
> = { |
||||
return: InputReturnTypes[TypeName] extends UnionInputReturnType |
||||
? InputReturnTypes[TypeName]["return"] |
||||
: RecursivelyFindReturnType< |
||||
Types, |
||||
InputReturnTypes, |
||||
Type["typeNames"], |
||||
VisitedTypeNames |
||||
>; |
||||
}; |
||||
|
||||
export type ApplyTransformerPrimitiveReturnTypeDefault< |
||||
Types extends TraverserTypes<any>, |
||||
InputReturnTypes extends TransformerInputReturnTypes<Types>, |
||||
TypeName extends keyof Types, |
||||
Type extends PrimitiveType, |
||||
> = { |
||||
return: InputReturnTypes[TypeName] extends PrimitiveReturnType |
||||
? InputReturnTypes[TypeName]["return"] |
||||
: Type["type"]; |
||||
}; |
||||
|
||||
export type ApplyTransformerReturnTypeDefault< |
||||
Types extends TraverserTypes<any>, |
||||
InputReturnTypes extends TransformerInputReturnTypes<Types>, |
||||
TypeName extends keyof Types, |
||||
VisitedTypeNames extends keyof Types, |
||||
> = Types[TypeName] extends InterfaceType<keyof Types> |
||||
? ApplyTransformerInterfaceReturnTypeDefault< |
||||
Types, |
||||
InputReturnTypes, |
||||
TypeName, |
||||
Types[TypeName], |
||||
VisitedTypeNames |
||||
> |
||||
: Types[TypeName] extends UnionType<keyof Types> |
||||
? ApplyTransformerUnionReturnTypeDefault< |
||||
Types, |
||||
InputReturnTypes, |
||||
TypeName, |
||||
Types[TypeName], |
||||
VisitedTypeNames |
||||
> |
||||
: Types[TypeName] extends PrimitiveType |
||||
? ApplyTransformerPrimitiveReturnTypeDefault< |
||||
Types, |
||||
InputReturnTypes, |
||||
TypeName, |
||||
Types[TypeName] |
||||
> |
||||
: never; |
||||
|
||||
export type ApplyTransformerReturnTypesDefaults< |
||||
Types extends TraverserTypes<any>, |
||||
InputReturnTypes extends TransformerInputReturnTypes<Types>, |
||||
> = { |
||||
[TypeName in keyof Types]: ApplyTransformerReturnTypeDefault< |
||||
Types, |
||||
InputReturnTypes, |
||||
TypeName, |
||||
never |
||||
>; |
||||
}; |
@ -0,0 +1,244 @@ |
||||
/* eslint-disable @typescript-eslint/no-explicit-any */ |
||||
import type { |
||||
ApplyTransformerReturnTypesDefaults, |
||||
InterfaceReturnType, |
||||
InterfaceType, |
||||
PrimitiveReturnType, |
||||
PrimitiveType, |
||||
TransformerInputReturnTypes, |
||||
TransformerReturnTypes, |
||||
TraverserTypes, |
||||
UnionReturnType, |
||||
UnionType, |
||||
} from "."; |
||||
|
||||
export type GetTransformedChildrenFunction<TransformedChildrenType> = |
||||
() => Promise<TransformedChildrenType>; |
||||
|
||||
export type SetReturnPointerFunction<ReturnType> = ( |
||||
returnPointer: ReturnType, |
||||
) => void; |
||||
|
||||
export type InterfaceTransformerFunction< |
||||
Types extends TraverserTypes<any>, |
||||
Type extends InterfaceType<keyof Types>, |
||||
ReturnType extends InterfaceReturnType<Type>, |
||||
Context, |
||||
> = ( |
||||
originalData: Type["type"], |
||||
getTransformedChildren: GetTransformedChildrenFunction<{ |
||||
[PropertyName in keyof ReturnType["properties"]]: ReturnType["properties"][PropertyName]; |
||||
}>, |
||||
setReturnPointer: SetReturnPointerFunction<ReturnType["return"]>, |
||||
context: Context, |
||||
) => Promise<ReturnType["return"]>; |
||||
|
||||
export type InterfaceTransformerPropertyFunction< |
||||
Types extends TraverserTypes<any>, |
||||
Type extends InterfaceType<keyof Types>, |
||||
ReturnTypes extends TransformerReturnTypes<Types>, |
||||
ReturnType extends InterfaceReturnType<Type>, |
||||
PropertyName extends keyof Type["properties"], |
||||
Context, |
||||
> = ( |
||||
originalData: Types[Type["properties"][PropertyName]]["type"], |
||||
getTransfromedChildren: GetTransformedChildrenFunction< |
||||
ReturnTypes[Type["properties"][PropertyName]]["return"] |
||||
>, |
||||
context: Context, |
||||
) => Promise<ReturnType["properties"][PropertyName]>; |
||||
|
||||
export type InterfaceTransformerDefinition< |
||||
Types extends TraverserTypes<any>, |
||||
Type extends InterfaceType<keyof Types>, |
||||
ReturnTypes extends TransformerReturnTypes<Types>, |
||||
ReturnType extends InterfaceReturnType<Type>, |
||||
Context, |
||||
> = { |
||||
transformer: InterfaceTransformerFunction<Types, Type, ReturnType, Context>; |
||||
properties: { |
||||
[PropertyName in keyof Type["properties"]]: InterfaceTransformerPropertyFunction< |
||||
Types, |
||||
Type, |
||||
ReturnTypes, |
||||
ReturnType, |
||||
PropertyName, |
||||
Context |
||||
>; |
||||
}; |
||||
}; |
||||
|
||||
export type UnionTransformerFunction< |
||||
Types extends TraverserTypes<any>, |
||||
Type extends UnionType<keyof Types>, |
||||
ReturnTypes extends TransformerReturnTypes<Types>, |
||||
ReturnType extends UnionReturnType, |
||||
Context, |
||||
> = ( |
||||
originalData: Type["type"], |
||||
getTransformedChildren: GetTransformedChildrenFunction< |
||||
ReturnTypes[Type["typeNames"]]["return"] |
||||
>, |
||||
setReturnPointer: SetReturnPointerFunction<ReturnType["return"]>, |
||||
context: Context, |
||||
) => Promise<ReturnType["return"]>; |
||||
|
||||
export type UnionTransformerDefinition< |
||||
Types extends TraverserTypes<any>, |
||||
Type extends UnionType<keyof Types>, |
||||
ReturnTypes extends TransformerReturnTypes<Types>, |
||||
ReturnType extends UnionReturnType, |
||||
Context, |
||||
> = UnionTransformerFunction<Types, Type, ReturnTypes, ReturnType, Context>; |
||||
|
||||
export type PrimitiveTransformerFunction< |
||||
Type extends PrimitiveType, |
||||
ReturnType extends PrimitiveReturnType, |
||||
Context, |
||||
> = ( |
||||
originalData: Type["type"], |
||||
context: Context, |
||||
) => Promise<ReturnType["return"]>; |
||||
|
||||
export type PrimitiveTransformerDefinition< |
||||
Type extends PrimitiveType, |
||||
ReturnType extends PrimitiveReturnType, |
||||
Context, |
||||
> = PrimitiveTransformerFunction<Type, ReturnType, Context>; |
||||
|
||||
export type TransformerDefinition< |
||||
Types extends TraverserTypes<any>, |
||||
ReturnTypes extends TransformerReturnTypes<Types>, |
||||
TypeName extends keyof Types, |
||||
Context, |
||||
> = Types[TypeName] extends InterfaceType<keyof Types> |
||||
? ReturnTypes[TypeName] extends InterfaceReturnType<Types[TypeName]> |
||||
? InterfaceTransformerDefinition< |
||||
Types, |
||||
Types[TypeName], |
||||
ReturnTypes, |
||||
ReturnTypes[TypeName], |
||||
Context |
||||
> |
||||
: never |
||||
: Types[TypeName] extends UnionType<keyof Types> |
||||
? ReturnTypes[TypeName] extends UnionReturnType |
||||
? UnionTransformerDefinition< |
||||
Types, |
||||
Types[TypeName], |
||||
ReturnTypes, |
||||
ReturnTypes[TypeName], |
||||
Context |
||||
> |
||||
: never |
||||
: Types[TypeName] extends PrimitiveType |
||||
? ReturnTypes[TypeName] extends PrimitiveReturnType |
||||
? PrimitiveTransformerDefinition< |
||||
Types[TypeName], |
||||
ReturnTypes[TypeName], |
||||
Context |
||||
> |
||||
: never |
||||
: never; |
||||
|
||||
export type Transformers< |
||||
Types extends TraverserTypes<any>, |
||||
ReturnTypes extends TransformerReturnTypes<Types>, |
||||
Context, |
||||
> = { |
||||
[TypeName in keyof ReturnTypes]: TransformerDefinition< |
||||
Types, |
||||
ReturnTypes, |
||||
TypeName, |
||||
Context |
||||
>; |
||||
}; |
||||
|
||||
/** |
||||
* Input |
||||
*/ |
||||
export type InterfaceTransformerInputDefinition< |
||||
Types extends TraverserTypes<any>, |
||||
Type extends InterfaceType<keyof Types>, |
||||
ReturnTypes extends TransformerReturnTypes<Types>, |
||||
ReturnType extends InterfaceReturnType<Type>, |
||||
Context, |
||||
> = { |
||||
transformer: InterfaceTransformerFunction<Types, Type, ReturnType, Context>; |
||||
properties?: Partial<{ |
||||
[PropertyName in keyof Type["properties"]]: InterfaceTransformerPropertyFunction< |
||||
Types, |
||||
Type, |
||||
ReturnTypes, |
||||
ReturnType, |
||||
PropertyName, |
||||
Context |
||||
>; |
||||
}>; |
||||
}; |
||||
|
||||
export type UnionTransformerInputDefinition< |
||||
Types extends TraverserTypes<any>, |
||||
Type extends UnionType<keyof Types>, |
||||
ReturnTypes extends TransformerReturnTypes<Types>, |
||||
ReturnType extends UnionReturnType, |
||||
Context, |
||||
> = UnionTransformerFunction<Types, Type, ReturnTypes, ReturnType, Context>; |
||||
|
||||
export type PrimitiveTransformerInputDefinition< |
||||
Type extends PrimitiveType, |
||||
ReturnType extends PrimitiveReturnType, |
||||
Context, |
||||
> = PrimitiveTransformerFunction<Type, ReturnType, Context>; |
||||
|
||||
export type TransformerInputDefinition< |
||||
Types extends TraverserTypes<any>, |
||||
ReturnTypes extends TransformerReturnTypes<Types>, |
||||
TypeName extends keyof Types, |
||||
Context, |
||||
> = Types[TypeName] extends InterfaceType<keyof Types> |
||||
? ReturnTypes[TypeName] extends InterfaceReturnType<Types[TypeName]> |
||||
? InterfaceTransformerInputDefinition< |
||||
Types, |
||||
Types[TypeName], |
||||
ReturnTypes, |
||||
ReturnTypes[TypeName], |
||||
Context |
||||
> |
||||
: never |
||||
: Types[TypeName] extends UnionType<keyof Types> |
||||
? ReturnTypes[TypeName] extends UnionReturnType |
||||
? UnionTransformerInputDefinition< |
||||
Types, |
||||
Types[TypeName], |
||||
ReturnTypes, |
||||
ReturnTypes[TypeName], |
||||
Context |
||||
> |
||||
: never |
||||
: Types[TypeName] extends PrimitiveType |
||||
? ReturnTypes[TypeName] extends PrimitiveReturnType |
||||
? PrimitiveTransformerInputDefinition< |
||||
Types[TypeName], |
||||
ReturnTypes[TypeName], |
||||
Context |
||||
> |
||||
: never |
||||
: never; |
||||
|
||||
export type TransformersInput< |
||||
Types extends TraverserTypes<any>, |
||||
InputReturnTypes extends TransformerInputReturnTypes<Types>, |
||||
Context, |
||||
ReturnTypes extends |
||||
TransformerReturnTypes<Types> = ApplyTransformerReturnTypesDefaults< |
||||
Types, |
||||
InputReturnTypes |
||||
>, |
||||
> = Partial<{ |
||||
[TypeName in keyof ReturnTypes]: undefined extends InputReturnTypes[TypeName] |
||||
? |
||||
| TransformerInputDefinition<Types, ReturnTypes, TypeName, Context> |
||||
| undefined |
||||
: TransformerInputDefinition<Types, ReturnTypes, TypeName, Context>; |
||||
}>; |
@ -0,0 +1,36 @@ |
||||
/* eslint-disable @typescript-eslint/no-explicit-any */ |
||||
import type { |
||||
TransformerInputReturnTypes, |
||||
TraverserDefinition, |
||||
TraverserTypes, |
||||
VisitorsInput, |
||||
} from "."; |
||||
import { Transformer, Visitor } from "."; |
||||
import type { TransformersInput } from "./Transformers"; |
||||
|
||||
export class Traverser< |
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
Types extends TraverserTypes<any>, |
||||
> { |
||||
private traverserDefinition: TraverserDefinition<Types>; |
||||
|
||||
constructor(traverserDefinition: TraverserDefinition<Types>) { |
||||
this.traverserDefinition = traverserDefinition; |
||||
} |
||||
|
||||
public createTransformer< |
||||
ReturnTypes extends TransformerInputReturnTypes<Types>, |
||||
Context = undefined, |
||||
>(transformers: TransformersInput<Types, ReturnTypes, Context>) { |
||||
return new Transformer<Types, ReturnTypes, Context>( |
||||
this.traverserDefinition, |
||||
transformers, |
||||
); |
||||
} |
||||
|
||||
public createVisitor<Context = undefined>( |
||||
visitors: VisitorsInput<Types, Context>, |
||||
) { |
||||
return new Visitor<Types, Context>(this.traverserDefinition, visitors); |
||||
} |
||||
} |
@ -0,0 +1,35 @@ |
||||
/* eslint-disable @typescript-eslint/no-explicit-any */ |
||||
import type { |
||||
InterfaceType, |
||||
TraverserTypes, |
||||
UnionType, |
||||
PrimitiveType, |
||||
} from "."; |
||||
|
||||
export type InterfaceTraverserDefinition<Type extends InterfaceType<any>> = { |
||||
kind: "interface"; |
||||
properties: { |
||||
[PropertyField in keyof Type["properties"]]: Type["properties"][PropertyField]; |
||||
}; |
||||
}; |
||||
|
||||
export type UnionTraverserDefinition<Type extends UnionType<any>> = { |
||||
kind: "union"; |
||||
selector: (item: Type["type"]) => Type["typeNames"]; |
||||
}; |
||||
|
||||
export type PrimitiveTraverserDefinition = { |
||||
kind: "primitive"; |
||||
}; |
||||
|
||||
export type TraverserDefinition<Types extends TraverserTypes<any>> = { |
||||
[TypeField in keyof Types]: Types[TypeField] extends InterfaceType< |
||||
keyof Types |
||||
> |
||||
? InterfaceTraverserDefinition<Types[TypeField]> |
||||
: Types[TypeField] extends UnionType<keyof Types> |
||||
? UnionTraverserDefinition<Types[TypeField]> |
||||
: Types[TypeField] extends PrimitiveType |
||||
? PrimitiveTraverserDefinition |
||||
: never; |
||||
}; |
@ -0,0 +1,33 @@ |
||||
/* eslint-disable @typescript-eslint/no-explicit-any */ |
||||
import type { AssertExtends, KeyTypes } from "./UtilTypes"; |
||||
|
||||
export interface InterfaceType<TypeNames extends KeyTypes> { |
||||
kind: "interface"; |
||||
type: any; |
||||
properties: { |
||||
[key: string]: TypeNames; |
||||
}; |
||||
} |
||||
|
||||
export interface UnionType<TypeNames extends KeyTypes> { |
||||
kind: "union"; |
||||
type: any; |
||||
typeNames: TypeNames; |
||||
} |
||||
|
||||
export interface PrimitiveType { |
||||
kind: "primitive"; |
||||
type: any; |
||||
} |
||||
|
||||
export type BaseTraverserTypes<TypeNames extends KeyTypes> = |
||||
| InterfaceType<TypeNames> |
||||
| UnionType<TypeNames> |
||||
| PrimitiveType; |
||||
|
||||
export type TraverserTypes<TypeNames extends KeyTypes> = { |
||||
[Property in TypeNames]: BaseTraverserTypes<TypeNames>; |
||||
}; |
||||
|
||||
export type ValidateTraverserTypes<Types extends TraverserTypes<any>> = |
||||
AssertExtends<TraverserTypes<keyof Types>, Types>; |
@ -0,0 +1,6 @@ |
||||
export type KeyTypes = string | number | symbol; |
||||
|
||||
export type AssertExtends< |
||||
Extended, |
||||
Extends extends Extended, |
||||
> = Extends extends Extended ? Extends : never; |
@ -0,0 +1,145 @@ |
||||
/* eslint-disable @typescript-eslint/no-explicit-any */ |
||||
import type { |
||||
InterfaceTraverserDefinition, |
||||
InterfaceType, |
||||
InterfaceVisitorDefinition, |
||||
InterfaceVisitorInputDefinition, |
||||
KeyTypes, |
||||
PrimitiveType, |
||||
PrimitiveVisitorDefinition, |
||||
PrimitiveVisitorInputDefinition, |
||||
TraverserDefinition, |
||||
TraverserTypes, |
||||
UnionType, |
||||
UnionVisitorDefinition, |
||||
UnionVisitorInputDefinition, |
||||
Visitors, |
||||
VisitorsInput, |
||||
} from "."; |
||||
import { MultiSet } from "./transformerSubTraversers/util/MultiSet"; |
||||
import { visitorParentSubTraverser } from "./visitorSubTraversers/VisitorParentSubTraverser"; |
||||
|
||||
// TODO: Lots of "any" in this file. I'm just done with fancy typescript,
|
||||
// but if I ever feel so inclined, I should fix this in the future.
|
||||
|
||||
export class Visitor< |
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
Types extends TraverserTypes<any>, |
||||
Context = undefined, |
||||
> { |
||||
private traverserDefinition: TraverserDefinition<Types>; |
||||
private visitors: Visitors<Types, Context>; |
||||
|
||||
constructor( |
||||
traverserDefinition: TraverserDefinition<Types>, |
||||
visitors: VisitorsInput<Types, Context>, |
||||
) { |
||||
this.traverserDefinition = traverserDefinition; |
||||
this.visitors = this.applyDefaultVisitors(visitors); |
||||
} |
||||
|
||||
private applyDefaultInterfaceVisitorProperties< |
||||
Type extends InterfaceType<keyof Types>, |
||||
>( |
||||
typeName: keyof Types, |
||||
typePropertiesInput: InterfaceVisitorInputDefinition< |
||||
Types, |
||||
Type, |
||||
Context |
||||
>["properties"], |
||||
): InterfaceVisitorDefinition<Types, Type, Context>["properties"] { |
||||
return Object.keys( |
||||
(this.traverserDefinition[typeName] as InterfaceTraverserDefinition<Type>) |
||||
.properties, |
||||
).reduce<Record<KeyTypes, any>>((agg, key: keyof Type["properties"]) => { |
||||
if (typePropertiesInput && typePropertiesInput[key]) { |
||||
agg[key] = typePropertiesInput[key]; |
||||
} else { |
||||
agg[key] = () => { |
||||
return; |
||||
}; |
||||
} |
||||
return agg; |
||||
}, {}) as InterfaceVisitorDefinition<Types, Type, Context>["properties"]; |
||||
} |
||||
|
||||
private applyDefaultInterfaceVisitor<Type extends InterfaceType<keyof Types>>( |
||||
typeName: keyof Types, |
||||
typeInput?: InterfaceVisitorInputDefinition<Types, Type, Context>, |
||||
): InterfaceVisitorDefinition<Types, Type, Context> { |
||||
if (!typeInput) { |
||||
return { |
||||
visitor: async () => { |
||||
return; |
||||
}, |
||||
properties: this.applyDefaultInterfaceVisitorProperties(typeName, {}), |
||||
}; |
||||
} |
||||
return { |
||||
visitor: typeInput.visitor, |
||||
properties: this.applyDefaultInterfaceVisitorProperties( |
||||
typeName, |
||||
typeInput.properties, |
||||
), |
||||
}; |
||||
} |
||||
|
||||
private applyDefaultUnionVisitor<Type extends UnionType<keyof Types>>( |
||||
typeInput?: UnionVisitorInputDefinition<Types, Type, Context>, |
||||
): UnionVisitorDefinition<Types, Type, Context> { |
||||
if (!typeInput) { |
||||
return async () => { |
||||
return; |
||||
}; |
||||
} |
||||
return typeInput; |
||||
} |
||||
|
||||
private applyDefaultPrimitiveVisitor<Type extends PrimitiveType>( |
||||
typeInput?: PrimitiveVisitorInputDefinition<Type, Context>, |
||||
): PrimitiveVisitorDefinition<Type, Context> { |
||||
if (!typeInput) { |
||||
return async () => { |
||||
return; |
||||
}; |
||||
} |
||||
return typeInput; |
||||
} |
||||
|
||||
private applyDefaultVisitors( |
||||
inputVisitors: VisitorsInput<Types, Context>, |
||||
): Visitors<Types, Context> { |
||||
const finalVisitors: Partial<Visitors<Types, Context>> = {}; |
||||
Object.keys(this.traverserDefinition).forEach((typeName: keyof Types) => { |
||||
if (this.traverserDefinition[typeName].kind === "interface") { |
||||
finalVisitors[typeName] = this.applyDefaultInterfaceVisitor( |
||||
typeName, |
||||
inputVisitors[typeName] as any, |
||||
) as any; |
||||
} else if (this.traverserDefinition[typeName].kind === "union") { |
||||
finalVisitors[typeName] = this.applyDefaultUnionVisitor( |
||||
inputVisitors[typeName] as any, |
||||
) as any; |
||||
} else if (this.traverserDefinition[typeName].kind === "primitive") { |
||||
finalVisitors[typeName] = this.applyDefaultPrimitiveVisitor( |
||||
inputVisitors[typeName] as any, |
||||
) as any; |
||||
} |
||||
}); |
||||
return finalVisitors as Visitors<Types, Context>; |
||||
} |
||||
|
||||
public async visit<TypeName extends keyof Types>( |
||||
item: Types[TypeName]["type"], |
||||
itemTypeName: TypeName, |
||||
context: Context, |
||||
): Promise<void> { |
||||
const toReturn = await visitorParentSubTraverser(item, itemTypeName, { |
||||
traverserDefinition: this.traverserDefinition, |
||||
visitors: this.visitors, |
||||
visitedObjects: new MultiSet(), |
||||
context, |
||||
}); |
||||
return toReturn; |
||||
} |
||||
} |
@ -0,0 +1,126 @@ |
||||
/* eslint-disable @typescript-eslint/no-explicit-any */ |
||||
import type { |
||||
InterfaceType, |
||||
PrimitiveType, |
||||
TraverserTypes, |
||||
UnionType, |
||||
} from "."; |
||||
|
||||
export type InterfaceVisitorFunction< |
||||
Types extends TraverserTypes<any>, |
||||
Type extends InterfaceType<keyof Types>, |
||||
Context, |
||||
> = (originalData: Type["type"], context: Context) => Promise<void>; |
||||
|
||||
export type InterfaceVisitorPropertyFunction< |
||||
Types extends TraverserTypes<any>, |
||||
Type extends InterfaceType<keyof Types>, |
||||
PropertyName extends keyof Type["properties"], |
||||
Context, |
||||
> = ( |
||||
originalData: Types[Type["properties"][PropertyName]]["type"], |
||||
context: Context, |
||||
) => Promise<void>; |
||||
|
||||
export type InterfaceVisitorDefinition< |
||||
Types extends TraverserTypes<any>, |
||||
Type extends InterfaceType<keyof Types>, |
||||
Context, |
||||
> = { |
||||
visitor: InterfaceVisitorFunction<Types, Type, Context>; |
||||
properties: { |
||||
[PropertyName in keyof Type["properties"]]: InterfaceVisitorPropertyFunction< |
||||
Types, |
||||
Type, |
||||
PropertyName, |
||||
Context |
||||
>; |
||||
}; |
||||
}; |
||||
|
||||
export type UnionVisitorFunction< |
||||
Types extends TraverserTypes<any>, |
||||
Type extends UnionType<keyof Types>, |
||||
Context, |
||||
> = (originalData: Type["type"], context: Context) => Promise<void>; |
||||
|
||||
export type UnionVisitorDefinition< |
||||
Types extends TraverserTypes<any>, |
||||
Type extends UnionType<keyof Types>, |
||||
Context, |
||||
> = UnionVisitorFunction<Types, Type, Context>; |
||||
|
||||
export type PrimitiveVisitorFunction<Type extends PrimitiveType, Context> = ( |
||||
originalData: Type["type"], |
||||
context: Context, |
||||
) => Promise<void>; |
||||
|
||||
export type PrimitiveVisitorDefinition< |
||||
Type extends PrimitiveType, |
||||
Context, |
||||
> = PrimitiveVisitorFunction<Type, Context>; |
||||
|
||||
export type VisitorDefinition< |
||||
Types extends TraverserTypes<any>, |
||||
TypeName extends keyof Types, |
||||
Context, |
||||
> = Types[TypeName] extends InterfaceType<keyof Types> |
||||
? InterfaceVisitorDefinition<Types, Types[TypeName], Context> |
||||
: Types[TypeName] extends UnionType<keyof Types> |
||||
? UnionVisitorDefinition<Types, Types[TypeName], Context> |
||||
: Types[TypeName] extends PrimitiveType |
||||
? PrimitiveVisitorDefinition<Types[TypeName], Context> |
||||
: never; |
||||
|
||||
export type Visitors<Types extends TraverserTypes<any>, Context> = { |
||||
[TypeName in keyof Types]: VisitorDefinition<Types, TypeName, Context>; |
||||
}; |
||||
|
||||
/** |
||||
* Input |
||||
*/ |
||||
export type InterfaceVisitorInputDefinition< |
||||
Types extends TraverserTypes<any>, |
||||
Type extends InterfaceType<keyof Types>, |
||||
Context, |
||||
> = { |
||||
visitor: InterfaceVisitorFunction<Types, Type, Context>; |
||||
properties?: Partial<{ |
||||
[PropertyName in keyof Type["properties"]]: InterfaceVisitorPropertyFunction< |
||||
Types, |
||||
Type, |
||||
PropertyName, |
||||
Context |
||||
>; |
||||
}>; |
||||
}; |
||||
|
||||
export type UnionVisitorInputDefinition< |
||||
Types extends TraverserTypes<any>, |
||||
Type extends UnionType<keyof Types>, |
||||
Context, |
||||
> = UnionVisitorFunction<Types, Type, Context>; |
||||
|
||||
export type PrimitiveVisitorInputDefinition< |
||||
Type extends PrimitiveType, |
||||
Context, |
||||
> = PrimitiveVisitorFunction<Type, Context>; |
||||
|
||||
export type VisitorInputDefinition< |
||||
Types extends TraverserTypes<any>, |
||||
TypeName extends keyof Types, |
||||
Context, |
||||
> = Types[TypeName] extends InterfaceType<keyof Types> |
||||
? InterfaceVisitorInputDefinition<Types, Types[TypeName], Context> |
||||
: Types[TypeName] extends UnionType<keyof Types> |
||||
? UnionVisitorInputDefinition<Types, Types[TypeName], Context> |
||||
: Types[TypeName] extends PrimitiveType |
||||
? PrimitiveVisitorInputDefinition<Types[TypeName], Context> |
||||
: never; |
||||
|
||||
export type VisitorsInput< |
||||
Types extends TraverserTypes<any>, |
||||
Context, |
||||
> = Partial<{ |
||||
[TypeName in keyof Types]: VisitorInputDefinition<Types, TypeName, Context>; |
||||
}>; |
@ -0,0 +1,9 @@ |
||||
export * from "./TraverserTypes"; |
||||
export * from "./UtilTypes"; |
||||
export * from "./TraverserDefinition"; |
||||
export * from "./TransformerReturnTypes"; |
||||
export * from "./TransformerReturnTypesDefaults"; |
||||
export * from "./Traverser"; |
||||
export * from "./Transformer"; |
||||
export * from "./Visitor"; |
||||
export * from "./Visitors"; |
@ -0,0 +1,123 @@ |
||||
/* eslint-disable @typescript-eslint/no-explicit-any */ |
||||
import type { TraverserTypes } from ".."; |
||||
import type { |
||||
InterfaceReturnType, |
||||
TransformerReturnTypes, |
||||
} from "../TransformerReturnTypes"; |
||||
import type { InterfaceTransformerDefinition } from "../Transformers"; |
||||
import type { InterfaceTraverserDefinition } from "../TraverserDefinition"; |
||||
import type { InterfaceType } from "../TraverserTypes"; |
||||
import { transformerParentSubTraverser } from "./TransformerParentSubTraverser"; |
||||
import type { TransformerSubTraverserGlobals } from "./util/transformerSubTraverserTypes"; |
||||
|
||||
export async function transformerInterfaceSubTraverser< |
||||
Types extends TraverserTypes<any>, |
||||
TypeName extends keyof Types, |
||||
ReturnTypes extends TransformerReturnTypes<Types>, |
||||
Type extends InterfaceType<keyof Types>, |
||||
ReturnType extends InterfaceReturnType<Type>, |
||||
Context, |
||||
>( |
||||
item: Type["type"], |
||||
itemTypeName: TypeName, |
||||
globals: TransformerSubTraverserGlobals<Types, ReturnTypes, Context>, |
||||
): Promise<ReturnType["return"]> { |
||||
const { |
||||
traverserDefinition, |
||||
transformers, |
||||
circularDependencyAwaiter, |
||||
executingPromises, |
||||
superPromise, |
||||
} = globals; |
||||
const resolveSuperPromise = superPromise.add(); |
||||
return new Promise<ReturnType["return"]>(async (resolve, reject) => { |
||||
try { |
||||
// Get the returns for properties
|
||||
const definition = traverserDefinition[ |
||||
itemTypeName |
||||
] as InterfaceTraverserDefinition<Type>; |
||||
const transformer = transformers[ |
||||
itemTypeName |
||||
] as unknown as InterfaceTransformerDefinition< |
||||
Types, |
||||
Type, |
||||
ReturnTypes, |
||||
ReturnType, |
||||
Context |
||||
>; |
||||
const transformedObject = await transformer.transformer( |
||||
item, |
||||
async () => { |
||||
const propertiesReturn: ReturnType["properties"] = Object.fromEntries( |
||||
await Promise.all( |
||||
Object.entries(definition.properties).map( |
||||
async ([propertyName]) => { |
||||
const originalObject = item[propertyName]; |
||||
const originalPropertyDefinition = |
||||
definition.properties[propertyName]; |
||||
const transformedProperty = await transformer.properties[ |
||||
propertyName |
||||
]( |
||||
originalObject, |
||||
async () => { |
||||
if (originalObject === undefined) { |
||||
return undefined; |
||||
} else if (Array.isArray(originalObject)) { |
||||
return Promise.all( |
||||
originalObject.map(async (subObject) => { |
||||
const onResolve = circularDependencyAwaiter.add( |
||||
item, |
||||
itemTypeName, |
||||
subObject, |
||||
originalPropertyDefinition, |
||||
executingPromises, |
||||
); |
||||
const toReturn = |
||||
await transformerParentSubTraverser( |
||||
subObject, |
||||
originalPropertyDefinition, |
||||
globals as any, |
||||
); |
||||
onResolve(); |
||||
return toReturn; |
||||
}), |
||||
); |
||||
} else { |
||||
const onResolve = circularDependencyAwaiter.add( |
||||
item, |
||||
itemTypeName, |
||||
originalObject, |
||||
originalPropertyDefinition, |
||||
executingPromises, |
||||
); |
||||
const toReturn = await transformerParentSubTraverser( |
||||
originalObject, |
||||
originalPropertyDefinition, |
||||
globals as any, |
||||
); |
||||
onResolve(); |
||||
return toReturn; |
||||
} |
||||
}, |
||||
globals.context, |
||||
); |
||||
return [propertyName, transformedProperty]; |
||||
}, |
||||
), |
||||
), |
||||
); |
||||
return propertiesReturn; |
||||
}, |
||||
(input) => { |
||||
resolve(input); |
||||
}, |
||||
globals.context, |
||||
); |
||||
resolve(transformedObject); |
||||
resolveSuperPromise(); |
||||
} catch (err) { |
||||
reject(err); |
||||
resolveSuperPromise(err); |
||||
} |
||||
}); |
||||
} |
@ -0,0 +1,59 @@ |
||||
/* eslint-disable @typescript-eslint/no-explicit-any */ |
||||
import type { BaseReturnType, BaseTraverserTypes, TraverserTypes } from ".."; |
||||
import type { TransformerReturnTypes } from "../TransformerReturnTypes"; |
||||
import { transformerInterfaceSubTraverser } from "./TransformerInterfaceSubTraverser"; |
||||
import { transformerPrimitiveSubTraverser } from "./TransformerPrimitiveSubTraverser"; |
||||
import { transformerUnionSubTraverser } from "./TransformerUnionSubTraverser"; |
||||
import type { |
||||
TransformerSubTraverser, |
||||
TransformerSubTraverserGlobals, |
||||
} from "./util/transformerSubTraverserTypes"; |
||||
import { timeout } from "./util/timeout"; |
||||
|
||||
const subTraversers: Record< |
||||
string, |
||||
TransformerSubTraverser<any, any, any, any, any, any> |
||||
> = { |
||||
interface: transformerInterfaceSubTraverser, |
||||
union: transformerUnionSubTraverser, |
||||
primitive: transformerPrimitiveSubTraverser, |
||||
}; |
||||
|
||||
export async function transformerParentSubTraverser< |
||||
Types extends TraverserTypes<any>, |
||||
TypeName extends keyof Types, |
||||
ReturnTypes extends TransformerReturnTypes<Types>, |
||||
Type extends BaseTraverserTypes<keyof Types>, |
||||
ReturnType extends BaseReturnType<Types, TypeName>, |
||||
Context, |
||||
>( |
||||
item: Type["type"], |
||||
itemTypeName: TypeName, |
||||
globals: TransformerSubTraverserGlobals<Types, ReturnTypes, Context>, |
||||
): Promise<ReturnType["return"]> { |
||||
const { traverserDefinition, executingPromises } = globals; |
||||
if (executingPromises.has(item, itemTypeName)) { |
||||
return executingPromises.get(item, itemTypeName)?.promise; |
||||
} |
||||
const subTraverser: TransformerSubTraverser< |
||||
Types, |
||||
TypeName, |
||||
ReturnTypes, |
||||
Type, |
||||
ReturnType, |
||||
Context |
||||
> = subTraversers[traverserDefinition[itemTypeName].kind]; |
||||
const executingPromise = { |
||||
promise: (async () => { |
||||
// This timeout exists to ensure that this promise is recorded
|
||||
// in executing promises before we continue traversing.
|
||||
await timeout(0); |
||||
return subTraverser(item, itemTypeName, globals); |
||||
})(), |
||||
isResolved: false, |
||||
}; |
||||
executingPromises.set(item, itemTypeName, executingPromise); |
||||
const toReturn = await executingPromise.promise; |
||||
executingPromise.isResolved = true; |
||||
return toReturn; |
||||
} |
@ -0,0 +1,28 @@ |
||||
/* eslint-disable @typescript-eslint/no-explicit-any */ |
||||
import type { TraverserTypes } from ".."; |
||||
import type { |
||||
PrimitiveReturnType, |
||||
TransformerReturnTypes, |
||||
} from "../TransformerReturnTypes"; |
||||
import type { PrimitiveTransformerDefinition } from "../Transformers"; |
||||
import type { PrimitiveType } from "../TraverserTypes"; |
||||
import type { TransformerSubTraverserGlobals } from "./util/transformerSubTraverserTypes"; |
||||
|
||||
export async function transformerPrimitiveSubTraverser< |
||||
Types extends TraverserTypes<any>, |
||||
TypeName extends keyof Types, |
||||
ReturnTypes extends TransformerReturnTypes<Types>, |
||||
Type extends PrimitiveType, |
||||
ReturnType extends PrimitiveReturnType, |
||||
Context, |
||||
>( |
||||
item: Type["type"], |
||||
itemTypeName: TypeName, |
||||
globals: TransformerSubTraverserGlobals<Types, ReturnTypes, Context>, |
||||
): Promise<ReturnType["return"]> { |
||||
const { transformers } = globals; |
||||
const transformer = transformers[ |
||||
itemTypeName |
||||
] as unknown as PrimitiveTransformerDefinition<Type, ReturnType, Context>; |
||||
return transformer(item, globals.context); |
||||
} |
@ -0,0 +1,78 @@ |
||||
/* eslint-disable @typescript-eslint/no-explicit-any */ |
||||
import type { TraverserTypes } from ".."; |
||||
import type { |
||||
TransformerReturnTypes, |
||||
UnionReturnType, |
||||
} from "../TransformerReturnTypes"; |
||||
import type { UnionTransformerDefinition } from "../Transformers"; |
||||
import type { UnionTraverserDefinition } from "../TraverserDefinition"; |
||||
import type { UnionType } from "../TraverserTypes"; |
||||
import { transformerParentSubTraverser } from "./TransformerParentSubTraverser"; |
||||
import type { TransformerSubTraverserGlobals } from "./util/transformerSubTraverserTypes"; |
||||
|
||||
export async function transformerUnionSubTraverser< |
||||
Types extends TraverserTypes<any>, |
||||
TypeName extends keyof Types, |
||||
ReturnTypes extends TransformerReturnTypes<Types>, |
||||
Type extends UnionType<keyof Types>, |
||||
ReturnType extends UnionReturnType, |
||||
Context, |
||||
>( |
||||
item: Type["type"], |
||||
itemTypeName: TypeName, |
||||
globals: TransformerSubTraverserGlobals<Types, ReturnTypes, Context>, |
||||
): Promise<ReturnType["return"]> { |
||||
const { |
||||
traverserDefinition, |
||||
transformers, |
||||
circularDependencyAwaiter, |
||||
executingPromises, |
||||
superPromise, |
||||
} = globals; |
||||
const resolveSuperPromise = superPromise.add(); |
||||
return new Promise<ReturnType["return"]>(async (resolve, reject) => { |
||||
try { |
||||
const definition = traverserDefinition[ |
||||
itemTypeName |
||||
] as UnionTraverserDefinition<Type>; |
||||
const transformer = transformers[ |
||||
itemTypeName |
||||
] as unknown as UnionTransformerDefinition< |
||||
Types, |
||||
Type, |
||||
ReturnTypes, |
||||
ReturnType, |
||||
Context |
||||
>; |
||||
const transformedObject = await transformer( |
||||
item, |
||||
async () => { |
||||
const itemSpecificTypeName = definition.selector(item); |
||||
const onResolve = circularDependencyAwaiter.add( |
||||
item, |
||||
itemTypeName, |
||||
item, |
||||
itemSpecificTypeName, |
||||
executingPromises, |
||||
); |
||||
const toReturn = await transformerParentSubTraverser( |
||||
item, |
||||
itemSpecificTypeName, |
||||
globals, |
||||
); |
||||
onResolve(); |
||||
return toReturn; |
||||
}, |
||||
(input) => { |
||||
resolve(input); |
||||
}, |
||||
globals.context, |
||||
); |
||||
resolve(transformedObject); |
||||
resolveSuperPromise(); |
||||
} catch (err) { |
||||
reject(err); |
||||
resolveSuperPromise(err); |
||||
} |
||||
}); |
||||
} |
@ -0,0 +1,71 @@ |
||||
import type { KeyTypes } from "../.."; |
||||
import { MultiMap } from "./MultiMap"; |
||||
import { MultiSet } from "./MultiSet"; |
||||
import type { TransformerSubTraverserExecutingPromises } from "./transformerSubTraverserTypes"; |
||||
|
||||
export class CircularDepenedencyAwaiter { |
||||
private graphNodes: MultiMap<object, KeyTypes, MultiSet<object, KeyTypes>> = |
||||
new MultiMap(); |
||||
|
||||
add( |
||||
subjectItem: object, |
||||
subjectItemName: KeyTypes, |
||||
awaitedItem: object, |
||||
awaitedItemName: KeyTypes, |
||||
executingPromises: TransformerSubTraverserExecutingPromises, |
||||
): () => void { |
||||
// If the promise has already resolved, do not add
|
||||
if (executingPromises.get(awaitedItem, awaitedItemName)?.isResolved) { |
||||
return () => { |
||||
/* Do Nothing */ |
||||
}; |
||||
} |
||||
// If it hasn't then add to the graph
|
||||
if (!this.graphNodes.has(subjectItem, subjectItemName)) { |
||||
this.graphNodes.set(subjectItem, subjectItemName, new MultiSet()); |
||||
} |
||||
this.graphNodes |
||||
.get(subjectItem, subjectItemName) |
||||
?.add(awaitedItem, awaitedItemName); |
||||
this.checkForCircuit( |
||||
awaitedItem, |
||||
awaitedItemName, |
||||
subjectItem, |
||||
subjectItemName, |
||||
); |
||||
return () => { |
||||
const awaitedSet = this.graphNodes.get(subjectItem, subjectItemName); |
||||
awaitedSet?.delete(awaitedItem, awaitedItemName); |
||||
if (awaitedSet?.size === 0) { |
||||
this.graphNodes.delete(subjectItem, subjectItemName); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
private checkForCircuit( |
||||
curItem: object, |
||||
curItemName: KeyTypes, |
||||
subjectItem: object, |
||||
subjectItemName: KeyTypes, |
||||
): void { |
||||
const nextNodes = this.graphNodes.get(curItem, curItemName); |
||||
if (!nextNodes) { |
||||
return; |
||||
} |
||||
nextNodes.forEach((nextItem, nextItemName) => { |
||||
if (subjectItem === nextItem && subjectItemName === nextItemName) { |
||||
throw new Error( |
||||
`Circular dependency found. Use the 'setReturnPointer' function. The loop includes the '${ |
||||
subjectItemName as string |
||||
}' type`,
|
||||
); |
||||
} |
||||
this.checkForCircuit( |
||||
nextItem, |
||||
nextItemName, |
||||
subjectItem, |
||||
subjectItemName, |
||||
); |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,63 @@ |
||||
/* eslint-disable @typescript-eslint/no-explicit-any */ |
||||
export class MultiMap<Key1, Key2, Value> { |
||||
private map: Map<Key1, Map<Key2, Value>> = new Map(); |
||||
// eslint-disable-next-line @typescript-eslint/no-inferrable-types
|
||||
private internalSize: number = 0; |
||||
|
||||
get(key1: Key1, key2: Key2): Value | undefined { |
||||
const firstValue = this.map.get(key1); |
||||
if (!firstValue) { |
||||
return undefined; |
||||
} |
||||
return firstValue.get(key2); |
||||
} |
||||
set(key1: Key1, key2: Key2, value: Value): void { |
||||
let nestedMap = this.map.get(key1); |
||||
if (!nestedMap) { |
||||
nestedMap = new Map(); |
||||
this.map.set(key1, nestedMap); |
||||
} |
||||
if (!nestedMap.has(key2)) { |
||||
this.internalSize++; |
||||
} |
||||
nestedMap.set(key2, value); |
||||
} |
||||
delete(key1: Key1, key2: Key2): void { |
||||
const nestedMap = this.map.get(key1); |
||||
if (!nestedMap) { |
||||
return; |
||||
} |
||||
if (nestedMap.has(key2)) { |
||||
this.internalSize--; |
||||
} |
||||
nestedMap.delete(key2); |
||||
if (nestedMap.size === 0) { |
||||
this.map.delete(key1); |
||||
} |
||||
} |
||||
has(key1: Key1, key2: Key2): boolean { |
||||
const firstValue = this.map.get(key1); |
||||
if (!firstValue) { |
||||
return false; |
||||
} |
||||
return firstValue.has(key2); |
||||
} |
||||
toString( |
||||
key1Transformer: (key: Key1) => any = (key) => key, |
||||
key2Transformer: (key: Key2) => any = (key) => key, |
||||
valueTransformer: (value: Value) => any = (value) => value, |
||||
): string { |
||||
let str = ""; |
||||
Array.from(this.map.entries()).forEach(([key1, value1]) => { |
||||
Array.from(value1.entries()).forEach(([key2, value2]) => { |
||||
str += ` [${key1Transformer(key1)}, ${key2Transformer( |
||||
key2, |
||||
)}] => ${valueTransformer(value2)}\n`;
|
||||
}); |
||||
}); |
||||
return str; |
||||
} |
||||
get size() { |
||||
return this.internalSize; |
||||
} |
||||
} |
@ -0,0 +1,68 @@ |
||||
export class MultiSet<Key1, Key2> { |
||||
private map: Map<Key1, Set<Key2>> = new Map(); |
||||
// eslint-disable-next-line @typescript-eslint/no-inferrable-types
|
||||
private internalSize: number = 0; |
||||
|
||||
add(key1: Key1, key2: Key2): void { |
||||
let nestedSet = this.map.get(key1); |
||||
if (!nestedSet) { |
||||
nestedSet = new Set(); |
||||
this.map.set(key1, nestedSet); |
||||
} |
||||
if (!nestedSet.has(key2)) { |
||||
this.internalSize++; |
||||
} |
||||
nestedSet.add(key2); |
||||
} |
||||
has(key1: Key1, key2: Key2): boolean { |
||||
const firstValue = this.map.get(key1); |
||||
if (!firstValue) { |
||||
return false; |
||||
} |
||||
return firstValue.has(key2); |
||||
} |
||||
delete(key1: Key1, key2: Key2): void { |
||||
const nestedSet = this.map.get(key1); |
||||
if (!nestedSet) { |
||||
return; |
||||
} |
||||
if (nestedSet.has(key2)) { |
||||
this.internalSize--; |
||||
} |
||||
nestedSet.delete(key2); |
||||
if (nestedSet.size === 0) { |
||||
this.map.delete(key1); |
||||
} |
||||
} |
||||
get size() { |
||||
return this.internalSize; |
||||
} |
||||
clone(): MultiSet<Key1, Key2> { |
||||
const newMultiSet = new MultiSet<Key1, Key2>(); |
||||
this.map.forEach((key2Set, key1) => { |
||||
key2Set.forEach((key2) => { |
||||
newMultiSet.add(key1, key2); |
||||
}); |
||||
}); |
||||
return newMultiSet; |
||||
} |
||||
toString( |
||||
key1Transformer: (key: Key1) => any = (key) => key, |
||||
key2Transformer: (key: Key2) => any = (key) => key, |
||||
) { |
||||
const multiSetValues: string[] = []; |
||||
this.forEach((item1, item2) => { |
||||
multiSetValues.push( |
||||
`(${key1Transformer(item1)},${key2Transformer(item2)})`, |
||||
); |
||||
}); |
||||
return `${multiSetValues.join(",")}`; |
||||
} |
||||
forEach(callback: (key1: Key1, key2: Key2) => void) { |
||||
this.map.forEach((key2Set, key1) => { |
||||
key2Set.forEach((key2) => { |
||||
callback(key1, key2); |
||||
}); |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,31 @@ |
||||
import { v4 } from "uuid"; |
||||
|
||||
export class SuperPromise { |
||||
private unfulfilled: Set<string> = new Set(); |
||||
private waitResolve: (() => void) | undefined = undefined; |
||||
private waitReject: ((err: unknown) => void) | undefined = undefined; |
||||
|
||||
add(): (error?: unknown) => void { |
||||
const id = v4(); |
||||
this.unfulfilled.add(id); |
||||
return (error?: unknown) => { |
||||
if (error && this.waitReject) { |
||||
this.waitReject(error); |
||||
} |
||||
this.unfulfilled.delete(id); |
||||
if (this.unfulfilled.size === 0 && this.waitResolve) { |
||||
this.waitResolve(); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
async wait(): Promise<void> { |
||||
if (this.unfulfilled.size === 0) { |
||||
return; |
||||
} |
||||
return new Promise<void>((resolve, reject) => { |
||||
this.waitResolve = resolve; |
||||
this.waitReject = reject; |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,3 @@ |
||||
export function timeout(ms: number) { |
||||
return new Promise((resolve) => setTimeout(resolve, ms)); |
||||
} |
@ -0,0 +1,43 @@ |
||||
/* eslint-disable @typescript-eslint/no-explicit-any */ |
||||
import type { |
||||
BaseReturnType, |
||||
BaseTraverserTypes, |
||||
KeyTypes, |
||||
TransformerReturnTypes, |
||||
TraverserDefinition, |
||||
TraverserTypes, |
||||
} from "../.."; |
||||
import type { Transformers } from "../../Transformers"; |
||||
import type { CircularDepenedencyAwaiter } from "./CircularDependencyAwaiter"; |
||||
import type { MultiMap } from "./MultiMap"; |
||||
import type { SuperPromise } from "./SuperPromise"; |
||||
|
||||
export type TransformerSubTraverser< |
||||
Types extends TraverserTypes<any>, |
||||
TypeName extends keyof Types, |
||||
ReturnTypes extends TransformerReturnTypes<Types>, |
||||
Type extends BaseTraverserTypes<keyof Types>, |
||||
ReturnType extends BaseReturnType<Types, TypeName>, |
||||
Context, |
||||
> = ( |
||||
item: Type["type"], |
||||
itemTypeName: TypeName, |
||||
globals: TransformerSubTraverserGlobals<Types, ReturnTypes, Context>, |
||||
) => Promise<ReturnType["return"]>; |
||||
|
||||
export interface TransformerSubTraverserGlobals< |
||||
Types extends TraverserTypes<any>, |
||||
ReturnTypes extends TransformerReturnTypes<Types>, |
||||
Context, |
||||
> { |
||||
traverserDefinition: TraverserDefinition<Types>; |
||||
transformers: Transformers<Types, ReturnTypes, Context>; |
||||
executingPromises: TransformerSubTraverserExecutingPromises<keyof Types>; |
||||
circularDependencyAwaiter: CircularDepenedencyAwaiter; |
||||
superPromise: SuperPromise; |
||||
context: Context; |
||||
} |
||||
|
||||
export type TransformerSubTraverserExecutingPromises< |
||||
Keys extends KeyTypes = KeyTypes, |
||||
> = MultiMap<object, Keys, { promise: Promise<any>; isResolved: boolean }>; |
@ -0,0 +1,61 @@ |
||||
/* eslint-disable @typescript-eslint/no-explicit-any */ |
||||
import type { InterfaceVisitorDefinition, TraverserTypes } from ".."; |
||||
import type { InterfaceTraverserDefinition } from "../TraverserDefinition"; |
||||
import type { InterfaceType } from "../TraverserTypes"; |
||||
import type { VisitorSubTraverserGlobals } from "./util/visitorSubTraverserTypes"; |
||||
import { visitorParentSubTraverser } from "./VisitorParentSubTraverser"; |
||||
|
||||
export async function visitorInterfaceSubTraverser< |
||||
Types extends TraverserTypes<any>, |
||||
TypeName extends keyof Types, |
||||
Type extends InterfaceType<keyof Types>, |
||||
Context, |
||||
>( |
||||
item: Type["type"], |
||||
itemTypeName: TypeName, |
||||
globals: VisitorSubTraverserGlobals<Types, Context>, |
||||
): Promise<void> { |
||||
const { traverserDefinition, visitors } = globals; |
||||
// Get the returns for properties
|
||||
const definition = traverserDefinition[ |
||||
itemTypeName |
||||
] as InterfaceTraverserDefinition<Type>; |
||||
const visitor = visitors[ |
||||
itemTypeName |
||||
] as unknown as InterfaceVisitorDefinition<Types, Type, Context>; |
||||
|
||||
await Promise.all([ |
||||
visitor.visitor(item, globals.context), |
||||
Promise.all( |
||||
Object.entries(definition.properties).map(async ([propertyName]) => { |
||||
const originalObject = item[propertyName]; |
||||
const originalPropertyDefinition = definition.properties[propertyName]; |
||||
const propertyVisitorPromise = visitor.properties[propertyName]( |
||||
originalObject, |
||||
globals.context, |
||||
); |
||||
let propertyTraverserPromise: Promise<void | void[]>; |
||||
if (originalObject === undefined) { |
||||
propertyTraverserPromise = Promise.resolve(); |
||||
} else if (Array.isArray(originalObject)) { |
||||
propertyTraverserPromise = Promise.all( |
||||
originalObject.map(async (subObject) => { |
||||
await visitorParentSubTraverser( |
||||
subObject, |
||||
originalPropertyDefinition, |
||||
globals as any, |
||||
); |
||||
}), |
||||
); |
||||
} else { |
||||
propertyTraverserPromise = visitorParentSubTraverser( |
||||
originalObject, |
||||
originalPropertyDefinition, |
||||
globals as any, |
||||
); |
||||
} |
||||
return Promise.all([propertyVisitorPromise, propertyTraverserPromise]); |
||||
}), |
||||
), |
||||
]); |
||||
} |
@ -0,0 +1,35 @@ |
||||
/* eslint-disable @typescript-eslint/no-explicit-any */ |
||||
import type { BaseTraverserTypes, TraverserTypes } from ".."; |
||||
import type { |
||||
VisitorSubTraverser, |
||||
VisitorSubTraverserGlobals, |
||||
} from "./util/visitorSubTraverserTypes"; |
||||
import { visitorInterfaceSubTraverser } from "./VisitorInterfaceSubTraverser"; |
||||
import { visitorUnionSubTraverser } from "./VisitorUnionSubTraverser"; |
||||
import { visitorPrimitiveSubTraverser } from "./VisitorPrimitiveSubTraverser"; |
||||
|
||||
const subTraversers: Record<string, VisitorSubTraverser<any, any, any, any>> = { |
||||
interface: visitorInterfaceSubTraverser, |
||||
union: visitorUnionSubTraverser, |
||||
primitive: visitorPrimitiveSubTraverser, |
||||
}; |
||||
|
||||
export async function visitorParentSubTraverser< |
||||
Types extends TraverserTypes<any>, |
||||
TypeName extends keyof Types, |
||||
Type extends BaseTraverserTypes<keyof Types>, |
||||
Context, |
||||
>( |
||||
item: Type["type"], |
||||
itemTypeName: TypeName, |
||||
globals: VisitorSubTraverserGlobals<Types, Context>, |
||||
): Promise<void> { |
||||
const { traverserDefinition, visitedObjects } = globals; |
||||
if (visitedObjects.has(item, itemTypeName)) { |
||||
return; |
||||
} |
||||
visitedObjects.add(item, itemTypeName); |
||||
const subTraverser: VisitorSubTraverser<Types, TypeName, Type, Context> = |
||||
subTraversers[traverserDefinition[itemTypeName].kind]; |
||||
return subTraverser(item, itemTypeName, globals); |
||||
} |
@ -0,0 +1,21 @@ |
||||
/* eslint-disable @typescript-eslint/no-explicit-any */ |
||||
import type { PrimitiveVisitorDefinition, TraverserTypes } from ".."; |
||||
import type { PrimitiveType } from "../TraverserTypes"; |
||||
import type { VisitorSubTraverserGlobals } from "./util/visitorSubTraverserTypes"; |
||||
|
||||
export async function visitorPrimitiveSubTraverser< |
||||
Types extends TraverserTypes<any>, |
||||
TypeName extends keyof Types, |
||||
Type extends PrimitiveType, |
||||
Context, |
||||
>( |
||||
item: Type["type"], |
||||
itemTypeName: TypeName, |
||||
globals: VisitorSubTraverserGlobals<Types, Context>, |
||||
): Promise<void> { |
||||
const { visitors } = globals; |
||||
const visitor = visitors[ |
||||
itemTypeName |
||||
] as unknown as PrimitiveVisitorDefinition<Type, Context>; |
||||
return visitor(item, globals.context); |
||||
} |
@ -0,0 +1,32 @@ |
||||
/* eslint-disable @typescript-eslint/no-explicit-any */ |
||||
import type { TraverserTypes, UnionVisitorDefinition } from ".."; |
||||
import type { UnionTraverserDefinition } from "../TraverserDefinition"; |
||||
import type { UnionType } from "../TraverserTypes"; |
||||
import type { VisitorSubTraverserGlobals } from "./util/visitorSubTraverserTypes"; |
||||
import { visitorParentSubTraverser } from "./VisitorParentSubTraverser"; |
||||
|
||||
export async function visitorUnionSubTraverser< |
||||
Types extends TraverserTypes<any>, |
||||
TypeName extends keyof Types, |
||||
Type extends UnionType<keyof Types>, |
||||
Context, |
||||
>( |
||||
item: Type["type"], |
||||
itemTypeName: TypeName, |
||||
globals: VisitorSubTraverserGlobals<Types, Context>, |
||||
): Promise<void> { |
||||
const { traverserDefinition, visitors } = globals; |
||||
const definition = traverserDefinition[ |
||||
itemTypeName |
||||
] as UnionTraverserDefinition<Type>; |
||||
const visitor = visitors[itemTypeName] as unknown as UnionVisitorDefinition< |
||||
Types, |
||||
Type, |
||||
Context |
||||
>; |
||||
const itemSpecificTypeName = definition.selector(item); |
||||
await Promise.all([ |
||||
visitor(item, globals.context), |
||||
visitorParentSubTraverser(item, itemSpecificTypeName, globals), |
||||
]); |
||||
} |
@ -0,0 +1,29 @@ |
||||
/* eslint-disable @typescript-eslint/no-explicit-any */ |
||||
import type { |
||||
BaseTraverserTypes, |
||||
TraverserDefinition, |
||||
TraverserTypes, |
||||
Visitors, |
||||
} from "../.."; |
||||
import type { MultiSet } from "../../transformerSubTraversers/util/MultiSet"; |
||||
|
||||
export type VisitorSubTraverser< |
||||
Types extends TraverserTypes<any>, |
||||
TypeName extends keyof Types, |
||||
Type extends BaseTraverserTypes<keyof Types>, |
||||
Context, |
||||
> = ( |
||||
item: Type["type"], |
||||
itemTypeName: TypeName, |
||||
globals: VisitorSubTraverserGlobals<Types, Context>, |
||||
) => Promise<void>; |
||||
|
||||
export interface VisitorSubTraverserGlobals< |
||||
Types extends TraverserTypes<any>, |
||||
Context, |
||||
> { |
||||
traverserDefinition: TraverserDefinition<Types>; |
||||
visitors: Visitors<Types, Context>; |
||||
visitedObjects: MultiSet<object, keyof Types>; |
||||
context: Context; |
||||
} |
@ -0,0 +1,69 @@ |
||||
import { Traverser } from "../../../lib"; |
||||
import { avatarTraverserDefinition } from "./AvatarTraverserDefinition"; |
||||
import { AvatarTraverserTypes } from "./AvatarTraverserTypes"; |
||||
|
||||
const avatarTraverser = new Traverser<AvatarTraverserTypes>( |
||||
avatarTraverserDefinition |
||||
); |
||||
|
||||
interface ActionablePerson { |
||||
doAction(): void; |
||||
friends: ActionablePerson[]; |
||||
} |
||||
|
||||
export const BrokenAvatarTransformer = avatarTraverser.createTransformer< |
||||
{ |
||||
Element: { |
||||
return: string; |
||||
}; |
||||
Bender: { |
||||
return: ActionablePerson; |
||||
properties: { |
||||
element: string; |
||||
}; |
||||
}; |
||||
NonBender: { |
||||
return: ActionablePerson; |
||||
}; |
||||
}, |
||||
undefined |
||||
>({ |
||||
Element: async (item) => { |
||||
return item.toUpperCase(); |
||||
}, |
||||
Bender: { |
||||
transformer: async (item, getTransformedChildren) => { |
||||
const transformedChildren = await getTransformedChildren(); |
||||
return { |
||||
doAction: () => { |
||||
console.log(`I can bend ${transformedChildren.element}`); |
||||
}, |
||||
friends: transformedChildren.friends, |
||||
}; |
||||
}, |
||||
properties: { |
||||
element: async (item, getTransformedChildren) => { |
||||
const transformedChildren = await getTransformedChildren(); |
||||
return `the element of ${transformedChildren}`; |
||||
}, |
||||
}, |
||||
}, |
||||
NonBender: { |
||||
transformer: async (item, getTransformedChildren) => { |
||||
const transformedChildren = await getTransformedChildren(); |
||||
return { |
||||
doAction: () => { |
||||
console.log(`I can't bend.`); |
||||
}, |
||||
friends: transformedChildren.friends, |
||||
}; |
||||
}, |
||||
}, |
||||
Person: async (item, getTransformedChildren, _setReturnPointer, _context) => { |
||||
const personToReturn: ActionablePerson = {} as ActionablePerson; |
||||
const transformedChildren = await getTransformedChildren(); |
||||
personToReturn.doAction = transformedChildren.doAction; |
||||
personToReturn.friends = transformedChildren.friends; |
||||
return personToReturn; |
||||
}, |
||||
}); |
@ -0,0 +1,64 @@ |
||||
import { Traverser } from "../../../lib"; |
||||
import { avatarTraverserDefinition } from "./AvatarTraverserDefinition"; |
||||
import { AvatarTraverserTypes } from "./AvatarTraverserTypes"; |
||||
|
||||
const avatarTraverser = new Traverser<AvatarTraverserTypes>( |
||||
avatarTraverserDefinition |
||||
); |
||||
|
||||
interface ActionablePerson { |
||||
doAction(): void; |
||||
friends: ActionablePerson[]; |
||||
} |
||||
|
||||
export const AvatarErroringTransformer = avatarTraverser.createTransformer< |
||||
{ |
||||
Element: { |
||||
return: string; |
||||
}; |
||||
Bender: { |
||||
return: ActionablePerson; |
||||
properties: { |
||||
element: string; |
||||
}; |
||||
}; |
||||
NonBender: { |
||||
return: ActionablePerson; |
||||
}; |
||||
}, |
||||
undefined |
||||
>({ |
||||
Element: async (item) => { |
||||
return item.toUpperCase(); |
||||
}, |
||||
Bender: { |
||||
transformer: async (item, getTransformedChildren) => { |
||||
const transformedChildren = await getTransformedChildren(); |
||||
return { |
||||
doAction: () => { |
||||
console.log(`I can bend ${transformedChildren.element}`); |
||||
}, |
||||
friends: transformedChildren.friends, |
||||
}; |
||||
}, |
||||
properties: { |
||||
element: async (item, getTransformedChildren) => { |
||||
const transformedChildren = await getTransformedChildren(); |
||||
return `the element of ${transformedChildren}`; |
||||
}, |
||||
}, |
||||
}, |
||||
NonBender: { |
||||
transformer: async (_item, _getTransformedChildren) => { |
||||
throw new Error("No Non Benders Allowed"); |
||||
}, |
||||
}, |
||||
Person: async (item, getTransformedChildren, setReturnPointer, _context) => { |
||||
const personToReturn: ActionablePerson = {} as ActionablePerson; |
||||
setReturnPointer(personToReturn); |
||||
const transformedChildren = await getTransformedChildren(); |
||||
personToReturn.doAction = transformedChildren.doAction; |
||||
personToReturn.friends = transformedChildren.friends; |
||||
return personToReturn; |
||||
}, |
||||
}); |
@ -0,0 +1,28 @@ |
||||
import { TraverserDefinition } from "../../../lib"; |
||||
import { AvatarTraverserTypes, Bender } from "./AvatarTraverserTypes"; |
||||
|
||||
export const avatarTraverserDefinition: TraverserDefinition<AvatarTraverserTypes> = |
||||
{ |
||||
Element: { |
||||
kind: "primitive", |
||||
}, |
||||
Bender: { |
||||
kind: "interface", |
||||
properties: { |
||||
element: "Element", |
||||
friends: "Person", |
||||
}, |
||||
}, |
||||
NonBender: { |
||||
kind: "interface", |
||||
properties: { |
||||
friends: "Person", |
||||
}, |
||||
}, |
||||
Person: { |
||||
kind: "union", |
||||
selector: (item) => { |
||||
return (item as Bender).element ? "Bender" : "NonBender"; |
||||
}, |
||||
}, |
||||
}; |
@ -0,0 +1,46 @@ |
||||
import { ValidateTraverserTypes } from "../../../lib"; |
||||
|
||||
/** |
||||
* Original Type Definition |
||||
*/ |
||||
export type Element = "Water" | "Earth" | "Fire" | "Air"; |
||||
export interface Bender { |
||||
name: string; |
||||
element: Element; |
||||
friends: Person[]; |
||||
} |
||||
export interface NonBender { |
||||
name: string; |
||||
friends: Person[]; |
||||
} |
||||
export type Person = Bender | NonBender; |
||||
|
||||
/** |
||||
* Traverser Types |
||||
*/ |
||||
export type AvatarTraverserTypes = ValidateTraverserTypes<{ |
||||
Element: { |
||||
kind: "primitive"; |
||||
type: Element; |
||||
}; |
||||
Bender: { |
||||
kind: "interface"; |
||||
type: Bender; |
||||
properties: { |
||||
element: "Element"; |
||||
friends: "Person"; |
||||
}; |
||||
}; |
||||
NonBender: { |
||||
kind: "interface"; |
||||
type: Bender; |
||||
properties: { |
||||
friends: "Person"; |
||||
}; |
||||
}; |
||||
Person: { |
||||
kind: "union"; |
||||
type: Person; |
||||
typeNames: "Bender" | "NonBender"; |
||||
}; |
||||
}>; |
@ -0,0 +1,19 @@ |
||||
import { BrokenAvatarTransformer } from "./AvatarBrokenTransformer"; |
||||
import { AvatarErroringTransformer } from "./AvatarErroringTransformer"; |
||||
import { aang } from "./sampleData"; |
||||
|
||||
describe("Avatar", () => { |
||||
it("Throws an error before entering an infinite loop", async () => { |
||||
await expect( |
||||
BrokenAvatarTransformer.transform(aang, "Bender", undefined) |
||||
).rejects.toThrow( |
||||
`Circular dependency found. Use the 'setReturnPointer' function. The loop includes the 'Bender' type` |
||||
); |
||||
}); |
||||
|
||||
it("Bubbles errors", async () => { |
||||
await expect( |
||||
AvatarErroringTransformer.transform(aang, "Bender", undefined) |
||||
).rejects.toThrow("No Non Benders Allowed"); |
||||
}); |
||||
}); |
@ -0,0 +1,21 @@ |
||||
import { Bender, NonBender } from "./AvatarTraverserTypes"; |
||||
/** |
||||
* Raw Data to Traverse |
||||
*/ |
||||
export const aang: Bender = { |
||||
name: "Aang", |
||||
element: "Air", |
||||
friends: [], |
||||
}; |
||||
export const sokka: NonBender = { |
||||
name: "Sokka", |
||||
friends: [], |
||||
}; |
||||
export const katara: Bender = { |
||||
name: "Katara", |
||||
element: "Water", |
||||
friends: [], |
||||
}; |
||||
aang.friends.push(sokka, katara); |
||||
sokka.friends.push(aang, katara); |
||||
katara.friends.push(aang, sokka); |
@ -0,0 +1,39 @@ |
||||
/* eslint-disable @typescript-eslint/no-explicit-any */ |
||||
import { ShexJTraverser } from "./ShexJTraverser"; |
||||
|
||||
interface ShapeReturn { |
||||
id: any; |
||||
closed: any; |
||||
extra: any[]; |
||||
expression: any; |
||||
semActs: any[]; |
||||
annotations: any[]; |
||||
} |
||||
|
||||
export const ShexJStringTransformer = ShexJTraverser.createTransformer({ |
||||
Shape: { |
||||
transformer: async ( |
||||
shape, |
||||
getTransformedChildren, |
||||
setReturnPointer |
||||
): Promise<ShapeReturn> => { |
||||
const toReturn: Partial<{ |
||||
id: any; |
||||
closed: any; |
||||
extra: any[]; |
||||
expression: any; |
||||
semActs: any[]; |
||||
annotations: any[]; |
||||
}> = {}; |
||||
setReturnPointer(toReturn as ShapeReturn); |
||||
const transformedChildren = await getTransformedChildren(); |
||||
toReturn.id = transformedChildren.id; |
||||
toReturn.annotations = transformedChildren.annotations; |
||||
toReturn.extra = transformedChildren.extra; |
||||
toReturn.expression = transformedChildren.expression; |
||||
toReturn.semActs = transformedChildren.semActs; |
||||
toReturn.annotations = transformedChildren.annotations; |
||||
return toReturn as ShapeReturn; |
||||
}, |
||||
}, |
||||
}); |
@ -0,0 +1,7 @@ |
||||
import { ShexJTraverserDefinition } from "./ShexJTraverserDefinition"; |
||||
import { ShexJTraverserTypes } from "./ShexJTraverserTypes"; |
||||
import { Traverser } from "../../../lib/Traverser"; |
||||
|
||||
export const ShexJTraverser = new Traverser<ShexJTraverserTypes>( |
||||
ShexJTraverserDefinition |
||||
); |
@ -0,0 +1,296 @@ |
||||
import { shapeExpr, valueSetValue } from "shexj"; |
||||
import { ShexJTraverserTypes } from "./ShexJTraverserTypes"; |
||||
import { TraverserDefinition } from "../../../lib"; |
||||
|
||||
export const ShexJTraverserDefinition: TraverserDefinition<ShexJTraverserTypes> = |
||||
{ |
||||
Schema: { |
||||
kind: "interface", |
||||
properties: { |
||||
startActs: "SemAct", |
||||
start: "shapeExpr", |
||||
imports: "IRIREF", |
||||
shapes: "shapeExpr", |
||||
}, |
||||
}, |
||||
shapeExpr: { |
||||
kind: "union", |
||||
selector: (item: shapeExpr) => { |
||||
if (typeof item === "string") { |
||||
return "shapeExprRef"; |
||||
} |
||||
return item.type; |
||||
}, |
||||
}, |
||||
ShapeOr: { |
||||
kind: "interface", |
||||
properties: { |
||||
id: "shapeExprRef", |
||||
shapeExprs: "shapeExpr", |
||||
}, |
||||
}, |
||||
ShapeAnd: { |
||||
kind: "interface", |
||||
properties: { |
||||
id: "shapeExprRef", |
||||
shapeExprs: "shapeExpr", |
||||
}, |
||||
}, |
||||
ShapeNot: { |
||||
kind: "interface", |
||||
properties: { |
||||
id: "shapeExprRef", |
||||
shapeExpr: "shapeExpr", |
||||
}, |
||||
}, |
||||
ShapeExternal: { |
||||
kind: "interface", |
||||
properties: { |
||||
id: "shapeExprRef", |
||||
}, |
||||
}, |
||||
shapeExprRef: { |
||||
kind: "primitive", |
||||
}, |
||||
NodeConstraint: { |
||||
kind: "interface", |
||||
properties: { |
||||
id: "shapeExprRef", |
||||
datatype: "IRIREF", |
||||
values: "valueSetValue", |
||||
length: "INTEGER", |
||||
minlength: "INTEGER", |
||||
maxlength: "INTEGER", |
||||
pattern: "STRING", |
||||
flags: "STRING", |
||||
mininclusive: "numericLiteral", |
||||
minexclusive: "numericLiteral", |
||||
maxinclusive: "numericLiteral", |
||||
maxexclusive: "numericLiteral", |
||||
totaldigits: "INTEGER", |
||||
fractiondigits: "INTEGER", |
||||
}, |
||||
}, |
||||
numericLiteral: { |
||||
kind: "primitive", |
||||
}, |
||||
valueSetValue: { |
||||
kind: "union", |
||||
selector: (item: valueSetValue) => { |
||||
if (typeof item === "string") { |
||||
return "objectValue"; |
||||
} else if ( |
||||
item.type && |
||||
[ |
||||
"IriStem", |
||||
"IriStemRange", |
||||
"LiteralStem", |
||||
"LiteralStemRange", |
||||
"Language", |
||||
"LanguageStem", |
||||
"LanguageStemRange", |
||||
].includes(item.type) |
||||
) { |
||||
return item.type as |
||||
| "IriStem" |
||||
| "IriStemRange" |
||||
| "LiteralStem" |
||||
| "LiteralStemRange" |
||||
| "Language" |
||||
| "LanguageStem" |
||||
| "LanguageStemRange"; |
||||
} else { |
||||
return "objectValue"; |
||||
} |
||||
}, |
||||
}, |
||||
objectValue: { |
||||
kind: "union", |
||||
selector: (item) => { |
||||
return typeof item === "string" ? "IRIREF" : "ObjectLiteral"; |
||||
}, |
||||
}, |
||||
ObjectLiteral: { |
||||
kind: "interface", |
||||
properties: { |
||||
value: "STRING", |
||||
language: "STRING", |
||||
type: "STRING", |
||||
}, |
||||
}, |
||||
IriStem: { |
||||
kind: "interface", |
||||
properties: { |
||||
stem: "IRIREF", |
||||
}, |
||||
}, |
||||
IriStemRange: { |
||||
kind: "interface", |
||||
properties: { |
||||
stem: "IriStemRangeStem", |
||||
exclusions: "IriStemRangeExclusions", |
||||
}, |
||||
}, |
||||
IriStemRangeStem: { |
||||
kind: "union", |
||||
selector: (item) => { |
||||
return typeof item === "string" ? "IRIREF" : "Wildcard"; |
||||
}, |
||||
}, |
||||
IriStemRangeExclusions: { |
||||
kind: "union", |
||||
selector: (item) => { |
||||
return typeof item === "string" ? "IRIREF" : "IriStem"; |
||||
}, |
||||
}, |
||||
LiteralStem: { |
||||
kind: "interface", |
||||
properties: { |
||||
stem: "STRING", |
||||
}, |
||||
}, |
||||
LiteralStemRange: { |
||||
kind: "interface", |
||||
properties: { |
||||
stem: "LiteralStemRangeStem", |
||||
exclusions: "LiteralStemRangeExclusions", |
||||
}, |
||||
}, |
||||
LiteralStemRangeStem: { |
||||
kind: "union", |
||||
selector: (item) => { |
||||
return typeof item === "string" ? "STRING" : "Wildcard"; |
||||
}, |
||||
}, |
||||
LiteralStemRangeExclusions: { |
||||
kind: "union", |
||||
selector: (item) => { |
||||
return typeof item === "string" ? "STRING" : "LiteralStem"; |
||||
}, |
||||
}, |
||||
Language: { |
||||
kind: "interface", |
||||
properties: { |
||||
languageTag: "LANGTAG", |
||||
}, |
||||
}, |
||||
LanguageStem: { |
||||
kind: "interface", |
||||
properties: { |
||||
stem: "LANGTAG", |
||||
}, |
||||
}, |
||||
LanguageStemRange: { |
||||
kind: "interface", |
||||
properties: { |
||||
stem: "LanguageStemRangeStem", |
||||
exclusions: "LanguageStemRangeExclusions", |
||||
}, |
||||
}, |
||||
LanguageStemRangeStem: { |
||||
kind: "union", |
||||
selector: (item) => { |
||||
return typeof item === "string" ? "LANGTAG" : "Wildcard"; |
||||
}, |
||||
}, |
||||
LanguageStemRangeExclusions: { |
||||
kind: "union", |
||||
selector: (item) => { |
||||
return typeof item === "string" ? "LANGTAG" : "LanguageStem"; |
||||
}, |
||||
}, |
||||
Wildcard: { |
||||
kind: "interface", |
||||
properties: {}, |
||||
}, |
||||
Shape: { |
||||
kind: "interface", |
||||
properties: { |
||||
id: "shapeExprRef", |
||||
closed: "BOOL", |
||||
extra: "IRIREF", |
||||
expression: "tripleExpr", |
||||
semActs: "SemAct", |
||||
annotations: "Annotation", |
||||
}, |
||||
}, |
||||
tripleExpr: { |
||||
kind: "union", |
||||
selector: (item) => { |
||||
if (typeof item === "string") { |
||||
return "tripleExprRef"; |
||||
} |
||||
return item.type; |
||||
}, |
||||
}, |
||||
EachOf: { |
||||
kind: "interface", |
||||
properties: { |
||||
expressions: "tripleExpr", |
||||
id: "shapeExprRef", |
||||
min: "INTEGER", |
||||
max: "INTEGER", |
||||
semActs: "SemAct", |
||||
annotations: "Annotation", |
||||
}, |
||||
}, |
||||
OneOf: { |
||||
kind: "interface", |
||||
properties: { |
||||
expressions: "tripleExpr", |
||||
id: "shapeExprRef", |
||||
min: "INTEGER", |
||||
max: "INTEGER", |
||||
semActs: "SemAct", |
||||
annotations: "Annotation", |
||||
}, |
||||
}, |
||||
TripleConstraint: { |
||||
kind: "interface", |
||||
properties: { |
||||
inverse: "BOOL", |
||||
predicate: "IRIREF", |
||||
valueExpr: "shapeExpr", |
||||
id: "shapeExprRef", |
||||
min: "INTEGER", |
||||
max: "INTEGER", |
||||
semActs: "SemAct", |
||||
annotations: "Annotation", |
||||
}, |
||||
}, |
||||
tripleExprRef: { |
||||
kind: "primitive", |
||||
}, |
||||
SemAct: { |
||||
kind: "interface", |
||||
properties: { |
||||
name: "IRIREF", |
||||
code: "STRING", |
||||
}, |
||||
}, |
||||
Annotation: { |
||||
kind: "interface", |
||||
properties: { |
||||
predicate: "IRI", |
||||
object: "objectValue", |
||||
}, |
||||
}, |
||||
IRIREF: { |
||||
kind: "primitive", |
||||
}, |
||||
STRING: { |
||||
kind: "primitive", |
||||
}, |
||||
LANGTAG: { |
||||
kind: "primitive", |
||||
}, |
||||
INTEGER: { |
||||
kind: "primitive", |
||||
}, |
||||
BOOL: { |
||||
kind: "primitive", |
||||
}, |
||||
IRI: { |
||||
kind: "primitive", |
||||
}, |
||||
}; |
@ -0,0 +1,333 @@ |
||||
import { |
||||
Annotation, |
||||
EachOf, |
||||
IriStem, |
||||
Language, |
||||
LanguageStem, |
||||
LanguageStemRange, |
||||
LiteralStem, |
||||
LiteralStemRange, |
||||
NodeConstraint, |
||||
ObjectLiteral, |
||||
OneOf, |
||||
Schema, |
||||
SemAct, |
||||
Shape, |
||||
ShapeAnd, |
||||
ShapeExternal, |
||||
ShapeNot, |
||||
ShapeOr, |
||||
Wildcard, |
||||
shapeExpr, |
||||
valueSetValue, |
||||
tripleExpr, |
||||
TripleConstraint, |
||||
shapeExprRef, |
||||
IRIREF, |
||||
STRING, |
||||
LANGTAG, |
||||
INTEGER, |
||||
numericLiteral, |
||||
BOOL, |
||||
tripleExprRef, |
||||
IRI, |
||||
objectValue, |
||||
} from "shexj"; |
||||
import { ValidateTraverserTypes } from "../../../lib"; |
||||
|
||||
export type ShexJTraverserTypes = ValidateTraverserTypes<{ |
||||
Schema: { |
||||
kind: "interface"; |
||||
type: Schema; |
||||
properties: { |
||||
startActs: "SemAct"; |
||||
start: "shapeExpr"; |
||||
imports: "IRIREF"; |
||||
shapes: "shapeExpr"; |
||||
}; |
||||
}; |
||||
shapeExpr: { |
||||
kind: "union"; |
||||
type: shapeExpr; |
||||
typeNames: |
||||
| "ShapeOr" |
||||
| "ShapeAnd" |
||||
| "ShapeNot" |
||||
| "NodeConstraint" |
||||
| "Shape" |
||||
| "ShapeExternal" |
||||
| "shapeExprRef"; |
||||
}; |
||||
ShapeOr: { |
||||
kind: "interface"; |
||||
type: ShapeOr; |
||||
properties: { |
||||
id: "shapeExprRef"; |
||||
shapeExprs: "shapeExpr"; |
||||
}; |
||||
}; |
||||
ShapeAnd: { |
||||
kind: "interface"; |
||||
type: ShapeAnd; |
||||
properties: { |
||||
id: "shapeExprRef"; |
||||
shapeExprs: "shapeExpr"; |
||||
}; |
||||
}; |
||||
ShapeNot: { |
||||
kind: "interface"; |
||||
type: ShapeNot; |
||||
properties: { |
||||
id: "shapeExprRef"; |
||||
shapeExpr: "shapeExpr"; |
||||
}; |
||||
}; |
||||
ShapeExternal: { |
||||
kind: "interface"; |
||||
type: ShapeExternal; |
||||
properties: { |
||||
id: "shapeExprRef"; |
||||
}; |
||||
}; |
||||
shapeExprRef: { |
||||
kind: "primitive"; |
||||
type: shapeExprRef; |
||||
}; |
||||
NodeConstraint: { |
||||
kind: "interface"; |
||||
type: NodeConstraint; |
||||
properties: { |
||||
id: "shapeExprRef"; |
||||
datatype: "IRIREF"; |
||||
values: "valueSetValue"; |
||||
length: "INTEGER"; |
||||
minlength: "INTEGER"; |
||||
maxlength: "INTEGER"; |
||||
pattern: "STRING"; |
||||
flags: "STRING"; |
||||
mininclusive: "numericLiteral"; |
||||
minexclusive: "numericLiteral"; |
||||
maxinclusive: "numericLiteral"; |
||||
maxexclusive: "numericLiteral"; |
||||
totaldigits: "INTEGER"; |
||||
fractiondigits: "INTEGER"; |
||||
}; |
||||
}; |
||||
numericLiteral: { |
||||
kind: "primitive"; |
||||
type: numericLiteral; |
||||
}; |
||||
valueSetValue: { |
||||
kind: "union"; |
||||
type: valueSetValue; |
||||
typeNames: |
||||
| "objectValue" |
||||
| "IriStem" |
||||
| "IriStemRange" |
||||
| "LiteralStem" |
||||
| "LiteralStemRange" |
||||
| "Language" |
||||
| "LanguageStem" |
||||
| "LanguageStemRange"; |
||||
}; |
||||
objectValue: { |
||||
kind: "union"; |
||||
type: objectValue; |
||||
typeNames: "IRIREF" | "ObjectLiteral"; |
||||
}; |
||||
ObjectLiteral: { |
||||
kind: "interface"; |
||||
type: ObjectLiteral; |
||||
properties: { |
||||
value: "STRING"; |
||||
language: "STRING"; |
||||
type: "STRING"; |
||||
}; |
||||
}; |
||||
IriStem: { |
||||
kind: "interface"; |
||||
type: IriStem; |
||||
properties: { |
||||
stem: "IRIREF"; |
||||
}; |
||||
}; |
||||
IriStemRange: { |
||||
kind: "interface"; |
||||
type: IriStem; |
||||
properties: { |
||||
stem: "IriStemRangeStem"; |
||||
exclusions: "IriStemRangeExclusions"; |
||||
}; |
||||
}; |
||||
IriStemRangeStem: { |
||||
kind: "union"; |
||||
type: IRIREF | Wildcard; |
||||
typeNames: "IRIREF" | "Wildcard"; |
||||
}; |
||||
IriStemRangeExclusions: { |
||||
kind: "union"; |
||||
type: IRIREF | IriStem; |
||||
typeNames: "IRIREF" | "IriStem"; |
||||
}; |
||||
LiteralStem: { |
||||
kind: "interface"; |
||||
type: LiteralStem; |
||||
properties: { |
||||
stem: "STRING"; |
||||
}; |
||||
}; |
||||
LiteralStemRange: { |
||||
kind: "interface"; |
||||
type: LiteralStemRange; |
||||
properties: { |
||||
stem: "LiteralStemRangeStem"; |
||||
exclusions: "LiteralStemRangeExclusions"; |
||||
}; |
||||
}; |
||||
LiteralStemRangeStem: { |
||||
kind: "union"; |
||||
type: STRING | Wildcard; |
||||
typeNames: "STRING" | "Wildcard"; |
||||
}; |
||||
LiteralStemRangeExclusions: { |
||||
kind: "union"; |
||||
type: STRING | LiteralStem; |
||||
typeNames: "STRING" | "LiteralStem"; |
||||
}; |
||||
Language: { |
||||
kind: "interface"; |
||||
type: Language; |
||||
properties: { |
||||
languageTag: "LANGTAG"; |
||||
}; |
||||
}; |
||||
LanguageStem: { |
||||
kind: "interface"; |
||||
type: LanguageStem; |
||||
properties: { |
||||
stem: "LANGTAG"; |
||||
}; |
||||
}; |
||||
LanguageStemRange: { |
||||
kind: "interface"; |
||||
type: LanguageStemRange; |
||||
properties: { |
||||
stem: "LanguageStemRangeStem"; |
||||
exclusions: "LanguageStemRangeExclusions"; |
||||
}; |
||||
}; |
||||
LanguageStemRangeStem: { |
||||
kind: "union"; |
||||
type: LANGTAG | Wildcard; |
||||
typeNames: "LANGTAG" | "Wildcard"; |
||||
}; |
||||
LanguageStemRangeExclusions: { |
||||
kind: "union"; |
||||
type: LANGTAG | LanguageStem; |
||||
typeNames: "LANGTAG" | "LanguageStem"; |
||||
}; |
||||
Wildcard: { |
||||
kind: "interface"; |
||||
type: Wildcard; |
||||
properties: Record<string, never>; |
||||
}; |
||||
Shape: { |
||||
kind: "interface"; |
||||
type: Shape; |
||||
properties: { |
||||
id: "shapeExprRef"; |
||||
closed: "BOOL"; |
||||
extra: "IRIREF"; |
||||
expression: "tripleExpr"; |
||||
semActs: "SemAct"; |
||||
annotations: "Annotation"; |
||||
}; |
||||
}; |
||||
tripleExpr: { |
||||
kind: "union"; |
||||
type: tripleExpr; |
||||
typeNames: "tripleExprRef" | "EachOf" | "OneOf" | "TripleConstraint"; |
||||
}; |
||||
EachOf: { |
||||
kind: "interface"; |
||||
type: EachOf; |
||||
properties: { |
||||
expressions: "tripleExpr"; |
||||
id: "shapeExprRef"; |
||||
min: "INTEGER"; |
||||
max: "INTEGER"; |
||||
semActs: "SemAct"; |
||||
annotations: "Annotation"; |
||||
}; |
||||
}; |
||||
OneOf: { |
||||
kind: "interface"; |
||||
type: OneOf; |
||||
properties: { |
||||
expressions: "tripleExpr"; |
||||
id: "shapeExprRef"; |
||||
min: "INTEGER"; |
||||
max: "INTEGER"; |
||||
semActs: "SemAct"; |
||||
annotations: "Annotation"; |
||||
}; |
||||
}; |
||||
TripleConstraint: { |
||||
kind: "interface"; |
||||
type: TripleConstraint; |
||||
properties: { |
||||
inverse: "BOOL"; |
||||
predicate: "IRIREF"; |
||||
valueExpr: "shapeExpr"; |
||||
id: "shapeExprRef"; |
||||
min: "INTEGER"; |
||||
max: "INTEGER"; |
||||
semActs: "SemAct"; |
||||
annotations: "Annotation"; |
||||
}; |
||||
}; |
||||
tripleExprRef: { |
||||
kind: "primitive"; |
||||
type: tripleExprRef; |
||||
}; |
||||
SemAct: { |
||||
kind: "interface"; |
||||
type: SemAct; |
||||
properties: { |
||||
name: "IRIREF"; |
||||
code: "STRING"; |
||||
}; |
||||
}; |
||||
Annotation: { |
||||
kind: "interface"; |
||||
type: Annotation; |
||||
properties: { |
||||
predicate: "IRI"; |
||||
object: "objectValue"; |
||||
}; |
||||
}; |
||||
IRIREF: { |
||||
kind: "primitive"; |
||||
type: IRIREF; |
||||
}; |
||||
STRING: { |
||||
kind: "primitive"; |
||||
type: STRING; |
||||
}; |
||||
LANGTAG: { |
||||
kind: "primitive"; |
||||
type: LANGTAG; |
||||
}; |
||||
INTEGER: { |
||||
kind: "primitive"; |
||||
type: INTEGER; |
||||
}; |
||||
BOOL: { |
||||
kind: "primitive"; |
||||
type: BOOL; |
||||
}; |
||||
IRI: { |
||||
kind: "primitive"; |
||||
type: IRI; |
||||
}; |
||||
}>; |
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,7 @@ |
||||
{ |
||||
"extends": "../../tsconfig.base.json", |
||||
"compilerOptions": { |
||||
"outDir": "./dist" |
||||
}, |
||||
"include": ["./src"] |
||||
} |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 48 KiB |
Loading…
Reference in new issue