@gjsify/utils 0.0.3 → 0.1.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.
Files changed (119) hide show
  1. package/README.md +26 -0
  2. package/lib/esm/base64.js +72 -0
  3. package/lib/esm/byte-array.js +6 -0
  4. package/lib/esm/cli.js +1 -2
  5. package/lib/esm/defer.js +6 -0
  6. package/lib/esm/encoding.js +35 -0
  7. package/lib/esm/fs.js +2 -1
  8. package/lib/esm/gio-errors.js +98 -0
  9. package/lib/esm/gio.js +39 -0
  10. package/lib/esm/globals.js +8 -0
  11. package/lib/esm/index.js +10 -8
  12. package/lib/esm/main-loop.js +28 -0
  13. package/lib/esm/next-tick.js +6 -0
  14. package/lib/esm/structured-clone.js +169 -0
  15. package/lib/types/base64.d.ts +8 -0
  16. package/lib/types/byte-array.d.ts +7 -0
  17. package/lib/types/defer.d.ts +7 -0
  18. package/lib/types/encoding.d.ts +11 -0
  19. package/lib/types/gio-errors.d.ts +28 -0
  20. package/lib/types/gio.d.ts +17 -0
  21. package/lib/types/globals.d.ts +5 -0
  22. package/lib/types/index.d.ts +10 -8
  23. package/lib/types/main-loop.d.ts +20 -0
  24. package/lib/types/next-tick.d.ts +5 -0
  25. package/lib/types/structured-clone.d.ts +15 -0
  26. package/package.json +18 -20
  27. package/src/base64.ts +78 -0
  28. package/src/byte-array.ts +12 -0
  29. package/src/defer.ts +11 -0
  30. package/src/encoding.ts +36 -0
  31. package/src/fs.ts +2 -2
  32. package/src/gio-errors.ts +101 -0
  33. package/src/gio.ts +67 -0
  34. package/src/globals.ts +13 -0
  35. package/src/index.ts +10 -9
  36. package/src/main-loop.ts +62 -0
  37. package/src/next-tick.ts +16 -0
  38. package/src/structured-clone.ts +242 -0
  39. package/src/test.ts +1 -3
  40. package/tsconfig.json +28 -11
  41. package/tsconfig.tsbuildinfo +1 -0
  42. package/lib/cjs/cancel-handler.js +0 -22
  43. package/lib/cjs/cli.js +0 -11
  44. package/lib/cjs/error.js +0 -21
  45. package/lib/cjs/file.js +0 -13
  46. package/lib/cjs/fs.js +0 -21
  47. package/lib/cjs/index.js +0 -14
  48. package/lib/cjs/log.js +0 -159
  49. package/lib/cjs/message.js +0 -13
  50. package/lib/cjs/os.js +0 -81
  51. package/lib/cjs/path.js +0 -50
  52. package/lib/cjs/process.js +0 -41
  53. package/lib/cjs/system.js +0 -47
  54. package/lib/cjs/tty.js +0 -13
  55. package/lib/cjs/types/cancel-signals.js +0 -0
  56. package/lib/cjs/types/error-data.js +0 -0
  57. package/lib/cjs/types/index.js +0 -8
  58. package/lib/cjs/types/signal-methods.js +0 -0
  59. package/lib/cjs/types/stack-trace-frame.js +0 -0
  60. package/lib/cjs/types/structured-log-data.js +0 -0
  61. package/lib/cjs/types/uncaught-exception-data.js +0 -0
  62. package/lib/cjs/types/unhandled-rejection-data.js +0 -0
  63. package/lib/cjs/types/user-info.js +0 -0
  64. package/lib/cjs/version.js +0 -7
  65. package/lib/esm/cancel-handler.js +0 -22
  66. package/lib/esm/log.js +0 -159
  67. package/lib/esm/os.js +0 -81
  68. package/lib/esm/process.js +0 -41
  69. package/lib/esm/system.js +0 -47
  70. package/lib/esm/tty.js +0 -13
  71. package/lib/esm/types/cancel-signals.js +0 -0
  72. package/lib/esm/types/error-data.js +0 -0
  73. package/lib/esm/types/index.js +0 -8
  74. package/lib/esm/types/signal-methods.js +0 -0
  75. package/lib/esm/types/stack-trace-frame.js +0 -0
  76. package/lib/esm/types/structured-log-data.js +0 -0
  77. package/lib/esm/types/uncaught-exception-data.js +0 -0
  78. package/lib/esm/types/unhandled-rejection-data.js +0 -0
  79. package/lib/esm/types/user-info.js +0 -0
  80. package/lib/esm/version.js +0 -7
  81. package/lib/types/cancel-handler.d.ts +0 -4
  82. package/lib/types/log.d.ts +0 -29
  83. package/lib/types/os.d.ts +0 -7
  84. package/lib/types/process.d.ts +0 -6
  85. package/lib/types/system.d.ts +0 -5
  86. package/lib/types/tty.d.ts +0 -5
  87. package/lib/types/types/cancel-signals.d.ts +0 -5
  88. package/lib/types/types/error-data.d.ts +0 -7
  89. package/lib/types/types/index.d.ts +0 -8
  90. package/lib/types/types/signal-methods.d.ts +0 -62
  91. package/lib/types/types/stack-trace-frame.d.ts +0 -6
  92. package/lib/types/types/structured-log-data.d.ts +0 -5
  93. package/lib/types/types/uncaught-exception-data.d.ts +0 -4
  94. package/lib/types/types/unhandled-rejection-data.d.ts +0 -5
  95. package/lib/types/types/user-info.d.ts +0 -8
  96. package/lib/types/version.d.ts +0 -1
  97. package/src/cancel-handler.ts +0 -24
  98. package/src/log.ts +0 -218
  99. package/src/os.ts +0 -94
  100. package/src/process.spec.ts +0 -24
  101. package/src/process.ts +0 -46
  102. package/src/system.ts +0 -56
  103. package/src/tty.spec.ts +0 -10
  104. package/src/tty.ts +0 -10
  105. package/src/types/cancel-signals.ts +0 -6
  106. package/src/types/error-data.ts +0 -8
  107. package/src/types/index.ts +0 -8
  108. package/src/types/signal-methods.ts +0 -63
  109. package/src/types/stack-trace-frame.ts +0 -7
  110. package/src/types/structured-log-data.ts +0 -5
  111. package/src/types/uncaught-exception-data.ts +0 -5
  112. package/src/types/unhandled-rejection-data.ts +0 -6
  113. package/src/types/user-info.ts +0 -9
  114. package/src/version.ts +0 -4
  115. package/test.gjs.js +0 -34793
  116. package/test.gjs.js.map +0 -7
  117. package/test.gjs.js.meta.json +0 -1
  118. package/tsconfig.types.json +0 -7
  119. package/tsconfig.types.tsbuildinfo +0 -1
