@fuzdev/fuz_app 0.110.0 → 0.111.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/dist/actions/action_rpc.d.ts +2 -2
- package/dist/auth/CLAUDE.md +24 -5
- package/dist/auth/account_action_specs.d.ts +0 -2
- package/dist/auth/account_action_specs.d.ts.map +1 -1
- package/dist/auth/account_queries.d.ts +22 -0
- package/dist/auth/account_queries.d.ts.map +1 -1
- package/dist/auth/account_queries.js +49 -15
- package/dist/auth/account_schema.d.ts +7 -9
- package/dist/auth/account_schema.d.ts.map +1 -1
- package/dist/auth/account_schema.js +1 -2
- package/dist/auth/admin_action_specs.d.ts +0 -2
- package/dist/auth/admin_action_specs.d.ts.map +1 -1
- package/dist/auth/api_token_queries.d.ts +9 -4
- package/dist/auth/api_token_queries.d.ts.map +1 -1
- package/dist/auth/api_token_queries.js +29 -7
- package/dist/auth/app_settings_queries.d.ts +8 -0
- package/dist/auth/app_settings_queries.d.ts.map +1 -1
- package/dist/auth/app_settings_queries.js +14 -3
- package/dist/auth/audit_log_queries.d.ts +15 -3
- package/dist/auth/audit_log_queries.d.ts.map +1 -1
- package/dist/auth/audit_log_queries.js +32 -7
- package/dist/auth/audit_log_schema.d.ts +0 -1
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/auth_ddl.d.ts +6 -0
- package/dist/auth/auth_ddl.d.ts.map +1 -1
- package/dist/auth/auth_ddl.js +9 -0
- package/dist/auth/invite_queries.d.ts +10 -0
- package/dist/auth/invite_queries.d.ts.map +1 -1
- package/dist/auth/invite_queries.js +25 -6
- package/dist/auth/migrations.d.ts.map +1 -1
- package/dist/auth/migrations.js +10 -0
- package/dist/auth/role_grant_offer_ddl.d.ts +23 -2
- package/dist/auth/role_grant_offer_ddl.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_ddl.js +43 -2
- package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_queries.js +29 -48
- package/dist/auth/role_grant_queries.d.ts +18 -0
- package/dist/auth/role_grant_queries.d.ts.map +1 -1
- package/dist/auth/role_grant_queries.js +43 -17
- package/dist/auth/session_queries.d.ts +12 -3
- package/dist/auth/session_queries.d.ts.map +1 -1
- package/dist/auth/session_queries.js +21 -6
- package/dist/db/CLAUDE.md +15 -0
- package/dist/db/cell_audit_queries.d.ts.map +1 -1
- package/dist/db/cell_audit_queries.js +3 -1
- package/dist/db/cell_field_queries.d.ts +11 -2
- package/dist/db/cell_field_queries.d.ts.map +1 -1
- package/dist/db/cell_field_queries.js +23 -7
- package/dist/db/cell_grant_queries.d.ts +9 -1
- package/dist/db/cell_grant_queries.d.ts.map +1 -1
- package/dist/db/cell_grant_queries.js +25 -9
- package/dist/db/cell_item_queries.d.ts +12 -1
- package/dist/db/cell_item_queries.d.ts.map +1 -1
- package/dist/db/cell_item_queries.js +25 -7
- package/dist/db/cell_queries.d.ts +29 -0
- package/dist/db/cell_queries.d.ts.map +1 -1
- package/dist/db/cell_queries.js +53 -20
- package/dist/db/fact_queries.d.ts +7 -0
- package/dist/db/fact_queries.d.ts.map +1 -1
- package/dist/db/fact_queries.js +19 -2
- package/dist/db/schema_ready.d.ts +6 -1
- package/dist/db/schema_ready.d.ts.map +1 -1
- package/dist/db/schema_ready.js +6 -2
- package/dist/db/sql_columns.d.ts +67 -0
- package/dist/db/sql_columns.d.ts.map +1 -0
- package/dist/db/sql_columns.js +77 -0
- package/dist/db/sql_identifier.d.ts +6 -5
- package/dist/db/sql_identifier.d.ts.map +1 -1
- package/dist/db/sql_identifier.js +6 -5
- package/dist/http/CLAUDE.md +1 -1
- package/dist/http/route_spec.d.ts +2 -2
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +1 -2
- package/dist/testing/CLAUDE.md +2 -1
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +47 -18
- package/dist/testing/cross_backend/expected_schema.json +1 -1
- package/dist/testing/db.d.ts +18 -0
- package/dist/testing/db.d.ts.map +1 -1
- package/dist/testing/db.js +24 -1
- package/dist/ui/AdminOverview.svelte +5 -10
- package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -7,7 +7,30 @@
|
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
9
|
import { assert_row } from '../db/assert_row.js';
|
|
10
|
-
import {
|
|
10
|
+
import { columns_sql, qualify_columns } from '../db/sql_columns.js';
|
|
11
|
+
import { ROLE_GRANT_OFFER_COLUMNS, ROLE_GRANT_OFFER_SCOPE_KIND_GLOBAL_TOKEN, ROLE_GRANT_OFFER_SCOPE_SENTINEL_UUID, ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT } from './role_grant_offer_ddl.js';
|
|
12
|
+
/**
|
|
13
|
+
* The full `role_grant` column set, named explicitly so a row read fails loud
|
|
14
|
+
* on schema drift (see `ACCOUNT_COLUMNS` in `auth/account_queries.ts` for the
|
|
15
|
+
* outage class). Column order follows `RoleGrant` — the frozen `fuz_auth` v0
|
|
16
|
+
* body plus the appended `scope_kind` / `scope_id` / `revoked_reason` /
|
|
17
|
+
* `source_offer_id` columns. Keep in sync with `RoleGrant` and the
|
|
18
|
+
* `role_grant` migration chain in `auth/auth_ddl.ts`.
|
|
19
|
+
*/
|
|
20
|
+
export const ROLE_GRANT_COLUMNS = [
|
|
21
|
+
'id',
|
|
22
|
+
'actor_id',
|
|
23
|
+
'role',
|
|
24
|
+
'scope_kind',
|
|
25
|
+
'scope_id',
|
|
26
|
+
'created_at',
|
|
27
|
+
'expires_at',
|
|
28
|
+
'revoked_at',
|
|
29
|
+
'revoked_by',
|
|
30
|
+
'revoked_reason',
|
|
31
|
+
'granted_by',
|
|
32
|
+
'source_offer_id'
|
|
33
|
+
];
|
|
11
34
|
/**
|
|
12
35
|
* Grant a role_grant to an actor.
|
|
13
36
|
* Idempotent — if an active role_grant already exists for this actor, role, and
|
|
@@ -39,7 +62,7 @@ export const query_create_role_grant = async (deps, input) => {
|
|
|
39
62
|
)
|
|
40
63
|
WHERE revoked_at IS NULL
|
|
41
64
|
DO NOTHING
|
|
42
|
-
RETURNING
|
|
65
|
+
RETURNING ${columns_sql(ROLE_GRANT_COLUMNS)}`, [
|
|
43
66
|
input.actor_id,
|
|
44
67
|
input.role,
|
|
45
68
|
input.scope_kind ?? null,
|
|
@@ -51,7 +74,7 @@ export const query_create_role_grant = async (deps, input) => {
|
|
|
51
74
|
if (inserted)
|
|
52
75
|
return inserted;
|
|
53
76
|
// Active role_grant already exists — return it (idempotent grant).
|
|
54
|
-
const existing = await deps.db.query_one(`SELECT
|
|
77
|
+
const existing = await deps.db.query_one(`SELECT ${columns_sql(ROLE_GRANT_COLUMNS)} FROM role_grant
|
|
55
78
|
WHERE actor_id = $1
|
|
56
79
|
AND role = $2
|
|
57
80
|
AND scope_kind IS NOT DISTINCT FROM $3
|
|
@@ -59,6 +82,15 @@ export const query_create_role_grant = async (deps, input) => {
|
|
|
59
82
|
AND revoked_at IS NULL`, [input.actor_id, input.role, input.scope_kind ?? null, input.scope_id ?? null]);
|
|
60
83
|
return assert_row(existing, 'idempotent role_grant grant');
|
|
61
84
|
};
|
|
85
|
+
/**
|
|
86
|
+
* Find a role_grant by id, regardless of revoked / expired state.
|
|
87
|
+
*
|
|
88
|
+
* Unscoped by design — the one caller is `query_accept_offer`'s race-loser
|
|
89
|
+
* path, which resolves an already-accepted offer's `resulting_role_grant_id`
|
|
90
|
+
* (a row the offer's own IDOR guard has already vouched for). Authority
|
|
91
|
+
* checks stay at the call site.
|
|
92
|
+
*/
|
|
93
|
+
export const query_role_grant_by_id = async (deps, id) => deps.db.query_one(`SELECT ${columns_sql(ROLE_GRANT_COLUMNS)} FROM role_grant WHERE id = $1`, [id]);
|
|
62
94
|
/**
|
|
63
95
|
* Look up the role of an active role_grant (constrained to a specific
|
|
64
96
|
* actor) plus the actor's `account_id`.
|
|
@@ -136,11 +168,9 @@ export const query_revoke_role_grant = async (deps, role_grant_id, actor_id, rev
|
|
|
136
168
|
AND o.declined_at IS NULL
|
|
137
169
|
AND o.retracted_at IS NULL
|
|
138
170
|
AND o.superseded_at IS NULL
|
|
139
|
-
RETURNING o
|
|
171
|
+
RETURNING ${qualify_columns(ROLE_GRANT_OFFER_COLUMNS, 'o')}
|
|
140
172
|
)
|
|
141
|
-
|
|
142
|
-
FROM updated u
|
|
143
|
-
JOIN actor grantor ON grantor.id = u.from_actor_id`, [actor_id, revoked.role, revoked.scope_id]);
|
|
173
|
+
${ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT}`, [actor_id, revoked.role, revoked.scope_id]);
|
|
144
174
|
return {
|
|
145
175
|
id: revoked.id,
|
|
146
176
|
role: revoked.role,
|
|
@@ -153,7 +183,7 @@ export const query_revoke_role_grant = async (deps, role_grant_id, actor_id, rev
|
|
|
153
183
|
* Find all active (non-revoked, non-expired) role_grants for an actor.
|
|
154
184
|
*/
|
|
155
185
|
export const query_role_grant_find_active_for_actor = async (deps, actor_id) => {
|
|
156
|
-
return deps.db.query(`SELECT
|
|
186
|
+
return deps.db.query(`SELECT ${columns_sql(ROLE_GRANT_COLUMNS)} FROM role_grant
|
|
157
187
|
WHERE actor_id = $1
|
|
158
188
|
AND revoked_at IS NULL
|
|
159
189
|
AND (expires_at IS NULL OR expires_at > NOW())
|
|
@@ -284,7 +314,7 @@ export const query_count_active_accounts_with_global_role = async (deps, role) =
|
|
|
284
314
|
* List all role_grants for an actor (including revoked/expired).
|
|
285
315
|
*/
|
|
286
316
|
export const query_role_grant_list_for_actor = async (deps, actor_id) => {
|
|
287
|
-
return deps.db.query(`SELECT
|
|
317
|
+
return deps.db.query(`SELECT ${columns_sql(ROLE_GRANT_COLUMNS)} FROM role_grant WHERE actor_id = $1 ORDER BY created_at DESC`, [actor_id]);
|
|
288
318
|
};
|
|
289
319
|
/**
|
|
290
320
|
* Find the account ID of an account that holds an active role_grant for a given role.
|
|
@@ -366,11 +396,9 @@ export const query_role_grant_revoke_for_scope = async (deps, scope_id, revoked_
|
|
|
366
396
|
AND o.declined_at IS NULL
|
|
367
397
|
AND o.retracted_at IS NULL
|
|
368
398
|
AND o.superseded_at IS NULL
|
|
369
|
-
RETURNING o
|
|
399
|
+
RETURNING ${qualify_columns(ROLE_GRANT_OFFER_COLUMNS, 'o')}
|
|
370
400
|
)
|
|
371
|
-
|
|
372
|
-
FROM updated u
|
|
373
|
-
JOIN actor grantor ON grantor.id = u.from_actor_id`, [scope_id]);
|
|
401
|
+
${ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT}`, [scope_id]);
|
|
374
402
|
return { revoked, superseded_offers };
|
|
375
403
|
};
|
|
376
404
|
/**
|
|
@@ -420,10 +448,8 @@ export const query_role_grant_revoke_role = async (deps, actor_id, role, revoked
|
|
|
420
448
|
AND o.declined_at IS NULL
|
|
421
449
|
AND o.retracted_at IS NULL
|
|
422
450
|
AND o.superseded_at IS NULL
|
|
423
|
-
RETURNING o
|
|
451
|
+
RETURNING ${qualify_columns(ROLE_GRANT_OFFER_COLUMNS, 'o')}
|
|
424
452
|
)
|
|
425
|
-
|
|
426
|
-
FROM updated u
|
|
427
|
-
JOIN actor grantor ON grantor.id = u.from_actor_id`, [actor_id, role]);
|
|
453
|
+
${ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT}`, [actor_id, role]);
|
|
428
454
|
return { revoked, superseded_offers };
|
|
429
455
|
};
|
|
@@ -43,6 +43,17 @@ export declare const generate_session_token: () => string;
|
|
|
43
43
|
* @mutates `auth_session` table - inserts a row keyed by `token_hash`
|
|
44
44
|
*/
|
|
45
45
|
export declare const query_create_session: (deps: QueryDeps, token_hash: string, account_id: string, expires_at: Date) => Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* The full `auth_session` column set, named explicitly so a row read fails
|
|
48
|
+
* loud on schema drift — `SELECT *` would silently carry a dropped or
|
|
49
|
+
* leftover column into the strict-validated wire shapes (see
|
|
50
|
+
* `ACCOUNT_COLUMNS` in `auth/account_queries.ts` for the outage class this
|
|
51
|
+
* discipline exists to prevent; the Rust twin already names columns at every
|
|
52
|
+
* session site). Keep in sync with `AuthSession` and the migration chain's
|
|
53
|
+
* end state — not the frozen v0 DDL in `auth/auth_ddl.ts`, which still
|
|
54
|
+
* creates `last_seen_at` for the appended drop migration to remove.
|
|
55
|
+
*/
|
|
56
|
+
export declare const AUTH_SESSION_COLUMNS: readonly ["id", "account_id", "created_at", "expires_at"];
|
|
46
57
|
/**
|
|
47
58
|
* Get a session if it exists, is not expired, and has not been revoked.
|
|
48
59
|
*
|
|
@@ -132,9 +143,7 @@ export declare const query_session_enforce_limit: (deps: QueryDeps, account_id:
|
|
|
132
143
|
* List all active sessions across all accounts with usernames.
|
|
133
144
|
*
|
|
134
145
|
* Ordered by `created_at DESC` (newest session first), matching the
|
|
135
|
-
* per-account listing.
|
|
136
|
-
* but is decorative — nothing updates it post-mint (it always equals
|
|
137
|
-
* `created_at`); it is slated for removal on its own twin migration.
|
|
146
|
+
* per-account listing.
|
|
138
147
|
*
|
|
139
148
|
* @param deps - query dependencies
|
|
140
149
|
* @param limit - maximum entries to return
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/session_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"session_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/session_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAElE;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,QAA2B,CAAC;AAEjE;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,KAAG,SAElD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,QAAO,MAEzC,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,EAClB,YAAY,IAAI,KACd,OAAO,CAAC,IAAI,CAMd,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,2DAKoB,CAAC;AAEtD;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,WAAW,GAAG,SAAS,CAKjC,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qCAAqC,GACjD,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,IAAI,CAEd,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,KAChB,OAAO,CAAC,OAAO,CAMjB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,oCAAoC,GAChD,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,MAAM,CAMhB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,cAAU,KACR,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAK5B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,cAAc,MAAM,KAClB,OAAO,CAAC,MAAM,CAYhB,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,cAAW,KACT,OAAO,CAAC,KAAK,CAAC,WAAW,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CASnD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,MAAM,CAKnF,CAAC"}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { hash_blake3 } from '@fuzdev/fuz_util/hash_blake3.ts';
|
|
10
10
|
import { generate_random_base64url } from '../crypto.js';
|
|
11
|
+
import { columns_sql, qualify_columns } from '../db/sql_columns.js';
|
|
11
12
|
/**
|
|
12
13
|
* Session lifetime in milliseconds (30 days).
|
|
13
14
|
*
|
|
@@ -53,6 +54,22 @@ export const query_create_session = async (deps, token_hash, account_id, expires
|
|
|
53
54
|
expires_at.toISOString()
|
|
54
55
|
]);
|
|
55
56
|
};
|
|
57
|
+
/**
|
|
58
|
+
* The full `auth_session` column set, named explicitly so a row read fails
|
|
59
|
+
* loud on schema drift — `SELECT *` would silently carry a dropped or
|
|
60
|
+
* leftover column into the strict-validated wire shapes (see
|
|
61
|
+
* `ACCOUNT_COLUMNS` in `auth/account_queries.ts` for the outage class this
|
|
62
|
+
* discipline exists to prevent; the Rust twin already names columns at every
|
|
63
|
+
* session site). Keep in sync with `AuthSession` and the migration chain's
|
|
64
|
+
* end state — not the frozen v0 DDL in `auth/auth_ddl.ts`, which still
|
|
65
|
+
* creates `last_seen_at` for the appended drop migration to remove.
|
|
66
|
+
*/
|
|
67
|
+
export const AUTH_SESSION_COLUMNS = [
|
|
68
|
+
'id',
|
|
69
|
+
'account_id',
|
|
70
|
+
'created_at',
|
|
71
|
+
'expires_at'
|
|
72
|
+
];
|
|
56
73
|
/**
|
|
57
74
|
* Get a session if it exists, is not expired, and has not been revoked.
|
|
58
75
|
*
|
|
@@ -60,7 +77,7 @@ export const query_create_session = async (deps, token_hash, account_id, expires
|
|
|
60
77
|
* @param token_hash - blake3 hash of the session token
|
|
61
78
|
*/
|
|
62
79
|
export const query_session_get_valid = async (deps, token_hash) => {
|
|
63
|
-
return deps.db.query_one(`SELECT
|
|
80
|
+
return deps.db.query_one(`SELECT ${columns_sql(AUTH_SESSION_COLUMNS)} FROM auth_session WHERE id = $1 AND expires_at > NOW()`, [token_hash]);
|
|
64
81
|
};
|
|
65
82
|
/**
|
|
66
83
|
* Revoke (delete) a session by its token hash, with no account scoping.
|
|
@@ -106,7 +123,7 @@ export const query_session_revoke_all_for_account = async (deps, account_id) =>
|
|
|
106
123
|
* List sessions for an account, newest first.
|
|
107
124
|
*/
|
|
108
125
|
export const query_session_list_for_account = async (deps, account_id, limit = 50) => {
|
|
109
|
-
return deps.db.query(`SELECT
|
|
126
|
+
return deps.db.query(`SELECT ${columns_sql(AUTH_SESSION_COLUMNS)} FROM auth_session WHERE account_id = $1 ORDER BY created_at DESC LIMIT $2`, [account_id, limit]);
|
|
110
127
|
};
|
|
111
128
|
/**
|
|
112
129
|
* Enforce a per-account session limit by evicting the oldest sessions.
|
|
@@ -163,16 +180,14 @@ export const query_session_enforce_limit = async (deps, account_id, max_sessions
|
|
|
163
180
|
* List all active sessions across all accounts with usernames.
|
|
164
181
|
*
|
|
165
182
|
* Ordered by `created_at DESC` (newest session first), matching the
|
|
166
|
-
* per-account listing.
|
|
167
|
-
* but is decorative — nothing updates it post-mint (it always equals
|
|
168
|
-
* `created_at`); it is slated for removal on its own twin migration.
|
|
183
|
+
* per-account listing.
|
|
169
184
|
*
|
|
170
185
|
* @param deps - query dependencies
|
|
171
186
|
* @param limit - maximum entries to return
|
|
172
187
|
* @returns active sessions joined with account usernames, newest first
|
|
173
188
|
*/
|
|
174
189
|
export const query_session_list_all_active = async (deps, limit = 200) => {
|
|
175
|
-
return deps.db.query(`SELECT
|
|
190
|
+
return deps.db.query(`SELECT ${qualify_columns(AUTH_SESSION_COLUMNS, 's')}, a.username
|
|
176
191
|
FROM auth_session s
|
|
177
192
|
JOIN account a ON a.id = s.account_id
|
|
178
193
|
WHERE s.expires_at > NOW()
|
package/dist/db/CLAUDE.md
CHANGED
|
@@ -27,6 +27,10 @@ the code.
|
|
|
27
27
|
- `assert_row.ts` — `assert_row(row, context)` for INSERT … RETURNING.
|
|
28
28
|
- `pg_error.ts` — `pg_error_code` (SQLSTATE extractor for `pg`/PGlite errors), `is_pg_unique_violation` (Postgres `23505`).
|
|
29
29
|
- `sql_identifier.ts` — `assert_valid_sql_identifier`.
|
|
30
|
+
- `sql_columns.ts` — `columns_sql` / `qualify_columns` / `omit_columns`,
|
|
31
|
+
the derivation helpers every `*_COLUMNS` projection const's variants
|
|
32
|
+
(alias-qualified JOIN reads, the client-safe `minus token_hash` listing)
|
|
33
|
+
are built from, so the drift guard on the base const covers them.
|
|
30
34
|
- `status.ts` — CLI DB status utility.
|
|
31
35
|
- `schema_ready.ts` — `/ready` deploy-gate core: `query_public_columns`
|
|
32
36
|
(keeps `schema_version`, unlike `query_schema_snapshot`), pure
|
|
@@ -98,6 +102,17 @@ child_id`, fractional-index keyed): `query_cell_item_insert` (throws
|
|
|
98
102
|
`target_id`), bitmap-OR over `idx_audit_log_metadata` (the `jsonb_path_ops`
|
|
99
103
|
GIN the `audit_log_metadata_gin_index` auth migration ships).
|
|
100
104
|
|
|
105
|
+
**Named projections** (rationale + helpers: ../../../docs/architecture.md
|
|
106
|
+
§Query Modules). The consts on this side: `CELL_COLUMNS`,
|
|
107
|
+
`CELL_GRANT_COLUMNS`, `CELL_FIELD_COLUMNS`, `CELL_ITEM_COLUMNS`, `FACT_COLUMNS`
|
|
108
|
+
(the meta read derives `minus bytes`). The derived `grant_count` stays an
|
|
109
|
+
appended expression in the exported `cell_row_projection(alias)` rather than a
|
|
110
|
+
`CELL_COLUMNS` entry, so the const is exactly the table's column set. Column
|
|
111
|
+
order twins the Rust `fuz_cell` `*_COLUMNS` consts. Every const is pinned to
|
|
112
|
+
the live schema — and every public table forced to carry a const or a named
|
|
113
|
+
exemption — by `src/test/db/column_projections.db.test.ts`; the cell
|
|
114
|
+
`*_queries.db.test.ts` files pin the aliased JOIN reads and `grant_count`.
|
|
115
|
+
|
|
101
116
|
## Fact layer
|
|
102
117
|
|
|
103
118
|
Content-addressed byte store. Cells reference facts by blake3 hash
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cell_audit_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/cell_audit_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"cell_audit_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/cell_audit_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAGjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAoBnD,MAAM,WAAW,oBAAoB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,SAAS,EACf,SAAS,IAAI,EACb,SAAS,oBAAoB,KAC3B,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,CAwB9B,CAAC"}
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
*
|
|
13
13
|
* @module
|
|
14
14
|
*/
|
|
15
|
+
import { AUDIT_LOG_COLUMNS } from '../auth/audit_log_queries.js';
|
|
16
|
+
import { columns_sql } from './sql_columns.js';
|
|
15
17
|
/**
|
|
16
18
|
* Metadata-jsonb keys cell-domain events use to name the cell. This
|
|
17
19
|
* array is the **only** source these key strings can come from —
|
|
@@ -51,5 +53,5 @@ export const query_audit_log_list_by_cell = async (deps, cell_id, options) => {
|
|
|
51
53
|
const where = `WHERE ${conditions.join(' AND ')}`;
|
|
52
54
|
const limit_placeholder = `$${i++}`;
|
|
53
55
|
params.push(options.limit);
|
|
54
|
-
return deps.db.query(`SELECT
|
|
56
|
+
return deps.db.query(`SELECT ${columns_sql(AUDIT_LOG_COLUMNS)} FROM audit_log ${where} ORDER BY seq DESC LIMIT ${limit_placeholder}`, params);
|
|
55
57
|
};
|
|
@@ -25,6 +25,13 @@ export interface CellFieldRow {
|
|
|
25
25
|
target_id: Uuid;
|
|
26
26
|
created_at: Date;
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* The full `cell_field` column set, named explicitly so a row read fails
|
|
30
|
+
* loud on schema drift (see `CELL_COLUMNS` in `db/cell_queries.ts`). Column
|
|
31
|
+
* order mirrors the Rust twin's `field_columns(alias)` (`fuz_cell`). Keep in
|
|
32
|
+
* sync with `CellFieldRow` and the `cell_field` DDL in `db/cell_ddl.ts`.
|
|
33
|
+
*/
|
|
34
|
+
export declare const CELL_FIELD_COLUMNS: readonly ["source_id", "name", "target_id", "created_at"];
|
|
28
35
|
/** Input for `query_cell_field_set`. */
|
|
29
36
|
export interface CellFieldSetQueryInput {
|
|
30
37
|
source_id: Uuid;
|
|
@@ -76,7 +83,9 @@ export declare const query_cell_field_delete: (deps: QueryDeps, source_id: Uuid,
|
|
|
76
83
|
*
|
|
77
84
|
* @param deps - query deps
|
|
78
85
|
* @param source_id - source cell id
|
|
79
|
-
* @
|
|
86
|
+
* @param options - `limit` caps the row count; `name_after` resumes after a
|
|
87
|
+
* name (keyset pagination)
|
|
88
|
+
* @returns matching rows in lex `name` order
|
|
80
89
|
*/
|
|
81
90
|
export declare const query_cell_field_list_for_source: (deps: QueryDeps, source_id: Uuid, options?: {
|
|
82
91
|
limit?: number;
|
|
@@ -97,7 +106,7 @@ export declare const query_cell_field_list_for_source: (deps: QueryDeps, source_
|
|
|
97
106
|
* @param deps - query deps
|
|
98
107
|
* @param target_id - target cell id
|
|
99
108
|
* @param options - `limit` caps the row count
|
|
100
|
-
* @returns matching rows, oldest first by
|
|
109
|
+
* @returns matching rows, oldest field first (by the field row's `created_at`)
|
|
101
110
|
*/
|
|
102
111
|
export declare const query_cell_field_list_for_target: (deps: QueryDeps, target_id: Uuid, options?: {
|
|
103
112
|
limit?: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cell_field_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/cell_field_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"cell_field_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/cell_field_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAInD,kDAAkD;AAClD,MAAM,WAAW,YAAY;IAC5B,SAAS,EAAE,IAAI,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,IAAI,CAAC;IAChB,UAAU,EAAE,IAAI,CAAC;CACjB;AAED;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,2DAKuB,CAAC;AAEvD,wCAAwC;AACxC,MAAM,WAAW,sBAAsB;IACtC,SAAS,EAAE,IAAI,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,IAAI,CAAC;CAChB;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,SAAS,EACf,OAAO,sBAAsB,KAC3B,OAAO,CAAC,YAAY,CAUtB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,SAAS,EACf,WAAW,IAAI,EACf,MAAM,MAAM,KACV,OAAO,CAAC,YAAY,GAAG,IAAI,CAM7B,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,SAAS,EACf,WAAW,IAAI,EACf,MAAM,MAAM,KACV,OAAO,CAAC,YAAY,GAAG,IAAI,CAO7B,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,SAAS,EACf,WAAW,IAAI,EACf,UAAU;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,KAC/C,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAa7B,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,SAAS,EACf,WAAW,IAAI,EACf,UAAU;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,KAC1B,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAS5B,CAAC"}
|
|
@@ -17,6 +17,19 @@
|
|
|
17
17
|
* @module
|
|
18
18
|
*/
|
|
19
19
|
import { assert_row } from './assert_row.js';
|
|
20
|
+
import { columns_sql, qualify_columns } from './sql_columns.js';
|
|
21
|
+
/**
|
|
22
|
+
* The full `cell_field` column set, named explicitly so a row read fails
|
|
23
|
+
* loud on schema drift (see `CELL_COLUMNS` in `db/cell_queries.ts`). Column
|
|
24
|
+
* order mirrors the Rust twin's `field_columns(alias)` (`fuz_cell`). Keep in
|
|
25
|
+
* sync with `CellFieldRow` and the `cell_field` DDL in `db/cell_ddl.ts`.
|
|
26
|
+
*/
|
|
27
|
+
export const CELL_FIELD_COLUMNS = [
|
|
28
|
+
'source_id',
|
|
29
|
+
'name',
|
|
30
|
+
'target_id',
|
|
31
|
+
'created_at'
|
|
32
|
+
];
|
|
20
33
|
/**
|
|
21
34
|
* Insert or update a field row.
|
|
22
35
|
*
|
|
@@ -35,7 +48,7 @@ export const query_cell_field_set = async (deps, input) => {
|
|
|
35
48
|
VALUES ($1, $2, $3)
|
|
36
49
|
ON CONFLICT (source_id, name)
|
|
37
50
|
DO UPDATE SET target_id = EXCLUDED.target_id, created_at = NOW()
|
|
38
|
-
RETURNING
|
|
51
|
+
RETURNING ${columns_sql(CELL_FIELD_COLUMNS)}`, [input.source_id, input.name, input.target_id]);
|
|
39
52
|
return assert_row(row, 'INSERT INTO cell_field');
|
|
40
53
|
};
|
|
41
54
|
/**
|
|
@@ -51,7 +64,7 @@ export const query_cell_field_set = async (deps, input) => {
|
|
|
51
64
|
* @returns the row or `null` when not found
|
|
52
65
|
*/
|
|
53
66
|
export const query_cell_field_get = async (deps, source_id, name) => {
|
|
54
|
-
const row = await deps.db.query_one(`SELECT
|
|
67
|
+
const row = await deps.db.query_one(`SELECT ${columns_sql(CELL_FIELD_COLUMNS)} FROM cell_field WHERE source_id = $1 AND name = $2`, [source_id, name]);
|
|
55
68
|
return row ?? null;
|
|
56
69
|
};
|
|
57
70
|
/**
|
|
@@ -63,7 +76,8 @@ export const query_cell_field_get = async (deps, source_id, name) => {
|
|
|
63
76
|
* @mutates `cell_field` - deletes one row
|
|
64
77
|
*/
|
|
65
78
|
export const query_cell_field_delete = async (deps, source_id, name) => {
|
|
66
|
-
const row = await deps.db.query_one(`DELETE FROM cell_field WHERE source_id = $1 AND name = $2
|
|
79
|
+
const row = await deps.db.query_one(`DELETE FROM cell_field WHERE source_id = $1 AND name = $2
|
|
80
|
+
RETURNING ${columns_sql(CELL_FIELD_COLUMNS)}`, [source_id, name]);
|
|
67
81
|
return row ?? null;
|
|
68
82
|
};
|
|
69
83
|
/**
|
|
@@ -75,12 +89,14 @@ export const query_cell_field_delete = async (deps, source_id, name) => {
|
|
|
75
89
|
*
|
|
76
90
|
* @param deps - query deps
|
|
77
91
|
* @param source_id - source cell id
|
|
78
|
-
* @
|
|
92
|
+
* @param options - `limit` caps the row count; `name_after` resumes after a
|
|
93
|
+
* name (keyset pagination)
|
|
94
|
+
* @returns matching rows in lex `name` order
|
|
79
95
|
*/
|
|
80
96
|
export const query_cell_field_list_for_source = async (deps, source_id, options) => {
|
|
81
97
|
const limit = options?.limit ?? null;
|
|
82
98
|
const name_after = options?.name_after ?? null;
|
|
83
|
-
return deps.db.query(`SELECT f
|
|
99
|
+
return deps.db.query(`SELECT ${qualify_columns(CELL_FIELD_COLUMNS, 'f')} FROM cell_field f
|
|
84
100
|
JOIN cell t ON t.id = f.target_id
|
|
85
101
|
WHERE f.source_id = $1
|
|
86
102
|
AND t.deleted_at IS NULL
|
|
@@ -103,9 +119,9 @@ export const query_cell_field_list_for_source = async (deps, source_id, options)
|
|
|
103
119
|
* @param deps - query deps
|
|
104
120
|
* @param target_id - target cell id
|
|
105
121
|
* @param options - `limit` caps the row count
|
|
106
|
-
* @returns matching rows, oldest first by
|
|
122
|
+
* @returns matching rows, oldest field first (by the field row's `created_at`)
|
|
107
123
|
*/
|
|
108
|
-
export const query_cell_field_list_for_target = async (deps, target_id, options) => deps.db.query(`SELECT f
|
|
124
|
+
export const query_cell_field_list_for_target = async (deps, target_id, options) => deps.db.query(`SELECT ${qualify_columns(CELL_FIELD_COLUMNS, 'f')} FROM cell_field f
|
|
109
125
|
JOIN cell s ON s.id = f.source_id
|
|
110
126
|
WHERE f.target_id = $1
|
|
111
127
|
AND s.deleted_at IS NULL
|
|
@@ -35,6 +35,13 @@ export interface CellGrantRow {
|
|
|
35
35
|
granted_by: Uuid | null;
|
|
36
36
|
created_at: Date;
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* The full `cell_grant` column set, named explicitly so a row read fails
|
|
40
|
+
* loud on schema drift (see `CELL_COLUMNS` in `db/cell_queries.ts`). Column
|
|
41
|
+
* order mirrors the Rust twin's `grant_columns(alias)` (`fuz_cell`). Keep in
|
|
42
|
+
* sync with `CellGrantRow` and the `cell_grant` DDL in `db/cell_ddl.ts`.
|
|
43
|
+
*/
|
|
44
|
+
export declare const CELL_GRANT_COLUMNS: readonly ["id", "cell_id", "level", "actor_id", "role", "scope_id", "granted_by", "created_at"];
|
|
38
45
|
/**
|
|
39
46
|
* Discriminated principal input for `query_cell_grant_create`. Wire
|
|
40
47
|
* and query shapes are aligned — actor-shaped principals carry a
|
|
@@ -121,6 +128,7 @@ export declare const query_cell_grant_list_for_cells: (deps: QueryDeps, cell_ids
|
|
|
121
128
|
* granted access. Returns grants for the given cells that match the caller's
|
|
122
129
|
* identity or role_grant set.
|
|
123
130
|
*
|
|
131
|
+
* @param deps - query deps
|
|
124
132
|
* @param cell_ids - cells to fetch grants for
|
|
125
133
|
* @param caller_actor_id - actor id of the caller (null for unauth)
|
|
126
134
|
* @param role_grant_roles - active role_grant roles (parallel array)
|
|
@@ -128,5 +136,5 @@ export declare const query_cell_grant_list_for_cells: (deps: QueryDeps, cell_ids
|
|
|
128
136
|
* @returns matching grants (may include grants the caller doesn't match; caller's
|
|
129
137
|
* list handler must filter when returning to the API)
|
|
130
138
|
*/
|
|
131
|
-
export declare const query_cell_grants_for_caller_in_cells: (deps: QueryDeps, cell_ids:
|
|
139
|
+
export declare const query_cell_grants_for_caller_in_cells: (deps: QueryDeps, cell_ids: ReadonlyArray<Uuid>, caller_actor_id: Uuid | null, role_grant_roles: ReadonlyArray<string>, role_grant_scope_ids: ReadonlyArray<Uuid | null>) => Promise<Array<CellGrantRow>>;
|
|
132
140
|
//# sourceMappingURL=cell_grant_queries.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cell_grant_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/cell_grant_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"cell_grant_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/cell_grant_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAIzE,kDAAkD;AAClD,MAAM,WAAW,YAAY;IAC5B,EAAE,EAAE,IAAI,CAAC;IACT,OAAO,EAAE,IAAI,CAAC;IACd,KAAK,EAAE,cAAc,CAAC;IACtB,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,IAAI,CAAC;CACjB;AAED;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,iGASuB,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,MAAM,4BAA4B,GACvC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAA;CAAE,CAAC;AAE3F,2CAA2C;AAC3C,MAAM,WAAW,yBAAyB;IACzC,OAAO,EAAE,IAAI,CAAC;IACd,KAAK,EAAE,cAAc,CAAC;IACtB,SAAS,EAAE,4BAA4B,CAAC;IACxC,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;CACxB;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,SAAS,EACf,OAAO,yBAAyB,KAC9B,OAAO,CAAC,YAAY,CAsBtB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,SAAS,EACf,UAAU,IAAI,KACZ,OAAO,CAAC,YAAY,GAAG,IAAI,CAM7B,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,SAAS,EACf,UAAU,IAAI,KACZ,OAAO,CAAC,YAAY,GAAG,IAAI,CAM7B,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,SAAS,EACf,SAAS,IAAI,KACX,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAM5B,CAAC;AAEH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,+BAA+B,GAC3C,MAAM,SAAS,EACf,UAAU,aAAa,CAAC,IAAI,CAAC,KAC3B,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAQ7B,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,qCAAqC,GACjD,MAAM,SAAS,EACf,UAAU,aAAa,CAAC,IAAI,CAAC,EAC7B,iBAAiB,IAAI,GAAG,IAAI,EAC5B,kBAAkB,aAAa,CAAC,MAAM,CAAC,EACvC,sBAAsB,aAAa,CAAC,IAAI,GAAG,IAAI,CAAC,KAC9C,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAmB7B,CAAC"}
|
|
@@ -22,6 +22,23 @@
|
|
|
22
22
|
* @module
|
|
23
23
|
*/
|
|
24
24
|
import { assert_row } from './assert_row.js';
|
|
25
|
+
import { columns_sql, qualify_columns } from './sql_columns.js';
|
|
26
|
+
/**
|
|
27
|
+
* The full `cell_grant` column set, named explicitly so a row read fails
|
|
28
|
+
* loud on schema drift (see `CELL_COLUMNS` in `db/cell_queries.ts`). Column
|
|
29
|
+
* order mirrors the Rust twin's `grant_columns(alias)` (`fuz_cell`). Keep in
|
|
30
|
+
* sync with `CellGrantRow` and the `cell_grant` DDL in `db/cell_ddl.ts`.
|
|
31
|
+
*/
|
|
32
|
+
export const CELL_GRANT_COLUMNS = [
|
|
33
|
+
'id',
|
|
34
|
+
'cell_id',
|
|
35
|
+
'level',
|
|
36
|
+
'actor_id',
|
|
37
|
+
'role',
|
|
38
|
+
'scope_id',
|
|
39
|
+
'granted_by',
|
|
40
|
+
'created_at'
|
|
41
|
+
];
|
|
25
42
|
/**
|
|
26
43
|
* Insert a grant, or update the existing row's `level` + `granted_by` when
|
|
27
44
|
* one already exists for the same `(cell_id, principal)` pair.
|
|
@@ -43,14 +60,14 @@ export const query_cell_grant_create = async (deps, input) => {
|
|
|
43
60
|
VALUES ($1, $2, $3, $4)
|
|
44
61
|
ON CONFLICT (cell_id, actor_id) WHERE actor_id IS NOT NULL
|
|
45
62
|
DO UPDATE SET level = EXCLUDED.level, granted_by = EXCLUDED.granted_by
|
|
46
|
-
RETURNING
|
|
63
|
+
RETURNING ${columns_sql(CELL_GRANT_COLUMNS)}`, [cell_id, level, principal.actor_id, granted_by]);
|
|
47
64
|
return assert_row(row, 'INSERT INTO cell_grant (actor)');
|
|
48
65
|
}
|
|
49
66
|
const row = await deps.db.query_one(`INSERT INTO cell_grant (cell_id, level, role, scope_id, granted_by)
|
|
50
67
|
VALUES ($1, $2, $3, $4, $5)
|
|
51
68
|
ON CONFLICT (cell_id, role, scope_id) WHERE role IS NOT NULL
|
|
52
69
|
DO UPDATE SET level = EXCLUDED.level, granted_by = EXCLUDED.granted_by
|
|
53
|
-
RETURNING
|
|
70
|
+
RETURNING ${columns_sql(CELL_GRANT_COLUMNS)}`, [cell_id, level, principal.role, principal.scope_id, granted_by]);
|
|
54
71
|
return assert_row(row, 'INSERT INTO cell_grant (role)');
|
|
55
72
|
};
|
|
56
73
|
/**
|
|
@@ -61,9 +78,7 @@ export const query_cell_grant_create = async (deps, input) => {
|
|
|
61
78
|
* @returns the row or `null` when not found
|
|
62
79
|
*/
|
|
63
80
|
export const query_cell_grant_get = async (deps, grant_id) => {
|
|
64
|
-
const row = await deps.db.query_one(`SELECT
|
|
65
|
-
grant_id
|
|
66
|
-
]);
|
|
81
|
+
const row = await deps.db.query_one(`SELECT ${columns_sql(CELL_GRANT_COLUMNS)} FROM cell_grant WHERE id = $1`, [grant_id]);
|
|
67
82
|
return row ?? null;
|
|
68
83
|
};
|
|
69
84
|
/**
|
|
@@ -79,7 +94,7 @@ export const query_cell_grant_get = async (deps, grant_id) => {
|
|
|
79
94
|
* @mutates `cell_grant` - deletes one row
|
|
80
95
|
*/
|
|
81
96
|
export const query_cell_grant_delete = async (deps, grant_id) => {
|
|
82
|
-
const row = await deps.db.query_one(`DELETE FROM cell_grant WHERE id = $1 RETURNING
|
|
97
|
+
const row = await deps.db.query_one(`DELETE FROM cell_grant WHERE id = $1 RETURNING ${columns_sql(CELL_GRANT_COLUMNS)}`, [grant_id]);
|
|
83
98
|
return row ?? null;
|
|
84
99
|
};
|
|
85
100
|
/**
|
|
@@ -92,7 +107,7 @@ export const query_cell_grant_delete = async (deps, grant_id) => {
|
|
|
92
107
|
* @param cell_id - cell id
|
|
93
108
|
* @returns matching rows
|
|
94
109
|
*/
|
|
95
|
-
export const query_cell_grant_list_for_cell = async (deps, cell_id) => deps.db.query(`SELECT
|
|
110
|
+
export const query_cell_grant_list_for_cell = async (deps, cell_id) => deps.db.query(`SELECT ${columns_sql(CELL_GRANT_COLUMNS)} FROM cell_grant
|
|
96
111
|
WHERE cell_id = $1
|
|
97
112
|
ORDER BY created_at ASC`, [cell_id]);
|
|
98
113
|
/**
|
|
@@ -109,7 +124,7 @@ export const query_cell_grant_list_for_cell = async (deps, cell_id) => deps.db.q
|
|
|
109
124
|
export const query_cell_grant_list_for_cells = async (deps, cell_ids) => {
|
|
110
125
|
if (cell_ids.length === 0)
|
|
111
126
|
return [];
|
|
112
|
-
return deps.db.query(`SELECT
|
|
127
|
+
return deps.db.query(`SELECT ${columns_sql(CELL_GRANT_COLUMNS)} FROM cell_grant
|
|
113
128
|
WHERE cell_id = ANY($1::uuid[])
|
|
114
129
|
ORDER BY cell_id, created_at ASC`, [cell_ids]);
|
|
115
130
|
};
|
|
@@ -119,6 +134,7 @@ export const query_cell_grant_list_for_cells = async (deps, cell_ids) => {
|
|
|
119
134
|
* granted access. Returns grants for the given cells that match the caller's
|
|
120
135
|
* identity or role_grant set.
|
|
121
136
|
*
|
|
137
|
+
* @param deps - query deps
|
|
122
138
|
* @param cell_ids - cells to fetch grants for
|
|
123
139
|
* @param caller_actor_id - actor id of the caller (null for unauth)
|
|
124
140
|
* @param role_grant_roles - active role_grant roles (parallel array)
|
|
@@ -130,7 +146,7 @@ export const query_cell_grants_for_caller_in_cells = async (deps, cell_ids, call
|
|
|
130
146
|
if (cell_ids.length === 0) {
|
|
131
147
|
return [];
|
|
132
148
|
}
|
|
133
|
-
return deps.db.query(`SELECT g
|
|
149
|
+
return deps.db.query(`SELECT ${qualify_columns(CELL_GRANT_COLUMNS, 'g')} FROM cell_grant g
|
|
134
150
|
WHERE g.cell_id = ANY($1::uuid[])
|
|
135
151
|
AND (
|
|
136
152
|
g.actor_id = $2
|
|
@@ -28,6 +28,13 @@ export interface CellItemRow {
|
|
|
28
28
|
child_id: Uuid;
|
|
29
29
|
created_at: Date;
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* The full `cell_item` column set, named explicitly so a row read fails
|
|
33
|
+
* loud on schema drift (see `CELL_COLUMNS` in `db/cell_queries.ts`). Column
|
|
34
|
+
* order mirrors the Rust twin's `item_columns(alias)` (`fuz_cell`). Keep in
|
|
35
|
+
* sync with `CellItemRow` and the `cell_item` DDL in `db/cell_ddl.ts`.
|
|
36
|
+
*/
|
|
37
|
+
export declare const CELL_ITEM_COLUMNS: readonly ["parent_id", "position", "child_id", "created_at"];
|
|
31
38
|
/** Input for `query_cell_item_insert`. */
|
|
32
39
|
export interface CellItemInsertQueryInput {
|
|
33
40
|
parent_id: Uuid;
|
|
@@ -83,7 +90,11 @@ export declare const query_cell_item_delete: (deps: QueryDeps, parent_id: Uuid,
|
|
|
83
90
|
* cells don't surface; the parent filter is the caller's responsibility
|
|
84
91
|
* (gated upstream by `can_view_cell(parent)`).
|
|
85
92
|
*
|
|
86
|
-
* @param
|
|
93
|
+
* @param deps - query deps
|
|
94
|
+
* @param parent_id - parent cell id
|
|
95
|
+
* @param options - `limit` caps the row count (passes through to SQL `LIMIT`);
|
|
96
|
+
* `position_after` resumes after a position (keyset pagination)
|
|
97
|
+
* @returns matching rows in lex `position` order
|
|
87
98
|
*/
|
|
88
99
|
export declare const query_cell_item_list_for_parent: (deps: QueryDeps, parent_id: Uuid, options?: {
|
|
89
100
|
limit?: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cell_item_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/cell_item_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"cell_item_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/cell_item_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAInD,iDAAiD;AACjD,MAAM,WAAW,WAAW;IAC3B,SAAS,EAAE,IAAI,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,IAAI,CAAC;IACf,UAAU,EAAE,IAAI,CAAC;CACjB;AAED;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,8DAKuB,CAAC;AAEtD,0CAA0C;AAC1C,MAAM,WAAW,wBAAwB;IACxC,SAAS,EAAE,IAAI,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,IAAI,CAAC;CACf;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,SAAS,EACf,OAAO,wBAAwB,KAC7B,OAAO,CAAC,WAAW,CAQrB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAC/B,MAAM,SAAS,EACf,WAAW,IAAI,EACf,UAAU,MAAM,KACd,OAAO,CAAC,WAAW,GAAG,IAAI,CAM5B,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,SAAS,EACf,WAAW,IAAI,EACf,cAAc,MAAM,EACpB,cAAc,MAAM,KAClB,OAAO,CAAC,WAAW,GAAG,IAAI,CAS5B,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,SAAS,EACf,WAAW,IAAI,EACf,UAAU,MAAM,KACd,OAAO,CAAC,WAAW,GAAG,IAAI,CAO5B,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,+BAA+B,GAC3C,MAAM,SAAS,EACf,WAAW,IAAI,EACf,UAAU;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,KACnD,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAa5B,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,SAAS,EACf,UAAU,IAAI,EACd,UAAU;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,KAC1B,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAS3B,CAAC"}
|