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
package/src/server/db.js
DELETED
|
@@ -1,442 +0,0 @@
|
|
|
1
|
-
import postgres from "postgres";
|
|
2
|
-
import { dbLogger, notifyLogger } from "./logger.js";
|
|
3
|
-
|
|
4
|
-
// Environment configuration
|
|
5
|
-
const DATABASE_URL =
|
|
6
|
-
process.env.DATABASE_URL ||
|
|
7
|
-
"postgresql://dzql:dzql@localhost:5432/dzql";
|
|
8
|
-
|
|
9
|
-
const DB_MAX_CONNECTIONS = parseInt(process.env.DB_MAX_CONNECTIONS || "10", 10);
|
|
10
|
-
const DB_IDLE_TIMEOUT = parseInt(process.env.DB_IDLE_TIMEOUT || "20", 10);
|
|
11
|
-
const DB_CONNECT_TIMEOUT = parseInt(process.env.DB_CONNECT_TIMEOUT || "10", 10);
|
|
12
|
-
|
|
13
|
-
// SSL configuration for Heroku and other hosted databases
|
|
14
|
-
const sslConfig = process.env.DATABASE_SSL === 'true'
|
|
15
|
-
? { rejectUnauthorized: false } // Heroku uses self-signed certs
|
|
16
|
-
: undefined;
|
|
17
|
-
|
|
18
|
-
// Main PostgreSQL connection for queries
|
|
19
|
-
export const sql = postgres(DATABASE_URL, {
|
|
20
|
-
max: DB_MAX_CONNECTIONS,
|
|
21
|
-
idle_timeout: DB_IDLE_TIMEOUT,
|
|
22
|
-
connect_timeout: DB_CONNECT_TIMEOUT,
|
|
23
|
-
ssl: sslConfig,
|
|
24
|
-
// Suppress NOTICE messages in test environment
|
|
25
|
-
onnotice: process.env.NODE_ENV === 'test' ? () => {} : undefined,
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
// Separate PostgreSQL connection for NOTIFY/LISTEN
|
|
29
|
-
export const listen_sql = postgres(DATABASE_URL, {
|
|
30
|
-
max: 1,
|
|
31
|
-
idle_timeout: 0,
|
|
32
|
-
connect_timeout: DB_CONNECT_TIMEOUT,
|
|
33
|
-
ssl: sslConfig,
|
|
34
|
-
// Suppress NOTICE messages in test environment
|
|
35
|
-
onnotice: process.env.NODE_ENV === 'test' ? () => {} : undefined,
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
// Only log connection info in development
|
|
39
|
-
if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
|
|
40
|
-
dbLogger.info(`Database connected: ${DATABASE_URL.replace(/\/\/.*@/, '//***@')}`);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Cache for function parameter metadata
|
|
44
|
-
const functionParamCache = new Map();
|
|
45
|
-
|
|
46
|
-
// Cache helpers
|
|
47
|
-
export async function getCache(key, ttlHours) {
|
|
48
|
-
const result = await sql`SELECT app._get_cache(${key}, ${ttlHours}) as data`;
|
|
49
|
-
return result[0]?.data ? JSON.parse(result[0].data) : null;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export async function setCache(key, data) {
|
|
53
|
-
await sql`SELECT app._set_cache(${key}, ${JSON.stringify(data)})`;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Auth helpers
|
|
57
|
-
export async function callAuthFunction(method, email, password, options = null) {
|
|
58
|
-
if (options !== null) {
|
|
59
|
-
const result = await sql`
|
|
60
|
-
SELECT ${sql(method)}(${email}, ${password}, ${options}) as result
|
|
61
|
-
`;
|
|
62
|
-
return result[0].result;
|
|
63
|
-
}
|
|
64
|
-
const result = await sql`
|
|
65
|
-
SELECT ${sql(method)}(${email}, ${password}) as result
|
|
66
|
-
`;
|
|
67
|
-
return result[0].result;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Get function parameter metadata
|
|
71
|
-
async function getFunctionParams(functionName) {
|
|
72
|
-
if (functionParamCache.has(functionName)) {
|
|
73
|
-
return functionParamCache.get(functionName);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const result = await sql`
|
|
77
|
-
SELECT
|
|
78
|
-
p.parameter_name,
|
|
79
|
-
p.parameter_default,
|
|
80
|
-
p.data_type,
|
|
81
|
-
p.ordinal_position
|
|
82
|
-
FROM information_schema.parameters p
|
|
83
|
-
WHERE p.specific_name IN (
|
|
84
|
-
SELECT r.specific_name
|
|
85
|
-
FROM information_schema.routines r
|
|
86
|
-
WHERE r.routine_name = ${functionName}
|
|
87
|
-
AND r.routine_type = 'FUNCTION'
|
|
88
|
-
)
|
|
89
|
-
AND (p.parameter_mode = 'IN' OR p.parameter_mode IS NULL)
|
|
90
|
-
ORDER BY p.ordinal_position
|
|
91
|
-
`;
|
|
92
|
-
|
|
93
|
-
const params = result.map((row) => ({
|
|
94
|
-
name: row.parameter_name,
|
|
95
|
-
type: row.data_type,
|
|
96
|
-
position: row.ordinal_position,
|
|
97
|
-
hasDefault: row.parameter_default !== null,
|
|
98
|
-
}));
|
|
99
|
-
|
|
100
|
-
functionParamCache.set(functionName, params);
|
|
101
|
-
return params;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Generic stored function call with user_id
|
|
105
|
-
export async function callUserFunction(method, userId, params) {
|
|
106
|
-
// Validate function name format (only alphanumeric and underscore, no special chars)
|
|
107
|
-
// This prevents SQL injection via function names like "foo(); DROP TABLE users--"
|
|
108
|
-
if (!/^[a-z_][a-z0-9_]*$/i.test(method)) {
|
|
109
|
-
throw new Error(`Invalid function name: ${method}`);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const functionParams = await getFunctionParams(method);
|
|
113
|
-
|
|
114
|
-
if (functionParams.length === 0) {
|
|
115
|
-
throw new Error(`Function ${method} not found`);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Build ordered parameter array
|
|
119
|
-
const orderedParams = [];
|
|
120
|
-
|
|
121
|
-
for (const param of functionParams) {
|
|
122
|
-
if (param.position === 1) {
|
|
123
|
-
// First parameter is always user_id
|
|
124
|
-
orderedParams.push(userId);
|
|
125
|
-
} else {
|
|
126
|
-
// Strip p_ prefix from parameter name for client API matching
|
|
127
|
-
const clientParamName = param.name.startsWith("p_")
|
|
128
|
-
? param.name.substring(2)
|
|
129
|
-
: param.name;
|
|
130
|
-
|
|
131
|
-
if (params && params[clientParamName] !== undefined) {
|
|
132
|
-
// Parameter exists in the params object
|
|
133
|
-
orderedParams.push(params[clientParamName]);
|
|
134
|
-
} else if (param.hasDefault) {
|
|
135
|
-
// Parameter has a default value, skip it
|
|
136
|
-
break;
|
|
137
|
-
} else {
|
|
138
|
-
// Required parameter missing
|
|
139
|
-
throw new Error(`Missing required parameter: ${clientParamName}`);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Try table format first - works for both single and multiple results
|
|
145
|
-
const query = `SELECT * FROM ${method}(${orderedParams.map((_, i) => `$${i + 1}`).join(", ")})`;
|
|
146
|
-
const result = await sql.unsafe(query, orderedParams);
|
|
147
|
-
|
|
148
|
-
// If single row with single column, return just the value
|
|
149
|
-
if (result.length === 1 && Object.keys(result[0]).length === 1) {
|
|
150
|
-
return Object.values(result[0])[0];
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Otherwise return the full result set
|
|
154
|
-
return result;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Get user profile
|
|
158
|
-
export async function getUserProfile(userId) {
|
|
159
|
-
const result = await sql`
|
|
160
|
-
SELECT _profile(${userId}::integer) as profile
|
|
161
|
-
`;
|
|
162
|
-
return result[0].profile;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Setup NOTIFY listeners
|
|
166
|
-
export async function setupListeners(callback) {
|
|
167
|
-
try {
|
|
168
|
-
// Listen to single dzql channel for all events
|
|
169
|
-
await listen_sql.listen("dzql", (payload) => {
|
|
170
|
-
const event = JSON.parse(payload);
|
|
171
|
-
notifyLogger.debug(`Received NOTIFY event:`, event.table, event.op);
|
|
172
|
-
callback(event);
|
|
173
|
-
});
|
|
174
|
-
notifyLogger.info("NOTIFY listener established on 'dzql' channel");
|
|
175
|
-
return true;
|
|
176
|
-
} catch (error) {
|
|
177
|
-
notifyLogger.error("Failed to setup listeners:", error.message);
|
|
178
|
-
return false;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Helper to detect if args contains a composite primary key (pk object or multiple PK fields, no 'id')
|
|
183
|
-
function isCompositePK(args) {
|
|
184
|
-
// If args has a 'pk' object, it's explicitly a composite PK call
|
|
185
|
-
if (args.pk && typeof args.pk === 'object') {
|
|
186
|
-
return true;
|
|
187
|
-
}
|
|
188
|
-
// If args has no 'id' field but has other fields, assume composite PK
|
|
189
|
-
// (the compiled function will validate the actual PK fields)
|
|
190
|
-
if (args.id === undefined && Object.keys(args).length > 0) {
|
|
191
|
-
return true;
|
|
192
|
-
}
|
|
193
|
-
return false;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// DZQL Generic Operations - Try compiled functions first, fall back to generic_exec
|
|
197
|
-
export async function callDZQLOperation(operation, entity, args, userId) {
|
|
198
|
-
dbLogger.trace(`DZQL ${operation}.${entity} for user ${userId}`);
|
|
199
|
-
|
|
200
|
-
const compiledFunctionName = `${operation}_${entity}`;
|
|
201
|
-
|
|
202
|
-
try {
|
|
203
|
-
// Try compiled function first
|
|
204
|
-
// Different operations have different signatures:
|
|
205
|
-
// - search: search_entity(p_user_id, p_filters, p_search, p_sort, p_page, p_limit)
|
|
206
|
-
// - get: get_entity(p_user_id, p_id, p_on_date) OR get_entity(p_user_id, p_pk, p_on_date) for composite PKs
|
|
207
|
-
// - save: save_entity(p_user_id, p_data, p_on_date)
|
|
208
|
-
// - delete: delete_entity(p_user_id, p_id) OR delete_entity(p_user_id, p_pk) for composite PKs
|
|
209
|
-
// - lookup: lookup_entity(p_user_id, p_term, p_limit)
|
|
210
|
-
|
|
211
|
-
if (operation === 'search') {
|
|
212
|
-
// Extract search parameters from args
|
|
213
|
-
const filters = args.filters || args.p_filters || {};
|
|
214
|
-
const search = args.search || null;
|
|
215
|
-
const sort = args.sort || null;
|
|
216
|
-
const page = args.page || 1;
|
|
217
|
-
const limit = args.limit || 25;
|
|
218
|
-
|
|
219
|
-
const result = await sql.unsafe(`
|
|
220
|
-
SELECT ${compiledFunctionName}($1::int, $2::jsonb, $3::text, $4::jsonb, $5::int, $6::int) as result
|
|
221
|
-
`, [userId, filters, search, sort, page, limit]);
|
|
222
|
-
return result[0].result;
|
|
223
|
-
} else if (operation === 'get') {
|
|
224
|
-
// Support composite primary keys: pass pk object or full args as JSONB
|
|
225
|
-
if (isCompositePK(args)) {
|
|
226
|
-
const pk = args.pk || args; // Use explicit pk object or treat all args as PK fields
|
|
227
|
-
const result = await sql.unsafe(`
|
|
228
|
-
SELECT ${compiledFunctionName}($1::int, $2::jsonb, NULL) as result
|
|
229
|
-
`, [userId, pk]);
|
|
230
|
-
return result[0].result;
|
|
231
|
-
}
|
|
232
|
-
// Simple PK (id)
|
|
233
|
-
const result = await sql.unsafe(`
|
|
234
|
-
SELECT ${compiledFunctionName}($1::int, $2::int, NULL) as result
|
|
235
|
-
`, [userId, args.id]);
|
|
236
|
-
return result[0].result;
|
|
237
|
-
} else if (operation === 'save') {
|
|
238
|
-
const result = await sql.unsafe(`
|
|
239
|
-
SELECT ${compiledFunctionName}($1::int, $2::jsonb) as result
|
|
240
|
-
`, [userId, args]);
|
|
241
|
-
return result[0].result;
|
|
242
|
-
} else if (operation === 'delete') {
|
|
243
|
-
// Support composite primary keys: pass pk object or full args as JSONB
|
|
244
|
-
if (isCompositePK(args)) {
|
|
245
|
-
const pk = args.pk || args; // Use explicit pk object or treat all args as PK fields
|
|
246
|
-
const result = await sql.unsafe(`
|
|
247
|
-
SELECT ${compiledFunctionName}($1::int, $2::jsonb) as result
|
|
248
|
-
`, [userId, pk]);
|
|
249
|
-
return result[0].result;
|
|
250
|
-
}
|
|
251
|
-
// Simple PK (id)
|
|
252
|
-
const result = await sql.unsafe(`
|
|
253
|
-
SELECT ${compiledFunctionName}($1::int, $2::int) as result
|
|
254
|
-
`, [userId, args.id]);
|
|
255
|
-
return result[0].result;
|
|
256
|
-
} else if (operation === 'lookup') {
|
|
257
|
-
const result = await sql.unsafe(`
|
|
258
|
-
SELECT ${compiledFunctionName}($1::int, $2::text, $3::int) as result
|
|
259
|
-
`, [userId, args.term || '', args.limit || 10]);
|
|
260
|
-
return result[0].result;
|
|
261
|
-
} else {
|
|
262
|
-
throw new Error(`Unknown operation: ${operation}`);
|
|
263
|
-
}
|
|
264
|
-
} catch (error) {
|
|
265
|
-
// Only fall back if the COMPILED function itself doesn't exist
|
|
266
|
-
// Don't fall back for other "does not exist" errors (e.g., missing tables, downstream functions)
|
|
267
|
-
const isMissingCompiledFunction =
|
|
268
|
-
(error.message?.includes('does not exist') || error.code === '42883') &&
|
|
269
|
-
error.message?.includes(compiledFunctionName);
|
|
270
|
-
|
|
271
|
-
if (isMissingCompiledFunction) {
|
|
272
|
-
dbLogger.trace(`Compiled function ${compiledFunctionName} not found, trying generic_exec`);
|
|
273
|
-
const result = await sql`
|
|
274
|
-
SELECT dzql.generic_exec(${operation}, ${entity}, ${args}, ${userId}) as result
|
|
275
|
-
`;
|
|
276
|
-
return result[0].result;
|
|
277
|
-
}
|
|
278
|
-
// Re-throw other errors
|
|
279
|
-
throw error;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// DZQL nested proxy factory
|
|
284
|
-
function createEntityProxy(operation) {
|
|
285
|
-
return new Proxy(
|
|
286
|
-
{},
|
|
287
|
-
{
|
|
288
|
-
get(target, entityName) {
|
|
289
|
-
return async (args = {}, userId) => {
|
|
290
|
-
// userId is required for DZQL operations
|
|
291
|
-
if (!userId) {
|
|
292
|
-
throw new Error("userId is required for DZQL operations");
|
|
293
|
-
}
|
|
294
|
-
return callDZQLOperation(operation, entityName, args, userId);
|
|
295
|
-
};
|
|
296
|
-
},
|
|
297
|
-
},
|
|
298
|
-
);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* DZQL database API
|
|
303
|
-
*
|
|
304
|
-
* Provides server-side access to DZQL operations and custom PostgreSQL functions.
|
|
305
|
-
* All operations require explicit userId parameter (unlike client API which auto-injects).
|
|
306
|
-
*
|
|
307
|
-
* @namespace db.api
|
|
308
|
-
*
|
|
309
|
-
* @property {Object} get - Get single record by primary key
|
|
310
|
-
* @property {Object} save - Create or update record (upsert)
|
|
311
|
-
* @property {Object} delete - Delete record by primary key
|
|
312
|
-
* @property {Object} lookup - Autocomplete lookup by label field
|
|
313
|
-
* @property {Object} search - Advanced search with filters, pagination, sorting
|
|
314
|
-
*
|
|
315
|
-
* @example
|
|
316
|
-
* // Get a single venue
|
|
317
|
-
* const venue = await db.api.get.venues({ id: 1 }, userId);
|
|
318
|
-
*
|
|
319
|
-
* @example
|
|
320
|
-
* // Create a new venue
|
|
321
|
-
* const venue = await db.api.save.venues({
|
|
322
|
-
* name: 'Madison Square Garden',
|
|
323
|
-
* org_id: 3,
|
|
324
|
-
* address: '4 Pennsylvania Plaza, New York'
|
|
325
|
-
* }, userId);
|
|
326
|
-
*
|
|
327
|
-
* @example
|
|
328
|
-
* // Update existing venue
|
|
329
|
-
* const updated = await db.api.save.venues({
|
|
330
|
-
* id: 1,
|
|
331
|
-
* name: 'Updated Name'
|
|
332
|
-
* }, userId);
|
|
333
|
-
*
|
|
334
|
-
* @example
|
|
335
|
-
* // Delete venue
|
|
336
|
-
* await db.api.delete.venues({ id: 1 }, userId);
|
|
337
|
-
*
|
|
338
|
-
* @example
|
|
339
|
-
* // Lookup for autocomplete
|
|
340
|
-
* const results = await db.api.lookup.venues({
|
|
341
|
-
* p_filter: 'garden' // Searches label field
|
|
342
|
-
* }, userId);
|
|
343
|
-
* // Returns: [{ value: 1, label: 'Madison Square Garden' }, ...]
|
|
344
|
-
*
|
|
345
|
-
* @example
|
|
346
|
-
* // Advanced search with filters
|
|
347
|
-
* const results = await db.api.search.venues({
|
|
348
|
-
* p_filters: {
|
|
349
|
-
* city: 'New York',
|
|
350
|
-
* capacity: { gte: 1000 },
|
|
351
|
-
* _search: 'garden' // Full-text search
|
|
352
|
-
* },
|
|
353
|
-
* p_sort: { field: 'name', order: 'asc' },
|
|
354
|
-
* p_page: 1,
|
|
355
|
-
* p_limit: 25
|
|
356
|
-
* }, userId);
|
|
357
|
-
* // Returns: { data: [...], total: 100, page: 1, limit: 25 }
|
|
358
|
-
*
|
|
359
|
-
* @example
|
|
360
|
-
* // Call custom PostgreSQL function
|
|
361
|
-
* const stats = await db.api.myCustomFunction({ param1: 'value' }, userId);
|
|
362
|
-
*
|
|
363
|
-
* @example
|
|
364
|
-
* // Call auth functions (no userId required)
|
|
365
|
-
* const user = await db.api.register_user({
|
|
366
|
-
* email: 'user@example.com',
|
|
367
|
-
* password: 'secure123'
|
|
368
|
-
* });
|
|
369
|
-
* const session = await db.api.login_user({
|
|
370
|
-
* email: 'user@example.com',
|
|
371
|
-
* password: 'secure123'
|
|
372
|
-
* });
|
|
373
|
-
*/
|
|
374
|
-
// DZQL database API proxy with custom function support
|
|
375
|
-
export const db = {
|
|
376
|
-
api: new Proxy(
|
|
377
|
-
{
|
|
378
|
-
get: createEntityProxy("get"),
|
|
379
|
-
save: createEntityProxy("save"),
|
|
380
|
-
delete: createEntityProxy("delete"),
|
|
381
|
-
lookup: createEntityProxy("lookup"),
|
|
382
|
-
search: createEntityProxy("search"),
|
|
383
|
-
exec: async (functionName, args, userId) => {
|
|
384
|
-
if (!userId) {
|
|
385
|
-
throw new Error("userId is required for function calls");
|
|
386
|
-
}
|
|
387
|
-
return callUserFunction(functionName, userId, args);
|
|
388
|
-
},
|
|
389
|
-
// Permission and path resolution utilities
|
|
390
|
-
checkPermission: async (userId, operation, entity, record) => {
|
|
391
|
-
const result = await sql`
|
|
392
|
-
SELECT dzql.check_permission(${userId}, ${operation}, ${entity}, ${JSON.stringify(record)}) as allowed
|
|
393
|
-
`;
|
|
394
|
-
return result[0].allowed;
|
|
395
|
-
},
|
|
396
|
-
resolveNotificationPath: async (tableName, record, path) => {
|
|
397
|
-
const result = await sql`
|
|
398
|
-
SELECT dzql.resolve_notification_path(${tableName}, ${JSON.stringify(record)}, ${path}) as user_ids
|
|
399
|
-
`;
|
|
400
|
-
return result[0].user_ids;
|
|
401
|
-
},
|
|
402
|
-
resolveNotificationPaths: async (tableName, record) => {
|
|
403
|
-
const result = await sql`
|
|
404
|
-
SELECT dzql.resolve_notification_paths(${tableName}, ${JSON.stringify(record)}) as user_ids
|
|
405
|
-
`;
|
|
406
|
-
return result[0].user_ids;
|
|
407
|
-
},
|
|
408
|
-
},
|
|
409
|
-
{
|
|
410
|
-
get(target, prop) {
|
|
411
|
-
// Return existing DZQL operations
|
|
412
|
-
if (target[prop]) {
|
|
413
|
-
return target[prop];
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// Handle custom functions
|
|
417
|
-
return async (userIdOrArgs, args = {}) => {
|
|
418
|
-
// Special handling for auth functions that don't require userId
|
|
419
|
-
if (prop === 'register_user' || prop === 'login_user') {
|
|
420
|
-
// For auth functions, first param is the args object
|
|
421
|
-
return callAuthFunction(prop, userIdOrArgs.email, userIdOrArgs.password, userIdOrArgs.options || null);
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
// For other functions, userId is required as first parameter
|
|
425
|
-
if (!userIdOrArgs) {
|
|
426
|
-
throw new Error(`userId is required for function ${prop}`);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
return callUserFunction(prop, userIdOrArgs, args);
|
|
430
|
-
};
|
|
431
|
-
},
|
|
432
|
-
}
|
|
433
|
-
),
|
|
434
|
-
};
|
|
435
|
-
|
|
436
|
-
// Graceful shutdown
|
|
437
|
-
export async function closeConnections() {
|
|
438
|
-
dbLogger.info("Closing database connections...");
|
|
439
|
-
await sql.end();
|
|
440
|
-
await listen_sql.end();
|
|
441
|
-
dbLogger.info("Database connections closed");
|
|
442
|
-
}
|