Skip to content

Commit 3a05507

Browse files
committed
feat: implementing mixin
1 parent d94fdf3 commit 3a05507

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1341
-1024
lines changed

.prettierignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
packages/language/src/generated/**
2-
**/schema.ts
2+
**/test/**/schema.ts

TODO.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
- [x] validate
1111
- [ ] format
1212
- [ ] db seed
13+
- [ ] ZModel
14+
- [ ] View support
1315
- [ ] ORM
1416
- [x] Create
1517
- [x] Input validation
@@ -56,13 +58,14 @@
5658
- [x] Aggregate
5759
- [x] Group by
5860
- [x] Raw queries
59-
- [ ] Transactions
61+
- [x] Transactions
6062
- [x] Interactive transaction
6163
- [x] Sequential transaction
6264
- [ ] Extensions
6365
- [x] Query builder API
6466
- [x] Computed fields
6567
- [x] Prisma client extension
68+
- [ ] Custom procedures
6669
- [ ] Misc
6770
- [x] JSDoc for CRUD methods
6871
- [x] Cache validation schemas
@@ -71,14 +74,16 @@
7174
- [x] Many-to-many relation
7275
- [ ] Empty AND/OR/NOT behavior
7376
- [?] Logging
74-
- [ ] Error system
77+
- [x] Error system
7578
- [x] Custom table name
7679
- [x] Custom field name
7780
- [ ] Strict undefined checks
7881
- [ ] DbNull vs JsonNull
7982
- [ ] Benchmark
8083
- [ ] Plugin
8184
- [ ] Post-mutation hooks should be called after transaction is committed
85+
- [x] TypeDef and mixin
86+
- [ ] Strongly typed JSON
8287
- [ ] Polymorphism
8388
- [ ] Validation
8489
- [ ] Access Policy

