dzql 0.5.33 → 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
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
|
|
3
|
-
import { readFileSync } from 'fs';
|
|
4
|
-
import { compileSubscribablesFromSQL } from '../compiler.js';
|
|
5
|
-
|
|
6
|
-
const sqlContent = readFileSync('./examples/subscribables/venue_detail_simple.sql', 'utf-8');
|
|
7
|
-
|
|
8
|
-
console.log('Compiling subscribable...\n');
|
|
9
|
-
|
|
10
|
-
try {
|
|
11
|
-
const result = compileSubscribablesFromSQL(sqlContent);
|
|
12
|
-
|
|
13
|
-
console.log('Summary:', result.summary);
|
|
14
|
-
|
|
15
|
-
if (result.errors.length > 0) {
|
|
16
|
-
console.log('\nErrors:');
|
|
17
|
-
result.errors.forEach(err => {
|
|
18
|
-
console.log(` ${err.subscribable}: ${err.error}`);
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (result.results.length > 0) {
|
|
23
|
-
const compiled = result.results[0];
|
|
24
|
-
console.log(`\n✓ Compiled '${compiled.name}' successfully!`);
|
|
25
|
-
console.log(` Checksum: ${compiled.checksum.substring(0, 16)}...`);
|
|
26
|
-
console.log(` Time: ${compiled.compilationTime}ms`);
|
|
27
|
-
console.log('\nGenerated SQL:\n');
|
|
28
|
-
console.log(compiled.sql);
|
|
29
|
-
}
|
|
30
|
-
} catch (error) {
|
|
31
|
-
console.error('Error:', error.message);
|
|
32
|
-
process.exit(1);
|
|
33
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Compile subscribable and output SQL only (for piping to psql)
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { readFileSync } from 'fs';
|
|
8
|
-
import { compileSubscribablesFromSQL } from '../compiler.js';
|
|
9
|
-
|
|
10
|
-
const args = process.argv.slice(2);
|
|
11
|
-
if (args.length === 0) {
|
|
12
|
-
console.error('Usage: compile-subscribable.js <sql-file>');
|
|
13
|
-
process.exit(1);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const sqlFile = args[0];
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
const sqlContent = readFileSync(sqlFile, 'utf-8');
|
|
20
|
-
const result = compileSubscribablesFromSQL(sqlContent);
|
|
21
|
-
|
|
22
|
-
if (result.errors.length > 0) {
|
|
23
|
-
console.error('Compilation errors:');
|
|
24
|
-
result.errors.forEach(err => {
|
|
25
|
-
console.error(` ${err.subscribable}: ${err.error}`);
|
|
26
|
-
});
|
|
27
|
-
process.exit(1);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (result.results.length === 0) {
|
|
31
|
-
console.error('No subscribables found in', sqlFile);
|
|
32
|
-
process.exit(1);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Output just the SQL
|
|
36
|
-
for (const compiled of result.results) {
|
|
37
|
-
console.log(compiled.sql);
|
|
38
|
-
console.log(''); // Blank line between subscribables
|
|
39
|
-
}
|
|
40
|
-
} catch (error) {
|
|
41
|
-
console.error('Error:', error.message);
|
|
42
|
-
process.exit(1);
|
|
43
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Test script for subscribable compilation
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { readFileSync } from 'fs';
|
|
8
|
-
import { compileSubscribablesFromSQL } from './src/compiler/compiler.js';
|
|
9
|
-
|
|
10
|
-
// Read the example subscribable
|
|
11
|
-
const sqlContent = readFileSync('./examples/subscribables/venue_detail_subscribable.sql', 'utf-8');
|
|
12
|
-
|
|
13
|
-
console.log('Compiling subscribable...\n');
|
|
14
|
-
|
|
15
|
-
try {
|
|
16
|
-
const result = compileSubscribablesFromSQL(sqlContent);
|
|
17
|
-
|
|
18
|
-
console.log('Compilation Summary:');
|
|
19
|
-
console.log(` Total: ${result.summary.total}`);
|
|
20
|
-
console.log(` Successful: ${result.summary.successful}`);
|
|
21
|
-
console.log(` Failed: ${result.summary.failed}\n`);
|
|
22
|
-
|
|
23
|
-
if (result.errors.length > 0) {
|
|
24
|
-
console.log('Errors:');
|
|
25
|
-
result.errors.forEach(err => {
|
|
26
|
-
console.log(` - ${err.subscribable}: ${err.error}`);
|
|
27
|
-
});
|
|
28
|
-
console.log('');
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (result.results.length > 0) {
|
|
32
|
-
const compiled = result.results[0];
|
|
33
|
-
console.log(`Generated SQL for '${compiled.name}':`);
|
|
34
|
-
console.log('='.repeat(80));
|
|
35
|
-
console.log(compiled.sql);
|
|
36
|
-
console.log('='.repeat(80));
|
|
37
|
-
console.log(`\nChecksum: ${compiled.checksum}`);
|
|
38
|
-
console.log(`Compilation time: ${compiled.compilationTime}ms`);
|
|
39
|
-
}
|
|
40
|
-
} catch (error) {
|
|
41
|
-
console.error('Compilation failed:', error.message);
|
|
42
|
-
console.error(error.stack);
|
|
43
|
-
process.exit(1);
|
|
44
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Test script for subscribable parsing (debug)
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { readFileSync } from 'fs';
|
|
8
|
-
import { SubscribableParser } from './src/compiler/parser/subscribable-parser.js';
|
|
9
|
-
|
|
10
|
-
// Read the example subscribable
|
|
11
|
-
const sqlContent = readFileSync('./examples/subscribables/venue_detail_subscribable.sql', 'utf-8');
|
|
12
|
-
|
|
13
|
-
console.log('Parsing subscribable...\n');
|
|
14
|
-
|
|
15
|
-
const parser = new SubscribableParser();
|
|
16
|
-
const subscribables = parser.parseAllFromSQL(sqlContent);
|
|
17
|
-
|
|
18
|
-
console.log('Found', subscribables.length, 'subscribables\n');
|
|
19
|
-
|
|
20
|
-
for (const sub of subscribables) {
|
|
21
|
-
console.log('Subscribable:', sub.name);
|
|
22
|
-
console.log('Root Entity:', sub.rootEntity);
|
|
23
|
-
console.log('Permission Paths:', JSON.stringify(sub.permissionPaths, null, 2));
|
|
24
|
-
console.log('Param Schema:', JSON.stringify(sub.paramSchema, null, 2));
|
|
25
|
-
console.log('Relations:', JSON.stringify(sub.relations, null, 2));
|
|
26
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
|
|
3
|
-
import { PathParser } from './src/compiler/parser/path-parser.js';
|
|
4
|
-
|
|
5
|
-
const parser = new PathParser();
|
|
6
|
-
|
|
7
|
-
const testPath = '@org_id->acts_for[org_id=$]{active}.user_id';
|
|
8
|
-
|
|
9
|
-
console.log('Parsing path:', testPath);
|
|
10
|
-
console.log('');
|
|
11
|
-
|
|
12
|
-
try {
|
|
13
|
-
const ast = parser.parse(testPath);
|
|
14
|
-
console.log('AST:', JSON.stringify(ast, null, 2));
|
|
15
|
-
} catch (error) {
|
|
16
|
-
console.error('Error:', error.message);
|
|
17
|
-
console.error(error.stack);
|
|
18
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
|
|
3
|
-
import { SubscribableParser } from '../parser/subscribable-parser.js';
|
|
4
|
-
import { readFileSync } from 'fs';
|
|
5
|
-
|
|
6
|
-
const sqlContent = readFileSync('./examples/subscribables/venue_detail_simple.sql', 'utf-8');
|
|
7
|
-
|
|
8
|
-
console.log('Parsing subscribable...\n');
|
|
9
|
-
|
|
10
|
-
const parser = new SubscribableParser();
|
|
11
|
-
const subscribables = parser.parseAllFromSQL(sqlContent);
|
|
12
|
-
|
|
13
|
-
console.log('Found:', subscribables.length, 'subscribables\n');
|
|
14
|
-
|
|
15
|
-
for (const sub of subscribables) {
|
|
16
|
-
console.log('Name:', sub.name);
|
|
17
|
-
console.log('Root Entity:', sub.rootEntity);
|
|
18
|
-
console.log('Permission Paths:', JSON.stringify(sub.permissionPaths, null, 2));
|
|
19
|
-
console.log('Param Schema:', JSON.stringify(sub.paramSchema, null, 2));
|
|
20
|
-
console.log('Relations:', JSON.stringify(sub.relations, null, 2));
|
|
21
|
-
}
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/**
|
|
3
|
-
* DZQL Compiler CLI
|
|
4
|
-
* Command-line interface for compiling entity definitions
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
8
|
-
import { resolve, dirname, basename } from 'path';
|
|
9
|
-
import { DZQLCompiler } from '../compiler.js';
|
|
10
|
-
|
|
11
|
-
const USAGE = `
|
|
12
|
-
DZQL Compiler - Transform entity definitions into PostgreSQL functions
|
|
13
|
-
|
|
14
|
-
Usage:
|
|
15
|
-
dzql-compile <input-file> [options]
|
|
16
|
-
|
|
17
|
-
Options:
|
|
18
|
-
-o, --output <dir> Output directory (default: ./compiled)
|
|
19
|
-
-w, --watch Watch for changes and recompile
|
|
20
|
-
-v, --verbose Verbose output
|
|
21
|
-
-h, --help Show this help message
|
|
22
|
-
|
|
23
|
-
Examples:
|
|
24
|
-
dzql-compile entities/venues.sql
|
|
25
|
-
dzql-compile database/init_db/009_venues_domain.sql -o compiled/
|
|
26
|
-
dzql-compile entities/*.sql -o dist/compiled/
|
|
27
|
-
|
|
28
|
-
Environment Variables:
|
|
29
|
-
DZQL_COMPILER_VERBOSE Enable verbose output
|
|
30
|
-
`;
|
|
31
|
-
|
|
32
|
-
class CLI {
|
|
33
|
-
constructor() {
|
|
34
|
-
this.args = process.argv.slice(2);
|
|
35
|
-
this.options = {
|
|
36
|
-
output: './compiled',
|
|
37
|
-
watch: false,
|
|
38
|
-
verbose: process.env.DZQL_COMPILER_VERBOSE === 'true'
|
|
39
|
-
};
|
|
40
|
-
this.compiler = new DZQLCompiler();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
run() {
|
|
44
|
-
this.parseArgs();
|
|
45
|
-
|
|
46
|
-
if (this.options.help || this.args.length === 0) {
|
|
47
|
-
console.log(USAGE);
|
|
48
|
-
process.exit(0);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const inputFile = this.args[0];
|
|
52
|
-
|
|
53
|
-
if (!existsSync(inputFile)) {
|
|
54
|
-
console.error(`Error: File not found: ${inputFile}`);
|
|
55
|
-
process.exit(1);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
this.compileFile(inputFile);
|
|
59
|
-
|
|
60
|
-
if (this.options.watch) {
|
|
61
|
-
this.watchFile(inputFile);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
parseArgs() {
|
|
66
|
-
for (let i = 0; i < this.args.length; i++) {
|
|
67
|
-
const arg = this.args[i];
|
|
68
|
-
|
|
69
|
-
switch (arg) {
|
|
70
|
-
case '-o':
|
|
71
|
-
case '--output':
|
|
72
|
-
this.options.output = this.args[++i];
|
|
73
|
-
break;
|
|
74
|
-
|
|
75
|
-
case '-w':
|
|
76
|
-
case '--watch':
|
|
77
|
-
this.options.watch = true;
|
|
78
|
-
break;
|
|
79
|
-
|
|
80
|
-
case '-v':
|
|
81
|
-
case '--verbose':
|
|
82
|
-
this.options.verbose = true;
|
|
83
|
-
break;
|
|
84
|
-
|
|
85
|
-
case '-h':
|
|
86
|
-
case '--help':
|
|
87
|
-
this.options.help = true;
|
|
88
|
-
break;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
compileFile(inputFile) {
|
|
94
|
-
try {
|
|
95
|
-
console.log(`\n🔨 Compiling: ${inputFile}`);
|
|
96
|
-
|
|
97
|
-
// Read input file
|
|
98
|
-
const sqlContent = readFileSync(inputFile, 'utf-8');
|
|
99
|
-
|
|
100
|
-
// Compile
|
|
101
|
-
const result = this.compiler.compileFromSQL(sqlContent);
|
|
102
|
-
|
|
103
|
-
// Display results
|
|
104
|
-
console.log(`\n📊 Compilation Summary:`);
|
|
105
|
-
console.log(` Total entities: ${result.summary.total}`);
|
|
106
|
-
console.log(` Successful: ${result.summary.successful}`);
|
|
107
|
-
console.log(` Failed: ${result.summary.failed}`);
|
|
108
|
-
|
|
109
|
-
if (result.errors.length > 0) {
|
|
110
|
-
console.log(`\n❌ Errors:`);
|
|
111
|
-
for (const error of result.errors) {
|
|
112
|
-
console.log(` - ${error.entity}: ${error.error}`);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Write output files
|
|
117
|
-
if (result.results.length > 0) {
|
|
118
|
-
this.writeOutputFiles(result.results);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
console.log(`\n✅ Compilation complete!\n`);
|
|
122
|
-
} catch (error) {
|
|
123
|
-
console.error(`\n❌ Compilation failed:`, error.message);
|
|
124
|
-
if (this.options.verbose) {
|
|
125
|
-
console.error(error.stack);
|
|
126
|
-
}
|
|
127
|
-
process.exit(1);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
writeOutputFiles(results) {
|
|
132
|
-
// Ensure output directory exists
|
|
133
|
-
if (!existsSync(this.options.output)) {
|
|
134
|
-
mkdirSync(this.options.output, { recursive: true });
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
console.log(`\n📝 Writing compiled files to: ${this.options.output}`);
|
|
138
|
-
|
|
139
|
-
const checksums = {};
|
|
140
|
-
|
|
141
|
-
for (const result of results) {
|
|
142
|
-
const outputFile = resolve(this.options.output, `${result.tableName}.sql`);
|
|
143
|
-
|
|
144
|
-
// Write SQL file
|
|
145
|
-
writeFileSync(outputFile, result.sql, 'utf-8');
|
|
146
|
-
|
|
147
|
-
// Store checksum
|
|
148
|
-
checksums[result.tableName] = {
|
|
149
|
-
checksum: result.checksum,
|
|
150
|
-
generatedAt: result.generatedAt,
|
|
151
|
-
compilationTime: result.compilationTime
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
console.log(` ✓ ${result.tableName}.sql (${result.checksum.substring(0, 8)}...)`);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Write checksums file
|
|
158
|
-
const checksumsFile = resolve(this.options.output, 'checksums.json');
|
|
159
|
-
writeFileSync(checksumsFile, JSON.stringify(checksums, null, 2), 'utf-8');
|
|
160
|
-
|
|
161
|
-
console.log(` ✓ checksums.json`);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
watchFile(inputFile) {
|
|
165
|
-
console.log(`\n👀 Watching for changes...`);
|
|
166
|
-
|
|
167
|
-
// TODO: Implement file watching
|
|
168
|
-
console.log(` (Watch mode not yet implemented)`);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Run CLI
|
|
173
|
-
const cli = new CLI();
|
|
174
|
-
cli.run();
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Auth Code Generator
|
|
3
|
-
* Generates PostgreSQL functions for user authentication
|
|
4
|
-
* Only generated when the entity is named 'users'
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export class AuthCodegen {
|
|
8
|
-
constructor(entity) {
|
|
9
|
-
this.entity = entity;
|
|
10
|
-
this.tableName = entity.tableName;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Check if this entity should have auth functions generated
|
|
15
|
-
* @returns {boolean}
|
|
16
|
-
*/
|
|
17
|
-
shouldGenerate() {
|
|
18
|
-
return this.tableName === 'users';
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Generate all auth functions
|
|
23
|
-
* @returns {string} SQL for auth functions
|
|
24
|
-
*/
|
|
25
|
-
generateAll() {
|
|
26
|
-
if (!this.shouldGenerate()) {
|
|
27
|
-
return '';
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return [
|
|
31
|
-
'-- Enable pgcrypto extension for password hashing',
|
|
32
|
-
'CREATE EXTENSION IF NOT EXISTS pgcrypto;',
|
|
33
|
-
'',
|
|
34
|
-
this._generateProfileFunction(),
|
|
35
|
-
this._generateRegisterFunction(),
|
|
36
|
-
this._generateLoginFunction()
|
|
37
|
-
].join('\n\n');
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Generate _profile function
|
|
42
|
-
* Returns all user columns except sensitive fields
|
|
43
|
-
* @private
|
|
44
|
-
*/
|
|
45
|
-
_generateProfileFunction() {
|
|
46
|
-
return `-- ============================================================================
|
|
47
|
-
-- Auth: _profile function for ${this.tableName}
|
|
48
|
-
-- Returns user record minus sensitive fields
|
|
49
|
-
-- ============================================================================
|
|
50
|
-
CREATE OR REPLACE FUNCTION _profile(p_user_id INT)
|
|
51
|
-
RETURNS JSONB
|
|
52
|
-
LANGUAGE SQL
|
|
53
|
-
SECURITY DEFINER
|
|
54
|
-
AS $$
|
|
55
|
-
SELECT jsonb_build_object('user_id', u.id) || (to_jsonb(u.*) - 'id' - 'password_hash' - 'password' - 'secret' - 'token')
|
|
56
|
-
FROM ${this.tableName} u
|
|
57
|
-
WHERE id = p_user_id;
|
|
58
|
-
$$;`;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Generate register_user function
|
|
63
|
-
* Supports optional fields via JSON parameter
|
|
64
|
-
* @private
|
|
65
|
-
*/
|
|
66
|
-
_generateRegisterFunction() {
|
|
67
|
-
return `-- ============================================================================
|
|
68
|
-
-- Auth: register_user function for ${this.tableName}
|
|
69
|
-
-- p_options: optional JSON object with additional fields to set on the user record
|
|
70
|
-
-- Example: register_user('test@example.com', 'password', '{"name": "Test User"}')
|
|
71
|
-
-- ============================================================================
|
|
72
|
-
CREATE OR REPLACE FUNCTION register_user(p_email TEXT, p_password TEXT, p_options JSON DEFAULT NULL)
|
|
73
|
-
RETURNS JSONB
|
|
74
|
-
LANGUAGE plpgsql
|
|
75
|
-
SECURITY DEFINER
|
|
76
|
-
AS $$
|
|
77
|
-
DECLARE
|
|
78
|
-
v_user_id INT;
|
|
79
|
-
v_salt TEXT;
|
|
80
|
-
v_hash TEXT;
|
|
81
|
-
v_insert_data JSONB;
|
|
82
|
-
BEGIN
|
|
83
|
-
-- Generate salt and hash password
|
|
84
|
-
v_salt := gen_salt('bf', 10);
|
|
85
|
-
v_hash := crypt(p_password, v_salt);
|
|
86
|
-
|
|
87
|
-
-- Build insert data: options fields + email + password_hash (options cannot override core fields)
|
|
88
|
-
-- Cast p_options to JSONB for internal operations (JSON type is for API boundary convenience)
|
|
89
|
-
v_insert_data := jsonb_build_object('email', p_email, 'password_hash', v_hash);
|
|
90
|
-
IF p_options IS NOT NULL THEN
|
|
91
|
-
v_insert_data := (p_options::jsonb - 'id' - 'email' - 'password_hash' - 'password') || v_insert_data;
|
|
92
|
-
END IF;
|
|
93
|
-
|
|
94
|
-
-- Dynamic INSERT from JSONB (same pattern as compiled save functions)
|
|
95
|
-
EXECUTE (
|
|
96
|
-
SELECT format(
|
|
97
|
-
'INSERT INTO ${this.tableName} (%s) VALUES (%s) RETURNING id',
|
|
98
|
-
string_agg(quote_ident(key), ', '),
|
|
99
|
-
string_agg(quote_nullable(value), ', ')
|
|
100
|
-
)
|
|
101
|
-
FROM jsonb_each_text(v_insert_data) kv(key, value)
|
|
102
|
-
) INTO v_user_id;
|
|
103
|
-
|
|
104
|
-
RETURN _profile(v_user_id);
|
|
105
|
-
EXCEPTION
|
|
106
|
-
WHEN unique_violation THEN
|
|
107
|
-
RAISE EXCEPTION 'Email already exists' USING errcode = '23505';
|
|
108
|
-
END $$;`;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Generate login_user function
|
|
113
|
-
* @private
|
|
114
|
-
*/
|
|
115
|
-
_generateLoginFunction() {
|
|
116
|
-
return `-- ============================================================================
|
|
117
|
-
-- Auth: login_user function for ${this.tableName}
|
|
118
|
-
-- ============================================================================
|
|
119
|
-
CREATE OR REPLACE FUNCTION login_user(p_email TEXT, p_password TEXT)
|
|
120
|
-
RETURNS JSONB
|
|
121
|
-
LANGUAGE plpgsql
|
|
122
|
-
SECURITY DEFINER
|
|
123
|
-
AS $$
|
|
124
|
-
DECLARE
|
|
125
|
-
v_user_record RECORD;
|
|
126
|
-
BEGIN
|
|
127
|
-
SELECT id, email, password_hash
|
|
128
|
-
INTO v_user_record
|
|
129
|
-
FROM ${this.tableName}
|
|
130
|
-
WHERE email = p_email;
|
|
131
|
-
|
|
132
|
-
IF NOT FOUND THEN
|
|
133
|
-
RAISE EXCEPTION 'Invalid credentials' USING errcode = '28000';
|
|
134
|
-
END IF;
|
|
135
|
-
|
|
136
|
-
IF NOT (v_user_record.password_hash = crypt(p_password, v_user_record.password_hash)) THEN
|
|
137
|
-
RAISE EXCEPTION 'Invalid credentials' USING errcode = '28000';
|
|
138
|
-
END IF;
|
|
139
|
-
|
|
140
|
-
RETURN _profile(v_user_record.id);
|
|
141
|
-
END $$;`;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Generate auth functions for an entity (only if it's the users table)
|
|
147
|
-
* @param {Object} entity - Entity configuration
|
|
148
|
-
* @returns {string} SQL for auth functions (empty string if not users table)
|
|
149
|
-
*/
|
|
150
|
-
export function generateAuthFunctions(entity) {
|
|
151
|
-
const codegen = new AuthCodegen(entity);
|
|
152
|
-
return codegen.generateAll();
|
|
153
|
-
}
|