Skip to content

Commit aec0096

Browse files
authored
Merge pull request #160 from zenstackhq/dev
merge dev to main (v3.0.0-alpha.22)
2 parents c10e48f + 8a99a4a commit aec0096

File tree

19 files changed

+108
-47
lines changed

19 files changed

+108
-47
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "zenstack-v3",
3-
"version": "3.0.0-alpha.21",
3+
"version": "3.0.0-alpha.22",
44
"description": "ZenStack",
55
"packageManager": "[email protected]",
66
"scripts": {

packages/cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"publisher": "zenstack",
44
"displayName": "ZenStack CLI",
55
"description": "FullStack database toolkit with built-in access control and automatic API generation.",
6-
"version": "3.0.0-alpha.21",
6+
"version": "3.0.0-alpha.22",
77
"type": "module",
88
"author": {
99
"name": "ZenStack Team"

packages/common-helpers/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@zenstackhq/common-helpers",
3-
"version": "3.0.0-alpha.21",
3+
"version": "3.0.0-alpha.22",
44
"description": "ZenStack Common Helpers",
55
"type": "module",
66
"scripts": {

packages/create-zenstack/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "create-zenstack",
3-
"version": "3.0.0-alpha.21",
3+
"version": "3.0.0-alpha.22",
44
"description": "Create a new ZenStack project",
55
"type": "module",
66
"scripts": {

packages/dialects/sql.js/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@zenstackhq/kysely-sql-js",
3-
"version": "3.0.0-alpha.21",
3+
"version": "3.0.0-alpha.22",
44
"description": "Kysely dialect for sql.js",
55
"type": "module",
66
"scripts": {

packages/eslint-config/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@zenstackhq/eslint-config",
3-
"version": "3.0.0-alpha.21",
3+
"version": "3.0.0-alpha.22",
44
"type": "module",
55
"private": true,
66
"license": "MIT"

packages/ide/vscode/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "zenstack",
33
"publisher": "zenstack",
4-
"version": "3.0.0-alpha.21",
4+
"version": "3.0.0-alpha.22",
55
"displayName": "ZenStack Language Tools",
66
"description": "VSCode extension for ZenStack ZModel language",
77
"private": true,

packages/language/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@zenstackhq/language",
33
"description": "ZenStack ZModel language specification",
4-
"version": "3.0.0-alpha.21",
4+
"version": "3.0.0-alpha.22",
55
"license": "MIT",
66
"author": "ZenStack Team",
77
"files": [

packages/runtime/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@zenstackhq/runtime",
3-
"version": "3.0.0-alpha.21",
3+
"version": "3.0.0-alpha.22",
44
"description": "ZenStack Runtime",
55
"type": "module",
66
"scripts": {
@@ -65,11 +65,12 @@
6565
}
6666
},
6767
"dependencies": {
68-
"@zenstackhq/common-helpers": "workspace:*",
6968
"@paralleldrive/cuid2": "^2.2.2",
69+
"@zenstackhq/common-helpers": "workspace:*",
7070
"decimal.js": "^10.4.3",
7171
"json-stable-stringify": "^1.3.0",
7272
"nanoid": "^5.0.9",
73+
"toposort": "^2.0.2",
7374
"ts-pattern": "catalog:",
7475
"ulid": "^3.0.0",
7576
"uuid": "^11.0.5"
@@ -91,6 +92,7 @@
9192
"devDependencies": {
9293
"@types/better-sqlite3": "^7.6.13",
9394
"@types/pg": "^8.0.0",
95+
"@types/toposort": "^2.0.7",
9496
"@zenstackhq/eslint-config": "workspace:*",
9597
"@zenstackhq/language": "workspace:*",
9698
"@zenstackhq/sdk": "workspace:*",

packages/runtime/src/client/helpers/schema-db-pusher.ts

Lines changed: 72 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { invariant } from '@zenstackhq/common-helpers';
22
import { CreateTableBuilder, sql, type ColumnDataType, type OnModifyForeignAction } from 'kysely';
3+
import toposort from 'toposort';
34
import { match } from 'ts-pattern';
45
import {
56
ExpressionUtils,
67
type BuiltinType,
78
type CascadeAction,
89
type FieldDef,
9-
type GetModels,
1010
type ModelDef,
1111
type SchemaDef,
1212
} from '../../schema';
@@ -24,32 +24,84 @@ export class SchemaDbPusher<Schema extends SchemaDef> {
2424
if (this.schema.enums && this.schema.provider.type === 'postgresql') {
2525
for (const [name, enumDef] of Object.entries(this.schema.enums)) {
2626
const createEnum = tx.schema.createType(name).asEnum(Object.values(enumDef));
27-
// console.log('Creating enum:', createEnum.compile().sql);
2827
await createEnum.execute();
2928
}
3029
}
3130

32-
for (const model of Object.keys(this.schema.models)) {
33-
const createTable = this.createModelTable(tx, model as GetModels<Schema>);
34-
// console.log('Creating table:', createTable.compile().sql);
31+
// sort models so that target of fk constraints are created first
32+
const sortedModels = this.sortModels(this.schema.models);
33+
for (const modelDef of sortedModels) {
34+
const createTable = this.createModelTable(tx, modelDef);
3535
await createTable.execute();
3636
}
3737
});
3838
}
3939

40-
private createModelTable(kysely: ToKysely<Schema>, model: GetModels<Schema>) {
41-
let table = kysely.schema.createTable(model).ifNotExists();
42-
const modelDef = requireModel(this.schema, model);
40+
private sortModels(models: Record<string, ModelDef>): ModelDef[] {
41+
const graph: [ModelDef, ModelDef | undefined][] = [];
42+
43+
for (const model of Object.values(models)) {
44+
let added = false;
45+
46+
if (model.baseModel) {
47+
// base model should be created before concrete model
48+
const baseDef = requireModel(this.schema, model.baseModel);
49+
// edge: concrete model -> base model
50+
graph.push([model, baseDef]);
51+
added = true;
52+
}
53+
54+
for (const field of Object.values(model.fields)) {
55+
// relation order
56+
if (field.relation && field.relation.fields && field.relation.references) {
57+
const targetModel = requireModel(this.schema, field.type);
58+
// edge: fk side -> target model
59+
graph.push([model, targetModel]);
60+
added = true;
61+
}
62+
}
63+
64+
if (!added) {
65+
// no relations, add self to graph to ensure it is included in the result
66+
graph.push([model, undefined]);
67+
}
68+
}
69+
70+
return toposort(graph)
71+
.reverse()
72+
.filter((m) => !!m);
73+
}
74+
75+
private createModelTable(kysely: ToKysely<Schema>, modelDef: ModelDef) {
76+
let table: CreateTableBuilder<string, any> = kysely.schema.createTable(modelDef.name).ifNotExists();
77+
4378
for (const [fieldName, fieldDef] of Object.entries(modelDef.fields)) {
79+
if (fieldDef.originModel && !fieldDef.id) {
80+
// skip non-id fields inherited from base model
81+
continue;
82+
}
83+
4484
if (fieldDef.relation) {
45-
table = this.addForeignKeyConstraint(table, model, fieldName, fieldDef);
85+
table = this.addForeignKeyConstraint(table, modelDef.name, fieldName, fieldDef);
4686
} else if (!this.isComputedField(fieldDef)) {
47-
table = this.createModelField(table, fieldName, fieldDef, modelDef);
87+
table = this.createModelField(table, fieldDef, modelDef);
4888
}
4989
}
5090

51-
table = this.addPrimaryKeyConstraint(table, model, modelDef);
52-
table = this.addUniqueConstraint(table, model, modelDef);
91+
if (modelDef.baseModel) {
92+
// create fk constraint
93+
const baseModelDef = requireModel(this.schema, modelDef.baseModel);
94+
table = table.addForeignKeyConstraint(
95+
`fk_${modelDef.baseModel}_delegate`,
96+
baseModelDef.idFields,
97+
modelDef.baseModel,
98+
baseModelDef.idFields,
99+
(cb) => cb.onDelete('cascade').onUpdate('cascade'),
100+
);
101+
}
102+
103+
table = this.addPrimaryKeyConstraint(table, modelDef);
104+
table = this.addUniqueConstraint(table, modelDef);
53105

54106
return table;
55107
}
@@ -58,11 +110,7 @@ export class SchemaDbPusher<Schema extends SchemaDef> {
58110
return fieldDef.attributes?.some((a) => a.name === '@computed');
59111
}
60112

61-
private addPrimaryKeyConstraint(
62-
table: CreateTableBuilder<string, any>,
63-
model: GetModels<Schema>,
64-
modelDef: ModelDef,
65-
) {
113+
private addPrimaryKeyConstraint(table: CreateTableBuilder<string, any>, modelDef: ModelDef) {
66114
if (modelDef.idFields.length === 1) {
67115
if (Object.values(modelDef.fields).some((f) => f.id)) {
68116
// @id defined at field level
@@ -71,13 +119,13 @@ export class SchemaDbPusher<Schema extends SchemaDef> {
71119
}
72120

73121
if (modelDef.idFields.length > 0) {
74-
table = table.addPrimaryKeyConstraint(`pk_${model}`, modelDef.idFields);
122+
table = table.addPrimaryKeyConstraint(`pk_${modelDef.name}`, modelDef.idFields);
75123
}
76124

77125
return table;
78126
}
79127

80-
private addUniqueConstraint(table: CreateTableBuilder<string, any>, model: string, modelDef: ModelDef) {
128+
private addUniqueConstraint(table: CreateTableBuilder<string, any>, modelDef: ModelDef) {
81129
for (const [key, value] of Object.entries(modelDef.uniqueFields)) {
82130
invariant(typeof value === 'object', 'expecting an object');
83131
if ('type' in value) {
@@ -86,22 +134,17 @@ export class SchemaDbPusher<Schema extends SchemaDef> {
86134
if (fieldDef.unique) {
87135
continue;
88136
}
89-
table = table.addUniqueConstraint(`unique_${model}_${key}`, [key]);
137+
table = table.addUniqueConstraint(`unique_${modelDef.name}_${key}`, [key]);
90138
} else {
91139
// multi-field constraint
92-
table = table.addUniqueConstraint(`unique_${model}_${key}`, Object.keys(value));
140+
table = table.addUniqueConstraint(`unique_${modelDef.name}_${key}`, Object.keys(value));
93141
}
94142
}
95143
return table;
96144
}
97145

98-
private createModelField(
99-
table: CreateTableBuilder<any>,
100-
fieldName: string,
101-
fieldDef: FieldDef,
102-
modelDef: ModelDef,
103-
) {
104-
return table.addColumn(fieldName, this.mapFieldType(fieldDef), (col) => {
146+
private createModelField(table: CreateTableBuilder<any>, fieldDef: FieldDef, modelDef: ModelDef) {
147+
return table.addColumn(fieldDef.name, this.mapFieldType(fieldDef), (col) => {
105148
// @id
106149
if (fieldDef.id && modelDef.idFields.length === 1) {
107150
col = col.primaryKey();
@@ -178,7 +221,7 @@ export class SchemaDbPusher<Schema extends SchemaDef> {
178221

179222
private addForeignKeyConstraint(
180223
table: CreateTableBuilder<string, any>,
181-
model: GetModels<Schema>,
224+
model: string,
182225
fieldName: string,
183226
fieldDef: FieldDef,
184227
) {

0 commit comments

Comments
 (0)