@aexol/axolotl-core 0.0.1

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.
package/.eslintrc.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": [
3
+ "../../.eslintrc.json"
4
+ ]
5
+ }
package/gen.test.ts ADDED
@@ -0,0 +1,54 @@
1
+ import test from 'node:test';
2
+ import { resolveFieldType } from './gen.js';
3
+ import { Options } from 'graphql-js-tree';
4
+ import * as assert from 'node:assert';
5
+
6
+ test('resolveFieldType', (t, done) => {
7
+ const possibleVariants = {
8
+ ['Person | undefined']: resolveFieldType('Person', {
9
+ type: Options.name,
10
+ name: 'Person',
11
+ }),
12
+ ['Person']: resolveFieldType('Person', {
13
+ type: Options.required,
14
+ nest: {
15
+ type: Options.name,
16
+ name: 'Person',
17
+ },
18
+ }),
19
+ ['Array<Person | undefined> | undefined']: resolveFieldType('Person', {
20
+ type: Options.array,
21
+ nest: {
22
+ type: Options.name,
23
+ name: 'Person',
24
+ },
25
+ }),
26
+ ['Array<Person | undefined>']: resolveFieldType('Person', {
27
+ type: Options.required,
28
+ nest: {
29
+ type: Options.array,
30
+ nest: {
31
+ type: Options.name,
32
+ name: 'Person',
33
+ },
34
+ },
35
+ }),
36
+ ['Array<Person>']: resolveFieldType('Person', {
37
+ type: Options.required,
38
+ nest: {
39
+ type: Options.array,
40
+ nest: {
41
+ type: Options.required,
42
+ nest: {
43
+ type: Options.name,
44
+ name: 'Person',
45
+ },
46
+ },
47
+ },
48
+ }),
49
+ };
50
+ Object.entries(possibleVariants).forEach(([k, v]) => {
51
+ assert.equal(k, v);
52
+ });
53
+ done();
54
+ });
package/gen.ts ADDED
@@ -0,0 +1,103 @@
1
+ import { readFileSync, writeFileSync } from 'fs';
2
+ import { FieldType, Options, Parser, ParserField, TypeDefinition, getTypeName } from 'graphql-js-tree';
3
+
4
+ const TAB = (n: number) =>
5
+ new Array(n)
6
+ .fill(1)
7
+ .map(() => ' ')
8
+ .join('');
9
+
10
+ const toTsType = (t: string) => {
11
+ if (t === 'String') return 'string';
12
+ if (t === 'Int') return 'number';
13
+ if (t === 'Float') return 'number';
14
+ if (t === 'ID') return 'unknown';
15
+ if (t === 'Boolean') return 'boolean';
16
+ return t;
17
+ };
18
+
19
+ export const resolveFieldType = (
20
+ name: string,
21
+ fType: FieldType,
22
+ fn: (str: string) => string = (x) => x,
23
+ isRequired = false,
24
+ ): string => {
25
+ if (fType.type === Options.name) {
26
+ return fn(isRequired ? name : `${name} | undefined`);
27
+ }
28
+ if (fType.type === Options.array) {
29
+ return resolveFieldType(
30
+ name,
31
+ fType.nest,
32
+ isRequired ? (x) => `Array<${fn(x)}>` : (x) => `Array<${fn(x)}> | undefined`,
33
+ false,
34
+ );
35
+ }
36
+ if (fType.type === Options.required) {
37
+ return resolveFieldType(name, fType.nest, fn, true);
38
+ }
39
+ throw new Error('Invalid field type');
40
+ };
41
+
42
+ export const resolveField = (f: ParserField): string => {
43
+ const isNullType = (type: string): string => {
44
+ return f.type.fieldType.type === Options.required ? `: ${type}` : `?: ${type}`;
45
+ };
46
+ return `${TAB(1)}${f.name}${isNullType(resolveFieldType(toTsType(getTypeName(f.type.fieldType)), f.type.fieldType))}`;
47
+ };
48
+
49
+ const buildArgs = (args: ParserField[]) => {
50
+ if (args.length === 0) return 'never;';
51
+ const inputFields = args.map((a) => `${TAB(3)}${resolveField(a)};`);
52
+ return `{\n${inputFields.join('\n')}\n${TAB(3)}};`;
53
+ };
54
+
55
+ const buildEnumArgs = (args: ParserField[]) => {
56
+ if (args.length === 0) return 'never';
57
+ const inputFields = args.map((a) => `${TAB(1)}${a.name} = "${a.name}"`);
58
+ return `{\n${inputFields.join(',\n')}\n}`;
59
+ };
60
+
61
+ const generateModelsString = (fileContent: string) => {
62
+ const { nodes } = Parser.parse(fileContent);
63
+
64
+ const scalars = nodes.filter((n) => n.data.type === TypeDefinition.ScalarTypeDefinition);
65
+ const scalarsString = scalars.map((s) => `export type ${s.name} = unknown`).join('\n');
66
+
67
+ const enums = nodes.filter((n) => n.data.type === TypeDefinition.EnumTypeDefinition);
68
+ const enumsString = enums.map((s) => `export enum ${s.name} ${buildEnumArgs(s.args)}`).join('\n');
69
+
70
+ const inputs = nodes.filter((n) => n.data.type === TypeDefinition.InputObjectTypeDefinition);
71
+ const inputsString = inputs
72
+ .map((i) => {
73
+ const inputFields = i.args.map((a) => `${resolveField(a)};`);
74
+ return `export interface ${i.name} {\n${inputFields.join('\n')}\n}`;
75
+ })
76
+ .join('\n');
77
+
78
+ const types = nodes.filter((n) => n.data.type === TypeDefinition.ObjectTypeDefinition);
79
+ const typesString = types
80
+ .map((t) => {
81
+ const typeFields = t.args.map((a) => {
82
+ return `${TAB(2)}${a.name}: {\n${TAB(3)}args: ${buildArgs(a.args)}\n${TAB(2)}};`;
83
+ });
84
+ return `${TAB(1)}['${t.name}']: {\n${typeFields.join('\n')}\n${TAB(1)}};`;
85
+ })
86
+ .join('\n');
87
+
88
+ const typesFullString = `export type Models = {\n${typesString}\n};\n`;
89
+
90
+ return [scalarsString, enumsString, inputsString, typesFullString].filter(Boolean).join('\n\n');
91
+ };
92
+
93
+ export const generateModels = ({
94
+ schemaPath = './schema.graphql',
95
+ modelsPath = './models.ts',
96
+ }: {
97
+ schemaPath: string;
98
+ modelsPath: string;
99
+ }) => {
100
+ const fileContent = readFileSync(schemaPath, 'utf8');
101
+ const modelsString = generateModelsString(fileContent);
102
+ writeFileSync(modelsPath, modelsString);
103
+ };
package/index.ts ADDED
@@ -0,0 +1,92 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
+
4
+ import { generateModels } from '@/gen.js';
5
+
6
+ interface CustomHandler<InputType, ArgumentsType = unknown> {
7
+ (input: InputType, args: ArgumentsType extends { args: infer R } ? R : never): any;
8
+ }
9
+ interface CustomMiddlewareHandler<InputType> {
10
+ (input: InputType): InputType;
11
+ }
12
+
13
+ export type ResolversUnknown<InputType> = {
14
+ [x: string]: {
15
+ [x: string]: (input: InputType, args?: any) => any | undefined | Promise<any | undefined>;
16
+ };
17
+ };
18
+
19
+ type InferAdapterType<ADAPTER extends (passedResolvers: ResolversUnknown<any>, production?: boolean) => any> =
20
+ Parameters<ADAPTER>[0] extends {
21
+ [x: string]: {
22
+ [y: string]: infer R;
23
+ };
24
+ }
25
+ ? R extends (...args: any[]) => any
26
+ ? Parameters<R>[0]
27
+ : never
28
+ : never;
29
+
30
+ export const AxolotlAdapter =
31
+ <Inp>() =>
32
+ <T>(fn: (passedResolvers: ResolversUnknown<Inp>, production?: boolean) => T) =>
33
+ fn;
34
+
35
+ export { generateModels };
36
+
37
+ export const Axolotl =
38
+ <ADAPTER extends (passedResolvers: ResolversUnknown<any>, production?: boolean) => any>(adapter: ADAPTER) =>
39
+ <Models>({
40
+ production,
41
+ schemaPath,
42
+ modelsPath,
43
+ }: {
44
+ // input is only required for frameworks with external routing
45
+ schemaPath: string;
46
+ modelsPath: string;
47
+ // Instead of controlling developer and production mode by some force envs we allow to control it however you want. Generators don't run on production
48
+ production?: boolean;
49
+ }) => {
50
+ type Inp = InferAdapterType<ADAPTER>;
51
+ type Resolvers = {
52
+ [P in keyof Models]?: {
53
+ [T in keyof Models[P]]?: CustomHandler<Inp, Models[P][T]>;
54
+ };
55
+ };
56
+ type Handler = CustomHandler<Inp>;
57
+ type MiddlewareHandler = CustomMiddlewareHandler<Inp>;
58
+
59
+ const createResolvers = <Z>(k: Z | Resolvers) => k as Z;
60
+
61
+ if (!production) {
62
+ // We need to generate models for Axolotl to work this is called with CLI but also on every dev run to keep things in sync
63
+ generateModels({ schemaPath, modelsPath });
64
+ }
65
+
66
+ const applyMiddleware = <Z>(
67
+ r: Z | Resolvers,
68
+ middlewares: MiddlewareHandler[],
69
+ k: {
70
+ [P in keyof Z]?: {
71
+ [Y in keyof Z[P]]?: true;
72
+ };
73
+ },
74
+ ) => {
75
+ Object.entries(k).forEach(([typeName, fields]) => {
76
+ Object.keys(fields as Record<string, true>).forEach((fieldName) => {
77
+ const oldResolver = (r as Record<string, Record<string, Handler>>)[typeName][fieldName];
78
+ (r as Record<string, Record<string, Handler>>)[typeName][fieldName] = middlewares.reduce((a, b) => {
79
+ return (input, args) => {
80
+ const middlewaredInput = b(input);
81
+ return a(middlewaredInput, args);
82
+ };
83
+ }, oldResolver);
84
+ });
85
+ });
86
+ };
87
+
88
+ return {
89
+ createResolvers,
90
+ applyMiddleware,
91
+ };
92
+ };
package/jest.config.js ADDED
@@ -0,0 +1,10 @@
1
+ module.exports = {
2
+ preset: 'ts-jest',
3
+ moduleFileExtensions: ['ts', 'tsx', 'js'],
4
+ moduleNameMapper: {
5
+ '@/(.*)': ['<rootDir>/$1'],
6
+ },
7
+ testMatch: ['<rootDir>/**/*.spec.(ts|tsx)'],
8
+ watchPathIgnorePatterns: ['node_modules'],
9
+ watchman: false,
10
+ };
package/package.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "@aexol/axolotl-core",
3
+ "version": "0.0.1",
4
+ "private": false,
5
+ "main": "./lib/index.js",
6
+ "author": "Aexol, Artur Czemiel",
7
+ "type": "module",
8
+ "scripts": {
9
+ "build": "tspc",
10
+ "start": "tspc --watch",
11
+ "lint": "tspc && eslint \"./src/**/*.{ts,js}\" --quiet --fix"
12
+ },
13
+ "dependencies": {
14
+ "graphql-js-tree": "^1.0.6"
15
+ }
16
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "compilerOptions": {
3
+ "sourceMap": true,
4
+ "target": "es2022",
5
+ "module": "es2022",
6
+ "moduleResolution": "node",
7
+ "experimentalDecorators": true,
8
+ "declaration": true,
9
+ "incremental": true,
10
+ "removeComments": true,
11
+ "noUnusedLocals": true,
12
+ "strictNullChecks": true,
13
+ "skipLibCheck": true,
14
+ "strict": true,
15
+ "outDir": "./lib",
16
+ "lib": [
17
+ "ESNext",
18
+ "DOM",
19
+ "DOM.Iterable"
20
+ ],
21
+ "rootDir": "./",
22
+ "baseUrl": "./",
23
+ "composite": true,
24
+ "paths": {
25
+ "@/*": [
26
+ "./*"
27
+ ]
28
+ },
29
+ "plugins": [
30
+ {
31
+ "transform": "typescript-transform-paths"
32
+ },
33
+ {
34
+ "transform": "typescript-transform-paths",
35
+ "afterDeclarations": true
36
+ }
37
+ ]
38
+ },
39
+ "exclude": [
40
+ "lib",
41
+ "node_modules",
42
+ "jest.config.js",
43
+ ]
44
+ }