@oh-my-pi/pi-utils 18.11.1 → 18.11.2

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/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.11.2] - 2026-10-16
6
+
7
+ ### Added
8
+
9
+ - Exported `sqliteFileIdentity()`, so a caller holding a long-lived SQLite connection can tell when its path now names a different file (e.g. after a corrupt store was quarantined and replaced) ([#14361](https://github.com/can1357/oh-my-pi/pull/14361) by [@F0Rextasy](https://github.com/F0Rextasy))
10
+ - Added `slugify()`, the shared lowercase ASCII kebab-case helper with an optional `maxLength` ([#14283](https://github.com/can1357/oh-my-pi/pull/14283) by [@jaredlyon](https://github.com/jaredlyon))
11
+
12
+ ### Fixed
13
+
14
+ - Fixed an unhandled rejection when a subprocess is spawned with an already-aborted signal ([#15750](https://github.com/can1357/oh-my-pi/pull/15750) by [@harshaygadekar](https://github.com/harshaygadekar))
15
+
5
16
  ## [18.10.0] - 2026-10-12
6
17
 
7
18
  ### Added
@@ -31,6 +31,7 @@ export * as ptree from "./ptree.js";
31
31
  export { AbortError, ChildProcess, Exception, NonZeroExitError } from "./ptree.js";
32
32
  export * from "./runtime-install.js";
33
33
  export * from "./sanitize-text.js";
34
+ export * from "./slugify.js";
34
35
  export * from "./snowflake.js";
35
36
  export * from "./sqlite.js";
36
37
  export * from "./stderr-guard.js";
@@ -0,0 +1,4 @@
1
+ /** Lowercase ASCII kebab slug: [^a-z0-9]+ → "-", edge dashes trimmed; optional maxLength truncates then re-trims trailing dashes; throws RangeError unless maxLength is a non-negative integer. Returns "" when nothing survives. */
2
+ export declare function slugify(input: string, options?: {
3
+ maxLength?: number;
4
+ }): string;
@@ -1,5 +1,17 @@
1
1
  /** Shared SQLite opening, error attribution, and result-code classification for persistent stores. */
2
2
  import { Database } from "bun:sqlite";
3
+ /**
4
+ * A store's identity as `dev:ino:birthtimeMs`, `null` when the path holds no
5
+ * file, or `undefined` when the stat itself failed and no conclusion is safe.
6
+ */
7
+ export type SqliteFileIdentity = string | null | undefined;
8
+ /**
9
+ * Identity of the file currently at `dbPath`. Quarantine moves a corrupt store
10
+ * aside and recreates one at the same path, so a connection cached on the path
11
+ * alone keeps writing to the unlinked inode: callers that hold a long-lived
12
+ * handle compare this before reusing it.
13
+ */
14
+ export declare function sqliteFileIdentity(dbPath: string): SqliteFileIdentity;
3
15
  /** Controls opt-in replacement of an unrecoverably corrupt SQLite store. */
4
16
  export interface SqliteOpenOptions {
5
17
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-utils",
3
- "version": "18.11.1",
3
+ "version": "18.11.2",
4
4
  "description": "Shared utilities for pi packages",
5
5
  "keywords": [
6
6
  "cli",
@@ -57,7 +57,7 @@
57
57
  "fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
58
58
  },
59
59
  "dependencies": {
60
- "@oh-my-pi/pi-natives": "18.11.1"
60
+ "@oh-my-pi/pi-natives": "18.11.2"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@types/bun": "^1.3.14"
package/src/index.ts CHANGED
@@ -31,6 +31,7 @@ export * as ptree from "./ptree";
31
31
  export { AbortError, ChildProcess, Exception, NonZeroExitError } from "./ptree";
32
32
  export * from "./runtime-install";
33
33
  export * from "./sanitize-text";
34
+ export * from "./slugify";
34
35
  export * from "./snowflake";
35
36
  export * from "./sqlite";
36
37
  export * from "./stderr-guard";
package/src/ptree.ts CHANGED
@@ -443,7 +443,9 @@ export class ChildProcess<In extends InMask = InMask> {
443
443
  // ── Output helpers ───────────────────────────────────────────────────
444
444
 
445
445
  async #throwIfAborted(): Promise<void> {
446
- const exitReason = this.exitReason;
446
+ // A kill() issued before Bun reports the root's exit only records the
447
+ // reason as pending; output readers that do not wait for exit must still see it.
448
+ const exitReason = this.#exitReason ?? this.#exitReasonPending;
447
449
  if (!exitReason?.aborted) return;
448
450
  if (this.#terminating) await this.#terminating;
449
451
  throw exitReason;
@@ -589,10 +591,11 @@ export class ChildProcess<In extends InMask = InMask> {
589
591
  // ── Signal / timeout ─────────────────────────────────────────────────
590
592
 
591
593
  attachSignal(signal: AbortSignal): void {
594
+ const cleanup = this.#exited.catch(() => {});
592
595
  const onAbort = () => this.kill(new AbortError(signal.reason, "<cancelled>"));
593
596
  if (signal.aborted) return void onAbort();
594
597
  signal.addEventListener("abort", onAbort, { once: true });
595
- this.#exited.catch(() => {}).finally(() => signal.removeEventListener("abort", onAbort));
598
+ cleanup.finally(() => signal.removeEventListener("abort", onAbort));
596
599
  }
597
600
 
598
601
  #clearTimeout(): void {
package/src/slugify.ts ADDED
@@ -0,0 +1,11 @@
1
+ /** Lowercase ASCII kebab slug: [^a-z0-9]+ → "-", edge dashes trimmed; optional maxLength truncates then re-trims trailing dashes; throws RangeError unless maxLength is a non-negative integer. Returns "" when nothing survives. */
2
+ export function slugify(input: string, options?: { maxLength?: number }): string {
3
+ if (options?.maxLength !== undefined && !(Number.isInteger(options.maxLength) && options.maxLength >= 0)) {
4
+ throw new RangeError(`slugify maxLength must be a non-negative integer, got ${options.maxLength}`);
5
+ }
6
+ const slug = input
7
+ .toLowerCase()
8
+ .replace(/[^a-z0-9]+/g, "-")
9
+ .replace(/^-+|-+$/g, "");
10
+ return options?.maxLength === undefined ? slug : slug.slice(0, options.maxLength).replace(/-+$/g, "");
11
+ }
package/src/sqlite.ts CHANGED
@@ -11,7 +11,11 @@ const BUSY_MAX_ATTEMPTS = 4;
11
11
  const BUSY_BASE_DELAY_MS = 100;
12
12
  const SQLITE_STORE_SUFFIXES = ["-wal", "-shm", "-journal", ""];
13
13
 
14
- type SqliteFileIdentity = string | null | undefined;
14
+ /**
15
+ * A store's identity as `dev:ino:birthtimeMs`, `null` when the path holds no
16
+ * file, or `undefined` when the stat itself failed and no conclusion is safe.
17
+ */
18
+ export type SqliteFileIdentity = string | null | undefined;
15
19
 
16
20
  /**
17
21
  * Corrupt handles held open by in-process openers awaiting recovery, keyed by
@@ -77,7 +81,13 @@ class SqliteAttemptFailure extends Error {
77
81
  }
78
82
  }
79
83
 
80
- function sqliteFileIdentity(dbPath: string): SqliteFileIdentity {
84
+ /**
85
+ * Identity of the file currently at `dbPath`. Quarantine moves a corrupt store
86
+ * aside and recreates one at the same path, so a connection cached on the path
87
+ * alone keeps writing to the unlinked inode: callers that hold a long-lived
88
+ * handle compare this before reusing it.
89
+ */
90
+ export function sqliteFileIdentity(dbPath: string): SqliteFileIdentity {
81
91
  try {
82
92
  const stat = fs.statSync(dbPath);
83
93
  return `${stat.dev}:${stat.ino}:${stat.birthtimeMs}`;