dzql 0.6.3 → 0.6.5
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/README.md +33 -0
- package/docs/for_ai.md +14 -18
- package/docs/project-setup.md +15 -14
- package/package.json +28 -6
- package/src/cli/codegen/client.ts +5 -6
- package/src/cli/codegen/subscribable_store.ts +5 -5
- package/src/runtime/ws.ts +16 -15
- package/.env.sample +0 -28
- package/compose.yml +0 -28
- package/dist/client/index.ts +0 -1
- package/dist/client/stores/useMyProfileStore.ts +0 -114
- package/dist/client/stores/useOrgDashboardStore.ts +0 -131
- package/dist/client/stores/useVenueDetailStore.ts +0 -117
- package/dist/client/ws.ts +0 -716
- package/dist/db/migrations/000_core.sql +0 -92
- package/dist/db/migrations/20260101T235039268Z_schema.sql +0 -3020
- package/dist/db/migrations/20260101T235039268Z_subscribables.sql +0 -371
- package/dist/runtime/manifest.json +0 -1562
- package/examples/blog.ts +0 -50
- package/examples/invalid.ts +0 -18
- package/examples/venues.js +0 -485
- package/tests/client.test.ts +0 -38
- package/tests/codegen.test.ts +0 -71
- package/tests/compiler.test.ts +0 -45
- package/tests/graph_rules.test.ts +0 -173
- package/tests/integration/db.test.ts +0 -174
- package/tests/integration/e2e.test.ts +0 -65
- package/tests/integration/features.test.ts +0 -922
- package/tests/integration/full_stack.test.ts +0 -262
- package/tests/integration/setup.ts +0 -45
- package/tests/ir.test.ts +0 -32
- package/tests/namespace.test.ts +0 -395
- package/tests/permissions.test.ts +0 -55
- package/tests/pinia.test.ts +0 -48
- package/tests/realtime.test.ts +0 -22
- package/tests/runtime.test.ts +0 -80
- package/tests/subscribable_gen.test.ts +0 -72
- package/tests/subscribable_reactivity.test.ts +0 -258
- package/tests/venues_gen.test.ts +0 -25
- package/tsconfig.json +0 -20
- package/tsconfig.tsbuildinfo +0 -1
package/examples/blog.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
// TZQL Entity Definition Example
|
|
2
|
-
|
|
3
|
-
export const entities = {
|
|
4
|
-
posts: {
|
|
5
|
-
schema: {
|
|
6
|
-
id: 'serial PRIMARY KEY',
|
|
7
|
-
title: 'text NOT NULL',
|
|
8
|
-
content: 'text',
|
|
9
|
-
author_id: 'int NOT NULL', // In a real app, this would reference users(id)
|
|
10
|
-
created_at: 'timestamptz DEFAULT now()'
|
|
11
|
-
},
|
|
12
|
-
permissions: {
|
|
13
|
-
view: [], // Public
|
|
14
|
-
create: ['@author_id == @user_id'], // Only create for self
|
|
15
|
-
update: ['@author_id == @user_id'], // Only owner
|
|
16
|
-
delete: ['@author_id == @user_id'] // Only owner
|
|
17
|
-
},
|
|
18
|
-
graphRules: {
|
|
19
|
-
on_create: {
|
|
20
|
-
actions: [
|
|
21
|
-
{ type: 'reactor', name: 'notify_subscribers', params: { post_id: '@id' } }
|
|
22
|
-
]
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
comments: {
|
|
27
|
-
schema: {
|
|
28
|
-
id: 'serial PRIMARY KEY',
|
|
29
|
-
post_id: 'int NOT NULL REFERENCES posts(id) ON DELETE CASCADE',
|
|
30
|
-
content: 'text NOT NULL',
|
|
31
|
-
author_id: 'int NOT NULL'
|
|
32
|
-
},
|
|
33
|
-
permissions: {
|
|
34
|
-
view: [],
|
|
35
|
-
create: [],
|
|
36
|
-
delete: ['@author_id == @user_id']
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
export const subscribables = {
|
|
42
|
-
post_detail: {
|
|
43
|
-
params: { post_id: 'int' },
|
|
44
|
-
root: { entity: 'posts', key: 'post_id' },
|
|
45
|
-
includes: {
|
|
46
|
-
comments: { entity: 'comments', filter: { post_id: '@id' } }
|
|
47
|
-
},
|
|
48
|
-
scopeTables: ['posts', 'comments']
|
|
49
|
-
}
|
|
50
|
-
};
|
package/examples/invalid.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
// TZQL Entity Definition Example (INVALID)
|
|
2
|
-
|
|
3
|
-
export const entities = {
|
|
4
|
-
posts: {
|
|
5
|
-
schema: { id: 'serial PRIMARY KEY' },
|
|
6
|
-
permissions: {}
|
|
7
|
-
}
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export const subscribables = {
|
|
11
|
-
broken_feed: {
|
|
12
|
-
params: {},
|
|
13
|
-
root: { entity: 'posts' },
|
|
14
|
-
includes: {
|
|
15
|
-
comments: { entity: 'missing_table' } // <--- Error: 'missing_table' does not exist
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
};
|
package/examples/venues.js
DELETED
|
@@ -1,485 +0,0 @@
|
|
|
1
|
-
// venues-domain.js - v2 format
|
|
2
|
-
// Complete domain definition: all tables, all config
|
|
3
|
-
|
|
4
|
-
export const entities = {
|
|
5
|
-
|
|
6
|
-
// === Users ===
|
|
7
|
-
users: {
|
|
8
|
-
schema: {
|
|
9
|
-
id: 'serial PRIMARY KEY',
|
|
10
|
-
name: 'text NOT NULL',
|
|
11
|
-
email: 'text UNIQUE NOT NULL',
|
|
12
|
-
password_hash: 'text NOT NULL',
|
|
13
|
-
created_at: 'timestamptz DEFAULT now()'
|
|
14
|
-
},
|
|
15
|
-
label: 'name',
|
|
16
|
-
searchable: ['name', 'email'],
|
|
17
|
-
hidden: ['password_hash'],
|
|
18
|
-
permissions: {
|
|
19
|
-
view: [],
|
|
20
|
-
create: [],
|
|
21
|
-
update: ['@id'],
|
|
22
|
-
delete: ['@id']
|
|
23
|
-
}
|
|
24
|
-
},
|
|
25
|
-
|
|
26
|
-
// === Organisations ===
|
|
27
|
-
organisations: {
|
|
28
|
-
schema: {
|
|
29
|
-
id: 'serial PRIMARY KEY',
|
|
30
|
-
name: 'text UNIQUE NOT NULL',
|
|
31
|
-
description: 'text'
|
|
32
|
-
},
|
|
33
|
-
label: 'name',
|
|
34
|
-
searchable: ['name', 'description'],
|
|
35
|
-
permissions: {
|
|
36
|
-
view: [],
|
|
37
|
-
create: [],
|
|
38
|
-
update: ['@id->acts_for[org_id=$]{active}.user_id'],
|
|
39
|
-
delete: ['@id->acts_for[org_id=$]{active}.user_id']
|
|
40
|
-
},
|
|
41
|
-
graphRules: {
|
|
42
|
-
on_create: {
|
|
43
|
-
establish_ownership: {
|
|
44
|
-
description: 'Creator becomes owner',
|
|
45
|
-
actions: [
|
|
46
|
-
{
|
|
47
|
-
type: 'create',
|
|
48
|
-
entity: 'acts_for',
|
|
49
|
-
data: {
|
|
50
|
-
user_id: '@user_id',
|
|
51
|
-
org_id: '@id',
|
|
52
|
-
valid_from: '@today'
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
]
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
on_delete: {
|
|
59
|
-
cleanup_relationships: {
|
|
60
|
-
description: 'Cascading delete for related entities',
|
|
61
|
-
actions: [
|
|
62
|
-
{ type: 'delete', target: 'acts_for', params: { org_id: '@id' } },
|
|
63
|
-
{ type: 'delete', target: 'venues', params: { org_id: '@id' } },
|
|
64
|
-
{ type: 'update', target: 'packages', params: { sponsor_org_id: '@id' }, data: { sponsor_org_id: null } } // SET NULL example
|
|
65
|
-
]
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
},
|
|
70
|
-
|
|
71
|
-
// === Acts For (temporal, composite PK) ===
|
|
72
|
-
acts_for: {
|
|
73
|
-
schema: {
|
|
74
|
-
user_id: 'int NOT NULL REFERENCES users(id)',
|
|
75
|
-
org_id: 'int NOT NULL REFERENCES organisations(id) ON DELETE CASCADE',
|
|
76
|
-
valid_from: 'date NOT NULL DEFAULT current_date',
|
|
77
|
-
valid_to: 'date',
|
|
78
|
-
active: 'boolean DEFAULT true' // Added for permission check compatibility
|
|
79
|
-
},
|
|
80
|
-
primaryKey: ['user_id', 'org_id', 'valid_from'],
|
|
81
|
-
label: 'org_id',
|
|
82
|
-
searchable: ['org_id', 'user_id'],
|
|
83
|
-
includes: {
|
|
84
|
-
user: 'users',
|
|
85
|
-
org: 'organisations'
|
|
86
|
-
},
|
|
87
|
-
temporal: {
|
|
88
|
-
validFrom: 'valid_from',
|
|
89
|
-
validTo: 'valid_to'
|
|
90
|
-
},
|
|
91
|
-
permissions: {}
|
|
92
|
-
},
|
|
93
|
-
|
|
94
|
-
// === Venues ===
|
|
95
|
-
venues: {
|
|
96
|
-
schema: {
|
|
97
|
-
id: 'serial PRIMARY KEY',
|
|
98
|
-
org_id: 'int NOT NULL REFERENCES organisations(id) ON DELETE CASCADE',
|
|
99
|
-
name: 'text UNIQUE NOT NULL',
|
|
100
|
-
address: 'text NOT NULL',
|
|
101
|
-
description: 'text'
|
|
102
|
-
},
|
|
103
|
-
label: 'name',
|
|
104
|
-
searchable: ['name', 'address', 'description'],
|
|
105
|
-
includes: {
|
|
106
|
-
org: 'organisations',
|
|
107
|
-
sites: 'sites'
|
|
108
|
-
},
|
|
109
|
-
permissions: {
|
|
110
|
-
view: [],
|
|
111
|
-
create: ['@org_id->acts_for[org_id=$]{active}.user_id'],
|
|
112
|
-
update: ['@org_id->acts_for[org_id=$]{active}.user_id'],
|
|
113
|
-
delete: ['@org_id->acts_for[org_id=$]{active}.user_id']
|
|
114
|
-
},
|
|
115
|
-
notifications: {
|
|
116
|
-
ownership: ['@org_id->acts_for[org_id=$]{active}.user_id']
|
|
117
|
-
}
|
|
118
|
-
},
|
|
119
|
-
|
|
120
|
-
// === Sites ===
|
|
121
|
-
sites: {
|
|
122
|
-
schema: {
|
|
123
|
-
id: 'serial PRIMARY KEY',
|
|
124
|
-
venue_id: 'int NOT NULL REFERENCES venues(id)',
|
|
125
|
-
name: 'text NOT NULL',
|
|
126
|
-
description: 'text'
|
|
127
|
-
},
|
|
128
|
-
label: 'name',
|
|
129
|
-
searchable: ['name', 'description'],
|
|
130
|
-
includes: {
|
|
131
|
-
venue: 'venues'
|
|
132
|
-
},
|
|
133
|
-
permissions: {
|
|
134
|
-
view: [],
|
|
135
|
-
create: ['@venue_id->venues.org_id->acts_for[org_id=$]{active}.user_id'],
|
|
136
|
-
update: ['@venue_id->venues.org_id->acts_for[org_id=$]{active}.user_id'],
|
|
137
|
-
delete: ['@venue_id->venues.org_id->acts_for[org_id=$]{active}.user_id']
|
|
138
|
-
},
|
|
139
|
-
notifications: {
|
|
140
|
-
ownership: ['@venue_id->venues.org_id->acts_for[org_id=$]{active}.user_id']
|
|
141
|
-
}
|
|
142
|
-
},
|
|
143
|
-
|
|
144
|
-
// === Products (with soft delete and field defaults) ===
|
|
145
|
-
products: {
|
|
146
|
-
schema: {
|
|
147
|
-
id: 'serial PRIMARY KEY',
|
|
148
|
-
org_id: 'int NOT NULL REFERENCES organisations(id) ON DELETE CASCADE',
|
|
149
|
-
name: 'text UNIQUE NOT NULL',
|
|
150
|
-
description: 'text',
|
|
151
|
-
price: 'decimal(10, 2) NOT NULL DEFAULT 0.00',
|
|
152
|
-
created_by: 'int REFERENCES users(id)',
|
|
153
|
-
created_at: 'timestamptz',
|
|
154
|
-
deleted_at: 'timestamptz' // For soft delete
|
|
155
|
-
},
|
|
156
|
-
label: 'name',
|
|
157
|
-
searchable: ['name', 'description'],
|
|
158
|
-
includes: {
|
|
159
|
-
org: 'organisations'
|
|
160
|
-
},
|
|
161
|
-
// Soft delete - sets deleted_at instead of DELETE
|
|
162
|
-
softDelete: true,
|
|
163
|
-
// Auto-populate on INSERT
|
|
164
|
-
fieldDefaults: {
|
|
165
|
-
created_by: '@user_id',
|
|
166
|
-
created_at: '@now'
|
|
167
|
-
},
|
|
168
|
-
permissions: {
|
|
169
|
-
view: [],
|
|
170
|
-
create: ['@org_id->acts_for[org_id=$]{active}.user_id'],
|
|
171
|
-
update: ['@org_id->acts_for[org_id=$]{active}.user_id'],
|
|
172
|
-
delete: ['@org_id->acts_for[org_id=$]{active}.user_id']
|
|
173
|
-
},
|
|
174
|
-
notifications: {
|
|
175
|
-
ownership: ['@org_id->acts_for[org_id=$]{active}.user_id']
|
|
176
|
-
}
|
|
177
|
-
},
|
|
178
|
-
|
|
179
|
-
// === Packages (with CHECK constraint) ===
|
|
180
|
-
packages: {
|
|
181
|
-
schema: {
|
|
182
|
-
id: 'serial PRIMARY KEY',
|
|
183
|
-
owner_org_id: 'int NOT NULL REFERENCES organisations(id) ON DELETE CASCADE',
|
|
184
|
-
sponsor_org_id: 'int REFERENCES organisations(id) ON DELETE SET NULL',
|
|
185
|
-
name: 'text NOT NULL',
|
|
186
|
-
price: 'decimal(10, 2) NOT NULL DEFAULT 0.00',
|
|
187
|
-
status: "text NOT NULL DEFAULT 'draft'"
|
|
188
|
-
},
|
|
189
|
-
constraints: [
|
|
190
|
-
"CHECK (status IN ('draft', 'available', 'sold', 'expired'))"
|
|
191
|
-
],
|
|
192
|
-
label: 'name',
|
|
193
|
-
searchable: ['name'],
|
|
194
|
-
includes: {
|
|
195
|
-
owner_org: 'organisations',
|
|
196
|
-
sponsor_org: 'organisations'
|
|
197
|
-
},
|
|
198
|
-
permissions: {
|
|
199
|
-
view: [
|
|
200
|
-
'@owner_org_id->acts_for[org_id=$]{active}.user_id',
|
|
201
|
-
'@sponsor_org_id->acts_for[org_id=$]{active}.user_id'
|
|
202
|
-
],
|
|
203
|
-
create: [
|
|
204
|
-
'@owner_org_id->acts_for[org_id=$]{active}.user_id',
|
|
205
|
-
'@sponsor_org_id->acts_for[org_id=$]{active}.user_id'
|
|
206
|
-
],
|
|
207
|
-
update: [
|
|
208
|
-
'@owner_org_id->acts_for[org_id=$]{active}.user_id',
|
|
209
|
-
'@sponsor_org_id->acts_for[org_id=$]{active}.user_id'
|
|
210
|
-
],
|
|
211
|
-
delete: ['@owner_org_id->acts_for[org_id=$]{active}.user_id']
|
|
212
|
-
},
|
|
213
|
-
notifications: {
|
|
214
|
-
ownership: ['@owner_org_id->acts_for[org_id=$]{active}.user_id'],
|
|
215
|
-
commercial: ['@sponsor_org_id->acts_for[org_id=$]{active}.user_id']
|
|
216
|
-
}
|
|
217
|
-
},
|
|
218
|
-
|
|
219
|
-
// === Allocations (complex permission paths) ===
|
|
220
|
-
allocations: {
|
|
221
|
-
schema: {
|
|
222
|
-
id: 'serial PRIMARY KEY',
|
|
223
|
-
package_id: 'int NOT NULL REFERENCES packages(id)',
|
|
224
|
-
site_id: 'int NOT NULL REFERENCES sites(id)',
|
|
225
|
-
from_date: 'date NOT NULL',
|
|
226
|
-
to_date: 'date NOT NULL'
|
|
227
|
-
},
|
|
228
|
-
label: 'id',
|
|
229
|
-
searchable: ['site_id'],
|
|
230
|
-
includes: {
|
|
231
|
-
package: 'packages',
|
|
232
|
-
site: 'sites'
|
|
233
|
-
},
|
|
234
|
-
permissions: {
|
|
235
|
-
view: [
|
|
236
|
-
'@site_id->sites.venue_id->venues.org_id->acts_for[org_id=$]{active}.user_id',
|
|
237
|
-
'@package_id->packages.owner_org_id->acts_for[org_id=$]{active}.user_id',
|
|
238
|
-
'@package_id->packages.sponsor_org_id->acts_for[org_id=$]{active}.user_id',
|
|
239
|
-
'contractor_rights[package_id=@package_id]{active}.contractor_org_id->acts_for[org_id=$]{active}.user_id'
|
|
240
|
-
],
|
|
241
|
-
create: ['@site_id->sites.venue_id->venues.org_id->acts_for[org_id=$]{active}.user_id'],
|
|
242
|
-
update: [
|
|
243
|
-
'@site_id->sites.venue_id->venues.org_id->acts_for[org_id=$]{active}.user_id',
|
|
244
|
-
'@package_id->packages.owner_org_id->acts_for[org_id=$]{active}.user_id'
|
|
245
|
-
],
|
|
246
|
-
delete: ['@site_id->sites.venue_id->venues.org_id->acts_for[org_id=$]{active}.user_id']
|
|
247
|
-
},
|
|
248
|
-
notifications: {
|
|
249
|
-
ownership: ['@site_id->sites.venue_id->venues.org_id->acts_for[org_id=$]{active}.user_id'],
|
|
250
|
-
commercial: [
|
|
251
|
-
'@package_id->packages.owner_org_id->acts_for[org_id=$]{active}.user_id',
|
|
252
|
-
'@package_id->packages.sponsor_org_id->acts_for[org_id=$]{active}.user_id'
|
|
253
|
-
],
|
|
254
|
-
delegated: [
|
|
255
|
-
'contractor_rights[package_id=@package_id]{active}.contractor_org_id->acts_for[org_id=$]{active}.user_id'
|
|
256
|
-
]
|
|
257
|
-
}
|
|
258
|
-
},
|
|
259
|
-
|
|
260
|
-
// === Contractor Rights (composite PK, temporal) ===
|
|
261
|
-
contractor_rights: {
|
|
262
|
-
schema: {
|
|
263
|
-
contractor_org_id: 'int NOT NULL REFERENCES organisations(id) ON DELETE CASCADE',
|
|
264
|
-
sponsor_org_id: 'int NOT NULL REFERENCES organisations(id) ON DELETE CASCADE',
|
|
265
|
-
package_id: 'int NOT NULL REFERENCES packages(id) ON DELETE CASCADE',
|
|
266
|
-
valid_from: 'date NOT NULL DEFAULT current_date',
|
|
267
|
-
valid_to: 'date'
|
|
268
|
-
},
|
|
269
|
-
primaryKey: ['contractor_org_id', 'package_id', 'valid_from'],
|
|
270
|
-
label: 'contractor_org_id',
|
|
271
|
-
searchable: ['contractor_org_id', 'sponsor_org_id'],
|
|
272
|
-
includes: {
|
|
273
|
-
contractor_org: 'organisations',
|
|
274
|
-
sponsor_org: 'organisations',
|
|
275
|
-
package: 'packages'
|
|
276
|
-
},
|
|
277
|
-
temporal: {
|
|
278
|
-
validFrom: 'valid_from',
|
|
279
|
-
validTo: 'valid_to'
|
|
280
|
-
},
|
|
281
|
-
permissions: {
|
|
282
|
-
view: [],
|
|
283
|
-
create: ['@sponsor_org_id->acts_for[org_id=$]{active}.user_id'],
|
|
284
|
-
update: ['@sponsor_org_id->acts_for[org_id=$]{active}.user_id'],
|
|
285
|
-
delete: ['@sponsor_org_id->acts_for[org_id=$]{active}.user_id']
|
|
286
|
-
},
|
|
287
|
-
notifications: {
|
|
288
|
-
parties: [
|
|
289
|
-
'@contractor_org_id->acts_for[org_id=$]{active}.user_id',
|
|
290
|
-
'@sponsor_org_id->acts_for[org_id=$]{active}.user_id'
|
|
291
|
-
]
|
|
292
|
-
}
|
|
293
|
-
},
|
|
294
|
-
|
|
295
|
-
// === Brands (with M2M tags) ===
|
|
296
|
-
brands: {
|
|
297
|
-
schema: {
|
|
298
|
-
id: 'serial PRIMARY KEY',
|
|
299
|
-
org_id: 'int NOT NULL REFERENCES organisations(id) ON DELETE CASCADE',
|
|
300
|
-
name: 'text NOT NULL',
|
|
301
|
-
description: 'text'
|
|
302
|
-
},
|
|
303
|
-
constraints: [
|
|
304
|
-
'UNIQUE(org_id, name)'
|
|
305
|
-
],
|
|
306
|
-
indexes: [
|
|
307
|
-
'CREATE INDEX idx_brands_org_id ON brands(org_id)'
|
|
308
|
-
],
|
|
309
|
-
label: 'name',
|
|
310
|
-
searchable: ['name', 'description'],
|
|
311
|
-
includes: {
|
|
312
|
-
org: 'organisations',
|
|
313
|
-
artwork: 'artwork'
|
|
314
|
-
},
|
|
315
|
-
manyToMany: {
|
|
316
|
-
tags: {
|
|
317
|
-
junctionTable: 'brand_tags',
|
|
318
|
-
localKey: 'brand_id',
|
|
319
|
-
foreignKey: 'tag_id',
|
|
320
|
-
targetEntity: 'tags',
|
|
321
|
-
idField: 'tag_ids',
|
|
322
|
-
expand: false
|
|
323
|
-
}
|
|
324
|
-
},
|
|
325
|
-
permissions: {
|
|
326
|
-
view: [],
|
|
327
|
-
create: ['@org_id->acts_for[org_id=$]{active}.user_id'],
|
|
328
|
-
update: ['@org_id->acts_for[org_id=$]{active}.user_id'],
|
|
329
|
-
delete: ['@org_id->acts_for[org_id=$]{active}.user_id']
|
|
330
|
-
},
|
|
331
|
-
notifications: {
|
|
332
|
-
org_members: ['@org_id->acts_for[org_id=$]{active}.user_id']
|
|
333
|
-
}
|
|
334
|
-
},
|
|
335
|
-
|
|
336
|
-
// === Artwork ===
|
|
337
|
-
artwork: {
|
|
338
|
-
schema: {
|
|
339
|
-
id: 'serial PRIMARY KEY',
|
|
340
|
-
brand_id: 'int NOT NULL REFERENCES brands(id) ON DELETE CASCADE',
|
|
341
|
-
url: 'text NOT NULL',
|
|
342
|
-
ratio: 'decimal(10, 4) NOT NULL'
|
|
343
|
-
},
|
|
344
|
-
constraints: [
|
|
345
|
-
'CHECK (ratio > 0)'
|
|
346
|
-
],
|
|
347
|
-
indexes: [
|
|
348
|
-
'CREATE INDEX idx_artwork_brand_id ON artwork(brand_id)'
|
|
349
|
-
],
|
|
350
|
-
label: 'url',
|
|
351
|
-
searchable: ['url'],
|
|
352
|
-
includes: {
|
|
353
|
-
brand: 'brands'
|
|
354
|
-
},
|
|
355
|
-
permissions: {
|
|
356
|
-
view: [],
|
|
357
|
-
create: ['@brand_id->brands.org_id->acts_for[org_id=$]{active}.user_id'],
|
|
358
|
-
update: ['@brand_id->brands.org_id->acts_for[org_id=$]{active}.user_id'],
|
|
359
|
-
delete: ['@brand_id->brands.org_id->acts_for[org_id=$]{active}.user_id']
|
|
360
|
-
},
|
|
361
|
-
notifications: {
|
|
362
|
-
brand_org_members: ['@brand_id->brands.org_id->acts_for[org_id=$]{active}.user_id']
|
|
363
|
-
}
|
|
364
|
-
},
|
|
365
|
-
|
|
366
|
-
// === Tags ===
|
|
367
|
-
tags: {
|
|
368
|
-
schema: {
|
|
369
|
-
id: 'serial PRIMARY KEY',
|
|
370
|
-
name: 'text NOT NULL UNIQUE',
|
|
371
|
-
color: 'text',
|
|
372
|
-
description: 'text'
|
|
373
|
-
},
|
|
374
|
-
label: 'name',
|
|
375
|
-
searchable: ['name', 'description'],
|
|
376
|
-
permissions: {
|
|
377
|
-
view: [],
|
|
378
|
-
create: [],
|
|
379
|
-
update: [],
|
|
380
|
-
delete: []
|
|
381
|
-
}
|
|
382
|
-
},
|
|
383
|
-
|
|
384
|
-
// === Brand Tags (junction table) ===
|
|
385
|
-
brand_tags: {
|
|
386
|
-
schema: {
|
|
387
|
-
brand_id: 'int NOT NULL REFERENCES brands(id) ON DELETE CASCADE',
|
|
388
|
-
tag_id: 'int NOT NULL REFERENCES tags(id) ON DELETE CASCADE'
|
|
389
|
-
},
|
|
390
|
-
primaryKey: ['brand_id', 'tag_id'],
|
|
391
|
-
indexes: [
|
|
392
|
-
'CREATE INDEX idx_brand_tags_tag_id ON brand_tags(tag_id)'
|
|
393
|
-
],
|
|
394
|
-
// Junction tables typically don't need DZQL CRUD - managed via M2M
|
|
395
|
-
managed: false
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
};
|
|
399
|
-
|
|
400
|
-
// === Subscribables (real-time documents) ===
|
|
401
|
-
export const subscribables = {
|
|
402
|
-
|
|
403
|
-
// Venue detail - venue with org, sites, and allocations
|
|
404
|
-
venue_detail: {
|
|
405
|
-
params: {
|
|
406
|
-
venue_id: 'int'
|
|
407
|
-
},
|
|
408
|
-
root: {
|
|
409
|
-
entity: 'venues',
|
|
410
|
-
key: 'venue_id'
|
|
411
|
-
},
|
|
412
|
-
includes: {
|
|
413
|
-
org: 'organisations',
|
|
414
|
-
sites: {
|
|
415
|
-
entity: 'sites',
|
|
416
|
-
includes: {
|
|
417
|
-
allocations: 'allocations'
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
},
|
|
421
|
-
// Tables that affect this document - for subscription routing
|
|
422
|
-
scopeTables: ['venues', 'organisations', 'sites', 'allocations'],
|
|
423
|
-
// Who can subscribe
|
|
424
|
-
canSubscribe: ['@venue_id->venues.org_id->acts_for[org_id=$]{active}.user_id']
|
|
425
|
-
},
|
|
426
|
-
|
|
427
|
-
// Org dashboard - everything an organisation member sees
|
|
428
|
-
org_dashboard: {
|
|
429
|
-
params: {
|
|
430
|
-
org_id: 'int'
|
|
431
|
-
},
|
|
432
|
-
root: {
|
|
433
|
-
entity: 'organisations',
|
|
434
|
-
key: 'org_id'
|
|
435
|
-
},
|
|
436
|
-
includes: {
|
|
437
|
-
venues: {
|
|
438
|
-
entity: 'venues',
|
|
439
|
-
filter: { org_id: '@org_id' },
|
|
440
|
-
includes: {
|
|
441
|
-
sites: 'sites'
|
|
442
|
-
}
|
|
443
|
-
},
|
|
444
|
-
products: {
|
|
445
|
-
entity: 'products',
|
|
446
|
-
filter: { org_id: '@org_id' }
|
|
447
|
-
},
|
|
448
|
-
packages: {
|
|
449
|
-
entity: 'packages',
|
|
450
|
-
filter: { owner_org_id: '@org_id' }
|
|
451
|
-
},
|
|
452
|
-
brands: {
|
|
453
|
-
entity: 'brands',
|
|
454
|
-
filter: { org_id: '@org_id' },
|
|
455
|
-
includes: {
|
|
456
|
-
artwork: 'artwork'
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
},
|
|
460
|
-
scopeTables: ['organisations', 'venues', 'sites', 'products', 'packages', 'brands', 'artwork'],
|
|
461
|
-
canSubscribe: ['@org_id->acts_for[org_id=$]{active}.user_id']
|
|
462
|
-
},
|
|
463
|
-
|
|
464
|
-
// User profile - current user's orgs and roles
|
|
465
|
-
my_profile: {
|
|
466
|
-
params: {}, // No params - uses current user
|
|
467
|
-
root: {
|
|
468
|
-
entity: 'users',
|
|
469
|
-
key: '@user_id' // Built-in: current user
|
|
470
|
-
},
|
|
471
|
-
includes: {
|
|
472
|
-
memberships: {
|
|
473
|
-
entity: 'acts_for',
|
|
474
|
-
filter: { user_id: '@user_id' },
|
|
475
|
-
temporal: true, // Only active memberships
|
|
476
|
-
includes: {
|
|
477
|
-
org: 'organisations'
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
},
|
|
481
|
-
scopeTables: ['users', 'acts_for', 'organisations'],
|
|
482
|
-
canSubscribe: [] // Anyone authenticated can subscribe to their own profile
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
};
|
package/tests/client.test.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from "bun:test";
|
|
2
|
-
import { generateClientSDK } from "../src/cli/codegen/client.js";
|
|
3
|
-
import { generateManifest } from "../src/cli/codegen/manifest.js";
|
|
4
|
-
import { generateIR } from "../src/cli/compiler/ir.js";
|
|
5
|
-
|
|
6
|
-
const mockManifest = generateManifest(generateIR({
|
|
7
|
-
entities: {
|
|
8
|
-
posts: {
|
|
9
|
-
schema: { id: "serial primary key", title: "text" },
|
|
10
|
-
permissions: { create: [], view: [] }
|
|
11
|
-
}
|
|
12
|
-
},
|
|
13
|
-
subscribables: {}
|
|
14
|
-
}));
|
|
15
|
-
|
|
16
|
-
describe("Client SDK Generation", () => {
|
|
17
|
-
test("should generate a TypeScript SDK with typed API", () => {
|
|
18
|
-
const tsCode = generateClientSDK(mockManifest);
|
|
19
|
-
|
|
20
|
-
// Check imports
|
|
21
|
-
expect(tsCode).toContain("import { WebSocketManager } from 'dzql/client'");
|
|
22
|
-
|
|
23
|
-
// Check interface definition
|
|
24
|
-
expect(tsCode).toContain("export interface DzqlAPI {");
|
|
25
|
-
expect(tsCode).toContain("save_posts: (params: SavePostsParams) => Promise<Posts>");
|
|
26
|
-
expect(tsCode).toContain("get_posts: (params: PostsPK) => Promise<Posts | null>");
|
|
27
|
-
|
|
28
|
-
// Check class definition
|
|
29
|
-
expect(tsCode).toContain("export class GeneratedWebSocketManager extends WebSocketManager");
|
|
30
|
-
expect(tsCode).toContain("api: DzqlAPI");
|
|
31
|
-
|
|
32
|
-
// Check API implementation
|
|
33
|
-
expect(tsCode).toContain("this.call('save_posts', params)");
|
|
34
|
-
|
|
35
|
-
// Check singleton export
|
|
36
|
-
expect(tsCode).toContain("export const ws = new GeneratedWebSocketManager()");
|
|
37
|
-
});
|
|
38
|
-
});
|
package/tests/codegen.test.ts
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from "bun:test";
|
|
2
|
-
import { generateCoreSQL, generateEntitySQL } from "../src/cli/codegen/sql.js";
|
|
3
|
-
import { generateManifest } from "../src/cli/codegen/manifest.js";
|
|
4
|
-
import { generateIR } from "../src/cli/compiler/ir.js";
|
|
5
|
-
|
|
6
|
-
const mockEntityIR = {
|
|
7
|
-
name: "posts",
|
|
8
|
-
table: "posts",
|
|
9
|
-
primaryKey: ["id"],
|
|
10
|
-
columns: [
|
|
11
|
-
{ name: "id", type: "serial PRIMARY KEY" },
|
|
12
|
-
{ name: "title", type: "text NOT NULL" }
|
|
13
|
-
],
|
|
14
|
-
permissions: {
|
|
15
|
-
create: [],
|
|
16
|
-
view: []
|
|
17
|
-
},
|
|
18
|
-
graphRules: {
|
|
19
|
-
onCreate: []
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
describe("SQL Code Generation", () => {
|
|
24
|
-
test("generateCoreSQL should produce migration table", () => {
|
|
25
|
-
const sql = generateCoreSQL();
|
|
26
|
-
expect(sql).toContain("CREATE SCHEMA IF NOT EXISTS dzql_v2");
|
|
27
|
-
expect(sql).toContain("CREATE TABLE IF NOT EXISTS dzql_v2.migrations");
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test("generateEntitySQL should produce save function", () => {
|
|
31
|
-
const sql = generateEntitySQL("posts", mockEntityIR);
|
|
32
|
-
expect(sql).toContain("CREATE OR REPLACE FUNCTION dzql_v2.save_posts");
|
|
33
|
-
expect(sql).toContain("AND EXISTS(SELECT 1 FROM posts WHERE"); // Check existence check (composite PK support)
|
|
34
|
-
expect(sql).toContain("UPDATE posts SET"); // Check update branch
|
|
35
|
-
expect(sql).toContain("INSERT INTO posts"); // Check insert branch
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
const mockRawEntity = {
|
|
40
|
-
schema: {
|
|
41
|
-
id: "serial PRIMARY KEY",
|
|
42
|
-
title: "text NOT NULL"
|
|
43
|
-
},
|
|
44
|
-
permissions: {
|
|
45
|
-
create: [],
|
|
46
|
-
view: []
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
describe("Manifest Generation", () => {
|
|
51
|
-
test("should generate allowlist", () => {
|
|
52
|
-
// Create IR first
|
|
53
|
-
const ir = generateIR({
|
|
54
|
-
entities: { posts: mockRawEntity },
|
|
55
|
-
subscribables: {}
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
const manifest = generateManifest(ir);
|
|
59
|
-
|
|
60
|
-
expect(manifest.version).toBe("2.0.0");
|
|
61
|
-
expect(manifest.functions).toBeDefined();
|
|
62
|
-
|
|
63
|
-
// Check allowlist
|
|
64
|
-
expect(manifest.functions["save_posts"]).toBeDefined();
|
|
65
|
-
expect(manifest.functions["get_posts"]).toBeDefined();
|
|
66
|
-
expect(manifest.functions["delete_posts"]).toBeDefined();
|
|
67
|
-
|
|
68
|
-
// Check signatures (basic check for now)
|
|
69
|
-
expect(manifest.functions["save_posts"].args).toEqual(["p_user_id", "p_data"]);
|
|
70
|
-
});
|
|
71
|
-
});
|