dzql 0.6.3 → 0.6.6

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.
Files changed (41) hide show
  1. package/README.md +33 -0
  2. package/docs/for_ai.md +14 -18
  3. package/docs/project-setup.md +15 -14
  4. package/package.json +28 -6
  5. package/src/cli/codegen/client.ts +5 -6
  6. package/src/cli/codegen/subscribable_store.ts +5 -5
  7. package/src/runtime/ws.ts +16 -15
  8. package/.env.sample +0 -28
  9. package/compose.yml +0 -28
  10. package/dist/client/index.ts +0 -1
  11. package/dist/client/stores/useMyProfileStore.ts +0 -114
  12. package/dist/client/stores/useOrgDashboardStore.ts +0 -131
  13. package/dist/client/stores/useVenueDetailStore.ts +0 -117
  14. package/dist/client/ws.ts +0 -716
  15. package/dist/db/migrations/000_core.sql +0 -92
  16. package/dist/db/migrations/20260101T235039268Z_schema.sql +0 -3020
  17. package/dist/db/migrations/20260101T235039268Z_subscribables.sql +0 -371
  18. package/dist/runtime/manifest.json +0 -1562
  19. package/examples/blog.ts +0 -50
  20. package/examples/invalid.ts +0 -18
  21. package/examples/venues.js +0 -485
  22. package/tests/client.test.ts +0 -38
  23. package/tests/codegen.test.ts +0 -71
  24. package/tests/compiler.test.ts +0 -45
  25. package/tests/graph_rules.test.ts +0 -173
  26. package/tests/integration/db.test.ts +0 -174
  27. package/tests/integration/e2e.test.ts +0 -65
  28. package/tests/integration/features.test.ts +0 -922
  29. package/tests/integration/full_stack.test.ts +0 -262
  30. package/tests/integration/setup.ts +0 -45
  31. package/tests/ir.test.ts +0 -32
  32. package/tests/namespace.test.ts +0 -395
  33. package/tests/permissions.test.ts +0 -55
  34. package/tests/pinia.test.ts +0 -48
  35. package/tests/realtime.test.ts +0 -22
  36. package/tests/runtime.test.ts +0 -80
  37. package/tests/subscribable_gen.test.ts +0 -72
  38. package/tests/subscribable_reactivity.test.ts +0 -258
  39. package/tests/venues_gen.test.ts +0 -25
  40. package/tsconfig.json +0 -20
  41. 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
- };
@@ -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
- };
@@ -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
- };
@@ -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
- });
@@ -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
- });