@gjsify/effect-platform 0.48.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 ADDED
@@ -0,0 +1,103 @@
1
+ # @gjsify/effect-platform
2
+
3
+ [Effect](https://effect.website)'s platform services over GNOME. `effect/FileSystem` on
4
+ `Gio.File`, `effect/Path` on GLib, `GError` mapped onto Effect's normalized platform errors, and a
5
+ `/gtk` subpath that binds GObject lifetimes to Effect `Scope`s.
6
+
7
+ Effect 4 runs on GJS unmodified. This package is not what makes it run; it is what makes it
8
+ **GNOME**. See [ADR 0050](../../../docs/adr/0050-effect-platform-services-for-gnome.md) for why
9
+ that is a platform package rather than a fourth renderer.
10
+
11
+ ```ts
12
+ import { Effect } from 'effect'
13
+ import * as FileSystem from 'effect/FileSystem'
14
+ import { fileSystemLayer } from '@gjsify/effect-platform'
15
+
16
+ const program = Effect.gen(function* () {
17
+ const fs = yield* FileSystem.FileSystem
18
+ return yield* fs.readDirectory('/etc')
19
+ })
20
+
21
+ await Effect.runPromise(Effect.provide(program, fileSystemLayer))
22
+ ```
23
+
24
+ ## Two entries, split by what they import
25
+
26
+ | entry | provides | reaches |
27
+ |---|---|---|
28
+ | `@gjsify/effect-platform` | `fileSystemLayer`, `pathLayer`, `reasonOf`, `isIoError`, `toPlatformError`, `gioAsync` | GLib, GIO |
29
+ | `@gjsify/effect-platform/gtk` | `widgetScope`, `windowScope`, `signalScope`, `runInScope`, `signalStream`, `propertyStream` | GTK, GObject |
30
+
31
+ The root entry declares `gjsify.headless` and CI walks its import graph to hold it. A service
32
+ layer that dragged in GTK could not be used from a daemon, a CLI or a test, which is where these
33
+ services earn their keep.
34
+
35
+ ## What only this layer can do
36
+
37
+ **Cancellation reaches GIO.** `Effect.callback` hands its register function an `AbortSignal`;
38
+ every GIO async call takes a `Gio.Cancellable`. `gioAsync` wires the two, so interrupting a fiber
39
+ stops the in-flight read rather than discarding its result. Nothing promise-based can offer that.
40
+
41
+ **`effect/Path` is held against `node:path` operation by operation.** GLib has
42
+ `g_path_get_basename` and `g_path_get_dirname`, and they are not `path.posix`: measured,
43
+ `basename('')` is `.` where Node says `''`, and `dirname('a/b/')` is `a/b` where Node says `a`. The
44
+ differential in `tests/integration/effect` found eleven such classes that hand-written expectations
45
+ had all missed, which is why it exists.
46
+
47
+ **A `GError` becomes a tag you can branch on.** Today every failing GIO call is a `GLib.Error`
48
+ with a number whose meaning depends on its domain. Measured: `Gio.IOErrorEnum.NOT_FOUND` and
49
+ `GLib.FileError.ISDIR` are both `1`, so a code read without its domain reports *is a directory* as
50
+ *not found*. `reasonOf` refuses that, and a GIO code Effect has no tag for comes back `Unknown`
51
+ rather than as a near miss.
52
+
53
+ **A `Scope` closes with the widget.** `windowScope(window)` closes on `close-request` **or**
54
+ `destroy`, whichever is first, and everything forked into it is interrupted. Two triggers, because
55
+ `GtkWidget::destroy` is emitted from *dispose*: with the application holding a reference, which is
56
+ always, `gtk_window_destroy()` emits `unrealize` and no `destroy`. The full measured matrix is in
57
+ `src/gtk/scope.ts`.
58
+
59
+ ## Conformance
60
+
61
+ Upstream authored `FileSystem.test-utils.ts` as a suite parameterised by a layer, so Node, Bun and
62
+ Deno answer the same questions. [`tests/integration/effect`](../../../tests/integration/effect)
63
+ ports it once and runs it twice: over `node:fs` and over this package's Gio layer. **Both pass all
64
+ of it**, including the cases about the file cursor that nobody here would have thought to ask —
65
+ seek backwards, `a+` with separate read and write positions, truncate under a live cursor. The
66
+ per-run counts live in
67
+ [`status/integration-coverage.md`](../../../status/integration-coverage.md), which is where a number
68
+ can be re-measured instead of remembered.
69
+
70
+ Three GIO semantics had to be corrected to get there, and each is recorded where it bites:
71
+
72
+ - `g_file_replace*` is GIO's *atomic replace*: it writes a temporary file and moves it into place
73
+ on close, so the path still holds the old bytes until then. POSIX `w`/`w+` truncate in place, so
74
+ those flags open read-write and truncate instead.
75
+ - A `GFileOutputStream` buffers where a descriptor does not, so every write flushes.
76
+ - `g_file_copy_async`/`move_async` are typed with `GObject.Closure` parameters by `@girs`, which
77
+ is not constructible from GJS in any reasonable way, so the layer takes the synchronous call.
78
+ **`copy`, `copyFile` and `rename` are therefore the three methods here that are NOT
79
+ interruptible** — the call blocks the fiber for its whole duration, so an interrupt cannot be
80
+ delivered until after it returns. Everything else goes through `gioAsync`.
81
+
82
+ ## What it does not implement
83
+
84
+ `realPath`, `link` and `glob` raise a **defect**, not a failure — GIO has no symlink-resolving
85
+ canonicalizer, no hard-link call and no glob matcher. The distinction matters: `FileSystem.makeNoop`
86
+ would answer `remove()` with a silent success and the rest with `NotFound`, which is the tag a
87
+ genuinely missing file carries and which a `catchTag` would swallow. This package spells out the
88
+ complete interface through `FileSystem.make` so a method added upstream cannot arrive as a silent
89
+ default.
90
+
91
+ ## Cost
92
+
93
+ Tree-shaking over Effect 4 works: you pay for what you import, and a GTK window using this layer
94
+ lands in the same size class as comparable showcases that use no Effect at all. The figures, and
95
+ which artifact each one came from, are in
96
+ [ADR 0050](../../../docs/adr/0050-effect-platform-services-for-gnome.md) § Cost.
97
+
98
+ ## Peer dependency
99
+
100
+ `effect` is a **peer** dependency, pinned to the exact RC rather than a range. A consumer's Effect
101
+ and this layer's must be the same instance or the service keys do not match, and across prerelease
102
+ tags a range does not guarantee that: `>=4.0.0-rc.112` admits `4.0.0-rc.113`, which is free to move
103
+ a key. It relaxes to a caret the day 4.0.0 is stable.
@@ -0,0 +1 @@
1
+ var e=Object.defineProperty,__name=(t,n)=>e(t,`name`,{value:n,configurable:!0});export{__name};
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";import e from"gi://Gio?version=2.0";import t from"gi://GLib?version=2.0";import{PlatformError as n,SystemError as r}from"effect/PlatformError";const i=new Map([[e.IOErrorEnum.NOT_FOUND,`NotFound`],[e.IOErrorEnum.EXISTS,`AlreadyExists`],[e.IOErrorEnum.PERMISSION_DENIED,`PermissionDenied`],[e.IOErrorEnum.BUSY,`Busy`],[e.IOErrorEnum.TIMED_OUT,`TimedOut`],[e.IOErrorEnum.WOULD_BLOCK,`WouldBlock`],[e.IOErrorEnum.INVALID_DATA,`InvalidData`],[e.IOErrorEnum.CLOSED,`BadResource`],[e.IOErrorEnum.NOT_INITIALIZED,`BadResource`],[e.IOErrorEnum.NO_SPACE,`WriteZero`],[e.IOErrorEnum.PARTIAL_INPUT,`UnexpectedEof`]]),a=t.quark_to_string(e.io_error_quark()),isIoError=n=>n instanceof t.Error&&n.domain===e.io_error_quark(),reasonOf=e=>isIoError(e)?i.get(e.code)??`Unknown`:`Unknown`,toPlatformError=e=>new n(new r({_tag:reasonOf(e.error),module:e.module??`FileSystem`,method:e.method,pathOrDescriptor:e.pathOrDescriptor,description:describe(e.error),cause:e.error})),describe=e=>{if(e instanceof t.Error){let n=t.quark_to_string(e.domain)??String(e.domain);return n===a?e.message:`${n}(${e.code}): ${e.message}`}return e instanceof Error?e.message:String(e)};export{isIoError,reasonOf,toPlatformError};
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";import{toPlatformError as e}from"./errors.js";import{gioAsync as t}from"./gio-async.js";import n from"gi://Gio?version=2.0";import r from"gi://GLib?version=2.0";import{Effect as i,Option as a}from"effect";import*as o from"effect/FileSystem";const s={r:{readable:!0,append:!1},"r+":{readable:!0,append:!1},w:{readable:!1,append:!1},"w+":{readable:!0,append:!1},wx:{readable:!1,append:!1},"wx+":{readable:!0,append:!1},a:{readable:!1,append:!0},"a+":{readable:!0,append:!0},ax:{readable:!1,append:!0},"ax+":{readable:!0,append:!0}},openOrCreate=(a,o,s,c)=>i.gen(function*(){yield*i.ignore(t({method:`open`,path:o,source:a,start:(e,t,i)=>e.create_async(n.FileCreateFlags.NONE,r.PRIORITY_DEFAULT,t,(e,t)=>i(t)),finish:(e,t)=>e.create_finish(t)}));let l=yield*t({method:`open`,path:o,source:a,start:(e,t,n)=>e.open_readwrite_async(r.PRIORITY_DEFAULT,t,(e,t)=>n(t)),finish:(e,t)=>e.open_readwrite_finish(t)});return c&&(yield*i.try({try:()=>l.truncate(0,null),catch:t=>e({method:`open`,pathOrDescriptor:o,error:t})})),{input:s.readable?l.get_input_stream():null,output:l.get_output_stream(),seekable:l,closable:l,plan:s}}),openStreams=(e,a)=>{let o=s[a],c=n.File.new_for_path(e),l=`open`,io=e=>({input:e.get_input_stream(),output:e.get_output_stream(),seekable:e,closable:e,plan:o});switch(a){case`r`:return i.map(t({method:l,path:e,source:c,start:(e,t,n)=>e.read_async(r.PRIORITY_DEFAULT,t,(e,t)=>n(t)),finish:(e,t)=>e.read_finish(t)}),e=>({input:e,output:null,seekable:e,closable:e,plan:o}));case`r+`:return i.map(t({method:l,path:e,source:c,start:(e,t,n)=>e.open_readwrite_async(r.PRIORITY_DEFAULT,t,(e,t)=>n(t)),finish:(e,t)=>e.open_readwrite_finish(t)}),io);case`w`:case`w+`:return openOrCreate(c,e,o,!0);case`wx`:case`ax`:return i.map(t({method:l,path:e,source:c,start:(e,t,i)=>e.create_async(n.FileCreateFlags.NONE,r.PRIORITY_DEFAULT,t,(e,t)=>i(t)),finish:(e,t)=>e.create_finish(t)}),e=>({input:null,output:e,seekable:e,closable:e,plan:o}));case`wx+`:case`ax+`:return i.map(t({method:l,path:e,source:c,start:(e,t,i)=>e.create_readwrite_async(n.FileCreateFlags.NONE,r.PRIORITY_DEFAULT,t,(e,t)=>i(t)),finish:(e,t)=>e.create_readwrite_finish(t)}),io);case`a`:return i.map(t({method:l,path:e,source:c,start:(e,t,i)=>e.append_to_async(n.FileCreateFlags.NONE,r.PRIORITY_DEFAULT,t,(e,t)=>i(t)),finish:(e,t)=>e.append_to_finish(t)}),e=>({input:null,output:e,seekable:e,closable:e,plan:o}));case`a+`:return openOrCreate(c,e,o,!1)}},makeFile=(s,c,l)=>{let u=0,fail=t=>n=>e({method:t,pathOrDescriptor:s,error:n,module:`FileSystem`}),seekTo=(e,t)=>i.try({try:()=>(c.seekable.seek(e,t,null),c.seekable.tell()),catch:fail(`seek`)}),readBytes=a=>{let o=c.input;return o===null?i.fail(e({method:`read`,pathOrDescriptor:s,module:`FileSystem`,error:r.Error.new_literal(n.io_error_quark(),n.IOErrorEnum.PERMISSION_DENIED,`file was not opened for reading`)})):i.gen(function*(){yield*seekTo(u,r.SeekType.SET);let e=(yield*t({method:`read`,path:s,source:o,start:(e,t,n)=>e.read_bytes_async(a,r.PRIORITY_DEFAULT,t,(e,t)=>n(t)),finish:(e,t)=>e.read_bytes_finish(t)})).toArray();return u+=e.length,e})},writeBytes=a=>{let o=c.output;return o===null?i.fail(e({method:`write`,pathOrDescriptor:s,module:`FileSystem`,error:r.Error.new_literal(n.io_error_quark(),n.IOErrorEnum.PERMISSION_DENIED,`file was not opened for writing`)})):i.gen(function*(){yield*seekTo(c.plan.append?0:u,c.plan.append?r.SeekType.END:r.SeekType.SET);let e=yield*t({method:`write`,path:s,source:o,start:(e,t,n)=>e.write_bytes_async(new r.Bytes(a),r.PRIORITY_DEFAULT,t,(e,t)=>n(t)),finish:(e,t)=>e.write_bytes_finish(t)});return yield*t({method:`write`,path:s,source:o,start:(e,t,n)=>e.flush_async(r.PRIORITY_DEFAULT,t,(e,t)=>n(t)),finish:(e,t)=>e.flush_finish(t)}),c.plan.append||(u+=e),e})};return{[o.FileTypeId]:o.FileTypeId,stat:l,seek:(e,t)=>i.sync(()=>{let n=t===`start`?Number(e):u+Number(e);return u=n<0?0:n,o.Size(u)}),sync:i.gen(function*(){let e=c.output;e!==null&&(yield*t({method:`sync`,path:s,source:e,start:(e,t,n)=>e.flush_async(r.PRIORITY_DEFAULT,t,(e,t)=>n(t)),finish:(e,t)=>e.flush_finish(t)}))}),read:e=>i.map(readBytes(e.length),t=>(e.set(t),o.Size(t.length))),readAlloc:e=>i.map(readBytes(Number(e)),e=>e.length===0?a.none():a.some(e)),truncate:e=>i.gen(function*(){let t=e===void 0?0:Number(e);yield*i.try({try:()=>c.seekable.truncate(t,null),catch:fail(`truncate`)}),u>t&&(u=t)}),write:e=>i.map(writeBytes(e),o.Size),writeAll:e=>i.gen(function*(){let t=0;for(;t<e.length;)t+=yield*writeBytes(e.subarray(t))})}},openFile=(e,t,n)=>i.acquireRelease(i.map(openStreams(e,t?.flag??`r`),t=>({streams:t,file:makeFile(e,t,n(e))})),({streams:e})=>i.ignore(i.try(()=>e.closable.close(null)))).pipe(i.map(({file:e})=>e));export{makeFile,openFile};
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";import{isIoError as e,toPlatformError as t}from"./errors.js";import{gioAsync as n}from"./gio-async.js";import{openFile as r}from"./file.js";import i from"gi://Gio?version=2.0";import a from"gi://GLib?version=2.0";import{badArgument as o}from"effect/PlatformError";import{Effect as s,Layer as c,Option as l,Result as u,Stream as d}from"effect";import*as f from"effect/FileSystem";import*as p from"effect/Queue";const m=[i.FILE_ATTRIBUTE_STANDARD_TYPE,i.FILE_ATTRIBUTE_STANDARD_SIZE,i.FILE_ATTRIBUTE_STANDARD_NAME,i.FILE_ATTRIBUTE_TIME_MODIFIED,i.FILE_ATTRIBUTE_TIME_ACCESS,i.FILE_ATTRIBUTE_TIME_CREATED,i.FILE_ATTRIBUTE_UNIX_MODE,i.FILE_ATTRIBUTE_UNIX_INODE,i.FILE_ATTRIBUTE_UNIX_NLINK,i.FILE_ATTRIBUTE_UNIX_UID,i.FILE_ATTRIBUTE_UNIX_GID,i.FILE_ATTRIBUTE_UNIX_DEVICE,i.FILE_ATTRIBUTE_UNIX_BLOCK_SIZE,i.FILE_ATTRIBUTE_UNIX_BLOCKS,i.FILE_ATTRIBUTE_ACCESS_CAN_READ,i.FILE_ATTRIBUTE_ACCESS_CAN_WRITE,i.FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE].join(`,`),fileFor=e=>i.File.new_for_path(e),queryInfo=(e,t,r=!0)=>n({method:t,path:e,source:fileFor(e),start:(e,t,n)=>e.query_info_async(m,r?i.FileQueryInfoFlags.NONE:i.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,a.PRIORITY_DEFAULT,t,(e,t)=>n(t)),finish:(e,t)=>e.query_info_finish(t)}),toDate=e=>e===null?l.none():l.some(new Date(e.to_unix()*1e3)),toNumber=e=>e===0?l.none():l.some(e),h=new Map([[i.FileType.REGULAR,`File`],[i.FileType.DIRECTORY,`Directory`],[i.FileType.SYMBOLIC_LINK,`SymbolicLink`],[i.FileType.SPECIAL,`Unknown`],[i.FileType.SHORTCUT,`Unknown`],[i.FileType.MOUNTABLE,`Unknown`],[i.FileType.UNKNOWN,`Unknown`]]),toInfo=e=>({type:h.get(e.get_file_type())??`Unknown`,mtime:toDate(e.get_modification_date_time()),atime:toDate(e.get_access_date_time()),birthtime:toDate(e.get_creation_date_time()),dev:e.get_attribute_uint32(i.FILE_ATTRIBUTE_UNIX_DEVICE),ino:toNumber(Number(e.get_attribute_uint64(i.FILE_ATTRIBUTE_UNIX_INODE))),mode:e.get_attribute_uint32(i.FILE_ATTRIBUTE_UNIX_MODE),nlink:toNumber(e.get_attribute_uint32(i.FILE_ATTRIBUTE_UNIX_NLINK)),uid:toNumber(e.get_attribute_uint32(i.FILE_ATTRIBUTE_UNIX_UID)),gid:toNumber(e.get_attribute_uint32(i.FILE_ATTRIBUTE_UNIX_GID)),rdev:l.none(),size:f.Size(e.get_size()),blksize:l.some(f.Size(e.get_attribute_uint32(i.FILE_ATTRIBUTE_UNIX_BLOCK_SIZE))),blocks:toNumber(Number(e.get_attribute_uint64(i.FILE_ATTRIBUTE_UNIX_BLOCKS)))}),readFile=e=>n({method:`readFile`,path:e,source:fileFor(e),start:(e,t,n)=>e.load_contents_async(t,(e,t)=>n(t)),finish:(e,t)=>{let[n,r]=e.load_contents_finish(t);if(!n)throw Error(`load_contents reported failure without raising`);return r}}),readDirectory=e=>s.scoped(readDirectoryScoped(e)),readDirectoryScoped=e=>s.gen(function*(){let t=yield*openEnumerator(e),r=[];for(;;){let i=yield*n({method:`readDirectory`,path:e,source:t,start:(e,t,n)=>e.next_files_async(64,a.PRIORITY_DEFAULT,t,(e,t)=>n(t)),finish:(e,t)=>e.next_files_finish(t)});if(i.length===0)break;for(let e of i)r.push(e.get_name())}return r}),openEnumerator=e=>s.acquireRelease(n({method:`readDirectory`,path:e,source:fileFor(e),start:(e,t,n)=>e.enumerate_children_async(i.FILE_ATTRIBUTE_STANDARD_NAME,i.FileQueryInfoFlags.NONE,a.PRIORITY_DEFAULT,t,(e,t)=>n(t)),finish:(e,t)=>e.enumerate_children_finish(t)}),e=>s.ignore(s.try(()=>e.close(null)))),dirnameOf=e=>a.path_get_dirname(e),makeTempDirectory=n=>s.try({try:()=>{let t=`${n?.prefix??`effect-platform-`}XXXXXX`;if(n?.directory===void 0)return a.Dir.make_tmp(t);for(let r=0;r<64;r++){let r=a.build_filenamev([n.directory,t.replace(`XXXXXX`,String(a.random_int()))]);try{return i.File.new_for_path(r).make_directory(null),r}catch(t){if(!e(t)||t.code!==i.IOErrorEnum.EXISTS)throw t}}throw Error(`no unused temporary directory name after 64 attempts`)},catch:e=>t({method:`makeTempDirectory`,error:e})}),makeTempFile=e=>s.flatMap(makeTempDirectory({directory:e?.directory,prefix:e?.prefix}),n=>s.try({try:()=>{let t=`${e?.prefix??`effect-platform-`}${a.random_int()}${e?.suffix??``}`,r=a.build_filenamev([n,t]);return i.File.new_for_path(r).create(i.FileCreateFlags.NONE,null).close(null),r},catch:e=>t({method:`makeTempFile`,error:e})})),remove=(e,t)=>s.gen(function*(){if(t?.recursive===!0){let n=yield*s.result(queryInfo(e,`remove`,!1));if(u.isSuccess(n)&&n.success.get_file_type()===i.FileType.DIRECTORY)for(let n of yield*readDirectory(e))yield*remove(a.build_filenamev([e,n]),t)}let r=yield*s.result(s.asVoid(n({method:`remove`,path:e,source:fileFor(e),start:(e,t,n)=>e.delete_async(a.PRIORITY_DEFAULT,t,(e,t)=>n(t)),finish:(e,t)=>e.delete_finish(t)})));if(u.isFailure(r)&&!(r.failure.reason._tag===`NotFound`&&(t?.force===!0||t?.recursive===!0)))return yield*s.fail(r.failure)}),copy=(e,n,r,c)=>s.gen(function*(){if(n===e||n.startsWith(`${e}${a.DIR_SEPARATOR_S}`))return yield*s.fail(o({module:`FileSystem`,method:c,description:`cannot copy a path into itself`}));if((yield*queryInfo(e,c,!1)).get_file_type()===i.FileType.DIRECTORY){yield*s.try({try:()=>{fileFor(n).make_directory_with_parents(null)},catch:e=>t({method:c,pathOrDescriptor:n,error:e})}).pipe(s.catchTag(`PlatformError`,e=>e.reason._tag===`AlreadyExists`?s.void:s.fail(e)));for(let t of yield*readDirectory(e))yield*copy(a.build_filenamev([e,t]),a.build_filenamev([n,t]),r,c);return}yield*copyOne(e,n,r,c)}),copyOne=(e,n,r,a)=>s.try({try:()=>{let t=i.FileCopyFlags.NONE;r?.overwrite===!0&&(t|=i.FileCopyFlags.OVERWRITE),r?.preserveTimestamps!==!0&&(t|=i.FileCopyFlags.TARGET_DEFAULT_MODIFIED_TIME),t|=i.FileCopyFlags.NOFOLLOW_SYMLINKS,fileFor(e).copy(fileFor(n),t,null,null)},catch:n=>t({method:a,pathOrDescriptor:e,error:n})}),watch=e=>d.callback(n=>s.gen(function*(){let r=yield*s.try({try:()=>fileFor(e).monitor(i.FileMonitorFlags.WATCH_MOVES,null),catch:n=>t({method:`watch`,pathOrDescriptor:e,error:n})}),a=r.connect(`changed`,(t,r,i,a)=>{let o=r.get_path()??e,s=g.get(a);s!==void 0&&p.offerUnsafe(n,s(o))});yield*s.addFinalizer(()=>s.sync(()=>{r.disconnect(a),r.cancel()}))})),g=new Map([[i.FileMonitorEvent.CREATED,e=>({_tag:`Create`,path:e})],[i.FileMonitorEvent.CHANGED,e=>({_tag:`Update`,path:e})],[i.FileMonitorEvent.ATTRIBUTE_CHANGED,e=>({_tag:`Update`,path:e})],[i.FileMonitorEvent.DELETED,e=>({_tag:`Remove`,path:e})],[i.FileMonitorEvent.MOVED_OUT,e=>({_tag:`Remove`,path:e})],[i.FileMonitorEvent.MOVED_IN,e=>({_tag:`Create`,path:e})]]),setAttribute=(e,t,r,o)=>s.gen(function*(){let s=yield*n({method:t,path:e,source:fileFor(e),start:(e,t,n)=>e.query_info_async(r,i.FileQueryInfoFlags.NONE,a.PRIORITY_DEFAULT,t,(e,t)=>n(t)),finish:(e,t)=>e.query_info_finish(t)});o(s),yield*n({method:t,path:e,source:fileFor(e),start:(e,t,n)=>e.set_attributes_async(s,i.FileQueryInfoFlags.NONE,a.PRIORITY_DEFAULT,t,(e,t)=>n(t)),finish:(e,t)=>e.set_attributes_finish(t)[1]})}),unimplemented=e=>s.die(Error(`@gjsify/effect-platform: FileSystem.${e} has no GIO equivalent`)),makeGioFileSystem=()=>f.make({access:(e,n)=>s.gen(function*(){let r=yield*queryInfo(e,`access`),denied=n=>s.fail(t({method:`access`,pathOrDescriptor:e,error:a.Error.new_literal(i.io_error_quark(),i.IOErrorEnum.PERMISSION_DENIED,`not ${n}`)}));if(n?.readable===!0&&!r.get_attribute_boolean(i.FILE_ATTRIBUTE_ACCESS_CAN_READ))return yield*denied(`readable`);if(n?.writable===!0&&!r.get_attribute_boolean(i.FILE_ATTRIBUTE_ACCESS_CAN_WRITE))return yield*denied(`writable`)}),stat:e=>s.map(queryInfo(e,`stat`),toInfo),readFile,readDirectory,readLink:e=>s.flatMap(queryInfo(e,`readLink`,!1),n=>{let r=n.get_symlink_target();return r===null?s.fail(t({method:`readLink`,pathOrDescriptor:e,error:a.Error.new_literal(i.io_error_quark(),i.IOErrorEnum.INVALID_ARGUMENT,`not a symbolic link`)})):s.succeed(r)}),realPath:()=>unimplemented(`realPath`),link:()=>unimplemented(`link`),glob:()=>unimplemented(`glob`),open:(e,t)=>r(e,t,e=>s.map(queryInfo(e,`stat`),toInfo)),makeDirectory:(e,r)=>r?.recursive===!0?s.try({try:()=>{fileFor(e).make_directory_with_parents(null)},catch:n=>t({method:`makeDirectory`,pathOrDescriptor:e,error:n})}):s.asVoid(n({method:`makeDirectory`,path:e,source:fileFor(e),start:(e,t,n)=>e.make_directory_async(a.PRIORITY_DEFAULT,t,(e,t)=>n(t)),finish:(e,t)=>e.make_directory_finish(t)})),remove:(e,t)=>remove(e,t),rename:(e,n)=>s.try({try:()=>{fileFor(e).move(fileFor(n),i.FileCopyFlags.OVERWRITE,null,null)},catch:n=>t({method:`rename`,pathOrDescriptor:e,error:n})}),copy:(e,t,n)=>copy(e,t,n,`copy`),copyFile:(e,t)=>copy(e,t,void 0,`copyFile`),symlink:(e,t)=>s.asVoid(n({method:`symlink`,path:t,source:fileFor(t),start:(t,n,r)=>t.make_symbolic_link_async(e,a.PRIORITY_DEFAULT,n,(e,t)=>r(t)),finish:(e,t)=>e.make_symbolic_link_finish(t)})),truncate:(e,t)=>s.scoped(s.flatMap(r(e,{flag:`r+`},e=>s.map(queryInfo(e,`stat`),toInfo)),e=>e.truncate(t))),chmod:(e,t)=>setAttribute(e,`chmod`,i.FILE_ATTRIBUTE_UNIX_MODE,e=>{e.set_attribute_uint32(i.FILE_ATTRIBUTE_UNIX_MODE,t)}),chown:(e,t,n)=>setAttribute(e,`chown`,i.FILE_ATTRIBUTE_UNIX_UID,e=>{e.set_attribute_uint32(i.FILE_ATTRIBUTE_UNIX_UID,t),e.set_attribute_uint32(i.FILE_ATTRIBUTE_UNIX_GID,n)}),utimes:(e,t,n)=>setAttribute(e,`utimes`,i.FILE_ATTRIBUTE_TIME_MODIFIED,e=>{let seconds=e=>BigInt(Math.floor(Number(e instanceof Date?e.getTime():e)/1e3));e.set_attribute_uint64(i.FILE_ATTRIBUTE_TIME_ACCESS,seconds(t)),e.set_attribute_uint64(i.FILE_ATTRIBUTE_TIME_MODIFIED,seconds(n))}),makeTempDirectory:e=>makeTempDirectory(e),makeTempDirectoryScoped:e=>s.acquireRelease(makeTempDirectory(e),e=>s.ignore(remove(e,{recursive:!0}))),makeTempFile:e=>makeTempFile(e),makeTempFileScoped:e=>s.acquireRelease(makeTempFile(e),e=>s.ignore(remove(dirnameOf(e),{recursive:!0}))),writeFile:(e,n,i)=>s.scoped(s.flatMap(r(e,{flag:i?.flag??`w`},e=>s.map(queryInfo(e,`stat`),toInfo)),e=>e.writeAll(n))).pipe(s.mapError(n=>n.reason._tag===`BadArgument`?n:t({method:`writeFile`,pathOrDescriptor:e,error:n.reason.cause??n}))),watch:e=>watch(e)}),_=c.sync(f.FileSystem)(makeGioFileSystem);export{_ as layer,makeGioFileSystem};
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";import{toPlatformError as e}from"./errors.js";import t from"gi://Gio?version=2.0";import{Effect as n}from"effect";const gioAsync=r=>n.callback((i,a)=>{let o=new t.Cancellable,onAbort=()=>o.cancel();a.addEventListener(`abort`,onAbort),r.start(r.source,o,t=>{if(a.removeEventListener(`abort`,onAbort),!o.is_cancelled())try{i(n.succeed(r.finish(r.source,t)))}catch(t){i(n.fail(e({method:r.method,pathOrDescriptor:r.path,error:t})))}})});export{gioAsync};
@@ -0,0 +1 @@
1
+ import"../_virtual/_rolldown/runtime.js";import{Effect as e,Exit as t,Scope as n}from"effect";const signalScope=(r,i)=>{let a=n.makeUnsafe(`sequential`),o=!1,s=[],disconnect=()=>{for(let e of s)r.disconnect(e);s=[]},close=()=>{if(o)return;o=!0,disconnect();let r=n.closeUnsafe(a,t.void);r!==void 0&&e.runFork(r)};return s=i.map(e=>r.connect(e,()=>(close(),!1))),{scope:a,close}},widgetScope=e=>signalScope(e,[`destroy`]),windowScope=e=>signalScope(e,[`close-request`,`destroy`]),runInScope=(t,n)=>e.runSync(e.forkIn(n,t));export{runInScope,signalScope,widgetScope,windowScope};
@@ -0,0 +1 @@
1
+ import"../_virtual/_rolldown/runtime.js";import{Effect as e,Stream as t}from"effect";import*as n from"effect/Queue";const signalStream=(r,i,a={})=>t.callback(t=>e.gen(function*(){let a=r.connect(i,(e,...r)=>{n.offerUnsafe(t,r)});yield*e.addFinalizer(()=>e.sync(()=>r.disconnect(a)))}),{bufferSize:a.bufferSize??16,strategy:a.strategy??`sliding`}),propertyStream=(e,n,r,i={})=>t.concat(t.sync(r),t.map(signalStream(e,`notify::${n}`,i),r));export{propertyStream,signalStream};
package/lib/esm/gtk.js ADDED
@@ -0,0 +1 @@
1
+ import{runInScope as e,signalScope as t,widgetScope as n,windowScope as r}from"./gtk/scope.js";import{propertyStream as i,signalStream as a}from"./gtk/signal.js";export{i as propertyStream,e as runInScope,t as signalScope,a as signalStream,n as widgetScope,r as windowScope};
@@ -0,0 +1 @@
1
+ import{isIoError as e,reasonOf as t,toPlatformError as n}from"./errors.js";import{gioAsync as r}from"./gio-async.js";import{layer as i,makeGioFileSystem as a}from"./filesystem.js";import{layer as o}from"./path.js";export{i as fileSystemLayer,r as gioAsync,e as isIoError,a as makeGioFileSystem,o as pathLayer,t as reasonOf,n as toPlatformError};
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";import e from"gi://GLib?version=2.0";import{BadArgument as t}from"effect/PlatformError";import{Effect as n,Layer as r}from"effect";import{Path as i,TypeId as a}from"effect/Path";const o=e.DIR_SEPARATOR_S,isAbsolute=t=>e.path_is_absolute(t),join=(...t)=>{let n=t.filter(e=>e.length>0);return n.length===0?`.`:normalize(e.build_filenamev(n))},resolve=(...t)=>{let n=e.get_current_dir();for(let r of t)r.length!==0&&(n=isAbsolute(r)?r:e.build_filenamev([n,r]));return e.canonicalize_filename(n,null).replace(/^\/\/+/,o)},normalize=e=>{if(e.length===0)return`.`;let t=isAbsolute(e),n=e.length>1&&e.endsWith(o),r=[];for(let n of e.split(o))if(!(n===``||n===`.`)){if(n!==`..`){r.push(n);continue}r.length>0&&r[r.length-1]!==`..`?r.pop():t||r.push(`..`)}let i=r.join(o);return t?n&&i.length>0?`${o}${i}${o}`:`${o}${i}`:i.length===0?`.`:n?`${i}${o}`:i},basename=(e,t)=>{let n=e.replace(/\/+$/,``),r=n.slice(n.lastIndexOf(o)+1);return t!==void 0&&t!==r&&r.endsWith(t)?r.slice(0,r.length-t.length):r},dirname=e=>{if(e.length===0)return`.`;let t=isAbsolute(e),n=e.replace(/\/+$/,``);if(n.length===0)return o;let r=n.lastIndexOf(o);return r<0?`.`:r===0?o:t||r>0?n.slice(0,r):`.`},extname=e=>{let t=basename(e);if(t===`.`||t===`..`)return``;let n=t.lastIndexOf(`.`);return n<=0?``:t.slice(n)},parse=e=>{let t=isAbsolute(e)?o:``,n=basename(e),r=extname(e),i=e.replace(/\/+$/,``).lastIndexOf(o);return{root:t,dir:n.length===0?t===o?o:``:i<=0?t:e.slice(0,i),base:n,ext:r,name:r.length===0?n:n.slice(0,n.length-r.length)}},format=e=>{let t=e.base??`${e.name??``}${e.ext??``}`,n=e.dir??e.root??``;return n.length===0?t:n===e.root?`${n}${t}`:`${n}${o}${t}`},relative=(e,t)=>{let n=resolve(e).split(o).filter(Boolean),r=resolve(t).split(o).filter(Boolean),i=0;for(;i<n.length&&i<r.length&&n[i]===r[i];)i++;return[...Array(n.length-i).fill(`..`),...r.slice(i)].join(o)},fromFileUrl=r=>n.try({try:()=>e.filename_from_uri(r.href)[0],catch:e=>new t({module:`Path`,method:`fromFileUrl`,cause:e})}),toFileUrl=r=>n.try({try:()=>new URL(e.filename_to_uri(resolve(r),null)),catch:e=>new t({module:`Path`,method:`toFileUrl`,cause:e})}),s=r.succeed(i)({[a]:a,sep:o,basename,dirname,extname,format,fromFileUrl,isAbsolute,join,normalize,parse,relative,resolve,toFileUrl,toNamespacedPath:e=>e});export{s as layer};
@@ -0,0 +1,23 @@
1
+ import GLib from 'gi://GLib?version=2.0';
2
+ import { PlatformError, type SystemErrorTag } from 'effect/PlatformError';
3
+ /** `true` when this value is a `GLib.Error` raised by GIO itself. */
4
+ export declare const isIoError: (error: unknown) => error is GLib.Error;
5
+ /**
6
+ * The normalized reason for a caught value. Non-GIO errors — a `TypeError` from
7
+ * our own binding code, a `GLib.Error` from another domain — are `Unknown` rather
8
+ * than guessed at.
9
+ */
10
+ export declare const reasonOf: (error: unknown) => SystemErrorTag;
11
+ /**
12
+ * Wrap a caught GIO failure as the platform error `effect/FileSystem` declares.
13
+ *
14
+ * `module` is the Effect service that failed, not the GIO class, so a consumer
15
+ * reading `NotFound: FileSystem.readFile (/etc/nope)` cannot tell which layer
16
+ * produced it — which is the point.
17
+ */
18
+ export declare const toPlatformError: (options: {
19
+ readonly method: string;
20
+ readonly pathOrDescriptor?: string | number;
21
+ readonly error: unknown;
22
+ readonly module?: string;
23
+ }) => PlatformError;
@@ -0,0 +1,2 @@
1
+ declare const _default: () => Promise<void>;
2
+ export default _default;
@@ -0,0 +1,27 @@
1
+ import Gio from 'gi://Gio?version=2.0';
2
+ import { Effect } from 'effect';
3
+ import type * as Scope from 'effect/Scope';
4
+ import * as FileSystem from 'effect/FileSystem';
5
+ import type { PlatformError } from 'effect/PlatformError';
6
+ /** How a flag maps onto GIO: which streams it opens, and whether writes append. */
7
+ export interface OpenPlan {
8
+ readonly readable: boolean;
9
+ /** Writes go to the end of the file regardless of the read position. */
10
+ readonly append: boolean;
11
+ }
12
+ /** The GIO streams behind one open file, plus what the flag asked for. */
13
+ interface Streams {
14
+ readonly input: Gio.InputStream | null;
15
+ readonly output: Gio.OutputStream | null;
16
+ readonly seekable: Gio.Seekable;
17
+ readonly closable: Gio.IOStream | Gio.InputStream | Gio.OutputStream;
18
+ readonly plan: OpenPlan;
19
+ }
20
+ /** Build the `File` handle Effect's `open` promises. */
21
+ export declare const makeFile: (path: string, streams: Streams, stat: Effect.Effect<FileSystem.File.Info, PlatformError>) => FileSystem.File;
22
+ /** Open `path` and close its streams when the scope closes. */
23
+ export declare const openFile: (path: string, options: {
24
+ readonly flag?: FileSystem.OpenFlag | undefined;
25
+ readonly mode?: number | undefined;
26
+ } | undefined, stat: (path: string) => Effect.Effect<FileSystem.File.Info, PlatformError>) => Effect.Effect<FileSystem.File, PlatformError, Scope.Scope>;
27
+ export {};
@@ -0,0 +1,14 @@
1
+ import { Layer } from 'effect';
2
+ import * as FileSystem from 'effect/FileSystem';
3
+ /**
4
+ * The Gio implementation.
5
+ *
6
+ * `FileSystem.make` and not `makeNoop`: `make` demands the COMPLETE interface, so a
7
+ * method added upstream cannot arrive here as a silent default. It derives `exists`
8
+ * from `access`, `readFileString` from `readFile`, and `stream`/`sink` from `open`,
9
+ * all four of which are implemented — so those derivations are correct here for
10
+ * free rather than inheriting a stub.
11
+ */
12
+ export declare const makeGioFileSystem: () => FileSystem.FileSystem;
13
+ /** Provide the Gio-backed `FileSystem` to a program. */
14
+ export declare const layer: Layer.Layer<FileSystem.FileSystem>;
@@ -0,0 +1,2 @@
1
+ declare const _default: () => Promise<void>;
2
+ export default _default;
@@ -0,0 +1,33 @@
1
+ import Gio from 'gi://Gio?version=2.0';
2
+ import type GObject from 'gi://GObject?version=2.0';
3
+ import { Effect } from 'effect';
4
+ import type { PlatformError } from 'effect/PlatformError';
5
+ /**
6
+ * The one adapter every method below goes through: a GIO async pair
7
+ * (`x_async`/`x_finish`) as an interruptible Effect.
8
+ *
9
+ * `Effect.callback`'s `signal` is what makes it interruptible for real. Aborting it
10
+ * cancels the `Gio.Cancellable`, GIO completes the operation with
11
+ * `Gio.IOErrorEnum.CANCELLED`, and the `finish` call throws it — at which point
12
+ * nobody is listening, because `resume` after an interrupt is a no-op. So the
13
+ * catch is not swallowing an error, it is declining to report one for work whose
14
+ * requester is gone; anything else would be a defect raised in a dead fiber.
15
+ *
16
+ * `source` IS A PARAMETER, and it has to be. `g_task_is_valid(result, source)`
17
+ * checks that the object finishing an operation is the object that started it, and
18
+ * `Gio.File.new_for_path(p)` returns a NEW GFile every call — two files for the
19
+ * same path are not the same source. Measured on the first run of this showcase:
20
+ * a `finish` on a freshly constructed GFile logged
21
+ * `g_file_real_enumerate_children_finish: assertion 'g_task_is_valid (res, file)'
22
+ * failed`, returned `null`, and the failure surfaced one call later as
23
+ * `can't access property "next_files_async", r is null` — a null-dereference that
24
+ * names nothing about the actual mistake.
25
+ */
26
+ export declare const gioAsync: <A, S extends GObject.Object>(options: {
27
+ readonly method: string;
28
+ readonly path: string;
29
+ /** The object the operation is started ON, and finished on. */
30
+ readonly source: S;
31
+ readonly start: (source: S, cancellable: Gio.Cancellable, done: (result: Gio.AsyncResult) => void) => void;
32
+ readonly finish: (source: S, result: Gio.AsyncResult) => A;
33
+ }) => Effect.Effect<A, PlatformError>;
@@ -0,0 +1,43 @@
1
+ import type GObject from 'gi://GObject?version=2.0';
2
+ import type Gtk from 'gi://Gtk?version=4.0';
3
+ import { Effect, Scope } from 'effect';
4
+ import type * as Fiber from 'effect/Fiber';
5
+ /** A scope plus the means to close it early and stop listening. */
6
+ export interface WidgetScope {
7
+ readonly scope: Scope.Closeable;
8
+ /** Close now and disconnect the triggers. Idempotent. */
9
+ readonly close: () => void;
10
+ }
11
+ /**
12
+ * A `Scope` closed by one or more signals on `source`, whichever fires first.
13
+ *
14
+ * Shared by the two functions below, and exported because a widget with a
15
+ * different end-of-life signal — a `Gtk.ListItem` being recycled, a page leaving
16
+ * an `Adw.NavigationView` — needs the same thing with a different name.
17
+ */
18
+ export declare const signalScope: (source: GObject.Object, signals: ReadonlyArray<string>) => WidgetScope;
19
+ /**
20
+ * A `Scope` closed when `widget` is DISPOSED — the boundary after which no fiber
21
+ * may touch it.
22
+ *
23
+ * Note what this does not do: `gtk_window_destroy()` does not emit `destroy` while
24
+ * a reference is held, so a window closed by the application keeps this scope open.
25
+ * For a window, use {@link windowScope}.
26
+ */
27
+ export declare const widgetScope: (widget: Gtk.Widget) => WidgetScope;
28
+ /**
29
+ * A `Scope` closed when `window` leaves the screen OR is disposed, whichever comes
30
+ * first — the lifetime an application means when it says "while this window is
31
+ * open".
32
+ */
33
+ export declare const windowScope: (window: Gtk.Window) => WidgetScope;
34
+ /**
35
+ * Start `effect` as a fiber owned by `scope`. Closing the scope interrupts it, and
36
+ * its finalizers run before the close completes.
37
+ *
38
+ * Returns the `Fiber` rather than a promise on purpose: the caller is a signal
39
+ * handler that must return NOW, and a promise there is a value GTK will
40
+ * misinterpret. If you need the outcome, that is `fiber.addObserver(exit => …)`,
41
+ * which is synchronous and one line.
42
+ */
43
+ export declare const runInScope: <A, E>(scope: Scope.Scope, effect: Effect.Effect<A, E, never>) => Fiber.Fiber<A, E>;
@@ -0,0 +1,58 @@
1
+ import type GObject from 'gi://GObject?version=2.0';
2
+ import { Stream } from 'effect';
3
+ export interface SignalStreamOptions {
4
+ /** Queue depth. Small on purpose: a UI signal's backlog has no value. */
5
+ readonly bufferSize?: number;
6
+ /**
7
+ * What a full buffer does. `'sliding'` drops the oldest; `'suspend'` blocks the
8
+ * emitter, which for a main-loop signal means the window stops drawing.
9
+ *
10
+ * `'suspend'` is offered rather than forbidden because the type is `Queue`'s own
11
+ * and a non-main-loop `GObject` — a worker's `Gio.Task`, an `EventBus` — can
12
+ * legitimately want it. For anything GTK emits it is the wrong answer.
13
+ */
14
+ readonly strategy?: 'sliding' | 'suspend';
15
+ }
16
+ /**
17
+ * Every emission of `signalName` on `source`, as a `Stream` of the handler's
18
+ * arguments (the emitting object itself is dropped, as it is always `source`).
19
+ *
20
+ * The handler returns nothing, so this is only correct for signals whose return
21
+ * value GTK ignores. A signal with a meaningful return — `key-pressed`,
22
+ * `close-request` — must be handled synchronously; see `runInScope` in `scope.ts`
23
+ * for the split.
24
+ *
25
+ * The connection is a resource: `Effect.addFinalizer` disconnects it when the
26
+ * stream's scope closes, so a stream held by a widget scope stops listening the
27
+ * moment the widget is destroyed.
28
+ *
29
+ * SUBSCRIBING IS ASYNCHRONOUS, and nothing buffers an emission that beats it. The
30
+ * `connect()` below runs when the stream is first PULLED, not when it is
31
+ * constructed and not when a fiber running it is forked — so an emission between
32
+ * those two moments is simply not seen. In an application this is invisible,
33
+ * because the subscription is set up in a constructor and the first emission comes
34
+ * from a user; it is visible the moment a test emits programmatically, and the
35
+ * showcase's probe has to sleep before it emits for that reason.
36
+ */
37
+ export declare const signalStream: <A extends ReadonlyArray<unknown> = ReadonlyArray<unknown>>(source: GObject.Object, signalName: string, options?: SignalStreamOptions) => Stream.Stream<A>;
38
+ /**
39
+ * The common special case: a GObject property, as a stream of its values, starting
40
+ * with the value it has now.
41
+ *
42
+ * Starting with the current value is what makes this usable as a source of truth
43
+ * rather than a change log — a consumer subscribing after the property was already
44
+ * set otherwise renders an empty view until the user types again. It does NOT close
45
+ * the subscription gap above: `Stream.concat` reads the seed before the second
46
+ * stream is pulled, so a change landing in between is still missed. What it fixes is
47
+ * the steady state, not the race.
48
+ *
49
+ * `read` IS EXPLICIT, and both reasons are worth stating. `GObject.Object.get_property`
50
+ * is the GIR method, which takes a name AND a `GObject.Value` to fill — a
51
+ * one-argument call throws `At least 2 arguments required, but only 1 passed`,
52
+ * measured here on the showcase's first probe run. And even given a working
53
+ * reflective read, the GObject name and the JS name differ (`icon-name` is
54
+ * `iconName`), so a helper that mapped between them would be guessing at a
55
+ * convention that has exceptions. Passing the getter costs one closure and is
56
+ * typed.
57
+ */
58
+ export declare const propertyStream: <T>(source: GObject.Object, property: string, read: () => T, options?: SignalStreamOptions) => Stream.Stream<T>;
@@ -0,0 +1,2 @@
1
+ export { runInScope, signalScope, widgetScope, windowScope, type WidgetScope } from './gtk/scope.js';
2
+ export { propertyStream, signalStream, type SignalStreamOptions } from './gtk/signal.js';
@@ -0,0 +1,4 @@
1
+ export { isIoError, reasonOf, toPlatformError } from './errors.js';
2
+ export { gioAsync } from './gio-async.js';
3
+ export { layer as fileSystemLayer, makeGioFileSystem } from './filesystem.js';
4
+ export { layer as pathLayer } from './path.js';
@@ -0,0 +1,4 @@
1
+ import { Layer } from 'effect';
2
+ import { Path } from 'effect/Path';
3
+ /** Provide the GLib-backed `Path` to a program. */
4
+ export declare const layer: Layer.Layer<Path>;
@@ -0,0 +1,2 @@
1
+ declare const _default: () => Promise<void>;
2
+ export default _default;
package/package.json ADDED
@@ -0,0 +1,89 @@
1
+ {
2
+ "name": "@gjsify/effect-platform",
3
+ "version": "0.48.0",
4
+ "description": "Effect's platform services over GNOME: effect/FileSystem on Gio.File with cancellation that reaches GIO, effect/Path on GLib, GError mapped onto Effect's normalized platform errors, and a /gtk subpath binding GObject lifetimes to Effect Scopes",
5
+ "keywords": [
6
+ "effect",
7
+ "gjs",
8
+ "gnome",
9
+ "gio",
10
+ "glib",
11
+ "gtk",
12
+ "filesystem",
13
+ "platform"
14
+ ],
15
+ "type": "module",
16
+ "main": "lib/esm/index.js",
17
+ "module": "lib/esm/index.js",
18
+ "types": "lib/types/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./lib/types/index.d.ts",
22
+ "default": "./lib/esm/index.js"
23
+ },
24
+ "./gtk": {
25
+ "types": "./lib/types/gtk.d.ts",
26
+ "default": "./lib/esm/gtk.js"
27
+ },
28
+ "./package.json": "./package.json"
29
+ },
30
+ "sideEffects": false,
31
+ "files": [
32
+ "lib"
33
+ ],
34
+ "scripts": {
35
+ "clear": "gjsify clear lib tmp tsconfig.tsbuildinfo test.gjs.mjs",
36
+ "check": "gjsify tsc --noEmit",
37
+ "build": "gjsify run build:gjsify && gjsify run build:types",
38
+ "build:gjsify": "gjsify build --library 'src/**/*.{ts,js}' --exclude 'src/**/*.spec.{mts,ts}' 'src/test.{mts,ts}'",
39
+ "build:types": "gjsify tsc",
40
+ "build:test": "gjsify run build:test:gjs",
41
+ "build:test:gjs": "gjsify build src/test.mts --app gjs --outfile test.gjs.mjs",
42
+ "test": "gjsify run build:gjsify && gjsify run build:test && gjsify run test:gjs",
43
+ "test:gjs": "gjsify run test.gjs.mjs"
44
+ },
45
+ "gjsify": {
46
+ "runtimes": {
47
+ "gjs": "polyfill",
48
+ "node": "none",
49
+ "browser": "none",
50
+ "nativescript": "none"
51
+ },
52
+ "tier": 3,
53
+ "headless": [
54
+ "Gdk",
55
+ "GdkPixbuf",
56
+ "Gsk",
57
+ "Gtk",
58
+ "Adw"
59
+ ]
60
+ },
61
+ "dependencies": {
62
+ "@girs/gio-2.0": "4.6.0",
63
+ "@girs/gjs": "4.6.0",
64
+ "@girs/glib-2.0": "4.6.0",
65
+ "@girs/gobject-2.0": "4.6.0",
66
+ "@girs/gtk-4.0": "4.6.0"
67
+ },
68
+ "peerDependencies": {
69
+ "effect": "4.0.0-rc.112"
70
+ },
71
+ "devDependencies": {
72
+ "@gjsify/cli": "^0.48.0",
73
+ "@gjsify/unit": "^0.48.0",
74
+ "@types/node": "^25.9.2",
75
+ "effect": "4.0.0-rc.112",
76
+ "typescript": "^6.0.3"
77
+ },
78
+ "author": "Pascal Garber <pascal@artandcode.studio>",
79
+ "license": "MIT",
80
+ "repository": {
81
+ "type": "git",
82
+ "url": "git+https://github.com/gjsify/gjsify.git",
83
+ "directory": "packages/framework/effect-platform"
84
+ },
85
+ "bugs": {
86
+ "url": "https://github.com/gjsify/gjsify/issues"
87
+ },
88
+ "homepage": "https://github.com/gjsify/gjsify/tree/main/packages/framework/effect-platform#readme"
89
+ }