dzql 0.5.33 → 0.6.1

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 (142) hide show
  1. package/.env.sample +28 -0
  2. package/compose.yml +28 -0
  3. package/dist/client/index.ts +1 -0
  4. package/dist/client/stores/useMyProfileStore.ts +114 -0
  5. package/dist/client/stores/useOrgDashboardStore.ts +131 -0
  6. package/dist/client/stores/useVenueDetailStore.ts +117 -0
  7. package/dist/client/ws.ts +716 -0
  8. package/dist/db/migrations/000_core.sql +92 -0
  9. package/dist/db/migrations/20251229T212912022Z_schema.sql +3020 -0
  10. package/dist/db/migrations/20251229T212912022Z_subscribables.sql +371 -0
  11. package/dist/runtime/manifest.json +1562 -0
  12. package/docs/README.md +309 -36
  13. package/docs/feature-requests/applyPatch-bug-report.md +85 -0
  14. package/docs/feature-requests/connection-ready-profile.md +57 -0
  15. package/docs/feature-requests/hidden-bug-report.md +111 -0
  16. package/docs/feature-requests/hidden-fields-subscribables.md +34 -0
  17. package/docs/feature-requests/subscribable-param-key-bug.md +38 -0
  18. package/docs/feature-requests/todo.md +146 -0
  19. package/docs/for_ai.md +653 -0
  20. package/docs/project-setup.md +456 -0
  21. package/examples/blog.ts +50 -0
  22. package/examples/invalid.ts +18 -0
  23. package/examples/venues.js +485 -0
  24. package/package.json +23 -60
  25. package/src/cli/codegen/client.ts +99 -0
  26. package/src/cli/codegen/manifest.ts +95 -0
  27. package/src/cli/codegen/pinia.ts +174 -0
  28. package/src/cli/codegen/realtime.ts +58 -0
  29. package/src/cli/codegen/sql.ts +698 -0
  30. package/src/cli/codegen/subscribable_sql.ts +547 -0
  31. package/src/cli/codegen/subscribable_store.ts +184 -0
  32. package/src/cli/codegen/types.ts +142 -0
  33. package/src/cli/compiler/analyzer.ts +52 -0
  34. package/src/cli/compiler/graph_rules.ts +251 -0
  35. package/src/cli/compiler/ir.ts +233 -0
  36. package/src/cli/compiler/loader.ts +132 -0
  37. package/src/cli/compiler/permissions.ts +227 -0
  38. package/src/cli/index.ts +166 -0
  39. package/src/client/index.ts +1 -0
  40. package/src/client/ws.ts +286 -0
  41. package/src/runtime/auth.ts +39 -0
  42. package/src/runtime/db.ts +33 -0
  43. package/src/runtime/errors.ts +51 -0
  44. package/src/runtime/index.ts +98 -0
  45. package/src/runtime/js_functions.ts +63 -0
  46. package/src/runtime/manifest_loader.ts +29 -0
  47. package/src/runtime/namespace.ts +483 -0
  48. package/src/runtime/server.ts +87 -0
  49. package/src/runtime/ws.ts +197 -0
  50. package/src/shared/ir.ts +197 -0
  51. package/tests/client.test.ts +38 -0
  52. package/tests/codegen.test.ts +71 -0
  53. package/tests/compiler.test.ts +45 -0
  54. package/tests/graph_rules.test.ts +173 -0
  55. package/tests/integration/db.test.ts +174 -0
  56. package/tests/integration/e2e.test.ts +65 -0
  57. package/tests/integration/features.test.ts +922 -0
  58. package/tests/integration/full_stack.test.ts +262 -0
  59. package/tests/integration/setup.ts +45 -0
  60. package/tests/ir.test.ts +32 -0
  61. package/tests/namespace.test.ts +395 -0
  62. package/tests/permissions.test.ts +55 -0
  63. package/tests/pinia.test.ts +48 -0
  64. package/tests/realtime.test.ts +22 -0
  65. package/tests/runtime.test.ts +80 -0
  66. package/tests/subscribable_gen.test.ts +72 -0
  67. package/tests/subscribable_reactivity.test.ts +258 -0
  68. package/tests/venues_gen.test.ts +25 -0
  69. package/tsconfig.json +20 -0
  70. package/tsconfig.tsbuildinfo +1 -0
  71. package/README.md +0 -90
  72. package/bin/cli.js +0 -727
  73. package/docs/compiler/ADVANCED_FILTERS.md +0 -183
  74. package/docs/compiler/CODING_STANDARDS.md +0 -415
  75. package/docs/compiler/COMPARISON.md +0 -673
  76. package/docs/compiler/QUICKSTART.md +0 -326
  77. package/docs/compiler/README.md +0 -134
  78. package/docs/examples/README.md +0 -38
  79. package/docs/examples/blog.sql +0 -160
  80. package/docs/examples/venue-detail-simple.sql +0 -8
  81. package/docs/examples/venue-detail-subscribable.sql +0 -45
  82. package/docs/for-ai/claude-guide.md +0 -1210
  83. package/docs/getting-started/quickstart.md +0 -125
  84. package/docs/getting-started/subscriptions-quick-start.md +0 -203
  85. package/docs/getting-started/tutorial.md +0 -1104
  86. package/docs/guides/atomic-updates.md +0 -299
  87. package/docs/guides/client-stores.md +0 -730
  88. package/docs/guides/composite-primary-keys.md +0 -158
  89. package/docs/guides/custom-functions.md +0 -362
  90. package/docs/guides/drop-semantics.md +0 -554
  91. package/docs/guides/field-defaults.md +0 -240
  92. package/docs/guides/interpreter-vs-compiler.md +0 -237
  93. package/docs/guides/many-to-many.md +0 -929
  94. package/docs/guides/subscriptions.md +0 -537
  95. package/docs/reference/api.md +0 -1373
  96. package/docs/reference/client.md +0 -224
  97. package/src/client/stores/index.js +0 -8
  98. package/src/client/stores/useAppStore.js +0 -285
  99. package/src/client/stores/useWsStore.js +0 -289
  100. package/src/client/ws.js +0 -762
  101. package/src/compiler/cli/compile-example.js +0 -33
  102. package/src/compiler/cli/compile-subscribable.js +0 -43
  103. package/src/compiler/cli/debug-compile.js +0 -44
  104. package/src/compiler/cli/debug-parse.js +0 -26
  105. package/src/compiler/cli/debug-path-parser.js +0 -18
  106. package/src/compiler/cli/debug-subscribable-parser.js +0 -21
  107. package/src/compiler/cli/index.js +0 -174
  108. package/src/compiler/codegen/auth-codegen.js +0 -153
  109. package/src/compiler/codegen/drop-semantics-codegen.js +0 -553
  110. package/src/compiler/codegen/graph-rules-codegen.js +0 -450
  111. package/src/compiler/codegen/notification-codegen.js +0 -232
  112. package/src/compiler/codegen/operation-codegen.js +0 -1382
  113. package/src/compiler/codegen/permission-codegen.js +0 -318
  114. package/src/compiler/codegen/subscribable-codegen.js +0 -827
  115. package/src/compiler/compiler.js +0 -371
  116. package/src/compiler/index.js +0 -11
  117. package/src/compiler/parser/entity-parser.js +0 -440
  118. package/src/compiler/parser/path-parser.js +0 -290
  119. package/src/compiler/parser/subscribable-parser.js +0 -244
  120. package/src/database/dzql-core.sql +0 -161
  121. package/src/database/migrations/001_schema.sql +0 -60
  122. package/src/database/migrations/002_functions.sql +0 -890
  123. package/src/database/migrations/003_operations.sql +0 -1135
  124. package/src/database/migrations/004_search.sql +0 -581
  125. package/src/database/migrations/005_entities.sql +0 -730
  126. package/src/database/migrations/006_auth.sql +0 -94
  127. package/src/database/migrations/007_events.sql +0 -133
  128. package/src/database/migrations/008_hello.sql +0 -18
  129. package/src/database/migrations/008a_meta.sql +0 -172
  130. package/src/database/migrations/009_subscriptions.sql +0 -240
  131. package/src/database/migrations/010_atomic_updates.sql +0 -157
  132. package/src/database/migrations/010_fix_m2m_events.sql +0 -94
  133. package/src/index.js +0 -40
  134. package/src/server/api.js +0 -9
  135. package/src/server/db.js +0 -442
  136. package/src/server/index.js +0 -317
  137. package/src/server/logger.js +0 -259
  138. package/src/server/mcp.js +0 -594
  139. package/src/server/meta-route.js +0 -251
  140. package/src/server/namespace.js +0 -292
  141. package/src/server/subscriptions.js +0 -351
  142. package/src/server/ws.js +0 -573
