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.
- package/.env.sample +28 -0
- package/compose.yml +28 -0
- package/dist/client/index.ts +1 -0
- package/dist/client/stores/useMyProfileStore.ts +114 -0
- package/dist/client/stores/useOrgDashboardStore.ts +131 -0
- package/dist/client/stores/useVenueDetailStore.ts +117 -0
- package/dist/client/ws.ts +716 -0
- package/dist/db/migrations/000_core.sql +92 -0
- package/dist/db/migrations/20251229T212912022Z_schema.sql +3020 -0
- package/dist/db/migrations/20251229T212912022Z_subscribables.sql +371 -0
- package/dist/runtime/manifest.json +1562 -0
- package/docs/README.md +293 -36
- package/docs/feature-requests/applyPatch-bug-report.md +85 -0
- package/docs/feature-requests/connection-ready-profile.md +57 -0
- package/docs/feature-requests/hidden-bug-report.md +111 -0
- package/docs/feature-requests/hidden-fields-subscribables.md +34 -0
- package/docs/feature-requests/subscribable-param-key-bug.md +38 -0
- package/docs/feature-requests/todo.md +146 -0
- package/docs/for_ai.md +641 -0
- package/docs/project-setup.md +432 -0
- package/examples/blog.ts +50 -0
- package/examples/invalid.ts +18 -0
- package/examples/venues.js +485 -0
- package/package.json +23 -60
- package/src/cli/codegen/client.ts +99 -0
- package/src/cli/codegen/manifest.ts +95 -0
- package/src/cli/codegen/pinia.ts +174 -0
- package/src/cli/codegen/realtime.ts +58 -0
- package/src/cli/codegen/sql.ts +698 -0
- package/src/cli/codegen/subscribable_sql.ts +547 -0
- package/src/cli/codegen/subscribable_store.ts +184 -0
- package/src/cli/codegen/types.ts +142 -0
- package/src/cli/compiler/analyzer.ts +52 -0
- package/src/cli/compiler/graph_rules.ts +251 -0
- package/src/cli/compiler/ir.ts +233 -0
- package/src/cli/compiler/loader.ts +132 -0
- package/src/cli/compiler/permissions.ts +227 -0
- package/src/cli/index.ts +164 -0
- package/src/client/index.ts +1 -0
- package/src/client/ws.ts +286 -0
- package/src/create/.env.example +8 -0
- package/src/create/README.md +101 -0
- package/src/create/compose.yml +14 -0
- package/src/create/domain.ts +153 -0
- package/src/create/package.json +24 -0
- package/src/create/server.ts +18 -0
- package/src/create/setup.sh +11 -0
- package/src/create/tsconfig.json +15 -0
- package/src/runtime/auth.ts +39 -0
- package/src/runtime/db.ts +33 -0
- package/src/runtime/errors.ts +51 -0
- package/src/runtime/index.ts +98 -0
- package/src/runtime/js_functions.ts +63 -0
- package/src/runtime/manifest_loader.ts +29 -0
- package/src/runtime/namespace.ts +483 -0
- package/src/runtime/server.ts +87 -0
- package/src/runtime/ws.ts +197 -0
- package/src/shared/ir.ts +197 -0
- package/tests/client.test.ts +38 -0
- package/tests/codegen.test.ts +71 -0
- package/tests/compiler.test.ts +45 -0
- package/tests/graph_rules.test.ts +173 -0
- package/tests/integration/db.test.ts +174 -0
- package/tests/integration/e2e.test.ts +65 -0
- package/tests/integration/features.test.ts +922 -0
- package/tests/integration/full_stack.test.ts +262 -0
- package/tests/integration/setup.ts +45 -0
- package/tests/ir.test.ts +32 -0
- package/tests/namespace.test.ts +395 -0
- package/tests/permissions.test.ts +55 -0
- package/tests/pinia.test.ts +48 -0
- package/tests/realtime.test.ts +22 -0
- package/tests/runtime.test.ts +80 -0
- package/tests/subscribable_gen.test.ts +72 -0
- package/tests/subscribable_reactivity.test.ts +258 -0
- package/tests/venues_gen.test.ts +25 -0
- package/tsconfig.json +20 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/README.md +0 -90
- package/bin/cli.js +0 -727
- package/docs/compiler/ADVANCED_FILTERS.md +0 -183
- package/docs/compiler/CODING_STANDARDS.md +0 -415
- package/docs/compiler/COMPARISON.md +0 -673
- package/docs/compiler/QUICKSTART.md +0 -326
- package/docs/compiler/README.md +0 -134
- package/docs/examples/README.md +0 -38
- package/docs/examples/blog.sql +0 -160
- package/docs/examples/venue-detail-simple.sql +0 -8
- package/docs/examples/venue-detail-subscribable.sql +0 -45
- package/docs/for-ai/claude-guide.md +0 -1210
- package/docs/getting-started/quickstart.md +0 -125
- package/docs/getting-started/subscriptions-quick-start.md +0 -203
- package/docs/getting-started/tutorial.md +0 -1104
- package/docs/guides/atomic-updates.md +0 -299
- package/docs/guides/client-stores.md +0 -730
- package/docs/guides/composite-primary-keys.md +0 -158
- package/docs/guides/custom-functions.md +0 -362
- package/docs/guides/drop-semantics.md +0 -554
- package/docs/guides/field-defaults.md +0 -240
- package/docs/guides/interpreter-vs-compiler.md +0 -237
- package/docs/guides/many-to-many.md +0 -929
- package/docs/guides/subscriptions.md +0 -537
- package/docs/reference/api.md +0 -1373
- package/docs/reference/client.md +0 -224
- package/src/client/stores/index.js +0 -8
- package/src/client/stores/useAppStore.js +0 -285
- package/src/client/stores/useWsStore.js +0 -289
- package/src/client/ws.js +0 -762
- package/src/compiler/cli/compile-example.js +0 -33
- package/src/compiler/cli/compile-subscribable.js +0 -43
- package/src/compiler/cli/debug-compile.js +0 -44
- package/src/compiler/cli/debug-parse.js +0 -26
- package/src/compiler/cli/debug-path-parser.js +0 -18
- package/src/compiler/cli/debug-subscribable-parser.js +0 -21
- package/src/compiler/cli/index.js +0 -174
- package/src/compiler/codegen/auth-codegen.js +0 -153
- package/src/compiler/codegen/drop-semantics-codegen.js +0 -553
- package/src/compiler/codegen/graph-rules-codegen.js +0 -450
- package/src/compiler/codegen/notification-codegen.js +0 -232
- package/src/compiler/codegen/operation-codegen.js +0 -1382
- package/src/compiler/codegen/permission-codegen.js +0 -318
- package/src/compiler/codegen/subscribable-codegen.js +0 -827
- package/src/compiler/compiler.js +0 -371
- package/src/compiler/index.js +0 -11
- package/src/compiler/parser/entity-parser.js +0 -440
- package/src/compiler/parser/path-parser.js +0 -290
- package/src/compiler/parser/subscribable-parser.js +0 -244
- package/src/database/dzql-core.sql +0 -161
- package/src/database/migrations/001_schema.sql +0 -60
- package/src/database/migrations/002_functions.sql +0 -890
- package/src/database/migrations/003_operations.sql +0 -1135
- package/src/database/migrations/004_search.sql +0 -581
- package/src/database/migrations/005_entities.sql +0 -730
- package/src/database/migrations/006_auth.sql +0 -94
- package/src/database/migrations/007_events.sql +0 -133
- package/src/database/migrations/008_hello.sql +0 -18
- package/src/database/migrations/008a_meta.sql +0 -172
- package/src/database/migrations/009_subscriptions.sql +0 -240
- package/src/database/migrations/010_atomic_updates.sql +0 -157
- package/src/database/migrations/010_fix_m2m_events.sql +0 -94
- package/src/index.js +0 -40
- package/src/server/api.js +0 -9
- package/src/server/db.js +0 -442
- package/src/server/index.js +0 -317
- package/src/server/logger.js +0 -259
- package/src/server/mcp.js +0 -594
- package/src/server/meta-route.js +0 -251
- package/src/server/namespace.js +0 -292
- package/src/server/subscriptions.js +0 -351
- 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
|
-
}
|