@webergency-utils/typechecker 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ import ts from 'typescript';
2
+ export { buildValidator, generateHash, buildJsonSchema } from './engine/resolver.js';
3
+ export default function transformer(program: ts.Program): (context: ts.TransformationContext) => (sourceFile: ts.SourceFile) => ts.SourceFile;
@@ -0,0 +1,134 @@
1
+ import ts from 'typescript';
2
+ import { buildValidator, generateHash, buildJsonSchema, objectToAst } from './engine/resolver.js';
3
+ export { buildValidator, generateHash, buildJsonSchema } from './engine/resolver.js';
4
+ import { hoistRegistrations } from './engine/hoister.js';
5
+ const TARGET_DECORATORS = ['Query', 'Body', 'Param'];
6
+ const RUNTIME_FUNCTIONS = ['is', 'assert', 'assertGuard', 'validate', 'jsonSchema'];
7
+ export default function transformer(program) {
8
+ const checker = program.getTypeChecker();
9
+ return (context) => {
10
+ return (sourceFile) => {
11
+ const validatorCache = new Map();
12
+ const schemasCache = new Map();
13
+ const requiredUtils = new Set();
14
+ const visitor = (node) => {
15
+ // Skip visiting ImportDeclarations to preserve their original symbols and avoid compiler crashes
16
+ if (ts.isImportDeclaration(node)) {
17
+ return node;
18
+ }
19
+ // Handle runtime function calls (is, assert, assertGuard, validate, jsonSchema)
20
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
21
+ const fnName = node.expression.text;
22
+ if (RUNTIME_FUNCTIONS.includes(fnName)) {
23
+ const typeArg = node.typeArguments?.[0];
24
+ if (typeArg) {
25
+ const type = checker.getTypeFromTypeNode(typeArg);
26
+ const hash = generateHash(type, checker);
27
+ if (!validatorCache.has(hash)) {
28
+ buildValidator(type, checker, validatorCache, requiredUtils);
29
+ }
30
+ if (!schemasCache.has(hash)) {
31
+ const schemaObj = buildJsonSchema(type, checker);
32
+ schemasCache.set(hash, objectToAst(schemaObj));
33
+ }
34
+ let getCall;
35
+ const arg0 = node.arguments[0] || ts.factory.createIdentifier('undefined');
36
+ const arg1 = node.arguments[1] || ts.factory.createIdentifier('undefined');
37
+ let hasSchema = false;
38
+ let schemaExpr;
39
+ if (node.arguments[1]) {
40
+ const optArg = node.arguments[1];
41
+ const optType = checker.getTypeAtLocation(optArg);
42
+ const schemaProp = optType.getProperty('schema');
43
+ if (schemaProp) {
44
+ hasSchema = true;
45
+ if (ts.isObjectLiteralExpression(optArg)) {
46
+ const prop = optArg.properties.find(p => {
47
+ if (ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.text === 'schema') {
48
+ return true;
49
+ }
50
+ if (ts.isShorthandPropertyAssignment(p) && p.name.text === 'schema') {
51
+ return true;
52
+ }
53
+ return false;
54
+ });
55
+ if (prop) {
56
+ if (ts.isPropertyAssignment(prop)) {
57
+ schemaExpr = prop.initializer;
58
+ }
59
+ else if (ts.isShorthandPropertyAssignment(prop)) {
60
+ schemaExpr = prop.name;
61
+ }
62
+ }
63
+ }
64
+ if (!schemaExpr) {
65
+ schemaExpr = ts.factory.createPropertyAccessExpression(optArg, 'schema');
66
+ }
67
+ }
68
+ }
69
+ if (hasSchema && schemaExpr) {
70
+ const compileAccess = ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('MetadataStore'), 'getOrCompileSchema');
71
+ getCall = ts.factory.createCallExpression(compileAccess, undefined, [schemaExpr]);
72
+ }
73
+ else {
74
+ const mdStoreAccess = ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('MetadataStore'), 'getValidator');
75
+ getCall = ts.factory.createCallExpression(mdStoreAccess, undefined, [ts.factory.createStringLiteral(hash)]);
76
+ }
77
+ if (fnName === 'jsonSchema') {
78
+ const getSchemaAccess = ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('MetadataStore'), 'getSchema');
79
+ return ts.factory.createCallExpression(getSchemaAccess, undefined, [ts.factory.createStringLiteral(hash)]);
80
+ }
81
+ else if (fnName === 'validate') {
82
+ const mdStoreAccess = ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('MetadataStore'), 'validate');
83
+ return ts.factory.createCallExpression(mdStoreAccess, undefined, [getCall, arg0, arg1]);
84
+ }
85
+ else if (fnName === 'is') {
86
+ const mdStoreAccess = ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('MetadataStore'), 'is');
87
+ return ts.factory.createCallExpression(mdStoreAccess, undefined, [getCall, arg0, arg1]);
88
+ }
89
+ else if (fnName === 'assert' || fnName === 'assertGuard') {
90
+ const mdStoreAccess = ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('MetadataStore'), 'assert');
91
+ return ts.factory.createCallExpression(mdStoreAccess, undefined, [getCall, arg0, arg1]);
92
+ }
93
+ }
94
+ }
95
+ }
96
+ // Handle decorators
97
+ if (ts.isParameter(node) && ts.canHaveDecorators(node)) {
98
+ const decorators = ts.getDecorators(node);
99
+ if (decorators) {
100
+ const updatedDecorators = decorators.map(decorator => {
101
+ if (ts.isCallExpression(decorator.expression) && ts.isIdentifier(decorator.expression.expression)) {
102
+ const decName = decorator.expression.expression.text;
103
+ if (TARGET_DECORATORS.includes(decName)) {
104
+ const typeNode = node.type;
105
+ if (typeNode) {
106
+ const type = checker.getTypeFromTypeNode(typeNode);
107
+ const hash = generateHash(type, checker);
108
+ if (!validatorCache.has(hash)) {
109
+ buildValidator(type, checker, validatorCache, requiredUtils);
110
+ }
111
+ if (!schemasCache.has(hash)) {
112
+ const schemaObj = buildJsonSchema(type, checker);
113
+ schemasCache.set(hash, objectToAst(schemaObj));
114
+ }
115
+ const mdStoreAccess = ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('MetadataStore'), 'getValidator');
116
+ const getCall = ts.factory.createCallExpression(mdStoreAccess, undefined, [ts.factory.createStringLiteral(hash)]);
117
+ const decoratorArgs = [...decorator.expression.arguments];
118
+ decoratorArgs[1] = getCall;
119
+ return ts.factory.updateDecorator(decorator, ts.factory.createCallExpression(decorator.expression.expression, decorator.expression.typeArguments, decoratorArgs));
120
+ }
121
+ }
122
+ }
123
+ return decorator;
124
+ });
125
+ return ts.factory.updateParameterDeclaration(node, ts.getModifiers(node), node.dotDotDotToken, node.name, node.questionToken, node.type, node.initializer);
126
+ }
127
+ }
128
+ return ts.visitEachChild(node, visitor, context);
129
+ };
130
+ const transformedFile = ts.visitNode(sourceFile, visitor);
131
+ return hoistRegistrations(transformedFile, validatorCache, requiredUtils, schemasCache);
132
+ };
133
+ };
134
+ }
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@webergency-utils/typechecker",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "TypeScript compiler plugin for runtime validation",
6
+ "author": "radixxko",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.js",
14
+ "default": "./dist/index.js"
15
+ },
16
+ "./runtime": {
17
+ "types": "./dist/runtime/validators.d.ts",
18
+ "import": "./dist/runtime/validators.js",
19
+ "require": "./dist/runtime/validators.js",
20
+ "default": "./dist/runtime/validators.js"
21
+ },
22
+ "./transformer": {
23
+ "types": "./dist/transformer.d.ts",
24
+ "import": "./dist/transformer.js",
25
+ "require": "./dist/transformer.js",
26
+ "default": "./dist/transformer.js"
27
+ }
28
+ },
29
+ "files": [
30
+ "dist/**/*"
31
+ ],
32
+ "scripts": {
33
+ "lint": "eslint .",
34
+ "clean": "rm -rf ./dist/* node_modules package-lock.json && npm i",
35
+ "commit": "node -e \"const version = require('./package.json').version.trim(); require('child_process').execSync('git add . && git commit -m \\\"Version ' + version + '\\\"');\"",
36
+ "build": "npm run clean && tsc",
37
+ "test": "vitest run",
38
+ "test:coverage": "vitest run --coverage",
39
+ "version": "npm run build && npm publish --access public && npm run commit && git push"
40
+ },
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/webergency-utils/typechecker.git"
44
+ },
45
+ "bugs": {
46
+ "url": "https://github.com/webergency-utils/typechecker/issues"
47
+ },
48
+ "homepage": "https://github.com/webergency-utils/typechecker#readme",
49
+ "peerDependencies": {
50
+ "typescript": ">=5.0.0"
51
+ },
52
+ "devDependencies": {
53
+ "@eslint/js": "^10.0.1",
54
+ "@stylistic/eslint-plugin": "^5.10.0",
55
+ "@types/node": "^24.10.1",
56
+ "@vitest/coverage-v8": "^4.1.6",
57
+ "eslint": "^10.4.1",
58
+ "typescript": "^6.0.3",
59
+ "typescript-eslint": "^8.60.0",
60
+ "vitest": "^4.1.6"
61
+ }
62
+ }