package/bin/cli.js DELETED
@@ -1,727 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from 'fs';
4
- import { resolve, join } from 'path';
5
- import { DZQLCompiler } from '../src/compiler/compiler.js';
6
- import postgres from 'postgres';
7
-
8
- const command = process.argv[2];
9
- const args = process.argv.slice(3);
10
-
11
- switch (command) {
12
- case 'create':
13
- console.log('🚧 Create command coming soon');
14
- console.log(`Would create project: ${args[0]}`);
15
- break;
16
- case 'dev':
17
- console.log('🚧 Dev command coming soon');
18
- break;
19
- case 'db:init':
20
- await runDbInit(args);
21
- break;
22
- case 'compile':
23
- await runCompile(args);
24
- break;
25
- case 'migrate:new':
26
- case 'migrate:init':
27
- await runMigrateNew(args);
28
- break;
29
- case 'migrate:up':
30
- await runMigrateUp(args);
31
- break;
32
- case 'migrate:down':
33
- await runMigrateDown(args);
34
- break;
35
- case 'migrate:status':
36
- await runMigrateStatus(args);
37
- break;
38
- case '--version':
39
- case '-v':
40
- const pkg = await import('../package.json', { assert: { type: 'json' } });
41
- console.log(pkg.default.version);
42
- break;
43
- default:
44
- console.log(`
45
- DZQL CLI
46
-
47
- Quick Start:
48
- 1. dzql db:init Initialize database with DZQL core (~70 lines SQL)
49
- 2. dzql compile app.sql Compile your entities to PostgreSQL functions
50
- 3. psql < compiled/*.sql Apply the compiled SQL to your database
51
-
52
- Commands:
53
- dzql db:init Initialize database with DZQL core schema
54
- dzql compile <input> Compile entity definitions to SQL functions
55
-
56
- dzql migrate:new <name> Create a new migration file
57
- dzql migrate:up Apply pending migrations
58
- dzql migrate:status Show migration status
59
-
60
- dzql --version Show version
61
-
62
- Examples:
63
- dzql db:init
64
- dzql compile entities/blog.sql -o init_db/
65
- `);
66
- }
67
-
68
- async function runCompile(args) {
69
- const options = {
70
- output: './compiled',
71
- verbose: false
72
- };
73
-
74
- // Parse args
75
- let inputFile = null;
76
- for (let i = 0; i < args.length; i++) {
77
- const arg = args[i];
78
-
79
- if (arg === '-o' || arg === '--output') {
80
- options.output = args[++i];
81
- } else if (arg === '-v' || arg === '--verbose') {
82
- options.verbose = true;
83
- } else if (arg === '-h' || arg === '--help') {
84
- console.log(`
85
- DZQL Compiler - Transform entity definitions into PostgreSQL functions
86
-
87
- Usage:
88
- dzql compile <input-file> [options]
89
-
90
- Options:
91
- -o, --output <dir> Output directory (default: ./compiled)
92
- -v, --verbose Verbose output
93
- -h, --help Show this help message
94
-
95
- Examples:
96
- dzql compile entities/venues.sql
97
- dzql compile database/init_db/009_venues_domain.sql -o compiled/
98
- `);
99
- return;
100
- } else if (!inputFile) {
101
- inputFile = arg;
102
- }
103
- }
104
-
105
- if (!inputFile) {
106
- console.error('Error: No input file specified');
107
- console.log('Run "dzql compile --help" for usage information');
108
- process.exit(1);
109
- }
110
-
111
- if (!existsSync(inputFile)) {
112
- console.error(`Error: File not found: ${inputFile}`);
113
- process.exit(1);
114
- }
115
-
116
- try {
117
- console.log(`\n🔨 Compiling: ${inputFile}`);
118
-
119
- // Read input file
120
- const sqlContent = readFileSync(inputFile, 'utf-8');
121
-
122
- // Compile
123
- const compiler = new DZQLCompiler();
124
- const result = compiler.compileFromSQL(sqlContent);
125
-
126
- // Display results
127
- console.log(`\n📊 Compilation Summary:`);
128
- console.log(` Total entities: ${result.summary.total}`);
129
- console.log(` Successful: ${result.summary.successful}`);
130
- console.log(` Failed: ${result.summary.failed}`);
131
-
132
- if (result.errors.length > 0) {
133
- console.log(`\n❌ Errors:`);
134
- for (const error of result.errors) {
135
- console.log(` - ${error.entity}: ${error.error}`);
136
- }
137
- }
138
-
139
- // Write output files
140
- if (result.results.length > 0) {
141
- // Ensure output directory exists
142
- if (!existsSync(options.output)) {
143
- mkdirSync(options.output, { recursive: true });
144
- }
145
-
146
- console.log(`\n📝 Writing compiled files to: ${options.output}`);
147
-
148
- // Write core DZQL infrastructure
149
- const coreSQL = `-- DZQL Core Schema and Tables
150
-
151
- CREATE SCHEMA IF NOT EXISTS dzql;
152
-
153
- -- Meta information
154
- CREATE TABLE IF NOT EXISTS dzql.meta (
155
- installed_at timestamptz DEFAULT now(),
156
- version text NOT NULL
157
- );
158
-
159
- INSERT INTO dzql.meta (version) VALUES ('3.0.0') ON CONFLICT DO NOTHING;
160
-
161
- -- Entity Configuration Table
162
- CREATE TABLE IF NOT EXISTS dzql.entities (
163
- table_name text PRIMARY KEY,
164
- label_field text NOT NULL,
165
- searchable_fields text[] NOT NULL,
166
- fk_includes jsonb DEFAULT '{}',
167
- soft_delete boolean DEFAULT false,
168
- temporal_fields jsonb DEFAULT '{}',
169
- notification_paths jsonb DEFAULT '{}',
170
- permission_paths jsonb DEFAULT '{}',
171
- graph_rules jsonb DEFAULT '{}',
172
- field_defaults jsonb DEFAULT '{}',
173
- many_to_many jsonb DEFAULT '{}'
174
- );
175
-
176
- -- Registry of callable functions
177
- CREATE TABLE IF NOT EXISTS dzql.registry (
178
- fn_regproc regproc PRIMARY KEY,
179
- description text
180
- );
181
-
182
- -- Event Audit Table for real-time notifications
183
- CREATE TABLE IF NOT EXISTS dzql.events (
184
- event_id bigserial PRIMARY KEY,
185
- table_name text NOT NULL,
186
- op text NOT NULL,
187
- pk jsonb NOT NULL,
188
- data jsonb,
189
- user_id int,
190
- notify_users int[],
191
- at timestamptz DEFAULT now()
192
- );
193
-
194
- CREATE INDEX IF NOT EXISTS dzql_events_table_pk_idx ON dzql.events (table_name, pk, at);
195
- CREATE INDEX IF NOT EXISTS dzql_events_user_idx ON dzql.events (user_id, at);
196
- CREATE INDEX IF NOT EXISTS dzql_events_event_id_idx ON dzql.events (event_id);
197
-
198
- -- Event notification trigger
199
- CREATE OR REPLACE FUNCTION dzql.notify_event()
200
- RETURNS TRIGGER LANGUAGE plpgsql AS $$
201
- BEGIN
202
- PERFORM pg_notify('dzql', jsonb_build_object(
203
- 'event_id', NEW.event_id,
204
- 'table', NEW.table_name,
205
- 'op', NEW.op,
206
- 'pk', NEW.pk,
207
- 'data', NEW.data,
208
- 'user_id', NEW.user_id,
209
- 'at', NEW.at,
210
- 'notify_users', NEW.notify_users
211
- )::text);
212
- RETURN NULL;
213
- END $$;
214
-
215
- DROP TRIGGER IF EXISTS dzql_events_notify ON dzql.events;
216
- CREATE TRIGGER dzql_events_notify
217
- AFTER INSERT ON dzql.events
218
- FOR EACH ROW EXECUTE FUNCTION dzql.notify_event();
219
- `;
220
-
221
- writeFileSync(resolve(options.output, '000_dzql_core.sql'), coreSQL, 'utf-8');
222
- console.log(` ✓ 000_dzql_core.sql`);
223
-
224
- // Extract schema SQL (everything before DZQL entity registrations)
225
- const schemaSQL = sqlContent.split(/-- DZQL Entity Registrations|select dzql\.register_entity/i)[0].trim();
226
- if (schemaSQL) {
227
- writeFileSync(resolve(options.output, '001_schema.sql'), schemaSQL + '\n', 'utf-8');
228
- console.log(` ✓ 001_schema.sql`);
229
- }
230
-
231
- // Generate auth functions (required for WebSocket server)
232
- // This is a fallback for when there's no users entity - otherwise users.sql has these
233
- const authSQL = `-- Authentication Functions (fallback)
234
- -- Required for DZQL WebSocket server
235
- -- Note: If you have a users entity, auth functions are in users.sql instead
236
-
237
- -- Enable pgcrypto extension for password hashing
238
- CREATE EXTENSION IF NOT EXISTS pgcrypto;
239
-
240
- -- Register new user
241
- -- p_options: optional JSON object with additional fields to set on the user record
242
- CREATE OR REPLACE FUNCTION register_user(p_email TEXT, p_password TEXT, p_options JSON DEFAULT NULL)
243
- RETURNS JSONB
244
- LANGUAGE plpgsql
245
- SECURITY DEFINER
246
- AS $$
247
- DECLARE
248
- v_user_id INT;
249
- v_salt TEXT;
250
- v_hash TEXT;
251
- v_insert_data JSONB;
252
- BEGIN
253
- -- Generate salt and hash password
254
- v_salt := gen_salt('bf', 10);
255
- v_hash := crypt(p_password, v_salt);
256
-
257
- -- Build insert data: options fields + email + password_hash
258
- -- Cast p_options to JSONB for internal operations (JSON type is for API boundary convenience)
259
- v_insert_data := jsonb_build_object('email', p_email, 'password_hash', v_hash);
260
- IF p_options IS NOT NULL THEN
261
- v_insert_data := (p_options::jsonb - 'id' - 'email' - 'password_hash' - 'password') || v_insert_data;
262
- END IF;
263
-
264
- -- Dynamic INSERT from JSONB
265
- EXECUTE (
266
- SELECT format(
267
- 'INSERT INTO users (%s) VALUES (%s) RETURNING id',
268
- string_agg(quote_ident(key), ', '),
269
- string_agg(quote_nullable(value), ', ')
270
- )
271
- FROM jsonb_each_text(v_insert_data) kv(key, value)
272
- ) INTO v_user_id;
273
-
274
- RETURN _profile(v_user_id);
275
- EXCEPTION
276
- WHEN unique_violation THEN
277
- RAISE EXCEPTION 'Email already exists' USING errcode = '23505';
278
- END $$;
279
-
280
- -- Login user
281
- CREATE OR REPLACE FUNCTION login_user(p_email TEXT, p_password TEXT)
282
- RETURNS JSONB
283
- LANGUAGE plpgsql
284
- SECURITY DEFINER
285
- AS $$
286
- DECLARE
287
- v_user_record RECORD;
288
- BEGIN
289
- SELECT id, email, password_hash
290
- INTO v_user_record
291
- FROM users
292
- WHERE email = p_email;
293
-
294
- IF NOT FOUND THEN
295
- RAISE EXCEPTION 'Invalid credentials' USING errcode = '28000';
296
- END IF;
297
-
298
- IF NOT (v_user_record.password_hash = crypt(p_password, v_user_record.password_hash)) THEN
299
- RAISE EXCEPTION 'Invalid credentials' USING errcode = '28000';
300
- END IF;
301
-
302
- RETURN _profile(v_user_record.id);
303
- END $$;
304
-
305
- -- Get user profile (private function, called after login/register)
306
- -- Returns all columns except sensitive fields
307
- CREATE OR REPLACE FUNCTION _profile(p_user_id INT)
308
- RETURNS JSONB
309
- LANGUAGE sql
310
- SECURITY DEFINER
311
- AS $$
312
- SELECT jsonb_build_object('user_id', u.id) || (to_jsonb(u.*) - 'id' - 'password_hash' - 'password' - 'secret' - 'token')
313
- FROM users u
314
- WHERE id = p_user_id;
315
- $$;
316
- `;
317
-
318
- // Only generate 002_auth.sql if there's no users entity (which has its own auth functions)
319
- const hasUsersEntity = result.results.some(r => r.tableName === 'users');
320
- if (!hasUsersEntity) {
321
- writeFileSync(resolve(options.output, '002_auth.sql'), authSQL, 'utf-8');
322
- console.log(` ✓ 002_auth.sql`);
323
- }
324
-
325
- const checksums = {};
326
-
327
- for (const compiledResult of result.results) {
328
- const outputFile = resolve(options.output, `${compiledResult.tableName}.sql`);
329
-
330
- // Write SQL file
331
- writeFileSync(outputFile, compiledResult.sql, 'utf-8');
332
-
333
- // Store checksum
334
- checksums[compiledResult.tableName] = {
335
- checksum: compiledResult.checksum,
336
- generatedAt: compiledResult.generatedAt,
337
- compilationTime: compiledResult.compilationTime
338
- };
339
-
340
- console.log(` ✓ ${compiledResult.tableName}.sql (${compiledResult.checksum.substring(0, 8)}...)`);
341
- }
342
-
343
- // Write checksums file
344
- const checksumsFile = resolve(options.output, 'checksums.json');
345
- writeFileSync(checksumsFile, JSON.stringify(checksums, null, 2), 'utf-8');
346
-
347
- console.log(` ✓ checksums.json`);
348
-
349
- // Write drop-semantics.json (drag-and-drop manifest for canvas UI)
350
- if (result.dropSemantics) {
351
- const semanticsFile = resolve(options.output, 'drop-semantics.json');
352
- writeFileSync(semanticsFile, JSON.stringify(result.dropSemantics, null, 2), 'utf-8');
353
- console.log(` ✓ drop-semantics.json`);
354
- }
355
- }
356
-
357
- // Compile subscribables (if any register_subscribable calls exist)
358
- const subscribableResult = compiler.compileSubscribablesFromSQL(sqlContent);
359
-
360
- if (subscribableResult.results.length > 0) {
361
- console.log(`\n📊 Subscribable Compilation:`);
362
- console.log(` Total subscribables: ${subscribableResult.summary.total}`);
363
- console.log(` Successful: ${subscribableResult.summary.successful}`);
364
- console.log(` Failed: ${subscribableResult.summary.failed}`);
365
-
366
- if (subscribableResult.errors.length > 0) {
367
- console.log(`\n❌ Subscribable Errors:`);
368
- for (const error of subscribableResult.errors) {
369
- console.log(` - ${error.subscribable}: ${error.error}`);
370
- }
371
- }
372
-
373
- // Ensure output directory exists
374
- if (!existsSync(options.output)) {
375
- mkdirSync(options.output, { recursive: true });
376
- }
377
-
378
- console.log(`\n📝 Writing subscribable files to: ${options.output}`);
379
-
380
- for (const subResult of subscribableResult.results) {
381
- const outputFile = resolve(options.output, `${subResult.name}.sql`);
382
- writeFileSync(outputFile, subResult.sql, 'utf-8');
383
- console.log(` ✓ ${subResult.name}.sql`);
384
- }
385
- }
386
-
387
- console.log(`\n✅ Compilation complete!\n`);
388
- } catch (error) {
389
- console.error(`\n❌ Compilation failed:`, error.message);
390
- if (options.verbose) {
391
- console.error(error.stack);
392
- }
393
- process.exit(1);
394
- }
395
- }
396
-
397
- // ============================================================================
398
- // Migration Commands
399
- // ============================================================================
400
-
401
- async function runMigrateNew(args) {
402
- const migrationName = args[0];
403
-
404
- if (!migrationName) {
405
- console.error('Error: Migration name required');
406
- console.log('Usage: dzql migrate:new <name>');
407
- console.log('Example: dzql migrate:new add_user_avatars');
408
- process.exit(1);
409
- }
410
-
411
- // Create migrations directory if it doesn't exist
412
- const migrationsDir = './migrations';
413
- if (!existsSync(migrationsDir)) {
414
- mkdirSync(migrationsDir, { recursive: true });
415
- }
416
-
417
- // Find next migration number
418
- const fs = await import('fs/promises');
419
- const files = await fs.readdir(migrationsDir).catch(() => []);
420
- const existingNumbers = files
421
- .filter(f => /^\d{3}_/.test(f))
422
- .map(f => parseInt(f.substring(0, 3)))
423
- .filter(n => !isNaN(n));
424
-
425
- const nextNumber = existingNumbers.length > 0 ? Math.max(...existingNumbers) + 1 : 1;
426
- const paddedNumber = String(nextNumber).padStart(3, '0');
427
- const fileName = `${paddedNumber}_${migrationName}.sql`;
428
- const filePath = resolve(migrationsDir, fileName);
429
-
430
- if (existsSync(filePath)) {
431
- console.error(`Error: Migration ${fileName} already exists`);
432
- process.exit(1);
433
- }
434
-
435
- // Generate migration template
436
- // Note: No BEGIN/COMMIT - postgres.js handles transactions automatically
437
- const template = `-- ============================================================================
438
- -- Migration ${paddedNumber}: ${migrationName.replace(/_/g, ' ')}
439
- -- Generated: ${new Date().toISOString().split('T')[0]}
440
- -- ============================================================================
441
-
442
- -- Part 1: Schema Changes
443
- -- ALTER TABLE example ADD COLUMN IF NOT EXISTS new_field TEXT;
444
-
445
- -- Part 2: Drop Old DZQL Functions (if updating entity)
446
- -- DROP FUNCTION IF EXISTS save_entity_name(INT, JSONB);
447
- -- DROP FUNCTION IF EXISTS get_entity_name(INT, INT, TIMESTAMPTZ);
448
- -- etc.
449
-
450
- -- Part 3: Install New Compiled Functions
451
- -- Compile your entities first: bun run compile
452
- -- Then paste the compiled function SQL here from init_db/entity_name.sql
453
-
454
- -- Part 4: Custom Functions (optional)
455
- -- CREATE OR REPLACE FUNCTION my_custom_function(
456
- -- p_user_id INT,
457
- -- p_params JSONB
458
- -- ) RETURNS JSONB AS $$
459
- -- BEGIN
460
- -- -- Your logic
461
- -- RETURN jsonb_build_object('result', 'success');
462
- -- END;
463
- -- $$ LANGUAGE plpgsql SECURITY DEFINER;
464
-
465
- -- Part 5: Register Custom Functions (optional)
466
- -- INSERT INTO dzql.registry (fn_regproc, description)
467
- -- VALUES
468
- -- ('my_custom_function'::regproc, 'Description of function')
469
- -- ON CONFLICT DO NOTHING;
470
-
471
- -- ============================================================================
472
- -- Rollback (for migrate:down support)
473
- -- ============================================================================
474
- -- To support rollback, add reverse operations in comments:
475
- --
476
- -- ROLLBACK INSTRUCTIONS:
477
- -- 1. Drop new functions
478
- -- 2. Restore old functions
479
- -- 3. Remove columns (if safe)
480
- -- 4. Drop tables (if safe)
481
- -- ============================================================================
482
- `;
483
-
484
- writeFileSync(filePath, template, 'utf-8');
485
-
486
- console.log(`\n✅ Created migration: ${fileName}`);
487
- console.log(`📝 Edit: ${filePath}`);
488
- console.log(`\n💡 Next steps:`);
489
- console.log(` 1. Update your entity definitions (entities/*.sql)`);
490
- console.log(` 2. Run: bun run compile (generates updated functions in init_db/)`);
491
- console.log(` 3. Copy compiled functions into migration file`);
492
- console.log(` 4. Test migration: psql $DATABASE_URL -f ${filePath}`);
493
- console.log(` 5. Apply to production: dzql migrate:up\n`);
494
- }
495
-
496
- async function runMigrateUp(args) {
497
- const databaseUrl = process.env.DATABASE_URL;
498
-
499
- if (!databaseUrl) {
500
- console.error('Error: DATABASE_URL environment variable not set');
501
- console.log('Set it to your PostgreSQL connection string:');
502
- console.log(' export DATABASE_URL="postgresql://user:pass@localhost:5432/dbname"');
503
- process.exit(1);
504
- }
505
-
506
- const migrationsDir = './migrations';
507
- if (!existsSync(migrationsDir)) {
508
- console.error(`Error: Migrations directory not found: ${migrationsDir}`);
509
- console.log('Create it with: dzql migrate:new <name>');
510
- process.exit(1);
511
- }
512
-
513
- const sql = postgres(databaseUrl);
514
-
515
- try {
516
- console.log('🔌 Connected to database');
517
-
518
- // 1. Create migrations table
519
- await sql`
520
- CREATE TABLE IF NOT EXISTS dzql.migrations (
521
- id SERIAL PRIMARY KEY,
522
- name TEXT NOT NULL UNIQUE,
523
- applied_at TIMESTAMPTZ DEFAULT NOW()
524
- );
525
- `;
526
-
527
- // 2. Get applied migrations
528
- const appliedRows = await sql`SELECT name, applied_at FROM dzql.migrations ORDER BY applied_at`;
529
- const appliedMigrations = new Set(appliedRows.map(row => row.name));
530
-
531
- if (appliedRows.length > 0) {
532
- console.log('\n📋 Already applied migrations:');
533
- appliedRows.forEach(row => {
534
- console.log(` ✓ ${row.name} (${new Date(row.applied_at).toISOString()})`);
535
- });
536
- } else {
537
- console.log('\n📋 No migrations applied yet.');
538
- }
539
-
540
- // 3. Read migration files
541
- console.log('📂 Reading migrations from:', migrationsDir);
542
-
543
- const files = readdirSync(migrationsDir)
544
- .filter(f => f.endsWith('.sql'))
545
- .sort(); // Alphabetical order (001, 002, 003...)
546
-
547
- console.log(`\n📁 Found ${files.length} migration file(s)\n`);
548
-
549
- // 4. Apply new migrations
550
- let appliedCount = 0;
551
- for (const file of files) {
552
- if (appliedMigrations.has(file)) {
553
- console.log(` ⏭ Skipping ${file} (already applied)`);
554
- continue;
555
- }
556
-
557
- console.log(`\n🚀 Applying migration: ${file}`);
558
- const content = readFileSync(join(migrationsDir, file), 'utf-8');
559
-
560
- try {
561
- // Execute migration (it should have its own BEGIN/COMMIT)
562
- await sql.unsafe(content);
563
-
564
- // Record migration
565
- await sql`INSERT INTO dzql.migrations (name) VALUES (${file})`;
566
-
567
- console.log(`✅ Applied: ${file}`);
568
- appliedCount++;
569
- } catch (err) {
570
- console.error(`\n❌ Failed to apply ${file}:`);
571
- console.error(err.message);
572
- console.error('\n💡 Check your migration file for errors.');
573
- console.error(' If migration has BEGIN/COMMIT, it should have rolled back.');
574
- process.exit(1);
575
- }
576
- }
577
-
578
- if (appliedCount === 0) {
579
- console.log('\n✨ No new migrations to apply. Database is up to date.');
580
- } else {
581
- console.log(`\n✨ Successfully applied ${appliedCount} migration(s).`);
582
- }
583
- } catch (err) {
584
- console.error('❌ Migration failed:', err.message);
585
- process.exit(1);
586
- } finally {
587
- await sql.end();
588
- }
589
- }
590
-
591
- async function runMigrateDown(args) {
592
- console.log('\n🚧 Migration:down command - Coming soon!');
593
- console.log('\nThis command will:');
594
- console.log(' 1. Find last applied migration');
595
- console.log(' 2. Parse rollback instructions');
596
- console.log(' 3. Execute rollback');
597
- console.log(' 4. Remove from dzql.migrations table\n');
598
- }
599
-
600
- async function runMigrateStatus(args) {
601
- const databaseUrl = process.env.DATABASE_URL;
602
-
603
- if (!databaseUrl) {
604
- console.error('Error: DATABASE_URL environment variable not set');
605
- process.exit(1);
606
- }
607
-
608
- const migrationsDir = './migrations';
609
- if (!existsSync(migrationsDir)) {
610
- console.log('📂 No migrations directory found');
611
- return;
612
- }
613
-
614
- const sql = postgres(databaseUrl);
615
-
616
- try {
617
- console.log('🔌 Connected to database\n');
618
-
619
- // Create migrations table if it doesn't exist
620
- await sql`
621
- CREATE TABLE IF NOT EXISTS dzql.migrations (
622
- id SERIAL PRIMARY KEY,
623
- name TEXT NOT NULL UNIQUE,
624
- applied_at TIMESTAMPTZ DEFAULT NOW()
625
- );
626
- `;
627
-
628
- // Get applied migrations
629
- const appliedRows = await sql`SELECT name, applied_at FROM dzql.migrations ORDER BY applied_at`;
630
- const appliedMigrations = new Set(appliedRows.map(row => row.name));
631
-
632
- // Get all migration files
633
- const files = readdirSync(migrationsDir)
634
- .filter(f => f.endsWith('.sql'))
635
- .sort();
636
-
637
- console.log('📊 Migration Status\n');
638
- console.log(`Total migrations: ${files.length}`);
639
- console.log(`Applied: ${appliedRows.length}`);
640
- console.log(`Pending: ${files.length - appliedRows.length}\n`);
641
-
642
- if (appliedRows.length > 0) {
643
- console.log('✅ Applied Migrations:');
644
- appliedRows.forEach(row => {
645
- console.log(` ${row.name} - ${new Date(row.applied_at).toLocaleString()}`);
646
- });
647
- }
648
-
649
- const pendingFiles = files.filter(f => !appliedMigrations.has(f));
650
- if (pendingFiles.length > 0) {
651
- console.log('\n⏳ Pending Migrations:');
652
- pendingFiles.forEach(file => {
653
- console.log(` ${file}`);
654
- });
655
- console.log('\nRun "dzql migrate:up" to apply pending migrations.');
656
- } else {
657
- console.log('\n✨ Database is up to date.');
658
- }
659
-
660
- console.log();
661
- } catch (err) {
662
- console.error('❌ Failed to get migration status:', err.message);
663
- process.exit(1);
664
- } finally {
665
- await sql.end();
666
- }
667
- }
668
-
669
- // ============================================================================
670
- // Database Initialization
671
- // ============================================================================
672
-
673
- async function runDbInit(args) {
674
- const databaseUrl = process.env.DATABASE_URL;
675
-
676
- if (!databaseUrl) {
677
- console.error('Error: DATABASE_URL environment variable not set');
678
- console.log('Set it to your PostgreSQL connection string:');
679
- console.log(' export DATABASE_URL="postgresql://user:pass@localhost:5432/dbname"');
680
- process.exit(1);
681
- }
682
-
683
- console.log('\n🚀 DZQL Database Initialization\n');
684
-
685
- const sql = postgres(databaseUrl);
686
-
687
- try {
688
- console.log('🔌 Connected to database');
689
-
690
- // Read the core SQL file
691
- const coreSQL = readFileSync(
692
- new URL('../src/database/dzql-core.sql', import.meta.url),
693
- 'utf-8'
694
- );
695
-
696
- console.log('📦 Applying DZQL core schema...');
697
- await sql.unsafe(coreSQL);
698
-
699
- // Check version
700
- const version = await sql`SELECT version FROM dzql.meta ORDER BY installed_at DESC LIMIT 1`;
701
- console.log(`✅ DZQL core initialized (v${version[0]?.version || 'unknown'})`);
702
-
703
- console.log(`
704
- Next steps:
705
- 1. Create your entity definitions (schema + DZQL registrations)
706
- 2. Compile: dzql compile entities.sql -o compiled/
707
- 3. Apply: psql $DATABASE_URL -f compiled/*.sql
708
-
709
- Example entity file (entities.sql):
710
- -- Schema
711
- CREATE TABLE users (
712
- id SERIAL PRIMARY KEY,
713
- email TEXT UNIQUE NOT NULL,
714
- name TEXT
715
- );
716
-
717
- -- DZQL Registration
718
- SELECT dzql.register_entity('users', 'name', ARRAY['name', 'email']);
719
- `);
720
-
721
- } catch (err) {
722
- console.error('❌ Initialization failed:', err.message);
723
- process.exit(1);
724
- } finally {
725
- await sql.end();
726
- }
727
- }