@@ -0,0 +1,20 @@
1
+ import type GLib from '@girs/glib-2.0';
2
+ /**
3
+ * Ensure a GLib MainLoop is running for async I/O dispatch (Soup.Server,
4
+ * Gio.SocketService, etc.). No-op on Node.js. Idempotent.
5
+ *
6
+ * - Called automatically by `http.Server.listen()`, `net.Server.listen()`,
7
+ * `dgram.Socket.bind()` etc.
8
+ * - GTK apps should NOT call this — they use `Gtk.Application.runAsync()` instead.
9
+ *
10
+ * @returns The MainLoop instance on GJS, or `undefined` on Node.js.
11
+ */
12
+ export declare function ensureMainLoop(): GLib.MainLoop | undefined;
13
+ /**
14
+ * Quit the MainLoop created by `ensureMainLoop()`. Idempotent, no-op on Node.js.
15
+ *
16
+ * Calling `quit()` on a loop that hasn't started yet pre-quits it — when the
17
+ * `setMainLoopHook` later fires and calls `run()`, it returns immediately.
18
+ * This is used by `@gjsify/unit` to prevent the loop from blocking after tests.
19
+ */
20
+ export declare function quitMainLoop(): void;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Schedule a function on the microtask queue.
3
+ * Fallback chain: process.nextTick → queueMicrotask → Promise.resolve().then()
4
+ */
5
+ export declare const nextTick: (fn: (...args: unknown[]) => void, ...args: unknown[]) => void;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * structuredClone polyfill implementing the HTML structured clone algorithm.
3
+ *
4
+ * Supports: primitives (incl. -0, NaN, Infinity, BigInt), wrapper objects,
5
+ * Date, RegExp, Error types, ArrayBuffer, TypedArrays, DataView, Map, Set,
6
+ * Blob, File, circular/shared references, plain objects and arrays.
7
+ *
8
+ * Throws DataCloneError for: functions, symbols, WeakMap, WeakSet, WeakRef, Promise.
9
+ *
10
+ * @param value The value to clone
11
+ * @param _options Reserved for future transfer list support (currently ignored)
12
+ */
13
+ export declare function structuredClone<T>(value: T, _options?: {
14
+ transfer?: unknown[];
15
+ }): T;
package/package.json CHANGED
@@ -1,32 +1,29 @@
1
1
  {
2
2
  "name": "@gjsify/utils",
3
- "version": "0.0.3",
3
+ "version": "0.1.0",
4
4
  "description": "Utils module for gjsify",
5
- "main": "lib/cjs/index.js",
6
5
  "module": "lib/esm/index.js",
7
6
  "types": "lib/types/index.d.ts",
8
7
  "type": "module",
9
8
  "exports": {
10
9
  ".": {
11
- "import": {
12
- "types": "./lib/types/index.d.ts",
13
- "default": "./lib/esm/index.js"
14
- },
15
- "require": {
16
- "types": "./lib/types/index.d.ts",
17
- "default": "./lib/cjs/index.js"
18
- }
10
+ "types": "./lib/types/index.d.ts",
11
+ "default": "./lib/esm/index.js"
12
+ },
13
+ "./main-loop": {
14
+ "types": "./lib/types/main-loop.d.ts",
15
+ "default": "./lib/esm/main-loop.js"
19
16
  }
20
17
  },
21
18
  "scripts": {
22
- "clear": "rm -rf lib tsconfig.tsbuildinfo tsconfig.types.tsbuildinfo || exit 0",
23
- "print:name": "echo '@gjsify/utils'",
24
- "build": "yarn print:name && yarn build:gjsify && yarn build:types",
19
+ "clear": "rm -rf lib tsconfig.tsbuildinfo tsconfig.types.tsbuildinfo test.gjs.mjs test.node.mjs || exit 0",
20
+ "check": "tsc --noEmit",
21
+ "build": "yarn build:gjsify && yarn build:types",
25
22
  "build:gjsify": "gjsify build --library 'src/**/*.{ts,js}' --exclude 'src/**/*.spec.{mts,ts}' 'src/test.{mts,ts}'",
26
- "build:types": "tsc --project tsconfig.types.json",
23
+ "build:types": "tsc",
27
24
  "build:test": "yarn build:test:gjs",
28
25
  "build:test:gjs": "gjsify build src/test.ts --app gjs --outfile test.gjs.js",
29
- "test": "yarn print:name && yarn build:gjsify && yarn build:test && yarn test:gjs",
26
+ "test": "yarn build:gjsify && yarn build:test && yarn test:gjs",
30
27
  "test:gjs": "gjs -m test.gjs.js"
31
28
  },
32
29
  "keywords": [
@@ -35,12 +32,13 @@
35
32
  "fs"
36
33
  ],
37
34
  "devDependencies": {
38
- "@gjsify/cli": "^0.0.3",
39
- "typescript": "^5.1.3"
35
+ "@gjsify/cli": "^0.1.0",
36
+ "typescript": "^6.0.2"
40
37
  },
41
38
  "dependencies": {
42
- "@girs/gio-2.0": "2.76.1-3.1.0",
43
- "@girs/gjs": "^3.1.0",
44
- "@girs/glib-2.0": "2.76.1-3.1.0"
39
+ "@girs/gio-2.0": "^2.88.0-4.0.0-beta.42",
40
+ "@girs/giounix-2.0": "^2.0.0-4.0.0-beta.42",
41
+ "@girs/gjs": "^4.0.0-beta.42",
42
+ "@girs/glib-2.0": "^2.88.0-4.0.0-beta.42"
45
43
  }
46
44
  }
