Skip to content

Commit eeaa81d

Browse files
committed
feat: Parse the parameter literal with typescript
1 parent 6d04e30 commit eeaa81d

File tree

9 files changed

+664
-788
lines changed

9 files changed

+664
-788
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
strategy:
1313
matrix:
1414
node-version: [12.13.x]
15-
ts-version: [3.9.x, 4.0.x]
15+
ts-version: [4.1.x, 4.2.x]
1616

1717
runs-on: ubuntu-latest
1818

package.json

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,19 @@
1111
"test": "nyc mocha ./tests/*.test.ts -r ts-node/register",
1212
"prepublishOnly": "sh ./scripts/prepublish.sh"
1313
},
14+
"peerDependencies": {
15+
"typescript": ">=4.1"
16+
},
1417
"devDependencies": {
15-
"@types/chai": "^4.2.12",
16-
"@types/mocha": "^8.0.3",
17-
"@types/node": "^14.10.1",
18-
"chai": "^4.2.0",
19-
"mocha": "^8.1.3",
18+
"@types/chai": "^4.2.15",
19+
"@types/mocha": "^8.2.1",
20+
"@types/node": "^14.14.32",
21+
"chai": "^4.3.3",
22+
"mocha": "^8.3.1",
2023
"nyc": "^15.1.0",
2124
"public-refactor": "^0.5.1",
22-
"ts-node": "^9.0.0",
23-
"typescript": "^4.0.3"
25+
"ts-node": "^9.1.1",
26+
"typescript": "^4.2.3"
2427
},
2528
"publishConfig": {
2629
"access": "public",

src/parameter.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,19 @@
11
import { ParseContext } from './parse';
22

33
export const parseParameter = (value: string) => {
4-
const alias_param = value.split(':');
5-
const param = alias_param.pop()!;
6-
const alias = alias_param.pop();
4+
const match = value.match(/^([a-z0-9_]+)(?:\sas\s([a-z0-9_]+))?\s*:\s?(.+)$/i);
75

8-
const item = param.split('_');
9-
10-
if (item.length < 2) {
11-
throw new Error(`Parmeter ${value} is invalid, try to define it like: "a_Int", "b_String", "alias:c_Int" and so on.`);
6+
if (!match) {
7+
throw new Error(`Parmeter ${value} is invalid, try to define it like: "a: Int", "b: String", "c as myAlias: Int" and so on.`);
128
}
139

14-
const itemType = item.pop()!;
15-
const itemName = item.join('_');
10+
const itemName = match[1];
11+
const alias = match[2];
1612

1713
return {
1814
name: itemName,
1915
variable: alias || itemName,
20-
type: itemType,
16+
type: match[3],
2117
};
2218
};
2319

src/parse.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ export interface ParseContext {
88
fragmentStrs: string[];
99
fragmentObjs: FragmentMeta[];
1010
fragmentIndex: Record<string, number>;
11-
paramAlias: Record<string, string>,
1211
}
1312

1413
export const parse = (type: string, name: string | undefined, nodes: TemplateObj, ctx: ParseContext): string => {
@@ -20,7 +19,6 @@ export const parse = (type: string, name: string | undefined, nodes: TemplateObj
2019
const body = cycleParse(nodes, ctx, -2);
2120
const params = ctx.params.map((key) => {
2221
const param = parseParameter(key);
23-
ctx.paramAlias[key] = param.variable;
2422

2523
return `$${param.variable}: ${param.type}`;
2624
});

src/query.ts

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,39 @@
11
import { parse, ParseContext } from './parse';
22
import { TemplateObj, Parse, VarParams } from './types';
33

4-
type Variable<T> = {
5-
[K in VarParams<T>]: string | number | boolean | object | undefined;
4+
export type Space = '' | ' ';
5+
export type Suffix = '!' | '';
6+
type AliasName<T> = T extends `${string} as ${infer R}` ? TrimRight<R> : TrimRight<T>;
7+
type TrimRight<T> = T extends `${infer R} ` ? TrimRight<R> : T;
8+
9+
type Variable<T, P = VarParams<T>> = {
10+
[K in P as K extends `${infer R}:${Space}${'Int' | 'Float'}${Suffix}` ? AliasName<R> : never]: number;
11+
}
12+
& {
13+
[K in P as K extends `${infer R}:${Space}[${'Int' | 'Float'}${Suffix}]${Suffix}` ? AliasName<R> : never]: number[];
14+
}
15+
& {
16+
[K in P as K extends `${infer R}:${Space}${'String' | 'ID'}${Suffix}` ? AliasName<R> : never]: string;
17+
}
18+
& {
19+
[K in P as K extends `${infer R}:${Space}[${'String' | 'ID'}${Suffix}]${Suffix}` ? AliasName<R> : never]: string[];
20+
}
21+
& {
22+
[K in P as K extends `${infer R}:${Space}Boolean${Suffix}` ? AliasName<R> : never]: boolean;
23+
}
24+
& {
25+
[K in P as K extends `${infer R}:${Space}[Boolean]${Suffix}` ? AliasName<R> : never]: boolean[];
26+
}
27+
& {
28+
[K in P as K extends `${string}:${Space}${'Int' | 'Float' | 'String' | 'Boolean' | 'ID'}${Suffix}`
29+
? never
30+
: K extends `${string}[${string}]${Suffix}`
31+
? never
32+
: K extends `${infer R}:${string}` ? AliasName<R> : never
33+
]: Record<string, any> | number | string | boolean;
34+
}
35+
& {
36+
[K in P as K extends `${string}:${Space}[${'Int' | 'Float' | 'String' | 'Boolean' | 'ID'}${Suffix}]${Suffix}` ? never : K extends `${infer R}:${Space}[${string}]${Suffix}` ? AliasName<R> : never]: (Record<string, any> | number | string | boolean)[];
637
};
738

839
type QueryThis = {
@@ -26,7 +57,6 @@ export function query<T extends TemplateObj<K, V>, K extends any, V extends any>
2657
): QueryReturn<T> {
2758
const ctx: ParseContext = {
2859
params: [],
29-
paramAlias: {},
3060
fragmentStrs: [],
3161
fragmentObjs: [],
3262
fragmentIndex: {},
@@ -38,16 +68,9 @@ export function query<T extends TemplateObj<K, V>, K extends any, V extends any>
3868
const getQuery = () => lazyQuery || (lazyQuery = parse(type, options && options.name, template, ctx));
3969

4070
const fn = (variables: object) => {
41-
const query = getQuery();
42-
const args = {};
43-
44-
Object.keys(variables).forEach((key) => {
45-
args[ctx.paramAlias[key]] = variables[key];
46-
});
47-
4871
return {
49-
query: query,
50-
variables: args,
72+
query: getQuery(),
73+
variables,
5174
};
5275
};
5376

src/types.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { createFragmentKey, FragmentMeta } from './fragment';
2+
import type { Space, Suffix } from './query';
23

34
export type Template<K = any, V = any> = Types<K, V> | TemplateObj<K, V>;
45

@@ -178,7 +179,7 @@ export class Types<T = never, U = never> {
178179
* }
179180
* ```
180181
*/
181-
include<P extends string>(param_Boolean: P): Types<T | undefined, U | P> {
182+
include<P extends `${string}:${Space}Boolean${Suffix}`>(param_Boolean: P): Types<T | undefined, U | P> {
182183
const that = this.clone();
183184
that.includeParam = param_Boolean;
184185
that.appendParams([param_Boolean]);
@@ -198,7 +199,7 @@ export class Types<T = never, U = never> {
198199
* }
199200
* ```
200201
*/
201-
skip<P extends string>(param_Boolean: P): Types<T | undefined, U | P> {
202+
skip<P extends `${string}:${Space}Boolean${Suffix}`>(param_Boolean: P): Types<T | undefined, U | P> {
202203
const that = this.clone();
203204
that.skipParam = param_Boolean;
204205
that.appendParams([param_Boolean]);
@@ -211,7 +212,7 @@ export class Types<T = never, U = never> {
211212
* For example: `page_Int` | `name_String` | `focus_Boolean` | `data_MyObject`
212213
* @param {Types} returns
213214
*/
214-
fn<U1 extends string, T1 extends Template>(
215+
fn<U1 extends `${string}:${string}`, T1 extends Template>(
215216
params_Type: U1[],
216217
returns: T1
217218
): Types<T | Parse<T1>, U | U1> {

tests/graphql.test.ts

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -109,17 +109,17 @@ describe('Graphql', () => {
109109
it ('function query', () => {
110110
const tpl = graphql.query({
111111
hello: types.number,
112-
hi: types.fn(['a_Int'], {
112+
hi: types.fn(['a: Int!'], {
113113
how: types.undefined.string,
114114
are: {
115115
you: types.boolean,
116116
},
117117
}),
118-
body: types.fn(['b_Int'], types.boolean).include('c_Boolean'),
118+
body: types.fn(['b: Int'], types.boolean).include('c: Boolean'),
119119
});
120120

121-
expect(tpl({ a_Int: 3, b_Int: 3, c_Boolean: true }).query).to.equal(
122-
`query Hello ($a: Int, $b: Int, $c: Boolean) {
121+
expect(tpl({ a: 3, b: 3, c: true }).query).to.equal(
122+
`query Hello ($a: Int!, $b: Int, $c: Boolean) {
123123
hello
124124
hi (a: $a) {
125125
how
@@ -134,6 +134,7 @@ describe('Graphql', () => {
134134

135135
it ('wrong function parameter name', () => {
136136
const tpl = graphql.query({
137+
// @ts-expect-error
137138
hello: types.fn(['a'], {}),
138139
});
139140

@@ -142,18 +143,18 @@ describe('Graphql', () => {
142143

143144
it ('function with duplicate parameter', () => {
144145
const tpl = graphql.query({
145-
hello: types.fn(['a_Int', 'b_String'], {
146+
hello: types.fn(['a: Int', 'b: String'], {
146147
id: types.number,
147148
}),
148-
hi: types.fn(['a_Int', 'b_String'], {
149+
hi: types.fn(['a: Int', 'b: String'], {
149150
how: types.undefined.string,
150151
are: {
151152
you: types.boolean,
152153
},
153154
}),
154155
});
155156

156-
expect(tpl({ a_Int: 3, b_String: '2' }).query).to.equal(
157+
expect(tpl({ a: 3, b: '2' }).query).to.equal(
157158
`query Hello ($a: Int, $b: String) {
158159
hello (a: $a, b: $b) {
159160
id
@@ -171,15 +172,15 @@ describe('Graphql', () => {
171172
it ('function parameter with alias variable', () => {
172173
const tpl = graphql.query({
173174
hello: types.number,
174-
hi: types.fn(['other:a_Int', 'b_String'], {
175+
hi: types.fn(['a as other: Int', 'b: String'], {
175176
how: types.undefined.string,
176177
are: {
177178
you: types.boolean,
178179
},
179180
}),
180181
});
181182

182-
expect(tpl({ 'other:a_Int': 3, b_String: '666' }).query).to.equal(
183+
expect(tpl({ other: 3, b: '666' }).query).to.equal(
183184
`query Hello ($other: Int, $b: String) {
184185
hello
185186
hi (a: $other, b: $b) {
@@ -191,21 +192,21 @@ describe('Graphql', () => {
191192
}`
192193
);
193194

194-
expect(tpl({ 'other:a_Int': 3, b_String: '666' }).variables).to.contain({
195+
expect(tpl({ other: 3, b: '666' }).variables).to.contain({
195196
other: 3,
196197
b: '666',
197198
});
198199
});
199200

200201
it ('function returns boolean, number or string', () => {
201202
const tpl = graphql.query({
202-
hello: types.fn(['a_Int'], types.boolean),
203-
hi: types.fn(['a_Int'], types.number),
204-
how: types.fn(['a_Int'], types.string),
205-
are: types.fn(['a_Int'], types.number.string.undefined.null),
203+
hello: types.fn(['a: Int'], types.boolean),
204+
hi: types.fn(['a: Int'], types.number),
205+
how: types.fn(['a: Int'], types.string),
206+
are: types.fn(['a: Int'], types.number.string.undefined.null),
206207
});
207208

208-
expect(tpl({ a_Int: 3 }).query).to.equal(
209+
expect(tpl({ a: 3 }).query).to.equal(
209210
`query Hello ($a: Int) {
210211
hello (a: $a)
211212
hi (a: $a)
@@ -222,12 +223,12 @@ describe('Graphql', () => {
222223
how: types.aliasOf('who').object({
223224
are: types.boolean,
224225
}),
225-
list: types.aliasOf('result').fn(['a_Int'], {
226+
list: types.aliasOf('result').fn(['a: Int'], {
226227
id: types.number,
227228
}),
228229
});
229230

230-
expect(tpl({ a_Int: 0 }).query).to.equal(
231+
expect(tpl({ a: 0 }).query).to.equal(
231232
`query Hello ($a: Int) {
232233
hello: h
233234
hi: hii
@@ -442,7 +443,7 @@ fragment customUserFragment on User {
442443
it ('fragment includes function', () => {
443444
const fragment = graphql.fragment('User', {
444445
id: types.number,
445-
name: types.fn(['id_Int'], {
446+
name: types.fn(['id: Int'], {
446447
name: types.string,
447448
}),
448449
});
@@ -456,7 +457,7 @@ fragment customUserFragment on User {
456457
}
457458
});
458459

459-
expect(tpl({ id_Int: 0 }).query).to.equal(
460+
expect(tpl({ id: 0 }).query).to.equal(
460461
`query Hello ($id: Int) {
461462
hello
462463
hi
@@ -478,7 +479,7 @@ fragment UserFragment on User {
478479
it ('fragment in fragment', () => {
479480
const fragment = graphql.fragment('User', {
480481
id: types.number,
481-
fn1: types.fn(['a_Int'], types.number),
482+
fn1: types.fn(['a: Int'], types.number),
482483
});
483484

484485
const fragment1 = graphql.fragment('Admin', {
@@ -498,7 +499,7 @@ fragment UserFragment on User {
498499
});
499500

500501
expect(tpl({
501-
a_Int: 2,
502+
a: 2,
502503
}).query).to.equal(
503504
`query Hello ($a: Int) {
504505
hello {
@@ -530,15 +531,15 @@ fragment AdminFragment on Admin {
530531
id: types.number,
531532
...types.on('Admin', {
532533
name: types.string,
533-
fn1: types.fn(['b_Int'], {
534+
fn1: types.fn(['b: Int'], {
534535
id: types.number,
535536
}),
536537
}),
537538
})
538539
}
539540
});
540541

541-
expect(tpl({ b_Int: 2 }).query).to.equal(
542+
expect(tpl({ b: 2 }).query).to.equal(
542543
`query Hello ($b: Int) {
543544
hello {
544545
... on User {
@@ -557,24 +558,24 @@ fragment AdminFragment on Admin {
557558

558559
it ('directives', () => {
559560
const tpl = graphql.query({
560-
hello: types.number.include('b_Boolean'),
561-
hi: types.include('b_Boolean').skip('c_Boolean').fn(['a_Int'], {
561+
hello: types.number.include('b:Boolean'),
562+
hi: types.include('b:Boolean').skip('c:Boolean').fn(['a: Int'], {
562563
how: types.undefined.string,
563564
are: {
564565
you: types.boolean,
565566
},
566567
}),
567-
...types.include('d_Boolean').on('User', {
568+
...types.include('d:Boolean').on('User', {
568569
name: types.string,
569570
}),
570-
...types.include('f_Boolean').on('User', {
571+
...types.include('f:Boolean').on('User', {
571572
lists: {
572573
id: types.number,
573574
}
574575
}),
575576
});
576577

577-
expect(tpl({ a_Int: 3, b_Boolean: false, c_Boolean: true, d_Boolean: true, f_Boolean: true }).query).to.equal(
578+
expect(tpl({ a: 3, b: false, c: true, d: true, f: true }).query).to.equal(
578579
`query Hello ($b: Boolean, $c: Boolean, $a: Int, $d: Boolean, $f: Boolean) {
579580
hello @include(if: $b)
580581
hi (a: $a) @include(if: $b) @skip(if: $c) {

0 commit comments

Comments
 (0)