@fuzdev/fuz_app 0.25.0 → 0.27.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.
@@ -1 +1 @@
1
- {"version":3,"file":"register_action_ws.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_action_ws.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,KAAK,EAAC,OAAO,EAAE,IAAI,EAAC,MAAM,MAAM,CAAC;AACxC,OAAO,KAAK,EAAC,gBAAgB,EAAE,SAAS,EAAC,MAAM,SAAS,CAAC;AAEzD,OAAO,EAAS,KAAK,MAAM,IAAI,UAAU,EAAC,MAAM,yBAAyB,CAAC;AAgB1E,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,YAAY,CAAC;AAErC,OAAO,EAAC,KAAK,MAAM,EAAE,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAC,MAAM,mBAAmB,CAAC;AAG7F,OAAO,EAAC,yBAAyB,EAAE,KAAK,kBAAkB,EAAC,MAAM,4BAA4B,CAAC;AAE9F,YAAY,EAAC,MAAM,EAAE,kBAAkB,EAAE,eAAe,EAAC,CAAC;AAE1D,0EAA0E;AAC1E,eAAO,MAAM,gCAAgC,QAAS,CAAC;AAEvD;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IACjC,qFAAqF;IACrF,EAAE,EAAE,SAAS,CAAC;IACd,4EAA4E;IAC5E,aAAa,EAAE,IAAI,CAAC;IACpB,oDAAoD;IACpD,QAAQ,EAAE,kBAAkB,CAAC;IAC7B;;;OAGG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD,wFAAwF;IACxF,MAAM,EAAE,WAAW,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,kBAAkB;IAClC,+CAA+C;IAC/C,EAAE,EAAE,SAAS,CAAC;IACd,2CAA2C;IAC3C,aAAa,EAAE,IAAI,CAAC;IACpB,kGAAkG;IAClG,QAAQ,EAAE,kBAAkB,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAsB;IACtC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wCAAwC;AACxC,MAAM,WAAW,uBAAuB,CAAC,IAAI,SAAS,kBAAkB;IACvE,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,GAAG,EAAE,IAAI,CAAC;IACV,iEAAiE;IACjE,gBAAgB,EAAE,gBAAgB,CAAC;IACnC;;;;;;OAMG;IACH,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IACrC;;;;;OAKG;IACH,cAAc,EAAE,CAAC,IAAI,EAAE,kBAAkB,EAAE,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAC/D;;;;OAIG;IACH,SAAS,CAAC,EAAE,yBAAyB,CAAC;IACtC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,sBAAsB,CAAC;IAC7C,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,qDAAqD;IACrD,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,iBAAiB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE;;;;;OAKG;IACH,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,kBAAkB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpE;AAED,sCAAsC;AACtC,MAAM,WAAW,sBAAsB;IACtC,yEAAyE;IACzE,SAAS,EAAE,yBAAyB,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,kBAAkB,GAAI,IAAI,SAAS,kBAAkB,EACjE,SAAS,uBAAuB,CAAC,IAAI,CAAC,KACpC,sBAwWF,CAAC"}
1
+ {"version":3,"file":"register_action_ws.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_action_ws.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,KAAK,EAAC,OAAO,EAAE,IAAI,EAAC,MAAM,MAAM,CAAC;AACxC,OAAO,KAAK,EAAC,gBAAgB,EAAE,SAAS,EAAC,MAAM,SAAS,CAAC;AAEzD,OAAO,EAAS,KAAK,MAAM,IAAI,UAAU,EAAC,MAAM,yBAAyB,CAAC;AAgB1E,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,YAAY,CAAC;AAErC,OAAO,EAAC,KAAK,MAAM,EAAE,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAC,MAAM,mBAAmB,CAAC;AAG7F,OAAO,EAAC,yBAAyB,EAAE,KAAK,kBAAkB,EAAC,MAAM,4BAA4B,CAAC;AAE9F,YAAY,EAAC,MAAM,EAAE,kBAAkB,EAAE,eAAe,EAAC,CAAC;AAE1D,0EAA0E;AAC1E,eAAO,MAAM,gCAAgC,QAAS,CAAC;AAEvD;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IACjC,qFAAqF;IACrF,EAAE,EAAE,SAAS,CAAC;IACd,4EAA4E;IAC5E,aAAa,EAAE,IAAI,CAAC;IACpB,oDAAoD;IACpD,QAAQ,EAAE,kBAAkB,CAAC;IAC7B;;;OAGG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD,wFAAwF;IACxF,MAAM,EAAE,WAAW,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,kBAAkB;IAClC,+CAA+C;IAC/C,EAAE,EAAE,SAAS,CAAC;IACd,2CAA2C;IAC3C,aAAa,EAAE,IAAI,CAAC;IACpB,kGAAkG;IAClG,QAAQ,EAAE,kBAAkB,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAsB;IACtC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wCAAwC;AACxC,MAAM,WAAW,uBAAuB,CAAC,IAAI,SAAS,kBAAkB;IACvE,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,GAAG,EAAE,IAAI,CAAC;IACV,iEAAiE;IACjE,gBAAgB,EAAE,gBAAgB,CAAC;IACnC;;;;;;OAMG;IACH,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IACrC;;;;;OAKG;IACH,cAAc,EAAE,CAAC,IAAI,EAAE,kBAAkB,EAAE,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAC/D;;;;OAIG;IACH,SAAS,CAAC,EAAE,yBAAyB,CAAC;IACtC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,sBAAsB,CAAC;IAC7C,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,qDAAqD;IACrD,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,iBAAiB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE;;;;;OAKG;IACH,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,kBAAkB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpE;AAED,sCAAsC;AACtC,MAAM,WAAW,sBAAsB;IACtC,yEAAyE;IACzE,SAAS,EAAE,yBAAyB,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,kBAAkB,GAAI,IAAI,SAAS,kBAAkB,EACjE,SAAS,uBAAuB,CAAC,IAAI,CAAC,KACpC,sBA8WF,CAAC"}
@@ -28,7 +28,7 @@ import { get_request_context, has_role } from '../auth/request_context.js';
28
28
  import { hash_session_token } from '../auth/session_queries.js';
