@smartive/graphql-magic 23.11.0 → 23.12.0-next.2
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/CHANGELOG.md +2 -2
- package/dist/bin/gqm.cjs +69 -36
- package/dist/cjs/index.cjs +96 -36
- package/dist/esm/db/generate.js +7 -0
- package/dist/esm/db/generate.js.map +1 -1
- package/dist/esm/migrations/generate.js +8 -0
- package/dist/esm/migrations/generate.js.map +1 -1
- package/dist/esm/models/model-definitions.d.ts +5 -0
- package/dist/esm/models/models.d.ts +2 -1
- package/dist/esm/models/models.js +3 -0
- package/dist/esm/models/models.js.map +1 -1
- package/dist/esm/resolvers/resolvers.js +14 -0
- package/dist/esm/resolvers/resolvers.js.map +1 -1
- package/dist/esm/utils/index.d.ts +1 -0
- package/dist/esm/utils/index.js +1 -0
- package/dist/esm/utils/index.js.map +1 -1
- package/dist/esm/utils/time.d.ts +7 -0
- package/dist/esm/utils/time.js +23 -0
- package/dist/esm/utils/time.js.map +1 -0
- package/docs/docs/3-fields.md +3 -0
- package/migrations/20230912185644_setup.ts +2 -0
- package/package.json +5 -5
- package/src/bin/gqm/codegen.ts +13 -2
- package/src/db/generate.ts +8 -0
- package/src/migrations/generate.ts +8 -0
- package/src/models/model-definitions.ts +2 -0
- package/src/models/models.ts +6 -0
- package/src/resolvers/resolvers.ts +15 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/time.ts +32 -0
- package/tests/api/__snapshots__/query.spec.ts.snap +19 -0
- package/tests/api/query.spec.ts +17 -0
- package/tests/unit/__snapshots__/resolve.spec.ts.snap +1 -0
- package/tests/utils/database/seed.ts +5 -2
- package/tests/utils/models.ts +6 -0
package/src/bin/gqm/codegen.ts
CHANGED
|
@@ -10,12 +10,18 @@ export const generateGraphqlApiTypes = async (dateLibrary: DateLibrary) => {
|
|
|
10
10
|
documents: undefined,
|
|
11
11
|
generates: {
|
|
12
12
|
[`${generatedFolderPath}/api/index.ts`]: {
|
|
13
|
-
plugins: [
|
|
13
|
+
plugins: [
|
|
14
|
+
'typescript',
|
|
15
|
+
'typescript-resolvers',
|
|
16
|
+
{ add: { content: DATE_CLASS_IMPORT[dateLibrary] } },
|
|
17
|
+
{ add: { content: `import type { Time } from '@smartive/graphql-magic';` } },
|
|
18
|
+
],
|
|
14
19
|
},
|
|
15
20
|
},
|
|
16
21
|
config: {
|
|
17
22
|
scalars: {
|
|
18
23
|
DateTime: DATE_CLASS[dateLibrary],
|
|
24
|
+
Time: 'Time',
|
|
19
25
|
},
|
|
20
26
|
},
|
|
21
27
|
});
|
|
@@ -30,7 +36,11 @@ export const generateGraphqlClientTypes = async () => {
|
|
|
30
36
|
documents: [graphqlQueriesPath, `${generatedFolderPath}/client/mutations.ts`],
|
|
31
37
|
generates: {
|
|
32
38
|
[`${generatedFolderPath}/client/index.ts`]: {
|
|
33
|
-
plugins: [
|
|
39
|
+
plugins: [
|
|
40
|
+
'typescript',
|
|
41
|
+
'typescript-operations',
|
|
42
|
+
{ add: { content: `import type { Time } from '@smartive/graphql-magic';` } },
|
|
43
|
+
],
|
|
34
44
|
},
|
|
35
45
|
},
|
|
36
46
|
config: {
|
|
@@ -43,6 +53,7 @@ export const generateGraphqlClientTypes = async () => {
|
|
|
43
53
|
},
|
|
44
54
|
scalars: {
|
|
45
55
|
DateTime: 'string',
|
|
56
|
+
Time: 'Time',
|
|
46
57
|
},
|
|
47
58
|
ignoreNoDocuments: true,
|
|
48
59
|
},
|
package/src/db/generate.ts
CHANGED
|
@@ -23,6 +23,11 @@ export const generateDBModels = (models: Models, dateLibrary: DateLibrary) => {
|
|
|
23
23
|
|
|
24
24
|
writer.write(DATE_CLASS_IMPORT[dateLibrary]).blankLine();
|
|
25
25
|
|
|
26
|
+
writer.write(`type Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';`);
|
|
27
|
+
writer.write(`type Hour = \`0\${Digit}\` | \`1\${Digit}\` | \`2\${'0' | '1' | '2' | '3'}\`;`);
|
|
28
|
+
writer.write(`type Minute = \`\${'0' | '1' | '2' | '3' | '4' | '5'}\${Digit}\`;`);
|
|
29
|
+
writer.write(`export type Time = \`\${Hour}:\${Minute}\`;`).blankLine();
|
|
30
|
+
|
|
26
31
|
for (const [key, value] of Object.entries(PRIMITIVE_TYPES)) {
|
|
27
32
|
writer.write(`export type ${key} = ${value};`).blankLine();
|
|
28
33
|
}
|
|
@@ -150,6 +155,9 @@ const getFieldType = (field: EntityField, dateLibrary: DateLibrary, input?: bool
|
|
|
150
155
|
if (field.type === 'DateTime') {
|
|
151
156
|
return (input ? `(${DATE_CLASS[dateLibrary]} | string)` : DATE_CLASS[dateLibrary]) + (field.list ? '[]' : '');
|
|
152
157
|
}
|
|
158
|
+
if (field.type === 'Time') {
|
|
159
|
+
return `Time${field.list ? '[]' : ''}`;
|
|
160
|
+
}
|
|
153
161
|
|
|
154
162
|
return get(PRIMITIVE_TYPES, field.type) + (field.list ? '[]' : '');
|
|
155
163
|
default: {
|
|
@@ -1565,6 +1565,9 @@ export class MigrationGenerator {
|
|
|
1565
1565
|
case 'DateTime':
|
|
1566
1566
|
col(`table.timestamp('${name}')`);
|
|
1567
1567
|
break;
|
|
1568
|
+
case 'Time':
|
|
1569
|
+
col(`table.specificType('${name}', 'time without time zone')`);
|
|
1570
|
+
break;
|
|
1568
1571
|
case 'ID':
|
|
1569
1572
|
col(`table.uuid('${name}')`);
|
|
1570
1573
|
break;
|
|
@@ -1666,6 +1669,11 @@ export class MigrationGenerator {
|
|
|
1666
1669
|
return true;
|
|
1667
1670
|
}
|
|
1668
1671
|
}
|
|
1672
|
+
if (field.type === 'Time') {
|
|
1673
|
+
if (!['time without time zone', 'time'].includes(col.data_type)) {
|
|
1674
|
+
return true;
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1669
1677
|
}
|
|
1670
1678
|
|
|
1671
1679
|
return false;
|
|
@@ -26,6 +26,7 @@ type FieldDefinitionBase2 =
|
|
|
26
26
|
endOfDay?: boolean;
|
|
27
27
|
endOfMonth?: boolean;
|
|
28
28
|
}
|
|
29
|
+
| { type: 'Time' }
|
|
29
30
|
| ({
|
|
30
31
|
type: 'Int';
|
|
31
32
|
intType?: 'currency';
|
|
@@ -112,6 +113,7 @@ export type IDFieldDefinition = Extract<PrimitiveFieldDefinition, { type: 'ID' }
|
|
|
112
113
|
export type BooleanFieldDefinition = Extract<PrimitiveFieldDefinition, { type: 'Boolean' }>;
|
|
113
114
|
export type StringFieldDefinition = Extract<PrimitiveFieldDefinition, { type: 'String' }>;
|
|
114
115
|
export type DateTimeFieldDefinition = Extract<PrimitiveFieldDefinition, { type: 'DateTime' }>;
|
|
116
|
+
export type TimeFieldDefinition = Extract<PrimitiveFieldDefinition, { type: 'Time' }>;
|
|
115
117
|
export type IntFieldDefinition = Extract<PrimitiveFieldDefinition, { type: 'Int' }>;
|
|
116
118
|
export type FloatFieldDefinition = Extract<PrimitiveFieldDefinition, { type: 'Float' }>;
|
|
117
119
|
export type UploadFieldDefinition = Extract<PrimitiveFieldDefinition, { type: 'Upload' }>;
|
package/src/models/models.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
PrimitiveFieldDefinition,
|
|
19
19
|
RawEnumModelDefinition,
|
|
20
20
|
StringFieldDefinition,
|
|
21
|
+
TimeFieldDefinition,
|
|
21
22
|
UnionModelDefinition,
|
|
22
23
|
UploadFieldDefinition,
|
|
23
24
|
} from '..';
|
|
@@ -52,6 +53,7 @@ export type IDField = IDFieldDefinition;
|
|
|
52
53
|
export type BooleanField = BooleanFieldDefinition;
|
|
53
54
|
export type StringField = StringFieldDefinition;
|
|
54
55
|
export type DateTimeField = DateTimeFieldDefinition;
|
|
56
|
+
export type TimeField = TimeFieldDefinition;
|
|
55
57
|
export type IntField = IntFieldDefinition;
|
|
56
58
|
export type FloatField = FloatFieldDefinition;
|
|
57
59
|
export type UploadField = UploadFieldDefinition;
|
|
@@ -80,6 +82,10 @@ export class Models {
|
|
|
80
82
|
kind: 'scalar',
|
|
81
83
|
name: 'DateTime',
|
|
82
84
|
},
|
|
85
|
+
{
|
|
86
|
+
kind: 'scalar',
|
|
87
|
+
name: 'Time',
|
|
88
|
+
},
|
|
83
89
|
{ kind: 'scalar', name: 'Upload' },
|
|
84
90
|
{
|
|
85
91
|
kind: 'raw-enum',
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { GraphQLScalarType, Kind } from 'graphql';
|
|
1
2
|
import { Models } from '../models/models';
|
|
2
3
|
import { and, isCreatable, isRootModel, isUpdatable, merge, not, typeToField } from '../models/utils';
|
|
4
|
+
import { parseTime, serializeTime } from '../utils/time';
|
|
3
5
|
import { mutationResolver } from './mutations';
|
|
4
6
|
import { queryResolver } from './resolver';
|
|
5
7
|
|
|
@@ -25,6 +27,19 @@ export const getResolvers = (models: Models) => {
|
|
|
25
27
|
[`${model.pluralField}_AGGREGATE`]: queryResolver,
|
|
26
28
|
})),
|
|
27
29
|
]),
|
|
30
|
+
Time: new GraphQLScalarType({
|
|
31
|
+
name: 'Time',
|
|
32
|
+
description: 'Time without date and timezone (HH:mm)',
|
|
33
|
+
serialize: (value) => (value == null ? value : serializeTime(value)),
|
|
34
|
+
parseValue: (value) => parseTime(value),
|
|
35
|
+
parseLiteral: (ast) => {
|
|
36
|
+
if (ast.kind !== Kind.STRING) {
|
|
37
|
+
throw new Error(`Invalid literal for Time scalar. Expected STRING, got ${ast.kind}.`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return parseTime(ast.value);
|
|
41
|
+
},
|
|
42
|
+
}),
|
|
28
43
|
};
|
|
29
44
|
const mutations = [
|
|
30
45
|
...models.entities.filter(and(not(isRootModel), isCreatable)).map((model) => ({
|
package/src/utils/index.ts
CHANGED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
type Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
|
|
2
|
+
type Hour = `0${Digit}` | `1${Digit}` | `2${'0' | '1' | '2' | '3'}`;
|
|
3
|
+
type Minute = `${'0' | '1' | '2' | '3' | '4' | '5'}${Digit}`;
|
|
4
|
+
|
|
5
|
+
export type Time = `${Hour}:${Minute}`;
|
|
6
|
+
|
|
7
|
+
const PARSE_TIME_RE = /^([01]\d|2[0-3]):([0-5]\d)$/;
|
|
8
|
+
const SERIALIZE_TIME_RE = /^([01]\d|2[0-3]):([0-5]\d)(?::[0-5]\d(?:\.\d+)?)?$/;
|
|
9
|
+
|
|
10
|
+
export const parseTime = (value: unknown): Time => {
|
|
11
|
+
if (typeof value !== 'string') {
|
|
12
|
+
throw new Error(`Time must be a string in HH:mm format. Received: ${typeof value}`);
|
|
13
|
+
}
|
|
14
|
+
const match = value.match(PARSE_TIME_RE);
|
|
15
|
+
if (!match) {
|
|
16
|
+
throw new Error(`Invalid Time value "${value}". Expected HH:mm in 24-hour format.`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return `${match[1]}:${match[2]}` as Time;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const serializeTime = (value: unknown): Time => {
|
|
23
|
+
if (typeof value !== 'string') {
|
|
24
|
+
throw new Error(`Time must be a string in HH:mm format. Received: ${typeof value}`);
|
|
25
|
+
}
|
|
26
|
+
const match = value.match(SERIALIZE_TIME_RE);
|
|
27
|
+
if (!match) {
|
|
28
|
+
throw new Error(`Invalid Time value "${value}". Expected HH:mm or HH:mm:ss.`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return `${match[1]}:${match[2]}` as Time;
|
|
32
|
+
};
|
|
@@ -54,6 +54,7 @@ exports[`query can be executed 1`] = `
|
|
|
54
54
|
},
|
|
55
55
|
"field": null,
|
|
56
56
|
"id": "fc4e013e-4cb0-4ef8-9f2e-3d475bdf2b90",
|
|
57
|
+
"time": "09:15",
|
|
57
58
|
"xyz": 2,
|
|
58
59
|
},
|
|
59
60
|
{
|
|
@@ -68,6 +69,7 @@ exports[`query can be executed 1`] = `
|
|
|
68
69
|
},
|
|
69
70
|
"field": "Some value",
|
|
70
71
|
"id": "604ab55d-ec3e-4857-9f27-219158f80e64",
|
|
72
|
+
"time": "14:30",
|
|
71
73
|
"xyz": 1,
|
|
72
74
|
},
|
|
73
75
|
],
|
|
@@ -333,3 +335,20 @@ exports[`query processes reverseFilters correctly 1`] = `
|
|
|
333
335
|
],
|
|
334
336
|
}
|
|
335
337
|
`;
|
|
338
|
+
|
|
339
|
+
exports[`query returns Time values for SomeObject 1`] = `
|
|
340
|
+
{
|
|
341
|
+
"manyObjects": [
|
|
342
|
+
{
|
|
343
|
+
"id": "fc4e013e-4cb0-4ef8-9f2e-3d475bdf2b90",
|
|
344
|
+
"time": "09:15",
|
|
345
|
+
"xyz": 2,
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
"id": "604ab55d-ec3e-4857-9f27-219158f80e64",
|
|
349
|
+
"time": "14:30",
|
|
350
|
+
"xyz": 1,
|
|
351
|
+
},
|
|
352
|
+
],
|
|
353
|
+
}
|
|
354
|
+
`;
|
package/tests/api/query.spec.ts
CHANGED
|
@@ -12,6 +12,7 @@ describe('query', () => {
|
|
|
12
12
|
id
|
|
13
13
|
field
|
|
14
14
|
xyz
|
|
15
|
+
time
|
|
15
16
|
another {
|
|
16
17
|
id
|
|
17
18
|
manyObjects(where: { id: "${SOME_ID}" }) {
|
|
@@ -26,6 +27,22 @@ describe('query', () => {
|
|
|
26
27
|
});
|
|
27
28
|
});
|
|
28
29
|
|
|
30
|
+
it('returns Time values for SomeObject', async () => {
|
|
31
|
+
await withServer(async (request) => {
|
|
32
|
+
expect(
|
|
33
|
+
await request(gql`
|
|
34
|
+
query GetTimes {
|
|
35
|
+
manyObjects(where: { another: { id: "${ANOTHER_ID}" } }, orderBy: [{ xyz: DESC }]) {
|
|
36
|
+
id
|
|
37
|
+
xyz
|
|
38
|
+
time
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
`),
|
|
42
|
+
).toMatchSnapshot();
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
29
46
|
it('processes reverseFilters correctly', async () => {
|
|
30
47
|
await withServer(async (request) => {
|
|
31
48
|
expect(
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Knex } from 'knex';
|
|
2
2
|
import { pick } from 'lodash';
|
|
3
3
|
import { getColumnName, isInTable, modelNeedsTable } from '../../../src';
|
|
4
|
-
import { SeedData } from '../../generated/db';
|
|
5
4
|
import { models } from '../models';
|
|
6
5
|
|
|
7
6
|
export const ADMIN_ID = '04e45b48-04cf-4b38-bb25-b9af5ae0b2c4';
|
|
@@ -16,7 +15,7 @@ export const QUESTION_ID = '3d0f3254-282f-4f1f-95e3-c1f699f3c1e5';
|
|
|
16
15
|
export const ANSWER_ID = 'f2d7b3f1-8ea1-4c2c-91ec-024432da1b0d';
|
|
17
16
|
export const REVIEW_ID = '817c55de-2f77-4159-bd44-9837d868f889';
|
|
18
17
|
|
|
19
|
-
export const seed
|
|
18
|
+
export const seed = {
|
|
20
19
|
User: [
|
|
21
20
|
{
|
|
22
21
|
id: ADMIN_ID,
|
|
@@ -42,6 +41,7 @@ export const seed: SeedData = {
|
|
|
42
41
|
float: 0,
|
|
43
42
|
list: ['A'],
|
|
44
43
|
xyz: 1,
|
|
44
|
+
time: '14:30',
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
id: SOME_ID_2,
|
|
@@ -50,6 +50,7 @@ export const seed: SeedData = {
|
|
|
50
50
|
float: 0.5,
|
|
51
51
|
list: ['B'],
|
|
52
52
|
xyz: 2,
|
|
53
|
+
time: '09:15',
|
|
53
54
|
},
|
|
54
55
|
{
|
|
55
56
|
id: SOME_ID_3,
|
|
@@ -57,6 +58,7 @@ export const seed: SeedData = {
|
|
|
57
58
|
float: 0.5,
|
|
58
59
|
list: ['B'],
|
|
59
60
|
xyz: 2,
|
|
61
|
+
time: '23:45',
|
|
60
62
|
},
|
|
61
63
|
{
|
|
62
64
|
id: SOME_ID_4,
|
|
@@ -64,6 +66,7 @@ export const seed: SeedData = {
|
|
|
64
66
|
float: 0.5,
|
|
65
67
|
list: ['B'],
|
|
66
68
|
xyz: 2,
|
|
69
|
+
time: '00:05',
|
|
67
70
|
},
|
|
68
71
|
],
|
|
69
72
|
Question: [
|
package/tests/utils/models.ts
CHANGED