dzql 0.5.33 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +309 -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 +653 -0
- package/docs/project-setup.md +456 -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 +166 -0
- package/src/client/index.ts +1 -0
- package/src/client/ws.ts +286 -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,318 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Permission Code Generator
|
|
3
|
-
* Generates PostgreSQL permission check functions from path ASTs
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { PathParser } from '../parser/path-parser.js';
|
|
7
|
-
|
|
8
|
-
export class PermissionCodegen {
|
|
9
|
-
constructor(tableName, permissionPaths) {
|
|
10
|
-
this.tableName = tableName;
|
|
11
|
-
this.permissionPaths = permissionPaths;
|
|
12
|
-
this.parser = new PathParser();
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Generate all permission check functions
|
|
17
|
-
* @returns {string} SQL for permission functions
|
|
18
|
-
*/
|
|
19
|
-
generate() {
|
|
20
|
-
const functions = [];
|
|
21
|
-
|
|
22
|
-
// Always generate the 4 standard permission functions
|
|
23
|
-
const standardOperations = ['view', 'create', 'update', 'delete'];
|
|
24
|
-
|
|
25
|
-
for (const operation of standardOperations) {
|
|
26
|
-
// Clean the operation name (remove any comments or special characters)
|
|
27
|
-
const cleanOperation = this._cleanOperationName(operation);
|
|
28
|
-
|
|
29
|
-
// Get paths for this operation (checking both clean and original keys)
|
|
30
|
-
const paths = this.permissionPaths[operation]
|
|
31
|
-
|| this.permissionPaths[cleanOperation]
|
|
32
|
-
|| this._findPathsByPartialMatch(operation);
|
|
33
|
-
|
|
34
|
-
if (!paths || paths.length === 0) {
|
|
35
|
-
// Public access - always returns true
|
|
36
|
-
functions.push(this._generatePublicPermission(cleanOperation));
|
|
37
|
-
} else {
|
|
38
|
-
functions.push(this._generatePermissionFunction(cleanOperation, paths));
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return functions.join('\n\n');
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Clean operation name - remove comments, quotes, newlines
|
|
47
|
-
* @private
|
|
48
|
-
*/
|
|
49
|
-
_cleanOperationName(operation) {
|
|
50
|
-
if (!operation || typeof operation !== 'string') {
|
|
51
|
-
return 'unknown';
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Remove SQL comments (-- ... to end of line)
|
|
55
|
-
let clean = operation.replace(/--[^\n]*/g, '');
|
|
56
|
-
|
|
57
|
-
// Remove quotes
|
|
58
|
-
clean = clean.replace(/['"]/g, '');
|
|
59
|
-
|
|
60
|
-
// Remove newlines and extra whitespace
|
|
61
|
-
clean = clean.replace(/\s+/g, ' ').trim();
|
|
62
|
-
|
|
63
|
-
// Extract just the operation name (view, create, update, delete)
|
|
64
|
-
// Match common patterns
|
|
65
|
-
if (clean.includes('view')) return 'view';
|
|
66
|
-
if (clean.includes('create')) return 'create';
|
|
67
|
-
if (clean.includes('update')) return 'update';
|
|
68
|
-
if (clean.includes('delete')) return 'delete';
|
|
69
|
-
|
|
70
|
-
// If no match, return the first word
|
|
71
|
-
const firstWord = clean.split(/\s+/)[0].toLowerCase();
|
|
72
|
-
return firstWord || 'unknown';
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Find paths by partial match in permission keys
|
|
77
|
-
* Handles cases where keys might have comments embedded
|
|
78
|
-
* @private
|
|
79
|
-
*/
|
|
80
|
-
_findPathsByPartialMatch(operation) {
|
|
81
|
-
for (const [key, paths] of Object.entries(this.permissionPaths)) {
|
|
82
|
-
const cleanKey = this._cleanOperationName(key);
|
|
83
|
-
if (cleanKey === operation) {
|
|
84
|
-
return paths;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return null;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Generate a permission function for an operation
|
|
92
|
-
* @private
|
|
93
|
-
*/
|
|
94
|
-
_generatePermissionFunction(operation, paths) {
|
|
95
|
-
const functionName = `can_${operation}_${this.tableName}`;
|
|
96
|
-
const checks = [];
|
|
97
|
-
|
|
98
|
-
for (const path of paths) {
|
|
99
|
-
const ast = this.parser.parse(path);
|
|
100
|
-
const sql = this._generatePathSQL(ast);
|
|
101
|
-
if (sql) {
|
|
102
|
-
checks.push(sql);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Combine checks with OR logic
|
|
107
|
-
const checkSQL = checks.length > 0
|
|
108
|
-
? checks.join('\n OR ')
|
|
109
|
-
: 'false';
|
|
110
|
-
|
|
111
|
-
return `-- Permission check: ${operation} on ${this.tableName}
|
|
112
|
-
CREATE OR REPLACE FUNCTION can_${operation}_${this.tableName}(
|
|
113
|
-
p_user_id INT,
|
|
114
|
-
p_record JSONB
|
|
115
|
-
) RETURNS BOOLEAN AS $$
|
|
116
|
-
BEGIN
|
|
117
|
-
RETURN (
|
|
118
|
-
${checkSQL}
|
|
119
|
-
);
|
|
120
|
-
END;
|
|
121
|
-
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER;`;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Generate public permission (always true)
|
|
126
|
-
* @private
|
|
127
|
-
*/
|
|
128
|
-
_generatePublicPermission(operation) {
|
|
129
|
-
return `-- Permission check: ${operation} on ${this.tableName} (public access)
|
|
130
|
-
CREATE OR REPLACE FUNCTION can_${operation}_${this.tableName}(
|
|
131
|
-
p_user_id INT,
|
|
132
|
-
p_record JSONB
|
|
133
|
-
) RETURNS BOOLEAN AS $$
|
|
134
|
-
BEGIN
|
|
135
|
-
RETURN true; -- Public access
|
|
136
|
-
END;
|
|
137
|
-
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER;`;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Generate SQL for a path AST
|
|
142
|
-
* @private
|
|
143
|
-
*/
|
|
144
|
-
_generatePathSQL(ast) {
|
|
145
|
-
switch (ast.type) {
|
|
146
|
-
case 'empty':
|
|
147
|
-
return 'true'; // No restriction
|
|
148
|
-
|
|
149
|
-
case 'direct_field':
|
|
150
|
-
return this._generateDirectFieldCheck(ast);
|
|
151
|
-
|
|
152
|
-
case 'traversal':
|
|
153
|
-
return this._generateTraversalCheck(ast);
|
|
154
|
-
|
|
155
|
-
case 'dot_path':
|
|
156
|
-
return this._generateDotPathCheck(ast);
|
|
157
|
-
|
|
158
|
-
default:
|
|
159
|
-
console.warn('Unknown AST type:', ast.type);
|
|
160
|
-
return 'false';
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Generate direct field check: @owner_id
|
|
166
|
-
* @private
|
|
167
|
-
*/
|
|
168
|
-
_generateDirectFieldCheck(ast) {
|
|
169
|
-
return `(p_record->>'${ast.field}')::int = p_user_id`;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Generate traversal check: @org_id->acts_for[org_id=$]{active}.user_id
|
|
174
|
-
* Supports multi-hop paths like: @product_id->products.organisation_id->acts_for[organisation_id=$].user_id
|
|
175
|
-
* @private
|
|
176
|
-
*/
|
|
177
|
-
_generateTraversalCheck(ast) {
|
|
178
|
-
const steps = ast.steps;
|
|
179
|
-
|
|
180
|
-
// First step should be the source field reference
|
|
181
|
-
if (steps.length === 0 || steps[0].type !== 'field_ref') {
|
|
182
|
-
return 'false';
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const sourceField = steps[0].field;
|
|
186
|
-
|
|
187
|
-
// Collect all table_ref steps (these are the hops)
|
|
188
|
-
const tableSteps = steps.filter(s => s.type === 'table_ref');
|
|
189
|
-
|
|
190
|
-
if (tableSteps.length === 0) {
|
|
191
|
-
return 'false';
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Build the value expression that resolves through intermediate tables
|
|
195
|
-
// Start with the record's source field
|
|
196
|
-
let valueExpr = `(p_record->>'${sourceField}')::int`;
|
|
197
|
-
|
|
198
|
-
// Process intermediate hops (all but the last table_ref)
|
|
199
|
-
// Each intermediate hop needs a subquery to resolve to the next field
|
|
200
|
-
for (let i = 0; i < tableSteps.length - 1; i++) {
|
|
201
|
-
const hop = tableSteps[i];
|
|
202
|
-
const table = hop.table;
|
|
203
|
-
const targetField = hop.targetField;
|
|
204
|
-
|
|
205
|
-
if (!targetField) {
|
|
206
|
-
// If no target field specified, assume 'id' for the lookup
|
|
207
|
-
// This shouldn't normally happen in well-formed paths
|
|
208
|
-
continue;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Build subquery: (SELECT targetField FROM table WHERE id = previousValue)
|
|
212
|
-
valueExpr = `(SELECT ${targetField} FROM ${table} WHERE id = ${valueExpr})`;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// The last table_ref is where we do the EXISTS check
|
|
216
|
-
const finalStep = tableSteps[tableSteps.length - 1];
|
|
217
|
-
const targetTable = finalStep.table;
|
|
218
|
-
const targetField = finalStep.targetField;
|
|
219
|
-
const filters = finalStep.filter || [];
|
|
220
|
-
const temporal = finalStep.temporal || false;
|
|
221
|
-
|
|
222
|
-
// Build WHERE conditions for the final EXISTS query
|
|
223
|
-
const conditions = [];
|
|
224
|
-
|
|
225
|
-
// Add filter conditions
|
|
226
|
-
for (const filter of filters) {
|
|
227
|
-
if (filter.operator === '=' && filter.value.type === 'param') {
|
|
228
|
-
// field=$ means match the resolved value from the path
|
|
229
|
-
conditions.push(`${targetTable}.${filter.field} = ${valueExpr}`);
|
|
230
|
-
} else if (filter.operator === '=') {
|
|
231
|
-
const value = this._formatValue(filter.value);
|
|
232
|
-
conditions.push(`${targetTable}.${filter.field} = ${value}`);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Add temporal condition
|
|
237
|
-
if (temporal) {
|
|
238
|
-
conditions.push(`${targetTable}.valid_from <= NOW()`);
|
|
239
|
-
conditions.push(`(${targetTable}.valid_to > NOW() OR ${targetTable}.valid_to IS NULL)`);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Add user_id check (final target)
|
|
243
|
-
if (targetField) {
|
|
244
|
-
conditions.push(`${targetTable}.${targetField} = p_user_id`);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Build EXISTS query
|
|
248
|
-
const whereClause = conditions.length > 0
|
|
249
|
-
? 'WHERE ' + conditions.join('\n AND ')
|
|
250
|
-
: '';
|
|
251
|
-
|
|
252
|
-
return `EXISTS (
|
|
253
|
-
SELECT 1 FROM ${targetTable}
|
|
254
|
-
${whereClause}
|
|
255
|
-
)`;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Generate filter condition SQL
|
|
260
|
-
* @private
|
|
261
|
-
*/
|
|
262
|
-
_generateFilterCondition(condition, tableAlias) {
|
|
263
|
-
const field = `${tableAlias}.${condition.field}`;
|
|
264
|
-
const value = this._formatValue(condition.value);
|
|
265
|
-
|
|
266
|
-
switch (condition.operator) {
|
|
267
|
-
case '=':
|
|
268
|
-
if (condition.value.type === 'param') {
|
|
269
|
-
// Special case: field=$ means use the record's value
|
|
270
|
-
return `${field} = (p_record->>'${condition.field}')::int`;
|
|
271
|
-
}
|
|
272
|
-
return `${field} = ${value}`;
|
|
273
|
-
|
|
274
|
-
default:
|
|
275
|
-
return `${field} ${condition.operator} ${value}`;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Format a value for SQL
|
|
281
|
-
* @private
|
|
282
|
-
*/
|
|
283
|
-
_formatValue(value) {
|
|
284
|
-
switch (value.type) {
|
|
285
|
-
case 'literal':
|
|
286
|
-
return `'${value.value}'`;
|
|
287
|
-
case 'number':
|
|
288
|
-
return value.value;
|
|
289
|
-
case 'field':
|
|
290
|
-
return `(p_record->>'${value.value}')`;
|
|
291
|
-
case 'param':
|
|
292
|
-
return '?'; // Will be replaced by caller
|
|
293
|
-
default:
|
|
294
|
-
return 'NULL';
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Generate dot path check (less common)
|
|
300
|
-
* @private
|
|
301
|
-
*/
|
|
302
|
-
_generateDotPathCheck(ast) {
|
|
303
|
-
// For now, treat as a field reference to the last field
|
|
304
|
-
const lastField = ast.fields[ast.fields.length - 1];
|
|
305
|
-
return `(p_record->>'${lastField}')::int = p_user_id`;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Generate permission check functions for an entity
|
|
311
|
-
* @param {string} tableName - Table name
|
|
312
|
-
* @param {Object} permissionPaths - Permission paths object
|
|
313
|
-
* @returns {string} SQL for permission functions
|
|
314
|
-
*/
|
|
315
|
-
export function generatePermissionFunctions(tableName, permissionPaths) {
|
|
316
|
-
const codegen = new PermissionCodegen(tableName, permissionPaths);
|
|
317
|
-
return codegen.generate();
|
|
318
|
-
}
|