@fuzdev/fuz_app 0.108.0 → 0.110.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/README.md +4 -0
- package/dist/actions/CLAUDE.md +1 -1
- package/dist/auth/CLAUDE.md +7 -6
- package/dist/auth/account_action_specs.d.ts +18 -0
- package/dist/auth/account_action_specs.d.ts.map +1 -1
- package/dist/auth/account_action_specs.js +14 -1
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +6 -2
- package/dist/auth/audit_emitter.d.ts +9 -5
- package/dist/auth/audit_emitter.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.d.ts +7 -1
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +16 -1
- package/dist/auth/bootstrap_account.d.ts +7 -2
- package/dist/auth/bootstrap_account.d.ts.map +1 -1
- package/dist/auth/bootstrap_account.js +9 -4
- package/dist/auth/bootstrap_routes.d.ts +12 -4
- package/dist/auth/bootstrap_routes.d.ts.map +1 -1
- package/dist/auth/bootstrap_routes.js +26 -7
- package/dist/auth/daemon_token_middleware.d.ts +7 -69
- package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
- package/dist/auth/daemon_token_middleware.js +8 -104
- package/dist/auth/deps.d.ts +9 -5
- package/dist/auth/deps.d.ts.map +1 -1
- package/dist/auth/middleware.js +1 -1
- package/dist/auth/migrations.d.ts +7 -0
- package/dist/auth/migrations.d.ts.map +1 -1
- package/dist/auth/migrations.js +25 -8
- package/dist/auth/request_context.d.ts +1 -3
- package/dist/auth/request_context.d.ts.map +1 -1
- package/dist/auth/request_context.js +2 -5
- package/dist/auth/session_cookie.d.ts +18 -36
- package/dist/auth/session_cookie.d.ts.map +1 -1
- package/dist/auth/session_cookie.js +16 -26
- package/dist/auth/session_middleware.d.ts +1 -1
- package/dist/auth/session_middleware.d.ts.map +1 -1
- package/dist/auth/session_middleware.js +1 -4
- package/dist/auth/session_queries.d.ts +14 -30
- package/dist/auth/session_queries.d.ts.map +1 -1
- package/dist/auth/session_queries.js +15 -45
- package/dist/auth/token_lifetime.d.ts +51 -0
- package/dist/auth/token_lifetime.d.ts.map +1 -0
- package/dist/auth/token_lifetime.js +54 -0
- package/dist/db/CLAUDE.md +9 -5
- package/dist/db/cell_audit_queries.d.ts +4 -2
- package/dist/db/cell_audit_queries.d.ts.map +1 -1
- package/dist/db/cell_audit_queries.js +4 -2
- package/dist/db/pg_error.d.ts +12 -0
- package/dist/db/pg_error.d.ts.map +1 -1
- package/dist/db/pg_error.js +15 -1
- package/dist/dev/setup.d.ts +5 -9
- package/dist/dev/setup.d.ts.map +1 -1
- package/dist/dev/setup.js +18 -21
- package/dist/http/CLAUDE.md +78 -18
- package/dist/http/db_routes.d.ts +96 -3
- package/dist/http/db_routes.d.ts.map +1 -1
- package/dist/http/db_routes.js +263 -46
- package/dist/http/error_schemas.d.ts +12 -1
- package/dist/http/error_schemas.d.ts.map +1 -1
- package/dist/http/error_schemas.js +12 -1
- package/dist/http/pending_effects.d.ts +5 -5
- package/dist/http/pending_effects.d.ts.map +1 -1
- package/dist/http/pending_effects.js +1 -1
- package/dist/runtime/deno.d.ts.map +1 -1
- package/dist/runtime/deno.js +27 -1
- package/dist/runtime/deps.d.ts +38 -4
- package/dist/runtime/deps.d.ts.map +1 -1
- package/dist/runtime/fs.d.ts +24 -5
- package/dist/runtime/fs.d.ts.map +1 -1
- package/dist/runtime/fs.js +38 -7
- package/dist/runtime/mock.d.ts +7 -0
- package/dist/runtime/mock.d.ts.map +1 -1
- package/dist/runtime/mock.js +41 -1
- package/dist/runtime/node.d.ts.map +1 -1
- package/dist/runtime/node.js +7 -1
- package/dist/runtime/secure_file.d.ts +71 -0
- package/dist/runtime/secure_file.d.ts.map +1 -0
- package/dist/runtime/secure_file.js +125 -0
- package/dist/server/app_backend.d.ts +6 -5
- package/dist/server/app_backend.d.ts.map +1 -1
- package/dist/server/app_backend.js +2 -3
- package/dist/testing/CLAUDE.md +36 -1
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +10 -2
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +13 -8
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +10 -2
- package/dist/testing/cross_backend/cell_grant_role.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_grant_role.js +34 -1
- package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_relations.js +53 -1
- package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
- package/dist/testing/cross_backend/conformance_table.js +2 -1
- package/dist/testing/cross_backend/query_shape.d.ts +10 -0
- package/dist/testing/cross_backend/query_shape.d.ts.map +1 -0
- package/dist/testing/cross_backend/query_shape.js +128 -0
- package/dist/testing/cross_backend/setup.js +1 -1
- package/dist/testing/cross_backend/token_lifetime.d.ts +15 -0
- package/dist/testing/cross_backend/token_lifetime.d.ts.map +1 -0
- package/dist/testing/cross_backend/token_lifetime.js +99 -0
- package/dist/testing/cross_backend/token_scope_surface.js +1 -1
- package/dist/testing/daemon_token_rotation.d.ts +83 -0
- package/dist/testing/daemon_token_rotation.d.ts.map +1 -0
- package/dist/testing/daemon_token_rotation.js +99 -0
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +15 -3
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +4 -4
- package/dist/ui/AccountSessions.svelte +0 -5
- package/dist/ui/AccountSessions.svelte.d.ts.map +1 -1
- package/dist/ui/AdminOverview.svelte +7 -4
- package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
- package/dist/ui/AdminSessions.svelte +0 -5
- package/dist/ui/AdminSessions.svelte.d.ts.map +1 -1
- package/dist/ui/CLAUDE.md +9 -4
- package/dist/ui/table_state.svelte.d.ts +18 -5
- package/dist/ui/table_state.svelte.d.ts.map +1 -1
- package/dist/ui/table_state.svelte.js +22 -7
- package/dist/ui/ui_format.d.ts.map +1 -1
- package/dist/ui/ui_format.js +4 -0
- package/package.json +1 -1
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import './assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Daemon-token **producer** — rotation + file persistence, test-harness only.
|
|
4
|
+
*
|
|
5
|
+
* No production assembly mints daemon tokens: the credential's only remaining
|
|
6
|
+
* role is the cross-process test harness's keeper channel (`_testing_reset`
|
|
7
|
+
* etc.), so the producer lives here behind `assert_dev_env` where it cannot
|
|
8
|
+
* reach a production bundle. The *consumer* half — validating a presented
|
|
9
|
+
* `X-Daemon-Token` — stays in `auth/daemon_token_middleware.ts`, mirroring
|
|
10
|
+
* the Rust spine, whose producer is confined to `fuz_testing`.
|
|
11
|
+
*
|
|
12
|
+
* The token file is written atomically at mode `0600` via
|
|
13
|
+
* `write_file_atomic` (unique temp name + exclusive create), so it never
|
|
14
|
+
* exists group/other-readable — the old optional-`chmod` pattern let a
|
|
15
|
+
* default umask land it at `0644`.
|
|
16
|
+
*
|
|
17
|
+
* @module
|
|
18
|
+
*/
|
|
19
|
+
import type { Logger } from '@fuzdev/fuz_util/log.ts';
|
|
20
|
+
import type { EnvDeps, FsWriteDeps, FsRemoveDeps } from '../runtime/deps.js';
|
|
21
|
+
import type { QueryDeps } from '../db/query_deps.js';
|
|
22
|
+
import { type DaemonTokenState } from '../auth/daemon_token.js';
|
|
23
|
+
/** Default rotation interval in milliseconds (30 seconds). */
|
|
24
|
+
export declare const DEFAULT_ROTATION_INTERVAL_MS = 30000;
|
|
25
|
+
/** Deps for writing the daemon token to disk. */
|
|
26
|
+
export type DaemonTokenWriteDeps = Pick<EnvDeps, 'env_get'> & Pick<FsWriteDeps, 'mkdir' | 'write_text_file' | 'rename'>;
|
|
27
|
+
/**
|
|
28
|
+
* Get the daemon token file path (`~/.{name}/run/daemon_token`).
|
|
29
|
+
*
|
|
30
|
+
* @param runtime - runtime with `env_get` capability
|
|
31
|
+
* @param name - application name
|
|
32
|
+
* @returns path to `daemon_token`, or `null` if `$HOME` is not set
|
|
33
|
+
*/
|
|
34
|
+
export declare const get_daemon_token_path: (runtime: Pick<EnvDeps, "env_get">, name: string) => string | null;
|
|
35
|
+
/**
|
|
36
|
+
* Write the current token to disk atomically at mode `0600`.
|
|
37
|
+
*
|
|
38
|
+
* On-disk format is JSON `{"token": "..."}` — the wrapper leaves room for
|
|
39
|
+
* future fields (rotated_at, version) without changing every reader. Both
|
|
40
|
+
* the TS cross-backend harness reader (`spawn_backend.read_daemon_token`)
|
|
41
|
+
* and the Rust daemon-token writer match this shape.
|
|
42
|
+
*
|
|
43
|
+
* @param runtime - runtime with file write capabilities
|
|
44
|
+
* @param token_path - path to write the token
|
|
45
|
+
* @param token - the raw token string
|
|
46
|
+
* @mutates filesystem - writes `token_path` atomically at mode `0600`
|
|
47
|
+
*/
|
|
48
|
+
export declare const write_daemon_token: (runtime: DaemonTokenWriteDeps, token_path: string, token: string) => Promise<void>;
|
|
49
|
+
/** Options for daemon token rotation. */
|
|
50
|
+
export interface DaemonTokenRotationOptions {
|
|
51
|
+
/**
|
|
52
|
+
* Absolute path the token file is written to. Caller computes from
|
|
53
|
+
* its own conventions — e.g. `get_daemon_token_path(runtime, app_name)`
|
|
54
|
+
* for the standard `~/.{name}/run/daemon_token` layout, or a path
|
|
55
|
+
* derived from `PUBLIC_<APP>_DIR` for cross-process test setups that
|
|
56
|
+
* isolate the app dir to a tmpdir.
|
|
57
|
+
*/
|
|
58
|
+
token_path: string;
|
|
59
|
+
/** Rotation interval in ms. Default: `30000` (30s). */
|
|
60
|
+
rotation_interval_ms?: number;
|
|
61
|
+
}
|
|
62
|
+
/** Result of starting daemon token rotation. */
|
|
63
|
+
export interface DaemonTokenRotation {
|
|
64
|
+
/** The mutable runtime state. Pass to `create_daemon_token_middleware`. */
|
|
65
|
+
state: DaemonTokenState;
|
|
66
|
+
/** Stop rotation, clean up the interval, and delete the token file. Call on graceful shutdown. */
|
|
67
|
+
stop: () => Promise<void>;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Start daemon token rotation.
|
|
71
|
+
*
|
|
72
|
+
* Generates an initial token, writes it to disk, resolves the keeper account,
|
|
73
|
+
* and sets up periodic rotation. Returns the mutable state object and a stop function.
|
|
74
|
+
*
|
|
75
|
+
* @param runtime - runtime with file and remove capabilities
|
|
76
|
+
* @param deps - query dependencies for resolving keeper account
|
|
77
|
+
* @param options - rotation configuration
|
|
78
|
+
* @param log - the logger instance
|
|
79
|
+
* @returns rotation state and stop function
|
|
80
|
+
* @mutates filesystem - writes the token file on each rotation; `stop` removes it
|
|
81
|
+
*/
|
|
82
|
+
export declare const start_daemon_token_rotation: (runtime: DaemonTokenWriteDeps & FsRemoveDeps, deps: QueryDeps, options: DaemonTokenRotationOptions, log: Logger) => Promise<DaemonTokenRotation>;
|
|
83
|
+
//# sourceMappingURL=daemon_token_rotation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon_token_rotation.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/daemon_token_rotation.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAG7E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAyB,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAGvF,8DAA8D;AAC9D,eAAO,MAAM,4BAA4B,QAAS,CAAC;AAEnD,iDAAiD;AACjD,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,GAC1D,IAAI,CAAC,WAAW,EAAE,OAAO,GAAG,iBAAiB,GAAG,QAAQ,CAAC,CAAC;AAE3D;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,GACjC,SAAS,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,EACjC,MAAM,MAAM,KACV,MAAM,GAAG,IAGX,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,kBAAkB,GAC9B,SAAS,oBAAoB,EAC7B,YAAY,MAAM,EAClB,OAAO,MAAM,KACX,OAAO,CAAC,IAAI,CAId,CAAC;AAEF,yCAAyC;AACzC,MAAM,WAAW,0BAA0B;IAC1C;;;;;;OAMG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,gDAAgD;AAChD,MAAM,WAAW,mBAAmB;IACnC,2EAA2E;IAC3E,KAAK,EAAE,gBAAgB,CAAC;IACxB,kGAAkG;IAClG,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,2BAA2B,GACvC,SAAS,oBAAoB,GAAG,YAAY,EAC5C,MAAM,SAAS,EACf,SAAS,0BAA0B,EACnC,KAAK,MAAM,KACT,OAAO,CAAC,mBAAmB,CAqD7B,CAAC"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import './assert_dev_env.js';
|
|
2
|
+
import { write_file_atomic } from '../runtime/fs.js';
|
|
3
|
+
import { get_app_dir } from '../cli/config.js';
|
|
4
|
+
import { generate_daemon_token } from '../auth/daemon_token.js';
|
|
5
|
+
import { resolve_keeper_account_id } from '../auth/daemon_token_middleware.js';
|
|
6
|
+
/** Default rotation interval in milliseconds (30 seconds). */
|
|
7
|
+
export const DEFAULT_ROTATION_INTERVAL_MS = 30_000;
|
|
8
|
+
/**
|
|
9
|
+
* Get the daemon token file path (`~/.{name}/run/daemon_token`).
|
|
10
|
+
*
|
|
11
|
+
* @param runtime - runtime with `env_get` capability
|
|
12
|
+
* @param name - application name
|
|
13
|
+
* @returns path to `daemon_token`, or `null` if `$HOME` is not set
|
|
14
|
+
*/
|
|
15
|
+
export const get_daemon_token_path = (runtime, name) => {
|
|
16
|
+
const app_dir = get_app_dir(runtime, name);
|
|
17
|
+
return app_dir ? `${app_dir}/run/daemon_token` : null;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Write the current token to disk atomically at mode `0600`.
|
|
21
|
+
*
|
|
22
|
+
* On-disk format is JSON `{"token": "..."}` — the wrapper leaves room for
|
|
23
|
+
* future fields (rotated_at, version) without changing every reader. Both
|
|
24
|
+
* the TS cross-backend harness reader (`spawn_backend.read_daemon_token`)
|
|
25
|
+
* and the Rust daemon-token writer match this shape.
|
|
26
|
+
*
|
|
27
|
+
* @param runtime - runtime with file write capabilities
|
|
28
|
+
* @param token_path - path to write the token
|
|
29
|
+
* @param token - the raw token string
|
|
30
|
+
* @mutates filesystem - writes `token_path` atomically at mode `0600`
|
|
31
|
+
*/
|
|
32
|
+
export const write_daemon_token = async (runtime, token_path, token) => {
|
|
33
|
+
await write_file_atomic(runtime, token_path, JSON.stringify({ token }) + '\n', {
|
|
34
|
+
mode: 0o600
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Start daemon token rotation.
|
|
39
|
+
*
|
|
40
|
+
* Generates an initial token, writes it to disk, resolves the keeper account,
|
|
41
|
+
* and sets up periodic rotation. Returns the mutable state object and a stop function.
|
|
42
|
+
*
|
|
43
|
+
* @param runtime - runtime with file and remove capabilities
|
|
44
|
+
* @param deps - query dependencies for resolving keeper account
|
|
45
|
+
* @param options - rotation configuration
|
|
46
|
+
* @param log - the logger instance
|
|
47
|
+
* @returns rotation state and stop function
|
|
48
|
+
* @mutates filesystem - writes the token file on each rotation; `stop` removes it
|
|
49
|
+
*/
|
|
50
|
+
export const start_daemon_token_rotation = async (runtime, deps, options, log) => {
|
|
51
|
+
const { token_path, rotation_interval_ms = DEFAULT_ROTATION_INTERVAL_MS } = options;
|
|
52
|
+
// ensure parent directory exists
|
|
53
|
+
const last_slash = token_path.lastIndexOf('/');
|
|
54
|
+
if (last_slash > 0) {
|
|
55
|
+
await runtime.mkdir(token_path.slice(0, last_slash), { recursive: true });
|
|
56
|
+
}
|
|
57
|
+
// resolve keeper account (may be null pre-bootstrap; the middleware
|
|
58
|
+
// lazily refreshes on the first null hit to cover the
|
|
59
|
+
// rotation-starts-before-bootstrap case)
|
|
60
|
+
const keeper_account_id = await resolve_keeper_account_id(deps);
|
|
61
|
+
// generate initial token and write to disk
|
|
62
|
+
const initial_token = generate_daemon_token();
|
|
63
|
+
await write_daemon_token(runtime, token_path, initial_token);
|
|
64
|
+
const state = {
|
|
65
|
+
current_token: initial_token,
|
|
66
|
+
previous_token: null,
|
|
67
|
+
rotated_at: new Date(),
|
|
68
|
+
keeper_account_id
|
|
69
|
+
};
|
|
70
|
+
let writing = false;
|
|
71
|
+
const interval_id = setInterval(async () => {
|
|
72
|
+
if (writing)
|
|
73
|
+
return; // skip if previous rotation write still in progress
|
|
74
|
+
writing = true;
|
|
75
|
+
try {
|
|
76
|
+
const new_token = generate_daemon_token();
|
|
77
|
+
state.previous_token = state.current_token;
|
|
78
|
+
state.current_token = new_token;
|
|
79
|
+
state.rotated_at = new Date();
|
|
80
|
+
await write_daemon_token(runtime, token_path, new_token);
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
log.error('Failed to write rotated token:', err);
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
writing = false;
|
|
87
|
+
}
|
|
88
|
+
}, rotation_interval_ms);
|
|
89
|
+
const stop = async () => {
|
|
90
|
+
clearInterval(interval_id);
|
|
91
|
+
try {
|
|
92
|
+
await runtime.remove(token_path);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// already removed or never written
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
return { state, stop };
|
|
99
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAsB7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAMhE,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAkB1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAW,KAAK,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACpF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAG1D;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC9C;;;;OAIG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;;;;;;;OASG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,kEAAkE;IAClE,YAAY,EAAE,mBAAmB,CAAC;IAClC;;;;OAIG;IACH,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;;;;;;;;;;OAYG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC;;;;;;;OAOG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,mCAAmC,GAC/C,SAAS,8BAA8B,KACrC,
|
|
1
|
+
{"version":3,"file":"integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAsB7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAMhE,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAkB1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAW,KAAK,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACpF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAG1D;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC9C;;;;OAIG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;;;;;;;OASG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,kEAAkE;IAClE,YAAY,EAAE,mBAAmB,CAAC;IAClC;;;;OAIG;IACH,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;;;;;;;;;;OAYG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC;;;;;;;OAOG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,mCAAmC,GAC/C,SAAS,8BAA8B,KACrC,IA07CF,CAAC"}
|
|
@@ -634,7 +634,11 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
634
634
|
app: { request: fixture.transport },
|
|
635
635
|
path: rpc_path,
|
|
636
636
|
spec: account_token_create_action_spec,
|
|
637
|
-
params: {
|
|
637
|
+
params: {
|
|
638
|
+
name: 'test-revoke',
|
|
639
|
+
scope: { kind: 'full' },
|
|
640
|
+
lifetime: { kind: 'eternal' }
|
|
641
|
+
},
|
|
638
642
|
headers: fixture.create_session_headers()
|
|
639
643
|
});
|
|
640
644
|
assert.ok(create_res.ok, 'account_token_create should succeed');
|
|
@@ -943,7 +947,11 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
943
947
|
app: { request: unauthed },
|
|
944
948
|
path: rpc_path,
|
|
945
949
|
spec: account_token_create_action_spec,
|
|
946
|
-
params: {
|
|
950
|
+
params: {
|
|
951
|
+
name: 'unauth-probe',
|
|
952
|
+
scope: { kind: 'full' },
|
|
953
|
+
lifetime: { kind: 'eternal' }
|
|
954
|
+
},
|
|
947
955
|
headers: { host: 'localhost' }
|
|
948
956
|
});
|
|
949
957
|
assert.strictEqual(token_create.status, 401);
|
|
@@ -1039,7 +1047,11 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
1039
1047
|
app: { request: fixture.transport },
|
|
1040
1048
|
path: rpc_path,
|
|
1041
1049
|
spec: account_token_create_action_spec,
|
|
1042
|
-
params: {
|
|
1050
|
+
params: {
|
|
1051
|
+
name: 'test-token',
|
|
1052
|
+
scope: { kind: 'full' },
|
|
1053
|
+
lifetime: { kind: 'eternal' }
|
|
1054
|
+
},
|
|
1043
1055
|
headers: fixture.create_session_headers()
|
|
1044
1056
|
});
|
|
1045
1057
|
assert.ok(create_res.ok, 'account_token_create should succeed');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stubs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/stubs.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAa7B,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAEjE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACjC,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAG3E,OAAO,EAEN,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,KAAK,EAAE,SAAS,EAAmB,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAA+B,KAAK,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAI9F;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,oBAAoB,GAAI,CAAC,GAAG,GAAG,EAAE,OAAO,MAAM,KAAG,CAqBtD,CAAC;AAET;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,GAAI,CAAC,GAAG,GAAG,EAAE,QAAQ,MAAM,EAAE,YAAY,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,CAOxF,CAAC;AAET,iEAAiE;AACjE,eAAO,MAAM,IAAI,EAAE,GAAkC,CAAC;AAEtD;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,QAAO,EAI/B,CAAC;AAEJ,gDAAgD;AAChD,eAAO,MAAM,YAAY,QAAO,QAAgC,CAAC;AAEjE,2CAA2C;AAC3C,eAAO,MAAM,OAAO,GAAU,IAAI,GAAG,EAAE,MAAM,GAAG,KAAG,OAAO,CAAC,IAAI,CAAW,CAAC;AAI3E;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB,QAAO,YAW3C,CAAC;AAEH;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,QAAO,WAUxC,CAAC;AAEF,2EAA2E;AAC3E,eAAO,MAAM,aAAa,EAAE,
|
|
1
|
+
{"version":3,"file":"stubs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/stubs.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAa7B,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAEjE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACjC,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAG3E,OAAO,EAEN,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,KAAK,EAAE,SAAS,EAAmB,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAA+B,KAAK,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAI9F;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,oBAAoB,GAAI,CAAC,GAAG,GAAG,EAAE,OAAO,MAAM,KAAG,CAqBtD,CAAC;AAET;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,GAAI,CAAC,GAAG,GAAG,EAAE,QAAQ,MAAM,EAAE,YAAY,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,CAOxF,CAAC;AAET,iEAAiE;AACjE,eAAO,MAAM,IAAI,EAAE,GAAkC,CAAC;AAEtD;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,QAAO,EAI/B,CAAC;AAEJ,gDAAgD;AAChD,eAAO,MAAM,YAAY,QAAO,QAAgC,CAAC;AAEjE,2CAA2C;AAC3C,eAAO,MAAM,OAAO,GAAU,IAAI,GAAG,EAAE,MAAM,GAAG,KAAG,OAAO,CAAC,IAAI,CAAW,CAAC;AAI3E;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB,QAAO,YAW3C,CAAC;AAEH;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,QAAO,WAUxC,CAAC;AAEF,2EAA2E;AAC3E,eAAO,MAAM,aAAa,EAAE,OAQ3B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,oBAAoB,QAAO,OAUtC,CAAC;AAEH,2FAA2F;AAC3F,eAAO,MAAM,0BAA0B,GAAI,UAAU;IACpD,iDAAiD;IACjD,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAC/B,KAAG,KAAK,CAAC,cAAc,CAqBvB,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,8BAA8B,GAC1C,iBAAiB,cAAc,CAAC,MAAM,CAAC,KACrC,gBAsBF,CAAC;AAEF,kDAAkD;AAClD,MAAM,WAAW,+BAA+B;IAC/C,6DAA6D;IAC7D,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,qFAAqF;IACrF,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,oEAAoE;IACpE,UAAU,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACzB,8CAA8C;IAC9C,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAC/B;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,KAAK,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC;IAC7F;;;;;;;;OAQG;IACH,YAAY,CAAC,EACZ,aAAa,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,gBAAgB,KAAK,aAAa,CAAC,cAAc,CAAC,CAAC,CAAC;IAC5F,mFAAmF;IACnF,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,KAAK,CAAC,cAAc,CAAC,CAAC;IAC/E;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,sBAAsB,CAAC;CACnC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,4BAA4B,GACxC,SAAS,+BAA+B,KACtC,cAmDF,CAAC"}
|
package/dist/testing/stubs.js
CHANGED
|
@@ -133,8 +133,7 @@ export const create_stub_audit_sse = () => {
|
|
|
133
133
|
};
|
|
134
134
|
/** Stub `AppDeps` for auth surface tests — throws on any method access. */
|
|
135
135
|
export const stub_app_deps = {
|
|
136
|
-
|
|
137
|
-
read_text_file: create_throwing_stub('read_text_file'),
|
|
136
|
+
read_secure_file: create_throwing_stub('read_secure_file'),
|
|
138
137
|
delete_file: create_throwing_stub('delete_file'),
|
|
139
138
|
keyring: create_throwing_stub('keyring'),
|
|
140
139
|
password: create_throwing_stub('password'),
|
|
@@ -146,8 +145,9 @@ export const stub_app_deps = {
|
|
|
146
145
|
* Create no-op `AppDeps` for auth surface testing.
|
|
147
146
|
*/
|
|
148
147
|
export const create_stub_app_deps = () => ({
|
|
149
|
-
|
|
150
|
-
|
|
148
|
+
read_secure_file: async (path) => {
|
|
149
|
+
throw new Error(`ENOENT: no such file or directory: ${path}`);
|
|
150
|
+
},
|
|
151
151
|
delete_file: async (_path) => { },
|
|
152
152
|
keyring: create_noop_stub('keyring'),
|
|
153
153
|
password: create_noop_stub('password'),
|
|
@@ -34,7 +34,6 @@
|
|
|
34
34
|
const columns: Array<DatatableColumn<AuthSessionJson>> = [
|
|
35
35
|
{ key: 'id', label: 'session', width: 140 },
|
|
36
36
|
{ key: 'created_at', label: 'created', width: 120 },
|
|
37
|
-
{ key: 'last_seen_at', label: 'last seen', width: 120 },
|
|
38
37
|
{ key: 'expires_at', label: 'expires', width: 120 },
|
|
39
38
|
{ key: 'account_id', label: '', width: 100 }
|
|
40
39
|
];
|
|
@@ -71,10 +70,6 @@
|
|
|
71
70
|
<span title={format_datetime_local(row.created_at)}>
|
|
72
71
|
{format_relative_time(row.created_at)}
|
|
73
72
|
</span>
|
|
74
|
-
{:else if column.key === 'last_seen_at'}
|
|
75
|
-
<span title={format_datetime_local(row.last_seen_at)}>
|
|
76
|
-
{format_relative_time(row.last_seen_at)}
|
|
77
|
-
</span>
|
|
78
73
|
{:else if column.key === 'expires_at'}
|
|
79
74
|
<span title={format_datetime_local(row.expires_at)}>
|
|
80
75
|
{format_relative_time(row.expires_at)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AccountSessions.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AccountSessions.svelte"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AccountSessions.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AccountSessions.svelte"],"names":[],"mappings":"AA4GA,UAAU,kCAAkC,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,MAAM;IACpM,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,KAAK,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC;IACjK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,KAAK,CAAA;KAAC,GAAG,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;IACtG,YAAY,CAAC,EAAE,QAAQ,CAAC;CAC3B;AAKD,QAAA,MAAM,eAAe;;kBAA+E,CAAC;AACnF,KAAK,eAAe,GAAG,YAAY,CAAC,OAAO,eAAe,CAAC,CAAC;AAC9D,eAAe,eAAe,CAAC"}
|
|
@@ -52,10 +52,13 @@
|
|
|
52
52
|
|
|
53
53
|
// sessions
|
|
54
54
|
const unique_users = $derived(new Set(sessions.sessions.map((s) => s.username)).size);
|
|
55
|
+
// newest session by mint time — sessions carry no activity signal
|
|
56
|
+
// (`last_seen_at` is decorative post-hard-cap; see `docs/security.md`
|
|
57
|
+
// §Session Security)
|
|
55
58
|
const most_recent = $derived(
|
|
56
59
|
sessions.sessions.length > 0
|
|
57
60
|
? sessions.sessions.reduce((a, b) =>
|
|
58
|
-
new Date(a.
|
|
61
|
+
new Date(a.created_at) > new Date(b.created_at) ? a : b
|
|
59
62
|
)
|
|
60
63
|
: null
|
|
61
64
|
);
|
|
@@ -146,10 +149,10 @@
|
|
|
146
149
|
</div>
|
|
147
150
|
{#if most_recent}
|
|
148
151
|
<div class="baseline-row gap_xs font_size_sm mt_sm">
|
|
149
|
-
<span class="text_50">
|
|
152
|
+
<span class="text_50">newest session:</span>
|
|
150
153
|
<strong>{most_recent.username}</strong>
|
|
151
|
-
<span class="text_50" title={format_datetime_local(most_recent.
|
|
152
|
-
>{format_relative_time(most_recent.
|
|
154
|
+
<span class="text_50" title={format_datetime_local(most_recent.created_at)}
|
|
155
|
+
>{format_relative_time(most_recent.created_at)}</span
|
|
153
156
|
>
|
|
154
157
|
</div>
|
|
155
158
|
{/if}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminOverview.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AdminOverview.svelte"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AdminOverview.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AdminOverview.svelte"],"names":[],"mappings":"AA8UA,QAAA,MAAM,aAAa,2DAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
{ key: 'username', label: 'user', width: 120 },
|
|
27
27
|
{ key: 'id', label: 'session', width: 130 },
|
|
28
28
|
{ key: 'created_at', label: 'created', width: 100 },
|
|
29
|
-
{ key: 'last_seen_at', label: 'last seen', width: 100 },
|
|
30
29
|
{ key: 'expires_at', label: 'expires', width: 100 },
|
|
31
30
|
{ key: 'account_id', label: '', width: 220 }
|
|
32
31
|
];
|
|
@@ -53,10 +52,6 @@
|
|
|
53
52
|
<span title={format_datetime_local(row.created_at)}>
|
|
54
53
|
{format_relative_time(row.created_at)}
|
|
55
54
|
</span>
|
|
56
|
-
{:else if column.key === 'last_seen_at'}
|
|
57
|
-
<span title={format_datetime_local(row.last_seen_at)}>
|
|
58
|
-
{format_relative_time(row.last_seen_at)}
|
|
59
|
-
</span>
|
|
60
55
|
{:else if column.key === 'expires_at'}
|
|
61
56
|
<span title={format_datetime_local(row.expires_at)}>
|
|
62
57
|
{format_relative_time(row.expires_at)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminSessions.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AdminSessions.svelte"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AdminSessions.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AdminSessions.svelte"],"names":[],"mappings":"AA6FA,UAAU,kCAAkC,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,MAAM;IACpM,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,KAAK,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC;IACjK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,KAAK,CAAA;KAAC,GAAG,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;IACtG,YAAY,CAAC,EAAE,QAAQ,CAAC;CAC3B;AAKD,QAAA,MAAM,aAAa;;kBAA+E,CAAC;AACjF,KAAK,aAAa,GAAG,YAAY,CAAC,OAAO,aAAa,CAAC,CAAC;AAC1D,eAAe,aAAa,CAAC"}
|
package/dist/ui/CLAUDE.md
CHANGED
|
@@ -244,9 +244,13 @@ destructive actions.
|
|
|
244
244
|
- `table_state.svelte.ts` — `TableState`. Paginated DB browser state.
|
|
245
245
|
Holds one `AsyncSlot` (`list`) + payload fields (`table_name`,
|
|
246
246
|
`columns`, `rows`, `total`, `offset`, `limit` capped by
|
|
247
|
-
`
|
|
248
|
-
`
|
|
249
|
-
`
|
|
247
|
+
`http/db_routes.ts`'s `DB_TABLE_ROWS_LIMIT_MAX`, `primary_key`,
|
|
248
|
+
`deletable`). Derived
|
|
249
|
+
`showing_start`/`showing_end`/`has_prev`/`has_next`, plus
|
|
250
|
+
`can_delete` (`deletable && primary_key !== null`) — gate the
|
|
251
|
+
delete affordance on that, not on `primary_key`, or a
|
|
252
|
+
policy-excluded table shows a button that only ever refuses.
|
|
253
|
+
Methods: `fetch`, `go_prev`/`go_next`, `delete_row`. `delete_row` uses
|
|
250
254
|
plain try/catch + scalar `deleting` / `delete_error` fields (no
|
|
251
255
|
slot — error must survive past `list.run()` retries).
|
|
252
256
|
- `form_state.svelte.ts` — `FormState`. Enter-advance between
|
|
@@ -421,4 +425,5 @@ format_scope, global_label)` helper — `global_label = null` renders no
|
|
|
421
425
|
- `format_value(value)` — table-cell stringifier (NULL / undefined /
|
|
422
426
|
JSON / primitive).
|
|
423
427
|
- `format_audit_metadata(event_type, metadata)` — event-type-
|
|
424
|
-
specific metadata summary (
|
|
428
|
+
specific metadata summary (bespoke cases for the common event types,
|
|
429
|
+
JSON-stringified fallback for the rest).
|
|
@@ -40,9 +40,7 @@
|
|
|
40
40
|
* @module
|
|
41
41
|
*/
|
|
42
42
|
import { AsyncSlot } from './async_slot.svelte.js';
|
|
43
|
-
import type
|
|
44
|
-
/** Maximum number of rows that can be fetched in a single page. */
|
|
45
|
-
export declare const TABLE_LIMIT_MAX = 1000;
|
|
43
|
+
import { type ColumnInfo } from '../http/db_routes.js';
|
|
46
44
|
export declare class TableState {
|
|
47
45
|
readonly list: AsyncSlot<void, string>;
|
|
48
46
|
table_name: string;
|
|
@@ -52,15 +50,30 @@ export declare class TableState {
|
|
|
52
50
|
offset: number;
|
|
53
51
|
limit: number;
|
|
54
52
|
primary_key: string | null;
|
|
53
|
+
/**
|
|
54
|
+
* Whether the server will accept a row `DELETE` on this table. False for a
|
|
55
|
+
* table with no single-column primary key *and* for one the server excludes
|
|
56
|
+
* by policy (the audit trail, singleton bookkeeping rows), which `primary_key`
|
|
57
|
+
* alone can't distinguish — it reports the key shape, not the policy.
|
|
58
|
+
*/
|
|
59
|
+
deletable: boolean;
|
|
55
60
|
deleting: string | null;
|
|
56
61
|
delete_error: string | null;
|
|
62
|
+
/**
|
|
63
|
+
* Whether to offer a delete affordance at all. Gate rendering on this rather
|
|
64
|
+
* than on `primary_key`, or a policy-excluded table shows a button whose only
|
|
65
|
+
* outcome is a `table_not_deletable` refusal.
|
|
66
|
+
*/
|
|
67
|
+
readonly can_delete: boolean;
|
|
57
68
|
readonly showing_start: number;
|
|
58
69
|
readonly showing_end: number;
|
|
59
70
|
readonly has_prev: boolean;
|
|
60
71
|
readonly has_next: boolean;
|
|
61
72
|
/**
|
|
62
73
|
* Fetch a page of rows for `table_name` from `GET /api/db/tables/{table_name}`.
|
|
63
|
-
* `limit` is clamped to `[1,
|
|
74
|
+
* `limit` is clamped to `[1, DB_TABLE_ROWS_LIMIT_MAX]` — the same bounds
|
|
75
|
+
* the route 400s on, single-sourced so the client guard can't drift into
|
|
76
|
+
* sending a request the server refuses.
|
|
64
77
|
*
|
|
65
78
|
* @mutates `this`
|
|
66
79
|
*/
|
|
@@ -72,7 +85,7 @@ export declare class TableState {
|
|
|
72
85
|
* Optimistically drops it from `rows` and decrements `total` on success;
|
|
73
86
|
* surfaces server errors on `delete_error`.
|
|
74
87
|
*
|
|
75
|
-
* @returns `true` when the row was removed; `false`
|
|
88
|
+
* @returns `true` when the row was removed; `false` when the table isn't deletable or the server refused
|
|
76
89
|
* @mutates `this`
|
|
77
90
|
*/
|
|
78
91
|
delete_row(row: Record<string, unknown>): Promise<boolean>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/table_state.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAIH,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAGnD,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"table_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/table_state.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAIH,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAGnD,OAAO,EAGN,KAAK,UAAU,EACf,MAAM,sBAAsB,CAAC;AAE9B,qBAAa,UAAU;IACtB,QAAQ,CAAC,IAAI,0BAAyB;IAEtC,UAAU,EAAE,MAAM,CAAkB;IACpC,OAAO,EAAE,KAAK,CAAC,UAAU,CAAC,CAAkB;IAC5C,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAkB;IACtD,KAAK,SAAiB;IACtB,MAAM,SAAiB;IACvB,KAAK,SAA2C;IAChD,WAAW,EAAE,MAAM,GAAG,IAAI,CAAoB;IAC9C;;;;;OAKG;IACH,SAAS,UAAqB;IAC9B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAoB;IAC3C,YAAY,EAAE,MAAM,GAAG,IAAI,CAAoB;IAE/C;;;;OAIG;IACH,QAAQ,CAAC,UAAU,UAAyD;IAG5E,QAAQ,CAAC,aAAa,SAAoD;IAC1E,QAAQ,CAAC,WAAW,SAAkE;IACtF,QAAQ,CAAC,QAAQ,UAA6B;IAC9C,QAAQ,CAAC,QAAQ,UAAmD;IAEpE;;;;;;;OAOG;IACG,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,SAAI,EAAE,KAAK,SAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBvE,OAAO,IAAI,IAAI;IAIf,OAAO,IAAI,IAAI;IAIf;;;;;;;OAOG;IACG,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;CAoChE"}
|
|
@@ -43,8 +43,7 @@ import { to_error_message } from '@fuzdev/fuz_util/error.ts';
|
|
|
43
43
|
import { AsyncSlot } from './async_slot.svelte.js';
|
|
44
44
|
import { parse_response_error, ui_fetch } from './ui_fetch.js';
|
|
45
45
|
import { format_value } from './ui_format.js';
|
|
46
|
-
|
|
47
|
-
export const TABLE_LIMIT_MAX = 1000;
|
|
46
|
+
import { DB_TABLE_ROWS_DEFAULT_LIMIT, DB_TABLE_ROWS_LIMIT_MAX } from '../http/db_routes.js';
|
|
48
47
|
export class TableState {
|
|
49
48
|
list = new AsyncSlot();
|
|
50
49
|
table_name = $state.raw('');
|
|
@@ -52,10 +51,23 @@ export class TableState {
|
|
|
52
51
|
rows = $state.raw([]);
|
|
53
52
|
total = $state.raw(0);
|
|
54
53
|
offset = $state.raw(0);
|
|
55
|
-
limit = $state.raw(
|
|
54
|
+
limit = $state.raw(DB_TABLE_ROWS_DEFAULT_LIMIT);
|
|
56
55
|
primary_key = $state.raw(null);
|
|
56
|
+
/**
|
|
57
|
+
* Whether the server will accept a row `DELETE` on this table. False for a
|
|
58
|
+
* table with no single-column primary key *and* for one the server excludes
|
|
59
|
+
* by policy (the audit trail, singleton bookkeeping rows), which `primary_key`
|
|
60
|
+
* alone can't distinguish — it reports the key shape, not the policy.
|
|
61
|
+
*/
|
|
62
|
+
deletable = $state.raw(false);
|
|
57
63
|
deleting = $state.raw(null);
|
|
58
64
|
delete_error = $state.raw(null);
|
|
65
|
+
/**
|
|
66
|
+
* Whether to offer a delete affordance at all. Gate rendering on this rather
|
|
67
|
+
* than on `primary_key`, or a policy-excluded table shows a button whose only
|
|
68
|
+
* outcome is a `table_not_deletable` refusal.
|
|
69
|
+
*/
|
|
70
|
+
can_delete = $derived(this.deletable && this.primary_key !== null);
|
|
59
71
|
// Pagination computed values
|
|
60
72
|
showing_start = $derived(this.total === 0 ? 0 : this.offset + 1);
|
|
61
73
|
showing_end = $derived(Math.min(this.offset + this.rows.length, this.total));
|
|
@@ -63,14 +75,16 @@ export class TableState {
|
|
|
63
75
|
has_next = $derived(this.offset + this.limit < this.total);
|
|
64
76
|
/**
|
|
65
77
|
* Fetch a page of rows for `table_name` from `GET /api/db/tables/{table_name}`.
|
|
66
|
-
* `limit` is clamped to `[1,
|
|
78
|
+
* `limit` is clamped to `[1, DB_TABLE_ROWS_LIMIT_MAX]` — the same bounds
|
|
79
|
+
* the route 400s on, single-sourced so the client guard can't drift into
|
|
80
|
+
* sending a request the server refuses.
|
|
67
81
|
*
|
|
68
82
|
* @mutates `this`
|
|
69
83
|
*/
|
|
70
84
|
async fetch(table_name, offset = 0, limit = 100) {
|
|
71
85
|
this.table_name = table_name;
|
|
72
86
|
this.offset = offset;
|
|
73
|
-
this.limit = Math.max(1, Math.min(
|
|
87
|
+
this.limit = Math.max(1, Math.min(DB_TABLE_ROWS_LIMIT_MAX, limit));
|
|
74
88
|
await this.list.run(async () => {
|
|
75
89
|
const response = await ui_fetch(`/api/db/tables/${table_name}?offset=${this.offset}&limit=${this.limit}`);
|
|
76
90
|
if (!response.ok) {
|
|
@@ -81,6 +95,7 @@ export class TableState {
|
|
|
81
95
|
this.rows = data.rows ?? [];
|
|
82
96
|
this.total = data.total ?? 0;
|
|
83
97
|
this.primary_key = data.primary_key ?? null;
|
|
98
|
+
this.deletable = data.deletable ?? false;
|
|
84
99
|
});
|
|
85
100
|
}
|
|
86
101
|
go_prev() {
|
|
@@ -94,11 +109,11 @@ export class TableState {
|
|
|
94
109
|
* Optimistically drops it from `rows` and decrements `total` on success;
|
|
95
110
|
* surfaces server errors on `delete_error`.
|
|
96
111
|
*
|
|
97
|
-
* @returns `true` when the row was removed; `false`
|
|
112
|
+
* @returns `true` when the row was removed; `false` when the table isn't deletable or the server refused
|
|
98
113
|
* @mutates `this`
|
|
99
114
|
*/
|
|
100
115
|
async delete_row(row) {
|
|
101
|
-
if (!this.primary_key)
|
|
116
|
+
if (!this.can_delete || !this.primary_key)
|
|
102
117
|
return false;
|
|
103
118
|
const pk_value = row[this.primary_key];
|
|
104
119
|
if (pk_value === null || pk_value === undefined)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui_format.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/ui_format.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAChC,WAAW,MAAM,GAAG,MAAM,GAAG,IAAI,EACjC,MAAK,MAAmB,KACtB,MA2BF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,IAAI,MAAM,KAAG,MAY1C,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAAI,KAAK,MAAM,EAAE,YAAY,MAAM,EAAE,kBAAe,KAAG,MAOlF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,KAAG,MAAmC,CAAC;AAEjF;;GAEG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,OAAO,KAAG,MAS7C,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,GAAI,WAAW,MAAM,GAAG,MAAM,GAAG,IAAI,KAAG,MAOzE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,GACjC,YAAY,MAAM,EAClB,UAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,KACtC,
|
|
1
|
+
{"version":3,"file":"ui_format.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/ui_format.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAChC,WAAW,MAAM,GAAG,MAAM,GAAG,IAAI,EACjC,MAAK,MAAmB,KACtB,MA2BF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,IAAI,MAAM,KAAG,MAY1C,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAAI,KAAK,MAAM,EAAE,YAAY,MAAM,EAAE,kBAAe,KAAG,MAOlF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,KAAG,MAAmC,CAAC;AAEjF;;GAEG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,OAAO,KAAG,MAS7C,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,GAAI,WAAW,MAAM,GAAG,MAAM,GAAG,IAAI,KAAG,MAOzE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,GACjC,YAAY,MAAM,EAClB,UAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,KACtC,MAgEF,CAAC"}
|
package/dist/ui/ui_format.js
CHANGED
|
@@ -178,6 +178,10 @@ export const format_audit_metadata = (event_type, metadata) => {
|
|
|
178
178
|
return metadata.setting
|
|
179
179
|
? `${metadata.setting}: ${JSON.stringify(metadata.old_value)} → ${JSON.stringify(metadata.new_value)}`
|
|
180
180
|
: '';
|
|
181
|
+
case 'db_admin_row_delete':
|
|
182
|
+
return metadata.table
|
|
183
|
+
? `${metadata.table}.${metadata.pk_column}: ${truncate_middle(metadata.id, 12)}`
|
|
184
|
+
: '';
|
|
181
185
|
default:
|
|
182
186
|
return JSON.stringify(metadata);
|
|
183
187
|
}
|