packages/cli/src/actions/generate.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ type Options = {
1717
* CLI action for generating code from schema
1818
*/
1919
export async function run(options: Options) {
20+
const start = Date.now();
21+
2022
const schemaFile = getSchemaFile(options.schema);
2123

2224
const model = await loadSchemaDocument(schemaFile);
@@ -40,7 +42,7 @@ export async function run(options: Options) {
4042
}
4143

4244
if (!options.silent) {
43-
console.log(colors.green('Generation completed successfully.'));
45+
console.log(colors.green(`Generation completed successfully in ${Date.now() - start}ms.`));
4446
console.log(`You can now create a ZenStack client with it.
4547
4648
\`\`\`ts

packages/cli/test/ts-schema-gen.test.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,4 +181,89 @@ model Post {
181181
},
182182
});
183183
});
184+
185+
it('merges fields and attributes from mixins', async () => {
186+
const { schema } = await generateTsSchema(`
187+
type Timestamped {
188+
createdAt DateTime @default(now())
189+
updatedAt DateTime @updatedAt
190+
}
191+
192+
type Named {
193+
name String
194+
@@unique([name])
195+
}
196+
197+
model User with Timestamped Named {
198+
id String @id @default(uuid())
199+
email String @unique
200+
}
201+
`);
202+
expect(schema).toMatchObject({
203+
models: {
204+
User: {
205+
fields: {
206+
id: { type: 'String' },
207+
email: { type: 'String' },
208+
createdAt: {
209+
type: 'DateTime',
210+
default: expect.objectContaining({ function: 'now', kind: 'call' }),
211+
},
212+
updatedAt: { type: 'DateTime', updatedAt: true },
213+
name: { type: 'String' },
214+
},
215+
uniqueFields: expect.objectContaining({
216+
name: { type: 'String' },
217+
}),
218+
},
219+
},
220+
});
221+
});
222+
223+
it('generates type definitions', async () => {
224+
const { schema } = await generateTsSchema(`
225+
type Base {
226+
name String
227+
@@meta('foo', 'bar')
228+
}
229+
230+
type Address with Base {
231+
street String
232+
city String
233+
}
234+
`);
235+
expect(schema).toMatchObject({
236+
typeDefs: {
237+
Base: {
238+
fields: {
239+
name: { type: 'String' },
240+
},
241+
attributes: [
242+
{
243+
name: '@@meta',
244+
args: [
245+
{ name: 'name', value: { kind: 'literal', value: 'foo' } },
246+
{ name: 'value', value: { kind: 'literal', value: 'bar' } },
247+
],
248+
},
249+
],
250+
},
251+
Address: {
252+
fields: {
253+
street: { type: 'String' },
254+
city: { type: 'String' },
255+
},
256+
attributes: [
257+
{
258+
name: '@@meta',
259+
args: [
260+
{ name: 'name', value: { kind: 'literal', value: 'foo' } },
261+
{ name: 'value', value: { kind: 'literal', value: 'bar' } },
262+
],
263+
},
264+
],
265+
},
266+
},
267+
});
268+
});
184269
});

packages/language/package.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,16 @@
3737
"default": "./dist/ast.cjs"
3838
}
3939
},
40+
"./utils": {
41+
"import": {
42+
"types": "./dist/utils.d.ts",
43+
"default": "./dist/utils.js"
44+
},
45+
"require": {
46+
"types": "./dist/utils.d.cts",
47+
"default": "./dist/utils.cjs"
48+
}
49+
},
4050
"./package.json": {
4151
"import": "./package.json",
4252
"require": "./package.json"
@@ -51,6 +61,7 @@
5161
"@types/pluralize": "^0.0.33",
5262
"@zenstackhq/eslint-config": "workspace:*",
5363
"@zenstackhq/typescript-config": "workspace:*",
64+
"@zenstackhq/common-helpers": "workspace:*",
5465
"langium-cli": "catalog:"
5566
},
5667
"volta": {

packages/language/res/stdlib.zmodel

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -199,11 +199,6 @@ function currentOperation(casing: String?): String {
199199
*/
200200
attribute @@@targetField(_ targetField: AttributeTargetField[])
201201

202-
/**
203-
* Marks an attribute to be applicable to type defs and fields.
204-
*/
205-
attribute @@@supportTypeDef()
206-
207202
/**
208203
* Marks an attribute to be used for data validation.
209204
*/
@@ -237,13 +232,13 @@ attribute @@@once()
237232
* @param sort: Allows you to specify in what order the entries of the ID are stored in the database. The available options are Asc and Desc.
238233
* @param clustered: Defines whether the ID is clustered or non-clustered. Defaults to true.
239234
*/
240-
attribute @id(map: String?, length: Int?, sort: SortOrder?, clustered: Boolean?) @@@prisma @@@supportTypeDef @@@once
235+
attribute @id(map: String?, length: Int?, sort: SortOrder?, clustered: Boolean?) @@@prisma @@@once
241236

242237
/**
243238
* Defines a default value for a field.
244239
* @param value: An expression (e.g. 5, true, now(), auth()).
245240
*/
246-
attribute @default(_ value: ContextType, map: String?) @@@prisma @@@supportTypeDef
241+
attribute @default(_ value: ContextType, map: String?) @@@prisma
247242

248243
/**
249244
* Defines a unique constraint for this field.
@@ -264,7 +259,7 @@ attribute @unique(map: String?, length: Int?, sort: SortOrder?, clustered: Boole
264259
* @param sort: Allows you to specify in what order the entries of the ID are stored in the database. The available options are Asc and Desc.
265260
* @param clustered: Defines whether the ID is clustered or non-clustered. Defaults to true.
266261
*/
267-
attribute @@id(_ fields: FieldReference[], name: String?, map: String?, length: Int?, sort: SortOrder?, clustered: Boolean?) @@@prisma
262+
attribute @@id(_ fields: FieldReference[], name: String?, map: String?, length: Int?, sort: SortOrder?, clustered: Boolean?) @@@prisma @@@once
268263

269264
/**
270265
* Defines a compound unique constraint for the specified fields.
@@ -560,82 +555,82 @@ attribute @omit()
560555
/**
561556
* Validates length of a string field.
562557
*/
563-
attribute @length(_ min: Int?, _ max: Int?, _ message: String?) @@@targetField([StringField]) @@@validation @@@supportTypeDef
558+
attribute @length(_ min: Int?, _ max: Int?, _ message: String?) @@@targetField([StringField]) @@@validation
564559

565560
/**
566561
* Validates a string field value starts with the given text.
567562
*/
568-
attribute @startsWith(_ text: String, _ message: String?) @@@targetField([StringField]) @@@validation @@@supportTypeDef
563+
attribute @startsWith(_ text: String, _ message: String?) @@@targetField([StringField]) @@@validation
569564

570565
/**
571566
* Validates a string field value ends with the given text.
572567
*/
573-
attribute @endsWith(_ text: String, _ message: String?) @@@targetField([StringField]) @@@validation @@@supportTypeDef
568+
attribute @endsWith(_ text: String, _ message: String?) @@@targetField([StringField]) @@@validation
574569

575570
/**
576571
* Validates a string field value contains the given text.
577572
*/
578-
attribute @contains(_ text: String, _ message: String?) @@@targetField([StringField]) @@@validation @@@supportTypeDef
573+
attribute @contains(_ text: String, _ message: String?) @@@targetField([StringField]) @@@validation
579574

580575
/**
581576
* Validates a string field value matches a regex.
582577
*/
583-
attribute @regex(_ regex: String, _ message: String?) @@@targetField([StringField]) @@@validation @@@supportTypeDef
578+
attribute @regex(_ regex: String, _ message: String?) @@@targetField([StringField]) @@@validation
584579

585580
/**
586581
* Validates a string field value is a valid email address.
587582
*/
588-
attribute @email(_ message: String?) @@@targetField([StringField]) @@@validation @@@supportTypeDef
583+
attribute @email(_ message: String?) @@@targetField([StringField]) @@@validation
589584

590585
/**
591586
* Validates a string field value is a valid ISO datetime.
592587
*/
593-
attribute @datetime(_ message: String?) @@@targetField([StringField]) @@@validation @@@supportTypeDef
588+
attribute @datetime(_ message: String?) @@@targetField([StringField]) @@@validation
594589

595590
/**
596591
* Validates a string field value is a valid url.
597592
*/
598-
attribute @url(_ message: String?) @@@targetField([StringField]) @@@validation @@@supportTypeDef
593+
attribute @url(_ message: String?) @@@targetField([StringField]) @@@validation
599594

600595
/**
601596
* Trims whitespaces from the start and end of the string.
602597
*/
603-
attribute @trim() @@@targetField([StringField]) @@@validation @@@supportTypeDef
598+
attribute @trim() @@@targetField([StringField]) @@@validation
604599

605600
/**
606601
* Transform entire string toLowerCase.
607602
*/
608-
attribute @lower() @@@targetField([StringField]) @@@validation @@@supportTypeDef
603+
attribute @lower() @@@targetField([StringField]) @@@validation
609604

610605
/**
611606
* Transform entire string toUpperCase.
612607
*/
613-
attribute @upper() @@@targetField([StringField]) @@@validation @@@supportTypeDef
608+
attribute @upper() @@@targetField([StringField]) @@@validation
614609

615610
/**
616611
* Validates a number field is greater than the given value.
617612
*/
618-
attribute @gt(_ value: Int, _ message: String?) @@@targetField([IntField, FloatField, DecimalField]) @@@validation @@@supportTypeDef
613+
attribute @gt(_ value: Int, _ message: String?) @@@targetField([IntField, FloatField, DecimalField]) @@@validation
619614

620615
/**
621616
* Validates a number field is greater than or equal to the given value.
622617
*/
623-
attribute @gte(_ value: Int, _ message: String?) @@@targetField([IntField, FloatField, DecimalField]) @@@validation @@@supportTypeDef
618+
attribute @gte(_ value: Int, _ message: String?) @@@targetField([IntField, FloatField, DecimalField]) @@@validation
624619

625620
/**
626621
* Validates a number field is less than the given value.
627622
*/
628-
attribute @lt(_ value: Int, _ message: String?) @@@targetField([IntField, FloatField, DecimalField]) @@@validation @@@supportTypeDef
623+
attribute @lt(_ value: Int, _ message: String?) @@@targetField([IntField, FloatField, DecimalField]) @@@validation
629624

630625
/**
631626
* Validates a number field is less than or equal to the given value.
632627
*/
633-
attribute @lte(_ value: Int, _ message: String?) @@@targetField([IntField, FloatField, DecimalField]) @@@validation @@@supportTypeDef
628+
attribute @lte(_ value: Int, _ message: String?) @@@targetField([IntField, FloatField, DecimalField]) @@@validation
634629

635630
/**
636631
* Validates the entity with a complex condition.
637632
*/
638-
attribute @@validate(_ value: Boolean, _ message: String?, _ path: String[]?) @@@validation @@@supportTypeDef
633+
attribute @@validate(_ value: Boolean, _ message: String?, _ path: String[]?) @@@validation
639634

640635
/**
641636
* Validates length of a string field.
@@ -706,7 +701,7 @@ function raw(value: String): Any {
706701
/**
707702
* Marks a field to be strong-typed JSON.
708703
*/
709-
attribute @json() @@@targetField([TypeDefField])
704+
attribute @json() @@@targetField([TypeDefField]) @@@deprecated('The "@json" attribute is not needed anymore. ZenStack will automatically use JSON to store typed fields.')
710705

711706
/**
712707
* Marks a field to be computed.
@@ -723,4 +718,19 @@ function auth(): Any {
723718
* Used to specify the model for resolving `auth()` function call in access policies. A Zmodel
724719
* can have at most one model with this attribute. By default, the model named "User" is used.
725720
*/
726-
attribute @@auth() @@@supportTypeDef
721+
attribute @@auth()
722+
723+
/**
724+
* Attaches arbitrary metadata to a model or type def.
725+
*/
726+
attribute @@meta(_ name: String, _ value: Any)
727+
728+
/**
729+
* Attaches arbitrary metadata to a field.
730+
*/
731+
attribute @meta(_ name: String, _ value: Any)
732+
733+
/**
734+
* Marks an attribute as deprecated.
735+
*/
736+
attribute @@@deprecated(_ message: String)

packages/language/src/ast.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ declare module './ast' {
4646
$resolvedParam?: AttributeParam;
4747
}
4848

49-
interface DataModelField {
49+
interface DataField {
5050
$inheritedFrom?: DataModel;
5151
}
5252

@@ -55,15 +55,10 @@ declare module './ast' {
5555
}
5656

5757
export interface DataModel {
58-
/**
59-
* Indicates whether the model is already merged with the base types
60-
*/
61-
$baseMerged?: boolean;
62-
6358
/**
6459
* All fields including those marked with `@ignore`
6560
*/
66-
$allFields?: DataModelField[];
61+
$allFields?: DataField[];
6762
}
6863
}
6964

0 commit comments

Comments
 (0)