dzql 0.5.32 → 0.6.0

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 (150) 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 +293 -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 +641 -0
  20. package/docs/project-setup.md +432 -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 +164 -0
  39. package/src/client/index.ts +1 -0
  40. package/src/client/ws.ts +286 -0
  41. package/src/create/.env.example +8 -0
  42. package/src/create/README.md +101 -0
  43. package/src/create/compose.yml +14 -0
  44. package/src/create/domain.ts +153 -0
  45. package/src/create/package.json +24 -0
  46. package/src/create/server.ts +18 -0
  47. package/src/create/setup.sh +11 -0
  48. package/src/create/tsconfig.json +15 -0
  49. package/src/runtime/auth.ts +39 -0
  50. package/src/runtime/db.ts +33 -0
  51. package/src/runtime/errors.ts +51 -0
  52. package/src/runtime/index.ts +98 -0
  53. package/src/runtime/js_functions.ts +63 -0
  54. package/src/runtime/manifest_loader.ts +29 -0
  55. package/src/runtime/namespace.ts +483 -0
  56. package/src/runtime/server.ts +87 -0
  57. package/src/runtime/ws.ts +197 -0
  58. package/src/shared/ir.ts +197 -0
  59. package/tests/client.test.ts +38 -0
  60. package/tests/codegen.test.ts +71 -0
  61. package/tests/compiler.test.ts +45 -0
  62. package/tests/graph_rules.test.ts +173 -0
  63. package/tests/integration/db.test.ts +174 -0
  64. package/tests/integration/e2e.test.ts +65 -0
  65. package/tests/integration/features.test.ts +922 -0
  66. package/tests/integration/full_stack.test.ts +262 -0
  67. package/tests/integration/setup.ts +45 -0
  68. package/tests/ir.test.ts +32 -0
  69. package/tests/namespace.test.ts +395 -0
  70. package/tests/permissions.test.ts +55 -0
  71. package/tests/pinia.test.ts +48 -0
  72. package/tests/realtime.test.ts +22 -0
  73. package/tests/runtime.test.ts +80 -0
  74. package/tests/subscribable_gen.test.ts +72 -0
  75. package/tests/subscribable_reactivity.test.ts +258 -0
  76. package/tests/venues_gen.test.ts +25 -0
  77. package/tsconfig.json +20 -0
  78. package/tsconfig.tsbuildinfo +1 -0
  79. package/README.md +0 -90
  80. package/bin/cli.js +0 -727
  81. package/docs/compiler/ADVANCED_FILTERS.md +0 -183
  82. package/docs/compiler/CODING_STANDARDS.md +0 -415
  83. package/docs/compiler/COMPARISON.md +0 -673
  84. package/docs/compiler/QUICKSTART.md +0 -326
  85. package/docs/compiler/README.md +0 -134
  86. package/docs/examples/README.md +0 -38
  87. package/docs/examples/blog.sql +0 -160
  88. package/docs/examples/venue-detail-simple.sql +0 -8
  89. package/docs/examples/venue-detail-subscribable.sql +0 -45
  90. package/docs/for-ai/claude-guide.md +0 -1210
  91. package/docs/getting-started/quickstart.md +0 -125
  92. package/docs/getting-started/subscriptions-quick-start.md +0 -203
  93. package/docs/getting-started/tutorial.md +0 -1104
  94. package/docs/guides/atomic-updates.md +0 -299
  95. package/docs/guides/client-stores.md +0 -730
  96. package/docs/guides/composite-primary-keys.md +0 -158
  97. package/docs/guides/custom-functions.md +0 -362
  98. package/docs/guides/drop-semantics.md +0 -554
  99. package/docs/guides/field-defaults.md +0 -240
  100. package/docs/guides/interpreter-vs-compiler.md +0 -237
  101. package/docs/guides/many-to-many.md +0 -929
  102. package/docs/guides/subscriptions.md +0 -537
  103. package/docs/reference/api.md +0 -1373
  104. package/docs/reference/client.md +0 -224
  105. package/src/client/stores/index.js +0 -8
  106. package/src/client/stores/useAppStore.js +0 -285
  107. package/src/client/stores/useWsStore.js +0 -289
  108. package/src/client/ws.js +0 -762
  109. package/src/compiler/cli/compile-example.js +0 -33
  110. package/src/compiler/cli/compile-subscribable.js +0 -43
  111. package/src/compiler/cli/debug-compile.js +0 -44
  112. package/src/compiler/cli/debug-parse.js +0 -26
  113. package/src/compiler/cli/debug-path-parser.js +0 -18
  114. package/src/compiler/cli/debug-subscribable-parser.js +0 -21
  115. package/src/compiler/cli/index.js +0 -174
  116. package/src/compiler/codegen/auth-codegen.js +0 -153
  117. package/src/compiler/codegen/drop-semantics-codegen.js +0 -553
  118. package/src/compiler/codegen/graph-rules-codegen.js +0 -450
  119. package/src/compiler/codegen/notification-codegen.js +0 -232
  120. package/src/compiler/codegen/operation-codegen.js +0 -1382
  121. package/src/compiler/codegen/permission-codegen.js +0 -318
  122. package/src/compiler/codegen/subscribable-codegen.js +0 -827
  123. package/src/compiler/compiler.js +0 -371
  124. package/src/compiler/index.js +0 -11
  125. package/src/compiler/parser/entity-parser.js +0 -440
  126. package/src/compiler/parser/path-parser.js +0 -290
  127. package/src/compiler/parser/subscribable-parser.js +0 -244
  128. package/src/database/dzql-core.sql +0 -161
  129. package/src/database/migrations/001_schema.sql +0 -60
  130. package/src/database/migrations/002_functions.sql +0 -890
  131. package/src/database/migrations/003_operations.sql +0 -1135
  132. package/src/database/migrations/004_search.sql +0 -581
  133. package/src/database/migrations/005_entities.sql +0 -730
  134. package/src/database/migrations/006_auth.sql +0 -94
  135. package/src/database/migrations/007_events.sql +0 -133
  136. package/src/database/migrations/008_hello.sql +0 -18
  137. package/src/database/migrations/008a_meta.sql +0 -172
  138. package/src/database/migrations/009_subscriptions.sql +0 -240
  139. package/src/database/migrations/010_atomic_updates.sql +0 -157
  140. package/src/database/migrations/010_fix_m2m_events.sql +0 -94
  141. package/src/index.js +0 -40
  142. package/src/server/api.js +0 -9
  143. package/src/server/db.js +0 -442
  144. package/src/server/index.js +0 -317
  145. package/src/server/logger.js +0 -259
  146. package/src/server/mcp.js +0 -594
  147. package/src/server/meta-route.js +0 -251
  148. package/src/server/namespace.js +0 -292
  149. package/src/server/subscriptions.js +0 -351
  150. 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
- }