29
29
  import { ROLE_KEEPER } from '../auth/role_schema.js';
30
30
  import { JSONRPC_VERSION } from '../http/jsonrpc.js';
31
- import { jsonrpc_error_messages } from '../http/jsonrpc_errors.js';
31
+ import { jsonrpc_error_messages, ThrownJsonrpcError } from '../http/jsonrpc_errors.js';
32
32
  import { create_jsonrpc_error_response, create_jsonrpc_error_response_from_thrown, create_jsonrpc_notification, to_jsonrpc_message_id, to_jsonrpc_params, is_jsonrpc_request, } from '../http/jsonrpc_helpers.js';
33
33
  import { CREDENTIAL_TYPE_KEY, AUTH_API_TOKEN_ID_KEY } from '../hono_context.js';
34
34
  import {} from './action_types.js';
@@ -134,10 +134,10 @@ export const register_action_ws = (options) => {
134
134
  }
135
135
  };
136
136
  return {
137
- onOpen: async (event, ws) => {
137
+ onOpen: async (_event, ws) => {
138
138
  const connection_id = transport.add_connection(ws, token_hash, account_id, api_token_id);
139
139
  captured_connection_id = connection_id;
140
- log.debug('ws opened', connection_id, event);
140
+ log.debug('ws opened', connection_id);
141
141
  if (heartbeat_enabled) {
142
142
  last_receive_time = Date.now();
143
143
  heartbeat_timer = setInterval(() => {
@@ -294,7 +294,14 @@ export const register_action_ws = (options) => {
294
294
  ws.send(JSON.stringify({ jsonrpc: JSONRPC_VERSION, id, result: output }));
295
295
  }
296
296
  catch (error) {
297
- log.error('handler error:', method, error);
297
+ if (error instanceof ThrownJsonrpcError) {
298
+ // Expected handler outcome (conflict, not_found, invalid_params, ...).
299
+ // Log at debug without the stack — the throw site is part of protocol, not a bug.
300
+ log.debug('handler error:', method, `${error.code} ${error.message}`);
301
+ }
302
+ else {
303
+ log.error('handler error:', method, error);
304
+ }
298
305
  ws.send(JSON.stringify(create_jsonrpc_error_response_from_thrown(id, error)));
299
306
  }
300
307
  finally {
@@ -317,7 +324,7 @@ export const register_action_ws = (options) => {
317
324
  }
318
325
  }
319
326
  transport.remove_connection(ws);
320
- log.debug('ws closed', event);
327
+ log.debug('ws closed', captured_connection_id, { code: event.code, reason: event.reason });
321
328
  },
322
329
  };
323
330
  }));
@@ -9,6 +9,7 @@
9
9
  * @module
10
10
  */
11
11
  import type { CommandDeps, CommandResult, EnvDeps, FsReadDeps, FsRemoveDeps, FsWriteDeps } from '../runtime/deps.js';
12
+ import type { QueryDeps } from '../db/query_deps.js';
12
13
  /**
13
14
  * Optional logger for setup helpers.
14
15
  *
@@ -156,4 +157,37 @@ export declare const create_database: (deps: CommandDeps, db_name: string, optio
156
157
  * @returns result describing what happened
157
158
  */
158
159
  export declare const reset_database: (deps: CommandDeps & FsReadDeps & FsRemoveDeps, database_url: string, options?: ResetDatabaseOptions) => Promise<ResetDbResult>;
160
+ /** Input to `seed_dev_account`. */
161
+ export interface SeedDevAccountInput {
162
+ /** Account username. Policy is bypassed — any non-empty string is accepted. */
163
+ username: string;
164
+ /** Account password. Policy is bypassed — any non-empty string is accepted. */
165
+ password: string;
166
+ /** Roles to grant via permit (idempotent). */
167
+ roles?: ReadonlyArray<string>;
168
+ }
169
+ /** Result of `seed_dev_account`. */
170
+ export interface SeedDevAccountResult {
171
+ account_id: string;
172
+ actor_id: string;
173
+ /** True if a new account was created; false if one already existed. */
174
+ created: boolean;
175
+ }
176
+ /** Dependencies for `seed_dev_account`. */
177
+ export interface SeedDevAccountDeps extends QueryDeps {
178
+ /** Password hasher (e.g., `argon2_password_deps.hash_password`). */
179
+ hash_password: (password: string) => Promise<string>;
180
+ }
181
+ /**
182
+ * Seed a development test account, bypassing username/password policy.
183
+ *
184
+ * Idempotent by username — if an account with the given username already
185
+ * exists, reuses it and only reconciles the requested role grants. Never
186
+ * updates an existing password (rerun would silently rotate it).
187
+ *
188
+ * Intended for `scripts/dev_setup.ts` — do not call in production.
189
+ */
190
+ export declare const seed_dev_account: (deps: SeedDevAccountDeps, input: SeedDevAccountInput, options?: {
191
+ log?: SetupLogger;
192
+ }) => Promise<SeedDevAccountResult>;
159
193
  //# sourceMappingURL=setup.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"setup.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/dev/setup.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/dev/setup.ts"],"names":[],"mappings":"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"}
package/dist/dev/setup.js CHANGED
@@ -8,6 +8,8 @@
8
8
  *
9
9
  * @module
10
10
  */
11
+ import { query_account_by_username, query_actor_by_account, query_create_account_with_actor, } from '../auth/account_queries.js';
12
+ import { query_grant_permit } from '../auth/permit_queries.js';
11
13
  /** Default logger using bracket format. */
12
14
  export const default_setup_logger = {
13
15
  ok: (msg) => console.log(` [ok] ${msg}`),
@@ -263,3 +265,49 @@ export const reset_database = async (deps, database_url, options) => {
263
265
  log.ok(`Created database: ${db_name}`);
264
266
  return { reset: true, skipped: false, db_type: 'postgres' };
265
267
  };
268
+ /**
269
+ * Seed a development test account, bypassing username/password policy.
270
+ *
271
+ * Idempotent by username — if an account with the given username already
272
+ * exists, reuses it and only reconciles the requested role grants. Never
273
+ * updates an existing password (rerun would silently rotate it).
274
+ *
275
+ * Intended for `scripts/dev_setup.ts` — do not call in production.
276
+ */
277
+ export const seed_dev_account = async (deps, input, options) => {
278
+ const log = options?.log ?? default_setup_logger;
279
+ const query_deps = { db: deps.db };
280
+ const existing = await query_account_by_username(query_deps, input.username);
281
+ if (existing) {
282
+ const actor = await query_actor_by_account(query_deps, existing.id);
283
+ if (!actor) {
284
+ log.error(`dev account '${input.username}' exists but has no actor`);
285
+ throw new Error(`dev account '${input.username}' has no actor`);
286
+ }
287
+ for (const role of input.roles ?? []) {
288
+ await query_grant_permit(query_deps, {
289
+ actor_id: actor.id,
290
+ role,
291
+ granted_by: null,
292
+ expires_at: null,
293
+ });
294
+ }
295
+ log.skip(`Dev account '${input.username}' already exists`);
296
+ return { account_id: existing.id, actor_id: actor.id, created: false };
297
+ }
298
+ const password_hash = await deps.hash_password(input.password);
299
+ const { account, actor } = await query_create_account_with_actor(query_deps, {
300
+ username: input.username,
301
+ password_hash,
302
+ });
303
+ for (const role of input.roles ?? []) {
304
+ await query_grant_permit(query_deps, {
305
+ actor_id: actor.id,
306
+ role,
307
+ granted_by: null,
308
+ expires_at: null,
309
+ });
310
+ }
311
+ log.ok(`Seeded dev account '${input.username}'`);
312
+ return { account_id: account.id, actor_id: actor.id, created: true };
313
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"deno.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/runtime/deno.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,WAAW,EAA4B,MAAM,WAAW,CAAC;AAsCtE;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,GAAI,MAAM,aAAa,CAAC,MAAM,CAAC,KAAG,WAwEhE,CAAC"}
1
+ {"version":3,"file":"deno.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/runtime/deno.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,WAAW,EAA4B,MAAM,WAAW,CAAC;AAwDtE;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,GAAI,MAAM,aAAa,CAAC,MAAM,CAAC,KAAG,WA4HhE,CAAC"}
@@ -38,6 +38,33 @@ export const create_deno_runtime = (args) => ({
38
38
  mkdir: (path, options) => Deno.mkdir(path, options),
39
39
  read_text_file: (path) => Deno.readTextFile(path),
40
40
  read_file: (path) => Deno.readFile(path),
41
+ read_text_from_offset: async (path, offset) => {
42
+ const s = await Deno.stat(path);
43
+ const file_size = s.size;
44
+ const bytes_to_read = Math.max(0, file_size - offset);
45
+ if (bytes_to_read === 0)
46
+ return { content: '', bytes_read: 0, file_size };
47
+ const handle = await Deno.open(path, { read: true });
48
+ try {
49
+ await handle.seek(offset, Deno.SeekMode.Start);
50
+ const buffer = new Uint8Array(bytes_to_read);
51
+ const bytes_read = (await handle.read(buffer)) ?? 0;
52
+ return {
53
+ content: new TextDecoder().decode(buffer.subarray(0, bytes_read)),
54
+ bytes_read,
55
+ file_size,
56
+ };
57
+ }
58
+ finally {
59
+ handle.close();
60
+ }
61
+ },
62
+ readdir: async (path) => {
63
+ const names = [];
64
+ for await (const entry of Deno.readDir(path))
65
+ names.push(entry.name);
66
+ return names;
67
+ },
41
68
  write_text_file: (path, content) => Deno.writeTextFile(path, content),
42
69
  write_file: (path, data) => Deno.writeFile(path, data),
43
70
  rename: (old_path, new_path) => Deno.rename(old_path, new_path),
@@ -45,20 +72,46 @@ export const create_deno_runtime = (args) => ({
45
72
  // === HTTP ===
46
73
  fetch: globalThis.fetch,
47
74
  // === Local Commands ===
48
- run_command: async (cmd, args) => {
75
+ run_command: async (cmd, args, options) => {
49
76
  try {
50
- const proc = new Deno.Command(cmd, {
51
- args,
52
- stdout: 'piped',
53
- stderr: 'piped',
54
- });
55
- const result = await proc.output();
56
- return {
57
- success: result.code === 0,
58
- code: result.code,
59
- stdout: new TextDecoder().decode(result.stdout),
60
- stderr: new TextDecoder().decode(result.stderr),
61
- };
77
+ const controller = options?.timeout_ms !== undefined ? new AbortController() : null;
78
+ const signal = controller && options?.signal
79
+ ? AbortSignal.any([controller.signal, options.signal])
80
+ : (controller?.signal ?? options?.signal);
81
+ const timer = controller && options?.timeout_ms !== undefined
82
+ ? setTimeout(() => controller.abort(), options.timeout_ms)
83
+ : null;
84
+ let timed_out = false;
85
+ if (controller) {
86
+ controller.signal.addEventListener('abort', () => {
87
+ if (options?.signal?.aborted)
88
+ return;
89
+ timed_out = true;
90
+ }, { once: true });
91
+ }
92
+ try {
93
+ const proc = new Deno.Command(cmd, {
94
+ args,
95
+ cwd: options?.cwd,
96
+ signal,
97
+ stdout: 'piped',
98
+ stderr: 'piped',
99
+ });
100
+ const result = await proc.output();
101
+ const base = {
102
+ success: result.code === 0 && !timed_out,
103
+ code: result.code,
104
+ stdout: new TextDecoder().decode(result.stdout),
105
+ stderr: new TextDecoder().decode(result.stderr),
106
+ };
107
+ if (options?.timeout_ms !== undefined)
108
+ base.timed_out = timed_out;
109
+ return base;
110
+ }
111
+ finally {
112
+ if (timer !== null)
113
+ clearTimeout(timer);
114
+ }
62
115
  }
63
116
  catch (error) {
64
117
  const message = error instanceof Error ? error.message : String(error);
@@ -16,12 +16,28 @@ export interface StatResult {
16
16
  }
17
17
  /**
18
18
  * Result of executing a command.
19
+ *
20
+ * `timed_out` is present only when `timeout_ms` was passed in `RunCommandOptions`
21
+ * and the process was killed after exceeding the timeout. Callers that pass
22
+ * `timeout_ms` should check this flag to distinguish timeout from exit-code failure.
19
23
  */
20
24
  export interface CommandResult {
21
25
  success: boolean;
22
26
  code: number;
23
27
  stdout: string;
24
28
  stderr: string;
29
+ timed_out?: boolean;
30
+ }
31
+ /**
32
+ * Options for `run_command`.
33
+ */
34
+ export interface RunCommandOptions {
35
+ /** Working directory for the child process. */
36
+ cwd?: string;
37
+ /** AbortSignal to terminate the child process. */
38
+ signal?: AbortSignal;
39
+ /** Kill the process and return `timed_out: true` after this many milliseconds. */
40
+ timeout_ms?: number;
25
41
  }
26
42
  /**
27
43
  * Environment variable access.
@@ -32,16 +48,37 @@ export interface EnvDeps {
32
48
  /** Set an environment variable. */
33
49
  env_set: (name: string, value: string) => void;
34
50
  }
51
+ /**
52
+ * Result of reading text from a byte offset.
53
+ */
54
+ export interface ReadTextFromOffsetResult {
55
+ /** Decoded text content read from the offset. */
56
+ content: string;
57
+ /** Number of bytes actually read. */
58
+ bytes_read: number;
59
+ /** Total file size at the time of the read (for truncation detection). */
60
+ file_size: number;
61
+ }
35
62
  /**
36
63
  * File system read operations.
37
64
  */
38
65
  export interface FsReadDeps {
39
66
  /** Get file/directory stats, or null if path doesn't exist. */
40
67
  stat: (path: string) => Promise<StatResult | null>;
41
- /** Read a file as text. */
68
+ /** Read a file as text. Throws if the file does not exist. */
42
69
  read_text_file: (path: string) => Promise<string>;
43
- /** Read a file as bytes. */
70
+ /** Read a file as bytes. Throws if the file does not exist. */
44
71
  read_file: (path: string) => Promise<Uint8Array>;
72
+ /**
73
+ * Read text starting from a byte offset. Throws if the file does not exist.
74
+ *
75
+ * Returns `content`, `bytes_read`, and `file_size` so callers can detect
76
+ * truncation (when `file_size < offset`) and tail incrementally without
77
+ * re-reading the whole file.
78
+ */
79
+ read_text_from_offset: (path: string, offset: number) => Promise<ReadTextFromOffsetResult>;
80
+ /** List directory entries (names, not full paths). Throws if the directory does not exist. */
81
+ readdir: (path: string) => Promise<Array<string>>;
45
82
  }
46
83
  /**
47
84
  * File system write operations.
@@ -71,8 +108,15 @@ export interface FsRemoveDeps {
71
108
  * Command execution.
72
109
  */
73
110
  export interface CommandDeps {
74
- /** Run a command and return the result. */
75
- run_command: (cmd: string, args: Array<string>) => Promise<CommandResult>;
111
+ /**
112
+ * Run a command and return the result. Never throws — failures surface as
113
+ * `success: false`.
114
+ *
115
+ * `options.cwd` sets the child's working directory. `options.signal` aborts
116
+ * the child when the signal fires. `options.timeout_ms` kills the child
117
+ * after the given duration and returns `timed_out: true` on the result.
118
+ */
119
+ run_command: (cmd: string, args: Array<string>, options?: RunCommandOptions) => Promise<CommandResult>;
76
120
  }
77
121
  /**
78
122
  * HTTP fetch capability.
@@ -1 +1 @@
1
- {"version":3,"file":"deps.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/runtime/deps.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"deps.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/runtime/deps.ts"],"names":[],"mappings":"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"}
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * @module
9
9
  */
10
- import type { RuntimeDeps, CommandResult } from './deps.js';
10
+ import type { RuntimeDeps, CommandResult, RunCommandOptions } from './deps.js';
11
11
  /**
12
12
  * Mock `RuntimeDeps` with observable state for assertions.
13
13
  */
@@ -22,10 +22,11 @@ export interface MockRuntime extends RuntimeDeps {
22
22
  mock_dirs: Set<string>;
23
23
  /** Exit calls recorded (exit codes). */
24
24
  exit_calls: Array<number>;
25
- /** Commands executed. */
25
+ /** Commands executed. Captures `options` when passed so tests can assert cwd/timeout/signal. */
26
26
  command_calls: Array<{
27
27
  cmd: string;
28
28
  args: Array<string>;
29
+ options?: RunCommandOptions;
29
30
  }>;
30
31
  /** Commands executed with inherit. */
31
32
  command_inherit_calls: Array<{
@@ -1 +1 @@
1
- {"version":3,"file":"mock.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/runtime/mock.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,WAAW,EAAc,aAAa,EAAC,MAAM,WAAW,CAAC;AAItE;;GAEG;AACH,MAAM,WAAW,WAAY,SAAQ,WAAW;IAC/C,kCAAkC;IAClC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,0CAA0C;IAC1C,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,+CAA+C;IAC/C,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACvC,mCAAmC;IACnC,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACvB,wCAAwC;IACxC,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC1B,yBAAyB;IACzB,aAAa,EAAE,KAAK,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;KAAC,CAAC,CAAC;IACzD,sCAAsC;IACtC,qBAAqB,EAAE,KAAK,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;KAAC,CAAC,CAAC;IACjE,8BAA8B;IAC9B,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7B,4CAA4C;IAC5C,oBAAoB,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACjD,yCAAyC;IACzC,YAAY,EAAE,UAAU,GAAG,IAAI,CAAC;IAChC,4BAA4B;IAC5B,WAAW,EAAE,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,WAAW,CAAA;KAAC,CAAC,CAAC;IACxE,wDAAwD;IACxD,oBAAoB,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;CAC5C;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,mBAAmB,GAAI,OAAM,KAAK,CAAC,MAAM,CAAM,KAAG,WA4K9D,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,SAAS,WAAW,KAAG,IAazD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,SAAS,WAAW,EAAE,OAAO,MAAM,KAAG,IAEpE,CAAC;AAEF;;;;GAIG;AACH,qBAAa,aAAc,SAAQ,KAAK;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM;CAKxB"}
1
+ {"version":3,"file":"mock.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/runtime/mock.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,WAAW,EAAc,aAAa,EAAE,iBAAiB,EAAC,MAAM,WAAW,CAAC;AAIzF;;GAEG;AACH,MAAM,WAAW,WAAY,SAAQ,WAAW;IAC/C,kCAAkC;IAClC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,0CAA0C;IAC1C,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,+CAA+C;IAC/C,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACvC,mCAAmC;IACnC,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACvB,wCAAwC;IACxC,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC1B,gGAAgG;IAChG,aAAa,EAAE,KAAK,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QAAC,OAAO,CAAC,EAAE,iBAAiB,CAAA;KAAC,CAAC,CAAC;IACtF,sCAAsC;IACtC,qBAAqB,EAAE,KAAK,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;KAAC,CAAC,CAAC;IACjE,8BAA8B;IAC9B,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7B,4CAA4C;IAC5C,oBAAoB,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACjD,yCAAyC;IACzC,YAAY,EAAE,UAAU,GAAG,IAAI,CAAC;IAChC,4BAA4B;IAC5B,WAAW,EAAE,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,WAAW,CAAA;KAAC,CAAC,CAAC;IACxE,wDAAwD;IACxD,oBAAoB,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;CAC5C;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,mBAAmB,GAAI,OAAM,KAAK,CAAC,MAAM,CAAM,KAAG,WAkO9D,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,SAAS,WAAW,KAAG,IAazD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,SAAS,WAAW,EAAE,OAAO,MAAM,KAAG,IAEpE,CAAC;AAEF;;;;GAIG;AACH,qBAAa,aAAc,SAAQ,KAAK;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM;CAKxB"}
@@ -114,6 +114,55 @@ export const create_mock_runtime = (args = []) => {
114
114
  error.code = 'ENOENT';
115
115
  throw error;
116
116
  },
117
+ read_text_from_offset: async (path, offset) => {
118
+ let bytes;
119
+ const stored_bytes = mock_fs_bytes.get(path);
120
+ if (stored_bytes !== undefined) {
121
+ bytes = stored_bytes;
122
+ }
123
+ else {
124
+ const content = mock_fs.get(path);
125
+ if (content === undefined) {
126
+ const error = new Error(`ENOENT: no such file or directory: ${path}`);
127
+ error.code = 'ENOENT';
128
+ throw error;
129
+ }
130
+ bytes = new TextEncoder().encode(content);
131
+ }
132
+ const file_size = bytes.length;
133
+ const bytes_to_read = Math.max(0, file_size - offset);
134
+ if (bytes_to_read === 0)
135
+ return { content: '', bytes_read: 0, file_size };
136
+ const slice = bytes.subarray(offset, offset + bytes_to_read);
137
+ return {
138
+ content: new TextDecoder().decode(slice),
139
+ bytes_read: slice.length,
140
+ file_size,
141
+ };
142
+ },
143
+ readdir: async (path) => {
144
+ const prefix = path.endsWith('/') ? path : path + '/';
145
+ const seen = new Set();
146
+ const collect = (key) => {
147
+ if (!key.startsWith(prefix))
148
+ return;
149
+ const rest = key.slice(prefix.length);
150
+ const slash = rest.indexOf('/');
151
+ seen.add(slash === -1 ? rest : rest.slice(0, slash));
152
+ };
153
+ for (const key of mock_fs.keys())
154
+ collect(key);
155
+ for (const key of mock_fs_bytes.keys())
156
+ collect(key);
157
+ for (const key of mock_dirs)
158
+ collect(key);
159
+ if (seen.size === 0 && !mock_dirs.has(path)) {
160
+ const error = new Error(`ENOENT: no such file or directory: ${path}`);
161
+ error.code = 'ENOENT';
162
+ throw error;
163
+ }
164
+ return Array.from(seen).sort();
165
+ },
117
166
  write_text_file: async (path, content) => {
118
167
  mock_fs.set(path, content);
119
168
  },
@@ -163,13 +212,20 @@ export const create_mock_runtime = (args = []) => {
163
212
  throw new TypeError(`fetch failed (no mock for ${url})`);
164
213
  },
165
214
  // === Local Commands ===
166
- run_command: async (cmd, args) => {
167
- command_calls.push({ cmd, args });
215
+ run_command: async (cmd, args, options) => {
216
+ command_calls.push(options ? { cmd, args, options } : { cmd, args });
168
217
  const key = `${cmd} ${args.join(' ')}`;
169
218
  const mocked = mock_command_results.get(key);
170
- if (mocked)
219
+ if (mocked) {
220
+ if (options?.timeout_ms !== undefined && mocked.timed_out === undefined) {
221
+ return { ...mocked, timed_out: false };
222
+ }
171
223
  return mocked;
172
- return { success: true, code: 0, stdout: '', stderr: '' };
224
+ }
225
+ const result = { success: true, code: 0, stdout: '', stderr: '' };
226
+ if (options?.timeout_ms !== undefined)
227
+ result.timed_out = false;
228
+ return result;
173
229
  },
174
230
  run_command_inherit: async (cmd, args) => {
175
231
  command_inherit_calls.push({ cmd, args });
@@ -1 +1 @@
1
- {"version":3,"file":"node.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/runtime/node.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,OAAO,KAAK,EAAC,WAAW,EAA4B,MAAM,WAAW,CAAC;AAEtE;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAC/B,OAAM,aAAa,CAAC,MAAM,CAAyB,KACjD,WAkHD,CAAC"}
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/runtime/node.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,OAAO,KAAK,EAAC,WAAW,EAA4B,MAAM,WAAW,CAAC;AAEtE;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAC/B,OAAM,aAAa,CAAC,MAAM,CAAyB,KACjD,WAmKD,CAAC"}
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { Buffer } from 'node:buffer';
10
10
  import { spawn } from 'node:child_process';
11
- import { stat, mkdir, readFile, writeFile, rename, rm } from 'node:fs/promises';
11
+ import { stat, mkdir, readFile, readdir, writeFile, rename, rm, open } from 'node:fs/promises';
12
12
  import process from 'node:process';
13
13
  /**
14
14
  * Create a `RuntimeDeps` backed by Node.js APIs.
@@ -42,6 +42,27 @@ export const create_node_runtime = (args = process.argv.slice(2)) => ({
42
42
  },
43
43
  read_text_file: (path) => readFile(path, 'utf-8'),
44
44
  read_file: (path) => readFile(path).then((buf) => new Uint8Array(buf)),
45
+ read_text_from_offset: async (path, offset) => {
46
+ const s = await stat(path);
47
+ const file_size = s.size;
48
+ const bytes_to_read = Math.max(0, file_size - offset);
49
+ if (bytes_to_read === 0)
50
+ return { content: '', bytes_read: 0, file_size };
51
+ const handle = await open(path, 'r');
52
+ try {
53
+ const buffer = Buffer.alloc(bytes_to_read);
54
+ const { bytesRead } = await handle.read(buffer, 0, bytes_to_read, offset);
55
+ return {
56
+ content: buffer.toString('utf-8', 0, bytesRead),
57
+ bytes_read: bytesRead,
58
+ file_size,
59
+ };
60
+ }
61
+ finally {
62
+ await handle.close();
63
+ }
64
+ },
65
+ readdir: (path) => readdir(path),
45
66
  write_text_file: (path, content) => writeFile(path, content, 'utf-8'),
46
67
  write_file: (path, data) => writeFile(path, data),
47
68
  rename: (old_path, new_path) => rename(old_path, new_path),
@@ -49,17 +70,45 @@ export const create_node_runtime = (args = process.argv.slice(2)) => ({
49
70
  // === HTTP ===
50
71
  fetch: globalThis.fetch,
51
72
  // === Local Commands ===
52
- run_command: (cmd, args) => {
73
+ run_command: (cmd, args, options) => {
53
74
  return new Promise((resolve) => {
54
75
  const proc = spawn(cmd, args, {
55
76
  stdio: ['ignore', 'pipe', 'pipe'],
77
+ cwd: options?.cwd,
56
78
  });
57
79
  const stdout_chunks = [];
58
80
  const stderr_chunks = [];
81
+ let timed_out = false;
82
+ let done = false;
83
+ const finish = (result) => {
84
+ if (done)
85
+ return;
86
+ done = true;
87
+ if (timer !== null)
88
+ clearTimeout(timer);
89
+ if (options?.signal)
90
+ options.signal.removeEventListener('abort', on_abort);
91
+ resolve(result);
92
+ };
93
+ const on_abort = () => {
94
+ proc.kill();
95
+ };
96
+ const timer = options?.timeout_ms !== undefined
97
+ ? setTimeout(() => {
98
+ timed_out = true;
99
+ proc.kill();
100
+ }, options.timeout_ms)
101
+ : null;
102
+ if (options?.signal) {
103
+ if (options.signal.aborted)
104
+ proc.kill();
105
+ else
106
+ options.signal.addEventListener('abort', on_abort, { once: true });
107
+ }
59
108
  proc.stdout.on('data', (chunk) => stdout_chunks.push(chunk));
60
109
  proc.stderr.on('data', (chunk) => stderr_chunks.push(chunk));
61
110
  proc.on('error', (error) => {
62
- resolve({
111
+ finish({
63
112
  success: false,
64
113
  code: 1,
65
114
  stdout: '',
@@ -67,12 +116,15 @@ export const create_node_runtime = (args = process.argv.slice(2)) => ({
67
116
  });
68
117
  });
69
118
  proc.on('close', (code) => {
70
- resolve({
71
- success: code === 0,
119
+ const result = {
120
+ success: code === 0 && !timed_out,
72
121
  code: code ?? 1,
73
122
  stdout: Buffer.concat(stdout_chunks).toString('utf-8').trim(),
74
123
  stderr: Buffer.concat(stderr_chunks).toString('utf-8').trim(),
75
- });
124
+ };
125
+ if (options?.timeout_ms !== undefined)
126
+ result.timed_out = timed_out;
127
+ finish(result);
76
128
  });
77
129
  });
78
130
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_app",
3
- "version": "0.25.0",
3
+ "version": "0.27.0",
4
4
  "description": "fullstack app library",
5
5
  "glyph": "🗝",
6
6
  "logo": "logo.svg",