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.
Files changed (142) hide show
  1. package/.env.sample +28 -0
  2. package/compose.yml +28 -0
  3. package/dist/client/index.ts +1 -0
  4. package/dist/client/stores/useMyProfileStore.ts +114 -0
  5. package/dist/client/stores/useOrgDashboardStore.ts +131 -0
  6. package/dist/client/stores/useVenueDetailStore.ts +117 -0
  7. package/dist/client/ws.ts +716 -0
  8. package/dist/db/migrations/000_core.sql +92 -0
  9. package/dist/db/migrations/20251229T212912022Z_schema.sql +3020 -0
  10. package/dist/db/migrations/20251229T212912022Z_subscribables.sql +371 -0
  11. package/dist/runtime/manifest.json +1562 -0
  12. package/docs/README.md +309 -36
  13. package/docs/feature-requests/applyPatch-bug-report.md +85 -0
  14. package/docs/feature-requests/connection-ready-profile.md +57 -0
  15. package/docs/feature-requests/hidden-bug-report.md +111 -0
  16. package/docs/feature-requests/hidden-fields-subscribables.md +34 -0
  17. package/docs/feature-requests/subscribable-param-key-bug.md +38 -0
  18. package/docs/feature-requests/todo.md +146 -0
  19. package/docs/for_ai.md +653 -0
  20. package/docs/project-setup.md +456 -0
  21. package/examples/blog.ts +50 -0
  22. package/examples/invalid.ts +18 -0
  23. package/examples/venues.js +485 -0
  24. package/package.json +23 -60
  25. package/src/cli/codegen/client.ts +99 -0
  26. package/src/cli/codegen/manifest.ts +95 -0
  27. package/src/cli/codegen/pinia.ts +174 -0
  28. package/src/cli/codegen/realtime.ts +58 -0
  29. package/src/cli/codegen/sql.ts +698 -0
  30. package/src/cli/codegen/subscribable_sql.ts +547 -0
  31. package/src/cli/codegen/subscribable_store.ts +184 -0
  32. package/src/cli/codegen/types.ts +142 -0
  33. package/src/cli/compiler/analyzer.ts +52 -0
  34. package/src/cli/compiler/graph_rules.ts +251 -0
  35. package/src/cli/compiler/ir.ts +233 -0
  36. package/src/cli/compiler/loader.ts +132 -0
  37. package/src/cli/compiler/permissions.ts +227 -0
  38. package/src/cli/index.ts +166 -0
  39. package/src/client/index.ts +1 -0
  40. package/src/client/ws.ts +286 -0
  41. package/src/runtime/auth.ts +39 -0
  42. package/src/runtime/db.ts +33 -0
  43. package/src/runtime/errors.ts +51 -0
  44. package/src/runtime/index.ts +98 -0
  45. package/src/runtime/js_functions.ts +63 -0
  46. package/src/runtime/manifest_loader.ts +29 -0
  47. package/src/runtime/namespace.ts +483 -0
  48. package/src/runtime/server.ts +87 -0
  49. package/src/runtime/ws.ts +197 -0
  50. package/src/shared/ir.ts +197 -0
  51. package/tests/client.test.ts +38 -0
  52. package/tests/codegen.test.ts +71 -0
  53. package/tests/compiler.test.ts +45 -0
  54. package/tests/graph_rules.test.ts +173 -0
  55. package/tests/integration/db.test.ts +174 -0
  56. package/tests/integration/e2e.test.ts +65 -0
  57. package/tests/integration/features.test.ts +922 -0
  58. package/tests/integration/full_stack.test.ts +262 -0
  59. package/tests/integration/setup.ts +45 -0
  60. package/tests/ir.test.ts +32 -0
  61. package/tests/namespace.test.ts +395 -0
  62. package/tests/permissions.test.ts +55 -0
  63. package/tests/pinia.test.ts +48 -0
  64. package/tests/realtime.test.ts +22 -0
  65. package/tests/runtime.test.ts +80 -0
  66. package/tests/subscribable_gen.test.ts +72 -0
  67. package/tests/subscribable_reactivity.test.ts +258 -0
  68. package/tests/venues_gen.test.ts +25 -0
  69. package/tsconfig.json +20 -0
  70. package/tsconfig.tsbuildinfo +1 -0
  71. package/README.md +0 -90
  72. package/bin/cli.js +0 -727
  73. package/docs/compiler/ADVANCED_FILTERS.md +0 -183
  74. package/docs/compiler/CODING_STANDARDS.md +0 -415
  75. package/docs/compiler/COMPARISON.md +0 -673
  76. package/docs/compiler/QUICKSTART.md +0 -326
  77. package/docs/compiler/README.md +0 -134
  78. package/docs/examples/README.md +0 -38
  79. package/docs/examples/blog.sql +0 -160
  80. package/docs/examples/venue-detail-simple.sql +0 -8
  81. package/docs/examples/venue-detail-subscribable.sql +0 -45
  82. package/docs/for-ai/claude-guide.md +0 -1210
  83. package/docs/getting-started/quickstart.md +0 -125
  84. package/docs/getting-started/subscriptions-quick-start.md +0 -203
  85. package/docs/getting-started/tutorial.md +0 -1104
  86. package/docs/guides/atomic-updates.md +0 -299
  87. package/docs/guides/client-stores.md +0 -730
  88. package/docs/guides/composite-primary-keys.md +0 -158
  89. package/docs/guides/custom-functions.md +0 -362
  90. package/docs/guides/drop-semantics.md +0 -554
  91. package/docs/guides/field-defaults.md +0 -240
  92. package/docs/guides/interpreter-vs-compiler.md +0 -237
  93. package/docs/guides/many-to-many.md +0 -929
  94. package/docs/guides/subscriptions.md +0 -537
  95. package/docs/reference/api.md +0 -1373
  96. package/docs/reference/client.md +0 -224
  97. package/src/client/stores/index.js +0 -8
  98. package/src/client/stores/useAppStore.js +0 -285
  99. package/src/client/stores/useWsStore.js +0 -289
  100. package/src/client/ws.js +0 -762
  101. package/src/compiler/cli/compile-example.js +0 -33
  102. package/src/compiler/cli/compile-subscribable.js +0 -43
  103. package/src/compiler/cli/debug-compile.js +0 -44
  104. package/src/compiler/cli/debug-parse.js +0 -26
  105. package/src/compiler/cli/debug-path-parser.js +0 -18
  106. package/src/compiler/cli/debug-subscribable-parser.js +0 -21
  107. package/src/compiler/cli/index.js +0 -174
  108. package/src/compiler/codegen/auth-codegen.js +0 -153
  109. package/src/compiler/codegen/drop-semantics-codegen.js +0 -553
  110. package/src/compiler/codegen/graph-rules-codegen.js +0 -450
  111. package/src/compiler/codegen/notification-codegen.js +0 -232
  112. package/src/compiler/codegen/operation-codegen.js +0 -1382
  113. package/src/compiler/codegen/permission-codegen.js +0 -318
  114. package/src/compiler/codegen/subscribable-codegen.js +0 -827
  115. package/src/compiler/compiler.js +0 -371
  116. package/src/compiler/index.js +0 -11
  117. package/src/compiler/parser/entity-parser.js +0 -440
  118. package/src/compiler/parser/path-parser.js +0 -290
  119. package/src/compiler/parser/subscribable-parser.js +0 -244
  120. package/src/database/dzql-core.sql +0 -161
  121. package/src/database/migrations/001_schema.sql +0 -60
  122. package/src/database/migrations/002_functions.sql +0 -890
  123. package/src/database/migrations/003_operations.sql +0 -1135
  124. package/src/database/migrations/004_search.sql +0 -581
  125. package/src/database/migrations/005_entities.sql +0 -730
  126. package/src/database/migrations/006_auth.sql +0 -94
  127. package/src/database/migrations/007_events.sql +0 -133
  128. package/src/database/migrations/008_hello.sql +0 -18
  129. package/src/database/migrations/008a_meta.sql +0 -172
  130. package/src/database/migrations/009_subscriptions.sql +0 -240
  131. package/src/database/migrations/010_atomic_updates.sql +0 -157
  132. package/src/database/migrations/010_fix_m2m_events.sql +0 -94
  133. package/src/index.js +0 -40
  134. package/src/server/api.js +0 -9
  135. package/src/server/db.js +0 -442
  136. package/src/server/index.js +0 -317
  137. package/src/server/logger.js +0 -259
  138. package/src/server/mcp.js +0 -594
  139. package/src/server/meta-route.js +0 -251
  140. package/src/server/namespace.js +0 -292
  141. package/src/server/subscriptions.js +0 -351
  142. 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
- }