Skip to content

Commit 3dd0f06

Browse files
hyperpolymathclaude
andcommitted
feat: Gossamer migration — RuntimeBridge, gossamer.conf.json, Tauri→Gossamer conversion
Added Gossamer configuration and RuntimeBridge alongside existing Tauri setup. Tauri files preserved for transition period. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a1ee988 commit 3dd0f06

File tree

7 files changed

+1082
-0
lines changed

7 files changed

+1082
-0
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"name": "@hyperpolymath/rescript-gossamer",
3+
"version": "0.1.0",
4+
"exports": "./lib/es6/src/Gossamer.res.mjs",
5+
"imports": {
6+
"@rescript/core": "npm:@rescript/core@^1.0.0",
7+
"rescript": "^12.0.0"
8+
},
9+
"tasks": {
10+
"build": "deno run -A npm:rescript build",
11+
"watch": "deno run -A npm:rescript build -w",
12+
"clean": "deno run -A npm:rescript clean",
13+
"test": "deno test --allow-read --allow-env test/",
14+
"check": "deno check src/**/*.res.mjs"
15+
},
16+
"fmt": {
17+
"include": [
18+
"src/",
19+
"test/"
20+
],
21+
"exclude": [
22+
"lib/"
23+
]
24+
},
25+
"lint": {
26+
"include": [
27+
"src/",
28+
"test/"
29+
],
30+
"exclude": [
31+
"lib/"
32+
]
33+
}
34+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "rescript-gossamer",
3+
"sources": [
4+
{
5+
"dir": "src",
6+
"subdirs": true
7+
}
8+
],
9+
"package-specs": [
10+
{
11+
"module": "esmodule",
12+
"in-source": false
13+
}
14+
],
15+
"suffix": ".res.mjs",
16+
"bs-dependencies": [
17+
"@rescript/core"
18+
],
19+
"bs-dev-dependencies": [],
20+
"warnings": {
21+
"number": "+A-48-42"
22+
}
23+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
//
4+
// Gossamer.res — Top-level re-export module for rescript-gossamer bindings.
5+
//
6+
// Usage:
7+
// open Gossamer
8+
// let _ = Core.invoke("my_command")
9+
// let _ = Dialog.openSingle()
10+
// let _ = Fs.readTextFile("/path/to/file")
11+
// let _ = Window.Current.closeCurrent()
12+
13+
/// Core invoke and command bindings for Gossamer IPC.
14+
module Core = Gossamer_Core
15+
16+
/// File picker, save, message, and confirmation dialog bindings.
17+
module Dialog = Gossamer_Dialog
18+
19+
/// Filesystem operations (read, write, mkdir, stat, watch).
20+
module Fs = Gossamer_Fs
21+
22+
/// Window management (resize, move, minimize, maximize, etc.).
23+
module Window = Gossamer_Window
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
//
4+
// Gossamer_Core.res — Core Gossamer invoke and command bindings.
5+
//
6+
// Provides the primary IPC bridge to the Gossamer runtime via
7+
// `window.__gossamer_invoke`. This is the Gossamer equivalent of
8+
// Tauri_Core.res (which wraps @tauri-apps/api/core).
9+
//
10+
// All Gossamer commands are dispatched through a single invoke function
11+
// injected by gossamer_channel_open() into the webview global scope.
12+
13+
open RescriptCore
14+
15+
// ============================================================================
16+
// Types
17+
// ============================================================================
18+
19+
/// Invoke arguments — any JSON-serializable value.
20+
type invokeArgs = JSON.t
21+
22+
/// Invoke options for customizing command calls.
23+
type invokeOptions = {
24+
timeout?: int,
25+
retries?: int,
26+
}
27+
28+
/// Permission state for capability requests.
29+
type permissionState =
30+
| @as("granted") Granted
31+
| @as("denied") Denied
32+
| @as("prompt") Prompt
33+
34+
// ============================================================================
35+
// Runtime Detection
36+
// ============================================================================
37+
38+
/// Check whether the Gossamer runtime is available in the current context.
39+
%%raw(`
40+
function isGossamerAvailable() {
41+
return typeof window !== 'undefined'
42+
&& typeof window.__gossamer_invoke === 'function';
43+
}
44+
`)
45+
@val external isGossamerAvailable: unit => bool = "isGossamerAvailable"
46+
47+
// ============================================================================
48+
// Core Invoke API
49+
// ============================================================================
50+
51+
/// Raw invoke binding — calls window.__gossamer_invoke(cmd, args).
52+
/// This is the foundation for all Gossamer IPC.
53+
%%raw(`
54+
function gossamerInvokeRaw(cmd, args) {
55+
if (typeof window !== 'undefined' && typeof window.__gossamer_invoke === 'function') {
56+
return window.__gossamer_invoke(cmd, args);
57+
}
58+
return Promise.reject(new Error('Gossamer runtime not available'));
59+
}
60+
`)
61+
@val external invokeRaw: (string, 'a) => promise<'b> = "gossamerInvokeRaw"
62+
63+
/// Invoke a Gossamer command with typed arguments and response.
64+
/// Commands are defined in the Gossamer backend (Rust/Zig) and
65+
/// exposed via the IPC channel.
66+
let invoke = (command: string, ~args: invokeArgs=?): promise<'response> => {
67+
let finalArgs = switch args {
68+
| Some(a) => a
69+
| None => Obj.magic(Dict.make())
70+
}
71+
invokeRaw(command, finalArgs)
72+
}
73+
74+
/// Invoke a command with automatic JSON serialization/deserialization.
75+
/// Use this when you want type safety on both request and response.
76+
let invokeTyped = async (
77+
~command: string,
78+
~args: 'args,
79+
~decoder: JSON.t => result<'response, string>,
80+
): result<'response, string> => {
81+
try {
82+
let response = await invokeRaw(command, args)
83+
decoder(Obj.magic(response))
84+
} catch {
85+
| Exn.Error(err) =>
86+
Error(Exn.message(err)->Option.getOr("Unknown Gossamer invoke error"))
87+
}
88+
}
89+
90+
// ============================================================================
91+
// App Info API
92+
// ============================================================================
93+
94+
/// Get the application name from the Gossamer runtime.
95+
let getName = (): promise<string> => {
96+
invokeRaw("__gossamer_app_get_name", {})
97+
}
98+
99+
/// Get the application version from the Gossamer runtime.
100+
let getVersion = (): promise<string> => {
101+
invokeRaw("__gossamer_app_get_version", {})
102+
}
103+
104+
/// Get the Gossamer runtime version.
105+
let getGossamerVersion = (): promise<string> => {
106+
invokeRaw("__gossamer_app_get_runtime_version", {})
107+
}
108+
109+
/// Show the application window.
110+
let show = (): promise<unit> => {
111+
invokeRaw("__gossamer_app_show", {})
112+
}
113+
114+
/// Hide the application window.
115+
let hide = (): promise<unit> => {
116+
invokeRaw("__gossamer_app_hide", {})
117+
}
118+
119+
// ============================================================================
120+
// Capability API
121+
// ============================================================================
122+
123+
/// Check if a capability is available.
124+
let hasCapability = (capability: string): promise<bool> => {
125+
invokeRaw("__gossamer_capability_check", {"capability": capability})
126+
}
127+
128+
/// Request a capability from the runtime.
129+
let requestCapability = (capability: string): promise<permissionState> => {
130+
invokeRaw("__gossamer_capability_request", {"capability": capability})
131+
}

0 commit comments

Comments
 (0)