package/src/base64.ts ADDED
@@ -0,0 +1,78 @@
1
+ // Base64 encoding/decoding utilities for GJS
2
+ // Shared by @gjsify/buffer and @gjsify/string_decoder
3
+ // Reference: RFC 4648
4
+
5
+ const B64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
6
+ const B64_LOOKUP = new Uint8Array(256);
7
+ for (let i = 0; i < B64_CHARS.length; i++) B64_LOOKUP[B64_CHARS.charCodeAt(i)] = i;
8
+
9
+ /** Decode a base64 string to a binary string. Uses native atob when available. */
10
+ export function atobPolyfill(str: string): string {
11
+ if (typeof globalThis.atob === 'function') return globalThis.atob(str);
12
+ const cleaned = str.replace(/[=\s]/g, '');
13
+ let result = '';
14
+ let bits = 0;
15
+ let collected = 0;
16
+ for (let i = 0; i < cleaned.length; i++) {
17
+ bits = (bits << 6) | B64_LOOKUP[cleaned.charCodeAt(i)];
18
+ collected += 6;
19
+ if (collected >= 8) {
20
+ collected -= 8;
21
+ result += String.fromCharCode((bits >> collected) & 0xff);
22
+ }
23
+ }
24
+ return result;
25
+ }
26
+
27
+ /** Encode a binary string to base64. Uses native btoa when available. */
28
+ export function btoaPolyfill(str: string): string {
29
+ if (typeof globalThis.btoa === 'function') return globalThis.btoa(str);
30
+ let result = '';
31
+ let i = 0;
32
+ for (; i + 2 < str.length; i += 3) {
33
+ const n = (str.charCodeAt(i) << 16) | (str.charCodeAt(i + 1) << 8) | str.charCodeAt(i + 2);
34
+ result += B64_CHARS[(n >> 18) & 63] + B64_CHARS[(n >> 12) & 63] + B64_CHARS[(n >> 6) & 63] + B64_CHARS[n & 63];
35
+ }
36
+ if (i + 1 === str.length) {
37
+ const n = str.charCodeAt(i) << 16;
38
+ result += B64_CHARS[(n >> 18) & 63] + B64_CHARS[(n >> 12) & 63] + '==';
39
+ } else if (i + 2 === str.length) {
40
+ const n = (str.charCodeAt(i) << 16) | (str.charCodeAt(i + 1) << 8);
41
+ result += B64_CHARS[(n >> 18) & 63] + B64_CHARS[(n >> 12) & 63] + B64_CHARS[(n >> 6) & 63] + '=';
42
+ }
43
+ return result;
44
+ }
45
+
46
+ /** Decode a base64 string directly to Uint8Array (avoids lossy atob string round-trip). */
47
+ export function base64Decode(str: string): Uint8Array {
48
+ const cleaned = str.replace(/[=\s]/g, '');
49
+ const bytes = new Uint8Array((cleaned.length * 3) >> 2);
50
+ let bits = 0;
51
+ let collected = 0;
52
+ let pos = 0;
53
+ for (let i = 0; i < cleaned.length; i++) {
54
+ bits = (bits << 6) | B64_LOOKUP[cleaned.charCodeAt(i)];
55
+ collected += 6;
56
+ if (collected >= 8) {
57
+ collected -= 8;
58
+ bytes[pos++] = (bits >> collected) & 0xff;
59
+ }
60
+ }
61
+ return bytes.subarray(0, pos);
62
+ }
63
+
64
+ /** Encode a Uint8Array to base64 string. */
65
+ export function base64Encode(bytes: Uint8Array): string {
66
+ let result = '';
67
+ const len = bytes.length;
68
+ for (let i = 0; i < len; i += 3) {
69
+ const b0 = bytes[i];
70
+ const b1 = i + 1 < len ? bytes[i + 1] : 0;
71
+ const b2 = i + 2 < len ? bytes[i + 2] : 0;
72
+ result += B64_CHARS[b0 >> 2];
73
+ result += B64_CHARS[((b0 & 3) << 4) | (b1 >> 4)];
74
+ result += i + 1 < len ? B64_CHARS[((b1 & 0xf) << 2) | (b2 >> 6)] : '=';
75
+ result += i + 2 < len ? B64_CHARS[b2 & 0x3f] : '=';
76
+ }
77
+ return result;
78
+ }
@@ -0,0 +1,12 @@
1
+ import type GLib from '@girs/glib-2.0';
2
+
3
+ declare const imports: { byteArray: { fromGBytes(input: GLib.Bytes): Uint8Array } };
4
+
5
+ /**
6
+ * Convert GLib.Bytes to Uint8Array using GJS's byteArray module.
7
+ * This wraps the GJS-specific `imports.byteArray.fromGBytes()` API
8
+ * with proper typing to eliminate `as any` casts throughout the codebase.
9
+ */
10
+ export function gbytesToUint8Array(bytes: GLib.Bytes): Uint8Array {
11
+ return imports.byteArray.fromGBytes(bytes);
12
+ }
package/src/defer.ts ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Defer an event emission to the next macrotask, matching Node.js behavior
3
+ * for server 'listening', 'close', and 'error' events.
4
+ */
5
+ export function deferEmit(
6
+ emitter: { emit(event: string | symbol, ...args: unknown[]): boolean },
7
+ event: string,
8
+ ...args: unknown[]
9
+ ): void {
10
+ setTimeout(() => emitter.emit(event, ...args), 0);
11
+ }
@@ -0,0 +1,36 @@
1
+ // Shared encoding normalization utilities.
2
+ // Used by buffer, string_decoder, crypto, and other packages that deal with encodings.
3
+
4
+ /** Canonical encoding names (matches BufferEncoding from @types/node). */
5
+ export type Encoding = 'utf8' | 'ascii' | 'latin1' | 'base64' | 'base64url' | 'hex' | 'utf16le' | 'binary' | 'ucs2' | 'ucs-2' | 'utf-8';
6
+
7
+ const VALID_ENCODINGS = ['utf8', 'ascii', 'latin1', 'binary', 'base64', 'base64url', 'hex', 'ucs2', 'utf16le'];
8
+
9
+ /**
10
+ * Normalize an encoding string to a canonical encoding value.
11
+ * Returns 'utf8' as default for undefined/null/empty input.
12
+ */
13
+ export function normalizeEncoding(enc?: string): Encoding {
14
+ if (!enc || enc === 'utf8' || enc === 'utf-8') return 'utf8';
15
+ const lower = ('' + enc).toLowerCase().replace(/-/g, '');
16
+ switch (lower) {
17
+ case 'utf8': return 'utf8';
18
+ case 'ascii': return 'ascii';
19
+ case 'latin1': case 'binary': return 'latin1';
20
+ case 'base64': return 'base64';
21
+ case 'base64url': return 'base64url';
22
+ case 'hex': return 'hex';
23
+ case 'ucs2': case 'utf16le': return 'utf16le';
24
+ default: return 'utf8';
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Check that an encoding string is valid. Throws TypeError if not.
30
+ */
31
+ export function checkEncoding(encoding: string): void {
32
+ const lower = ('' + encoding).toLowerCase().replace(/-/g, '');
33
+ if (!VALID_ENCODINGS.includes(lower)) {
34
+ throw new TypeError(`Unknown encoding: ${encoding}`);
35
+ }
36
+ }
package/src/fs.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import Gio from '@girs/gio-2.0';
2
-
2
+ import GioUnix from '@girs/giounix-2.0';
3
3
  /** Check if a file descriptor exists */
4
4
  export const existsFD = (fd: number) => {
5
5
  try {
6
- let stream = Gio.UnixInputStream.new(fd, false);
6
+ let stream = GioUnix.InputStream.new(fd, false);
7
7
  stream.close(null);
8
8
  // File descriptor 12345 exists
9
9
  return true
@@ -0,0 +1,101 @@
1
+ // Shared Gio.IOErrorEnum → Node.js error code mapping.
2
+ // Used by fs, net, http, dns, child-process, and other packages that wrap Gio operations.
3
+ //
4
+ // The enum values are numeric constants from GLib — we use numbers directly
5
+ // to avoid importing Gio just for error handling (keeps this usable in Node.js tests too).
6
+
7
+ /** Map from Gio.IOErrorEnum numeric values to Node.js error code strings. */
8
+ export const GIO_ERROR_TO_NODE: Record<number, string> = {
9
+ 0: 'EIO', // FAILED
10
+ 1: 'ENOENT', // NOT_FOUND
11
+ 2: 'EEXIST', // EXISTS
12
+ 3: 'EISDIR', // IS_DIRECTORY
13
+ 4: 'ENOTDIR', // NOT_DIRECTORY
14
+ 5: 'ENOTEMPTY', // NOT_EMPTY
15
+ 6: 'ENOENT', // NOT_REGULAR_FILE
16
+ 7: 'ENFILE', // TOO_MANY_OPEN_FILES
17
+ 9: 'EACCES', // NOT_MOUNTABLE_FILE
18
+ 10: 'ENFILE', // FILENAME_TOO_LONG
19
+ 11: 'EINVAL', // INVALID_FILENAME
20
+ 12: 'ELOOP', // TOO_MANY_LINKS
21
+ 13: 'ENOSPC', // NO_SPACE
22
+ 14: 'EACCES', // PERMISSION_DENIED
23
+ 17: 'ELOOP', // TOO_MANY_LINKS (duplicate guard)
24
+ 19: 'ENOSPC', // NO_SPACE (duplicate guard)
25
+ 20: 'ENOTSUP', // NOT_SUPPORTED
26
+ 22: 'EMFILE', // TOO_MANY_OPEN_FILES
27
+ 24: 'EROFS', // READ_ONLY
28
+ 25: 'ECANCELED', // CANCELLED
29
+ 26: 'EBUSY', // BUSY
30
+ 27: 'ETIMEDOUT', // TIMED_OUT
31
+ 28: 'EHOSTUNREACH', // HOST_NOT_FOUND (was WOULD_BLOCK)
32
+ 30: 'EHOSTUNREACH', // HOST_NOT_FOUND
33
+ 31: 'ENETUNREACH', // NETWORK_UNREACHABLE
34
+ 32: 'ECONNREFUSED', // CONNECTION_REFUSED (legacy value)
35
+ 33: 'EADDRINUSE', // ADDRESS_IN_USE
36
+ 34: 'ECONNRESET', // CONNECTION_CLOSED (mapped to reset)
37
+ 36: 'EPIPE', // BROKEN_PIPE
38
+ 38: 'ENETUNREACH', // NETWORK_UNREACHABLE (actual GJS value)
39
+ 39: 'ECONNREFUSED', // CONNECTION_REFUSED (actual GJS value)
40
+ 40: 'ECONNREFUSED', // PROXY_FAILED
41
+ 41: 'EACCES', // PROXY_AUTH_FAILED
42
+ 44: 'ECONNRESET', // CONNECTION_CLOSED (actual GJS value)
43
+ 46: 'EMSGSIZE', // MESSAGE_TOO_LARGE
44
+ };
45
+
46
+ export interface NodeErrorDetails {
47
+ path?: string;
48
+ dest?: string;
49
+ address?: string;
50
+ port?: number;
51
+ hostname?: string;
52
+ }
53
+
54
+ /** Node.js-style ErrnoException (defined locally to avoid @types/node dependency). */
55
+ export interface ErrnoException extends Error {
56
+ errno?: number;
57
+ code?: string;
58
+ path?: string;
59
+ syscall?: string;
60
+ address?: string;
61
+ port?: number;
62
+ hostname?: string;
63
+ }
64
+
65
+ /**
66
+ * Create a Node.js-style ErrnoException from a Gio error.
67
+ * Works for fs, net, dns, child-process, and other modules.
68
+ */
69
+ export function createNodeError(
70
+ err: unknown,
71
+ syscall: string,
72
+ details?: NodeErrorDetails
73
+ ): ErrnoException {
74
+ const errObj = err as { code?: number; message?: string } | null | undefined;
75
+ const code = GIO_ERROR_TO_NODE[errObj?.code ?? -1] || 'EIO';
76
+
77
+ let msg = `${code}: ${errObj?.message || 'unknown error'}, ${syscall}`;
78
+ if (details?.path) msg += ` '${details.path}'`;
79
+ if (details?.dest) msg += ` -> '${details.dest}'`;
80
+ if (details?.address) msg += ` ${details.address}`;
81
+ if (details?.port != null) msg += `:${details.port}`;
82
+
83
+ const error = new Error(msg) as ErrnoException;
84
+ error.code = code;
85
+ error.syscall = syscall;
86
+ error.errno = -(errObj?.code || 0);
87
+
88
+ if (details?.path) error.path = details.path;
89
+ if (details?.address) error.address = details.address;
90
+ if (details?.port != null) error.port = details.port;
91
+
92
+ return error;
93
+ }
94
+
95
+ /**
96
+ * Check if a Gio error is a "not found" error.
97
+ */
98
+ export function isNotFoundError(err: unknown): boolean {
99
+ const errObj = err as { code?: number | string } | null | undefined;
100
+ return errObj?.code === 1 || errObj?.code === 'ENOENT';
101
+ }
package/src/gio.ts ADDED
@@ -0,0 +1,67 @@
1
+ import Gio from '@girs/gio-2.0';
2
+ import GLib from '@girs/glib-2.0';
3
+
4
+ const byteArray = imports.byteArray;
5
+
6
+ /**
7
+ * Generic promise wrapper for Gio async/finish method pairs.
8
+ *
9
+ * Example:
10
+ * const stream = await gioAsync<Gio.InputStream>(session, 'send_async', 'send_finish', msg, priority, null);
11
+ */
12
+ export function gioAsync<T>(
13
+ obj: any,
14
+ asyncMethod: string,
15
+ finishMethod: string,
16
+ ...args: any[]
17
+ ): Promise<T> {
18
+ return new Promise<T>((resolve, reject) => {
19
+ obj[asyncMethod](...args, (_self: any, asyncRes: Gio.AsyncResult) => {
20
+ try {
21
+ resolve(obj[finishMethod](asyncRes));
22
+ } catch (error) {
23
+ reject(error);
24
+ }
25
+ });
26
+ });
27
+ }
28
+
29
+ /**
30
+ * Promise wrapper around `Gio.InputStream.read_bytes_async` / `read_bytes_finish`.
31
+ * Returns a `Uint8Array` or `null` if the end of the stream is reached.
32
+ */
33
+ export async function readBytesAsync(
34
+ inputStream: Gio.InputStream,
35
+ count = 4096,
36
+ ioPriority = GLib.PRIORITY_DEFAULT,
37
+ cancellable: Gio.Cancellable | null = null
38
+ ): Promise<Uint8Array | null> {
39
+ return new Promise<Uint8Array | null>((resolve, reject) => {
40
+ inputStream.read_bytes_async(count, ioPriority, cancellable, (_self, asyncRes) => {
41
+ try {
42
+ const res = inputStream.read_bytes_finish(asyncRes);
43
+ if (res.get_size() === 0) {
44
+ return resolve(null);
45
+ }
46
+ return resolve(byteArray.fromGBytes(res));
47
+ } catch (error) {
48
+ reject(error);
49
+ }
50
+ });
51
+ });
52
+ }
53
+
54
+ /**
55
+ * Async generator that yields `Uint8Array` chunks from a `Gio.InputStream`.
56
+ */
57
+ export async function* inputStreamAsyncIterator(
58
+ inputStream: Gio.InputStream,
59
+ count = 4096,
60
+ ioPriority = GLib.PRIORITY_DEFAULT,
61
+ cancellable: Gio.Cancellable | null = null
62
+ ): AsyncGenerator<Uint8Array> {
63
+ let chunk: Uint8Array | null;
64
+ while ((chunk = await readBytesAsync(inputStream, count, ioPriority, cancellable)) !== null) {
65
+ yield chunk;
66
+ }
67
+ }
package/src/globals.ts ADDED
@@ -0,0 +1,13 @@
1
+ // Shared utility for registering global polyfills.
2
+ // Used by web packages (abort-controller, dom-exception, streams, webcrypto, etc.)
3
+ // to consistently register globals only when they're missing.
4
+
5
+ /**
6
+ * Register a value as a global property if it doesn't already exist.
7
+ * This is a no-op in environments where the global is already defined (e.g. Node.js).
8
+ */
9
+ export function registerGlobal(name: string, value: unknown): void {
10
+ if (typeof (globalThis as any)[name] === 'undefined') {
11
+ (globalThis as any)[name] = value;
12
+ }
13
+ }
package/src/index.ts CHANGED
@@ -1,15 +1,16 @@
1
- export * from './types/index.js';
2
-
3
- export * from './cancel-handler.js';
1
+ export * from './base64.js';
2
+ export * from './byte-array.js';
4
3
  export * from './cli.js';
4
+ export * from './defer.js';
5
+ export * from './encoding.js';
6
+ export * from './globals.js';
5
7
  export * from './error.js';
6
8
  export * from './file.js';
7
9
  export * from './fs.js';
8
- export * from './log.js';
10
+ export * from './gio.js';
11
+ export * from './gio-errors.js';
9
12
  export * from './message.js';
10
- export * from './os.js';
13
+ export * from './next-tick.js';
11
14
  export * from './path.js';
12
- export * from './process.js';
13
- export * from './system.js';
14
- export * from './tty.js';
15
- export * from './version.js';
15
+ export * from './structured-clone.js';
16
+ export * from './main-loop.js';
@@ -0,0 +1,62 @@
1
+ // GLib MainLoop management for GJS — original implementation
2
+ // Provides an implicit event loop analogous to Node.js's built-in event loop.
3
+
4
+ import type GLib from '@girs/glib-2.0';
5
+
6
+ /** Sentinel to prevent double-start (setMainLoopHook throws if called twice). */
7
+ let _started = false;
8
+
9
+ /** The singleton MainLoop instance, if created. */
10
+ let _loop: GLib.MainLoop | null = null;
11
+
12
+ /**
13
+ * Ensure a GLib MainLoop is running for async I/O dispatch (Soup.Server,
14
+ * Gio.SocketService, etc.). No-op on Node.js. Idempotent.
15
+ *
16
+ * - Called automatically by `http.Server.listen()`, `net.Server.listen()`,
17
+ * `dgram.Socket.bind()` etc.
18
+ * - GTK apps should NOT call this — they use `Gtk.Application.runAsync()` instead.
19
+ *
20
+ * @returns The MainLoop instance on GJS, or `undefined` on Node.js.
21
+ */
22
+ export function ensureMainLoop(): GLib.MainLoop | undefined {
23
+ const gjsImports = (globalThis as any).imports;
24
+ if (!gjsImports) return undefined; // Not GJS
25
+ if (_started) return _loop!;
26
+
27
+ const GLibModule = gjsImports.gi.GLib;
28
+ _loop = new GLibModule.MainLoop(null, false);
29
+ _started = true;
30
+
31
+ // Only call runAsync() if no mainloop is currently running on the default
32
+ // context. If one is already running (e.g., test runner's mainloop.run()
33
+ // or Gtk.Application.runAsync()), async I/O already works through the
34
+ // shared default context — calling runAsync() would register a
35
+ // setMainLoopHook whose loop.run() blocks forever after tests quit it
36
+ // (g_main_loop_run resets the quit flag on entry).
37
+ if (GLibModule.main_depth() === 0) {
38
+ try {
39
+ (_loop as any).runAsync();
40
+ } catch {
41
+ // setMainLoopHook throws if already called (e.g., Gtk.Application.runAsync()).
42
+ // In that case, a main loop hook is already registered — no action needed.
43
+ }
44
+ }
45
+
46
+ return _loop;
47
+ }
48
+
49
+ /**
50
+ * Quit the MainLoop created by `ensureMainLoop()`. Idempotent, no-op on Node.js.
51
+ *
52
+ * Calling `quit()` on a loop that hasn't started yet pre-quits it — when the
53
+ * `setMainLoopHook` later fires and calls `run()`, it returns immediately.
54
+ * This is used by `@gjsify/unit` to prevent the loop from blocking after tests.
55
+ */
56
+ export function quitMainLoop(): void {
57
+ if (_loop) {
58
+ _loop.quit();
59
+ _started = false;
60
+ _loop = null;
61
+ }
62
+ }
@@ -0,0 +1,16 @@
1
+ // Microtask scheduling utility for GJS
2
+ // Shared by @gjsify/stream, @gjsify/web-streams, and other packages
3
+ // Matches Node.js process.nextTick semantics with cross-platform fallbacks
4
+
5
+ declare const queueMicrotask: ((cb: () => void) => void) | undefined;
6
+
7
+ /**
8
+ * Schedule a function on the microtask queue.
9
+ * Fallback chain: process.nextTick → queueMicrotask → Promise.resolve().then()
10
+ */
11
+ export const nextTick: (fn: (...args: unknown[]) => void, ...args: unknown[]) => void =
12
+ typeof globalThis.process?.nextTick === 'function'
13
+ ? globalThis.process.nextTick
14
+ : typeof globalThis.queueMicrotask === 'function'
15
+ ? (fn: (...args: unknown[]) => void, ...args: unknown[]) => queueMicrotask!(() => fn(...args))
16
+ : (fn: (...args: unknown[]) => void, ...args: unknown[]) => { Promise.resolve().then(() => fn(...args)); };