dzql 0.4.8 → 0.5.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/bin/cli.js +39 -19
- package/docs/for-ai/claude-guide.md +21 -18
- package/docs/reference/api.md +74 -7
- package/docs/reference/client.md +41 -0
- package/package.json +1 -1
- package/src/client/ws.js +7 -0
- package/src/compiler/codegen/auth-codegen.js +11 -6
- package/src/database/migrations/006_auth.sql +8 -8
- package/src/server/db.js +8 -2
- package/src/server/ws.js +2 -0
package/bin/cli.js
CHANGED
|
@@ -230,33 +230,48 @@ CREATE TRIGGER dzql_events_notify
|
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
// Generate auth functions (required for WebSocket server)
|
|
233
|
-
|
|
233
|
+
// This is a fallback for when there's no users entity - otherwise users.sql has these
|
|
234
|
+
const authSQL = `-- Authentication Functions (fallback)
|
|
234
235
|
-- Required for DZQL WebSocket server
|
|
236
|
+
-- Note: If you have a users entity, auth functions are in users.sql instead
|
|
235
237
|
|
|
236
238
|
-- Enable pgcrypto extension for password hashing
|
|
237
239
|
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
|
238
240
|
|
|
239
241
|
-- Register new user
|
|
240
|
-
|
|
242
|
+
-- p_options: optional JSON object with additional fields to set on the user record
|
|
243
|
+
CREATE OR REPLACE FUNCTION register_user(p_email TEXT, p_password TEXT, p_options JSONB DEFAULT NULL)
|
|
241
244
|
RETURNS JSONB
|
|
242
245
|
LANGUAGE plpgsql
|
|
243
246
|
SECURITY DEFINER
|
|
244
247
|
AS $$
|
|
245
248
|
DECLARE
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
+
v_user_id INT;
|
|
250
|
+
v_salt TEXT;
|
|
251
|
+
v_hash TEXT;
|
|
252
|
+
v_insert_data JSONB;
|
|
249
253
|
BEGIN
|
|
250
254
|
-- Generate salt and hash password
|
|
251
|
-
|
|
252
|
-
|
|
255
|
+
v_salt := gen_salt('bf', 10);
|
|
256
|
+
v_hash := crypt(p_password, v_salt);
|
|
253
257
|
|
|
254
|
-
--
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
+
-- Build insert data: options fields + email + password_hash
|
|
259
|
+
v_insert_data := jsonb_build_object('email', p_email, 'password_hash', v_hash);
|
|
260
|
+
IF p_options IS NOT NULL THEN
|
|
261
|
+
v_insert_data := (p_options - 'id' - 'email' - 'password_hash' - 'password') || v_insert_data;
|
|
262
|
+
END IF;
|
|
258
263
|
|
|
259
|
-
|
|
264
|
+
-- Dynamic INSERT from JSONB
|
|
265
|
+
EXECUTE (
|
|
266
|
+
SELECT format(
|
|
267
|
+
'INSERT INTO users (%s) VALUES (%s) RETURNING id',
|
|
268
|
+
string_agg(quote_ident(key), ', '),
|
|
269
|
+
string_agg(quote_nullable(value), ', ')
|
|
270
|
+
)
|
|
271
|
+
FROM jsonb_each_text(v_insert_data) kv(key, value)
|
|
272
|
+
) INTO v_user_id;
|
|
273
|
+
|
|
274
|
+
RETURN _profile(v_user_id);
|
|
260
275
|
EXCEPTION
|
|
261
276
|
WHEN unique_violation THEN
|
|
262
277
|
RAISE EXCEPTION 'Email already exists' USING errcode = '23505';
|
|
@@ -269,10 +284,10 @@ LANGUAGE plpgsql
|
|
|
269
284
|
SECURITY DEFINER
|
|
270
285
|
AS $$
|
|
271
286
|
DECLARE
|
|
272
|
-
|
|
287
|
+
v_user_record RECORD;
|
|
273
288
|
BEGIN
|
|
274
|
-
SELECT id, email,
|
|
275
|
-
INTO
|
|
289
|
+
SELECT id, email, password_hash
|
|
290
|
+
INTO v_user_record
|
|
276
291
|
FROM users
|
|
277
292
|
WHERE email = p_email;
|
|
278
293
|
|
|
@@ -280,14 +295,15 @@ BEGIN
|
|
|
280
295
|
RAISE EXCEPTION 'Invalid credentials' USING errcode = '28000';
|
|
281
296
|
END IF;
|
|
282
297
|
|
|
283
|
-
IF NOT (
|
|
298
|
+
IF NOT (v_user_record.password_hash = crypt(p_password, v_user_record.password_hash)) THEN
|
|
284
299
|
RAISE EXCEPTION 'Invalid credentials' USING errcode = '28000';
|
|
285
300
|
END IF;
|
|
286
301
|
|
|
287
|
-
RETURN _profile(
|
|
302
|
+
RETURN _profile(v_user_record.id);
|
|
288
303
|
END $$;
|
|
289
304
|
|
|
290
305
|
-- Get user profile (private function, called after login/register)
|
|
306
|
+
-- Returns all columns except sensitive fields
|
|
291
307
|
CREATE OR REPLACE FUNCTION _profile(p_user_id INT)
|
|
292
308
|
RETURNS JSONB
|
|
293
309
|
LANGUAGE sql
|
|
@@ -299,8 +315,12 @@ AS $$
|
|
|
299
315
|
$$;
|
|
300
316
|
`;
|
|
301
317
|
|
|
302
|
-
|
|
303
|
-
|
|
318
|
+
// Only generate 002_auth.sql if there's no users entity (which has its own auth functions)
|
|
319
|
+
const hasUsersEntity = result.results.some(r => r.tableName === 'users');
|
|
320
|
+
if (!hasUsersEntity) {
|
|
321
|
+
writeFileSync(resolve(options.output, '002_auth.sql'), authSQL, 'utf-8');
|
|
322
|
+
console.log(` ✓ 002_auth.sql`);
|
|
323
|
+
}
|
|
304
324
|
|
|
305
325
|
const checksums = {};
|
|
306
326
|
|
|
@@ -635,7 +635,8 @@ invokej dzql.lookup organisations '{"query": "acme"}'
|
|
|
635
635
|
### Real-time Events
|
|
636
636
|
- Listen using `ws.onBroadcast((method, params) => {})`
|
|
637
637
|
- Method format: `{table}:{operation}` (e.g., "venues:update")
|
|
638
|
-
- Params include: `{table, op, pk,
|
|
638
|
+
- Params include: `{table, op, pk, data, user_id, at}`
|
|
639
|
+
- `data` contains: new state for insert/update, `null` for delete
|
|
639
640
|
- Target users via notification paths or broadcast to all
|
|
640
641
|
|
|
641
642
|
### Permissions
|
|
@@ -684,8 +685,7 @@ TABLE dzql.events {
|
|
|
684
685
|
table_name TEXT NOT NULL,
|
|
685
686
|
op TEXT NOT NULL, -- 'insert' | 'update' | 'delete'
|
|
686
687
|
pk JSONB NOT NULL, -- Primary key: {id: 1}
|
|
687
|
-
|
|
688
|
-
after JSONB, -- New values (null for delete)
|
|
688
|
+
data JSONB, -- Record data (new state for insert/update, null for delete)
|
|
689
689
|
user_id INT, -- Who made the change
|
|
690
690
|
notify_users INT[], -- Who to notify (null = all)
|
|
691
691
|
at TIMESTAMPTZ DEFAULT NOW()
|
|
@@ -753,43 +753,46 @@ try {
|
|
|
753
753
|
table: 'venues', // Table name
|
|
754
754
|
op: 'insert', // Operation: 'insert' | 'update' | 'delete'
|
|
755
755
|
pk: {id: 1}, // Primary key object
|
|
756
|
-
|
|
757
|
-
id: 1,
|
|
758
|
-
name: 'Old Name',
|
|
759
|
-
address: 'Old Address'
|
|
760
|
-
},
|
|
761
|
-
after: { // New values (null for delete)
|
|
756
|
+
data: { // Record data (new state for insert/update, null for delete)
|
|
762
757
|
id: 1,
|
|
763
758
|
name: 'New Name',
|
|
764
759
|
address: 'New Address'
|
|
765
760
|
},
|
|
766
761
|
user_id: 123, // User who made the change
|
|
767
|
-
at: '2025-01-01T12:00:00Z'
|
|
768
|
-
notify_users: [1, 2, 3] // Targeted users (null = all authenticated)
|
|
762
|
+
at: '2025-01-01T12:00:00Z' // Timestamp
|
|
769
763
|
}
|
|
770
764
|
```
|
|
771
765
|
|
|
766
|
+
**Event data by operation:**
|
|
767
|
+
| Operation | `data` field contains |
|
|
768
|
+
|-----------|----------------------|
|
|
769
|
+
| `insert` | Full new record |
|
|
770
|
+
| `update` | Full updated record (new state only) |
|
|
771
|
+
| `delete` | `null` |
|
|
772
|
+
|
|
773
|
+
**Note:** The `notify_users` field is used internally for routing but is stripped from the broadcast message sent to clients.
|
|
774
|
+
|
|
772
775
|
### Using Event Data
|
|
773
776
|
|
|
774
777
|
```javascript
|
|
775
778
|
ws.onBroadcast((method, params) => {
|
|
779
|
+
const { table, op, pk, data } = params;
|
|
780
|
+
|
|
776
781
|
// For insert
|
|
777
782
|
if (method === 'venues:insert') {
|
|
778
|
-
const newRecord =
|
|
779
|
-
// params.before is null
|
|
783
|
+
const newRecord = data;
|
|
780
784
|
}
|
|
781
785
|
|
|
782
786
|
// For update
|
|
783
787
|
if (method === 'venues:update') {
|
|
784
|
-
const
|
|
785
|
-
|
|
786
|
-
// Compare to detect what changed
|
|
788
|
+
const updatedRecord = data;
|
|
789
|
+
// Note: only new state is available, not the previous state
|
|
787
790
|
}
|
|
788
791
|
|
|
789
792
|
// For delete
|
|
790
793
|
if (method === 'venues:delete') {
|
|
791
|
-
|
|
792
|
-
|
|
794
|
+
// data is null for delete, use pk to identify the deleted record
|
|
795
|
+
const deletedId = pk.id;
|
|
793
796
|
}
|
|
794
797
|
});
|
|
795
798
|
```
|
package/docs/reference/api.md
CHANGED
|
@@ -892,6 +892,25 @@ const result = await ws.api.register_user({
|
|
|
892
892
|
});
|
|
893
893
|
```
|
|
894
894
|
|
|
895
|
+
**With options (for extended registration):**
|
|
896
|
+
```javascript
|
|
897
|
+
const result = await ws.api.register_user({
|
|
898
|
+
email: 'user@example.com',
|
|
899
|
+
password: 'secure-password',
|
|
900
|
+
options: {
|
|
901
|
+
org_name: 'Acme Corp',
|
|
902
|
+
role: 'admin'
|
|
903
|
+
}
|
|
904
|
+
});
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
**Parameters:**
|
|
908
|
+
| Field | Type | Required | Description |
|
|
909
|
+
|-------|------|----------|-------------|
|
|
910
|
+
| `email` | string | yes | User email address |
|
|
911
|
+
| `password` | string | yes | User password |
|
|
912
|
+
| `options` | object | no | Additional JSONB data passed to `register_user()` function |
|
|
913
|
+
|
|
895
914
|
**Returns:**
|
|
896
915
|
```javascript
|
|
897
916
|
{
|
|
@@ -902,6 +921,17 @@ const result = await ws.api.register_user({
|
|
|
902
921
|
}
|
|
903
922
|
```
|
|
904
923
|
|
|
924
|
+
**Note:** The `options` parameter requires your `register_user` PostgreSQL function to accept a third parameter:
|
|
925
|
+
```sql
|
|
926
|
+
CREATE OR REPLACE FUNCTION register_user(
|
|
927
|
+
p_email TEXT,
|
|
928
|
+
p_password TEXT,
|
|
929
|
+
p_options JSONB DEFAULT NULL
|
|
930
|
+
) RETURNS JSONB AS $$
|
|
931
|
+
-- Access options: p_options->>'org_name'
|
|
932
|
+
$$;
|
|
933
|
+
```
|
|
934
|
+
|
|
905
935
|
### Login
|
|
906
936
|
|
|
907
937
|
```javascript
|
|
@@ -911,8 +941,38 @@ const result = await ws.api.login_user({
|
|
|
911
941
|
});
|
|
912
942
|
```
|
|
913
943
|
|
|
944
|
+
**With options:**
|
|
945
|
+
```javascript
|
|
946
|
+
const result = await ws.api.login_user({
|
|
947
|
+
email: 'user@example.com',
|
|
948
|
+
password: 'password',
|
|
949
|
+
options: {
|
|
950
|
+
device_id: 'abc123',
|
|
951
|
+
remember_me: true
|
|
952
|
+
}
|
|
953
|
+
});
|
|
954
|
+
```
|
|
955
|
+
|
|
956
|
+
**Parameters:**
|
|
957
|
+
| Field | Type | Required | Description |
|
|
958
|
+
|-------|------|----------|-------------|
|
|
959
|
+
| `email` | string | yes | User email address |
|
|
960
|
+
| `password` | string | yes | User password |
|
|
961
|
+
| `options` | object | no | Additional JSONB data passed to `login_user()` function |
|
|
962
|
+
|
|
914
963
|
**Returns:** Same as register
|
|
915
964
|
|
|
965
|
+
**Note:** The `options` parameter requires your `login_user` PostgreSQL function to accept a third parameter:
|
|
966
|
+
```sql
|
|
967
|
+
CREATE OR REPLACE FUNCTION login_user(
|
|
968
|
+
p_email TEXT,
|
|
969
|
+
p_password TEXT,
|
|
970
|
+
p_options JSONB DEFAULT NULL
|
|
971
|
+
) RETURNS JSONB AS $$
|
|
972
|
+
-- Access options: p_options->>'device_id'
|
|
973
|
+
$$;
|
|
974
|
+
```
|
|
975
|
+
|
|
916
976
|
### Logout
|
|
917
977
|
|
|
918
978
|
```javascript
|
|
@@ -980,27 +1040,34 @@ unsubscribe();
|
|
|
980
1040
|
table: 'venues',
|
|
981
1041
|
op: 'insert' | 'update' | 'delete',
|
|
982
1042
|
pk: {id: 1}, // Primary key
|
|
983
|
-
|
|
984
|
-
after: {...}, // New values (null for delete)
|
|
1043
|
+
data: {...}, // Record data (new state for insert/update, null for delete)
|
|
985
1044
|
user_id: 123, // Who made the change
|
|
986
|
-
at: '2025-01-01T...'
|
|
987
|
-
notify_users: [1, 2] // Who to notify (null = all)
|
|
1045
|
+
at: '2025-01-01T...' // Timestamp
|
|
988
1046
|
}
|
|
989
1047
|
```
|
|
990
1048
|
|
|
1049
|
+
**Event data by operation:**
|
|
1050
|
+
| Operation | `data` field contains |
|
|
1051
|
+
|-----------|----------------------|
|
|
1052
|
+
| `insert` | Full new record |
|
|
1053
|
+
| `update` | Full updated record (new state only) |
|
|
1054
|
+
| `delete` | `null` |
|
|
1055
|
+
|
|
1056
|
+
**Note:** The `notify_users` field is used internally for routing but is stripped from the broadcast message sent to clients.
|
|
1057
|
+
|
|
991
1058
|
### Event Handling Pattern
|
|
992
1059
|
|
|
993
1060
|
```javascript
|
|
994
1061
|
ws.onBroadcast((method, params) => {
|
|
995
|
-
const data = params
|
|
1062
|
+
const { table, op, pk, data } = params;
|
|
996
1063
|
|
|
997
1064
|
if (method === 'todos:insert') {
|
|
998
1065
|
state.todos.push(data);
|
|
999
1066
|
} else if (method === 'todos:update') {
|
|
1000
|
-
const idx = state.todos.findIndex(t => t.id ===
|
|
1067
|
+
const idx = state.todos.findIndex(t => t.id === pk.id);
|
|
1001
1068
|
if (idx !== -1) state.todos[idx] = data;
|
|
1002
1069
|
} else if (method === 'todos:delete') {
|
|
1003
|
-
state.todos = state.todos.filter(t => t.id !==
|
|
1070
|
+
state.todos = state.todos.filter(t => t.id !== pk.id);
|
|
1004
1071
|
}
|
|
1005
1072
|
|
|
1006
1073
|
render();
|
package/docs/reference/client.md
CHANGED
|
@@ -82,6 +82,47 @@ async function login() {
|
|
|
82
82
|
</template>
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
+
**Registration with options (e.g., organisation name):**
|
|
86
|
+
```vue
|
|
87
|
+
<script setup>
|
|
88
|
+
import { ref } from 'vue'
|
|
89
|
+
import { useWsStore } from 'dzql/client/stores'
|
|
90
|
+
|
|
91
|
+
const wsStore = useWsStore()
|
|
92
|
+
const email = ref('')
|
|
93
|
+
const password = ref('')
|
|
94
|
+
const orgName = ref('')
|
|
95
|
+
|
|
96
|
+
async function register() {
|
|
97
|
+
try {
|
|
98
|
+
await wsStore.register({
|
|
99
|
+
email: email.value,
|
|
100
|
+
password: password.value,
|
|
101
|
+
options: { org_name: orgName.value }
|
|
102
|
+
})
|
|
103
|
+
} catch (err) {
|
|
104
|
+
alert(err.message)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
</script>
|
|
108
|
+
|
|
109
|
+
<template>
|
|
110
|
+
<form @submit.prevent="register">
|
|
111
|
+
<input v-model="email" type="email" placeholder="Email" required />
|
|
112
|
+
<input v-model="password" type="password" placeholder="Password" required />
|
|
113
|
+
<input v-model="orgName" type="text" placeholder="Organisation Name" />
|
|
114
|
+
<button type="submit">Register</button>
|
|
115
|
+
</form>
|
|
116
|
+
</template>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The `options` parameter allows passing additional JSONB data to the `register_user` and `login_user` PostgreSQL functions. This is useful for:
|
|
120
|
+
- Organisation name during registration
|
|
121
|
+
- Device ID for login tracking
|
|
122
|
+
- Any custom fields your auth functions support
|
|
123
|
+
|
|
124
|
+
See [API Reference - Authentication](./api.md#authentication) for details on configuring your PostgreSQL functions.
|
|
125
|
+
|
|
85
126
|
## 4. Setup main.js
|
|
86
127
|
|
|
87
128
|
**src/main.js:**
|
package/package.json
CHANGED
package/src/client/ws.js
CHANGED
|
@@ -23,6 +23,13 @@
|
|
|
23
23
|
* password: 'password123'
|
|
24
24
|
* });
|
|
25
25
|
*
|
|
26
|
+
* // Register with options (e.g., organisation name)
|
|
27
|
+
* const session = await ws.api.register_user({
|
|
28
|
+
* email: 'user@example.com',
|
|
29
|
+
* password: 'password123',
|
|
30
|
+
* options: { org_name: 'Acme Corp' }
|
|
31
|
+
* });
|
|
32
|
+
*
|
|
26
33
|
* // CRUD operations
|
|
27
34
|
* const venue = await ws.api.get.venues({ id: 1 });
|
|
28
35
|
* const created = await ws.api.save.venues({ name: 'New Venue' });
|
|
@@ -28,6 +28,9 @@ export class AuthCodegen {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
return [
|
|
31
|
+
'-- Enable pgcrypto extension for password hashing',
|
|
32
|
+
'CREATE EXTENSION IF NOT EXISTS pgcrypto;',
|
|
33
|
+
'',
|
|
31
34
|
this._generateProfileFunction(),
|
|
32
35
|
this._generateRegisterFunction(),
|
|
33
36
|
this._generateLoginFunction()
|
|
@@ -57,16 +60,16 @@ $$;`;
|
|
|
57
60
|
|
|
58
61
|
/**
|
|
59
62
|
* Generate register_user function
|
|
60
|
-
* Supports optional
|
|
63
|
+
* Supports optional fields via JSON parameter
|
|
61
64
|
* @private
|
|
62
65
|
*/
|
|
63
66
|
_generateRegisterFunction() {
|
|
64
67
|
return `-- ============================================================================
|
|
65
68
|
-- Auth: register_user function for ${this.tableName}
|
|
66
|
-
--
|
|
69
|
+
-- p_options: optional JSON object with additional fields to set on the user record
|
|
67
70
|
-- Example: register_user('test@example.com', 'password', '{"name": "Test User"}')
|
|
68
71
|
-- ============================================================================
|
|
69
|
-
CREATE OR REPLACE FUNCTION register_user(p_email TEXT, p_password TEXT,
|
|
72
|
+
CREATE OR REPLACE FUNCTION register_user(p_email TEXT, p_password TEXT, p_options JSONB DEFAULT NULL)
|
|
70
73
|
RETURNS JSONB
|
|
71
74
|
LANGUAGE plpgsql
|
|
72
75
|
SECURITY DEFINER
|
|
@@ -81,9 +84,11 @@ BEGIN
|
|
|
81
84
|
v_salt := gen_salt('bf', 10);
|
|
82
85
|
v_hash := crypt(p_password, v_salt);
|
|
83
86
|
|
|
84
|
-
-- Build insert data:
|
|
85
|
-
v_insert_data := (
|
|
86
|
-
|
|
87
|
+
-- Build insert data: options fields + email + password_hash (options cannot override core fields)
|
|
88
|
+
v_insert_data := jsonb_build_object('email', p_email, 'password_hash', v_hash);
|
|
89
|
+
IF p_options IS NOT NULL THEN
|
|
90
|
+
v_insert_data := (p_options - 'id' - 'email' - 'password_hash' - 'password') || v_insert_data;
|
|
91
|
+
END IF;
|
|
87
92
|
|
|
88
93
|
-- Dynamic INSERT from JSONB (same pattern as compiled save functions)
|
|
89
94
|
EXECUTE (
|
|
@@ -5,12 +5,10 @@
|
|
|
5
5
|
create extension if not exists pgcrypto;
|
|
6
6
|
|
|
7
7
|
-- === Users Table ===
|
|
8
|
-
--
|
|
9
|
-
-- Applications can add additional columns as needed
|
|
8
|
+
-- Minimal auth table - applications can add columns via migrations
|
|
10
9
|
-- Note: created_at is tracked via the action log, not here
|
|
11
10
|
create table if not exists users (
|
|
12
11
|
id serial primary key,
|
|
13
|
-
name text,
|
|
14
12
|
email text unique not null,
|
|
15
13
|
password_hash text not null
|
|
16
14
|
);
|
|
@@ -18,9 +16,9 @@ create table if not exists users (
|
|
|
18
16
|
-- === Auth Functions ===
|
|
19
17
|
|
|
20
18
|
-- Register new user
|
|
21
|
-
--
|
|
19
|
+
-- p_options: optional JSON object with additional fields to set on the user record
|
|
22
20
|
-- Example: register_user('test@example.com', 'password', '{"name": "Test User"}')
|
|
23
|
-
create or replace function register_user(p_email text, p_password text,
|
|
21
|
+
create or replace function register_user(p_email text, p_password text, p_options jsonb default null)
|
|
24
22
|
returns jsonb
|
|
25
23
|
language plpgsql
|
|
26
24
|
security definer
|
|
@@ -35,9 +33,11 @@ begin
|
|
|
35
33
|
v_salt := gen_salt('bf', 10);
|
|
36
34
|
v_hash := crypt(p_password, v_salt);
|
|
37
35
|
|
|
38
|
-
-- Build insert data:
|
|
39
|
-
v_insert_data := (
|
|
40
|
-
|
|
36
|
+
-- Build insert data: options fields + email + password_hash (options cannot override core fields)
|
|
37
|
+
v_insert_data := jsonb_build_object('email', p_email, 'password_hash', v_hash);
|
|
38
|
+
if p_options is not null then
|
|
39
|
+
v_insert_data := (p_options - 'id' - 'email' - 'password_hash' - 'password') || v_insert_data;
|
|
40
|
+
end if;
|
|
41
41
|
|
|
42
42
|
-- Dynamic INSERT from JSONB (same pattern as compiled save functions)
|
|
43
43
|
execute (
|
package/src/server/db.js
CHANGED
|
@@ -54,7 +54,13 @@ export async function setCache(key, data) {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
// Auth helpers
|
|
57
|
-
export async function callAuthFunction(method, email, password) {
|
|
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
|
+
}
|
|
58
64
|
const result = await sql`
|
|
59
65
|
SELECT ${sql(method)}(${email}, ${password}) as result
|
|
60
66
|
`;
|
|
@@ -380,7 +386,7 @@ export const db = {
|
|
|
380
386
|
// Special handling for auth functions that don't require userId
|
|
381
387
|
if (prop === 'register_user' || prop === 'login_user') {
|
|
382
388
|
// For auth functions, first param is the args object
|
|
383
|
-
return callAuthFunction(prop, userIdOrArgs.email, userIdOrArgs.password);
|
|
389
|
+
return callAuthFunction(prop, userIdOrArgs.email, userIdOrArgs.password, userIdOrArgs.options || null);
|
|
384
390
|
}
|
|
385
391
|
|
|
386
392
|
// For other functions, userId is required as first parameter
|
package/src/server/ws.js
CHANGED
|
@@ -231,6 +231,7 @@ export function createRPCHandler(customHandlers = {}) {
|
|
|
231
231
|
"login_user",
|
|
232
232
|
params.email,
|
|
233
233
|
params.password,
|
|
234
|
+
params.options || null,
|
|
234
235
|
);
|
|
235
236
|
|
|
236
237
|
// On successful auth, set user_id on WebSocket connection
|
|
@@ -268,6 +269,7 @@ export function createRPCHandler(customHandlers = {}) {
|
|
|
268
269
|
"register_user",
|
|
269
270
|
params.email,
|
|
270
271
|
params.password,
|
|
272
|
+
params.options || null,
|
|
271
273
|
);
|
|
272
274
|
|
|
273
275
|
// On successful registration, set user_id on WebSocket connection
|