@sanbus/galley 0.0.1

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,14 @@
1
+ # Universal Galley JavaScript Bindings
2
+
3
+ `@sanbus/galley-core` over native libraries (Node, Bun, Deno) with WebAssembly
4
+ fallback, selected per runtime. No native dependencies beyond the built
5
+ parser artifacts.
6
+
7
+ Call `await init()` once, then use the synchronous `Session` API; under
8
+ Node and Bun the backend also resolves synchronously on first use. When no
9
+ native library is found the WebAssembly backend serves instead (with a
10
+ one-time performance notice), otherwise `init()` explains how to build one.
11
+
12
+ See `docs/bindings_js_universal.md` for the consumer flow.
13
+
14
+ One module embeds one parser; sessions are not thread-safe.
package/build.mjs ADDED
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Single build entry for the Galley JavaScript bindings.
4
+ *
5
+ * Usage:
6
+ * galley build <language-dir> [--native-only|--wasm-only]
7
+ *
8
+ * Builds both artifacts next to the grammar by default: the canonical
9
+ * shared native library (serves the Node, Bun, and Deno adapters) and the
10
+ * wasm module (serves browsers and the universal fallback leg). The
11
+ * per-adapter builders (`@sanbus/galley-node`, `@sanbus/galley-bun`,
12
+ * `@sanbus/galley-wasm`, the Deno `build.ts`) remain as thin wrappers over the
13
+ * same shared gate for single-leg builds.
14
+ *
15
+ * Environment: `ZIG_EXECUTABLE` (default `zig`) and `GALLEY_CHECKOUT`
16
+ * (required): an existing Galley working tree holding `build.zig`. To
17
+ * fetch a checkout for convenience, use
18
+ * `examples/scripts/fetch-galley.sh` (examples-only, not core).
19
+ */
20
+
21
+ import * as path from "node:path";
22
+ import { fileURLToPath } from "node:url";
23
+ import {
24
+ NATIVE_LIBRARY_BASE,
25
+ WASM_LIBRARY_BASE,
26
+ buildParserArtifact,
27
+ } from "@sanbus/galley-core/build/builder.mjs";
28
+
29
+ function fatal(message) {
30
+ console.error(`galley-bindings: ${message}`);
31
+ process.exit(1);
32
+ }
33
+
34
+ const USAGE = "usage: galley build <language-dir> [--native-only|--wasm-only]";
35
+
36
+ async function main() {
37
+ const argumentList = process.argv.slice(2);
38
+ if (argumentList.length < 2 || argumentList[0] !== "build") fatal(USAGE);
39
+ const flagList = argumentList.slice(2);
40
+ for (const flag of flagList) {
41
+ if (flag !== "--native-only" && flag !== "--wasm-only") fatal(`unknown flag ${flag}; ${USAGE}`);
42
+ }
43
+ const nativeOnly = flagList.includes("--native-only");
44
+ const wasmOnly = flagList.includes("--wasm-only");
45
+ if (nativeOnly && wasmOnly) fatal(`pass at most one of --native-only, --wasm-only; ${USAGE}`);
46
+
47
+ const languageDirectory = argumentList[1];
48
+ const bindingsDirectory = path.dirname(fileURLToPath(import.meta.url));
49
+ if (!wasmOnly) {
50
+ await buildParserArtifact({
51
+ languageDirectory,
52
+ libraryName: NATIVE_LIBRARY_BASE,
53
+ bindingsDirectory,
54
+ dependencyName: "@sanbus/galley-core",
55
+ });
56
+ }
57
+ if (!nativeOnly) {
58
+ await buildParserArtifact({
59
+ languageDirectory,
60
+ libraryName: WASM_LIBRARY_BASE,
61
+ wasm: true,
62
+ posixOnly: false,
63
+ bindingsDirectory,
64
+ dependencyName: "@sanbus/galley-core",
65
+ });
66
+ }
67
+ }
68
+
69
+ main().catch((error) => fatal(error?.message ?? String(error)));
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Universal Galley JavaScript bindings — public surface.
3
+ *
4
+ * One package for Node, Bun, Deno, and browsers over `@sanbus/galley-core`,
5
+ * with native-first backend selection and WebAssembly fallback. Call
6
+ * `await init()` once, then use the synchronous `Session` API; under Node
7
+ * and Bun the backend also resolves synchronously on first use.
8
+ */
9
+ import { Session } from "./session.ts";
10
+ export * from "@sanbus/galley-core";
11
+ export { Session };
12
+ export { init, backend, currentBackend, detectRuntime, type InitOptions, type Runtime, type Backend, } from "./loader.ts";
13
+ export type { UniversalSessionOptions } from "./session.ts";
14
+ export type { SessionOptions, WalkStep, Diagnostic, TreeSnapshot } from "@sanbus/galley-core";
15
+ export declare function version(): string;
16
+ export declare function parserType(): number;
17
+ export declare function errorRecoveryMode(): number;
18
+ export declare function hasAst(): boolean;
19
+ export declare function hasProcedures(): boolean;
20
+ export declare function allowsNoAstTreeProcedures(): boolean;
21
+ export declare function sourceRetentionEnabled(): boolean;
22
+ export declare function hasPositionTracking(): boolean;
23
+ export declare function hasInputStreaming(): boolean;
24
+ export declare function usesVerbatim(): boolean;
25
+ export declare function stackOverflowRecoveryAvailable(): boolean;
26
+ export declare function symbolCount(): number;
27
+ export declare function variableCount(): number;
28
+ export declare function statusString(status: number): string | null;
29
+ export declare const has_ast: typeof hasAst;
30
+ export declare const has_procedures: typeof hasProcedures;
31
+ export declare const has_position_tracking: typeof hasPositionTracking;
package/dist/index.js ADDED
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Universal Galley JavaScript bindings — public surface.
3
+ *
4
+ * One package for Node, Bun, Deno, and browsers over `@sanbus/galley-core`,
5
+ * with native-first backend selection and WebAssembly fallback. Call
6
+ * `await init()` once, then use the synchronous `Session` API; under Node
7
+ * and Bun the backend also resolves synchronously on first use.
8
+ */
9
+ import { ensureSync } from "./loader.js";
10
+ import { Session } from "./session.js";
11
+ // Core surface (Session base is shadowed by the adapter subclass below).
12
+ export * from "@sanbus/galley-core";
13
+ export { Session };
14
+ export { init, backend, currentBackend, detectRuntime, } from "./loader.js";
15
+ // Module-level queries (mirror galley.h)
16
+ export function version() {
17
+ return ensureSync().version();
18
+ }
19
+ export function parserType() {
20
+ return ensureSync().parserType();
21
+ }
22
+ export function errorRecoveryMode() {
23
+ return ensureSync().errorRecoveryMode();
24
+ }
25
+ export function hasAst() {
26
+ return ensureSync().hasAst();
27
+ }
28
+ export function hasProcedures() {
29
+ return ensureSync().hasProcedures();
30
+ }
31
+ export function allowsNoAstTreeProcedures() {
32
+ return ensureSync().allowsNoAstTreeProcedures();
33
+ }
34
+ export function sourceRetentionEnabled() {
35
+ return ensureSync().sourceRetentionEnabled();
36
+ }
37
+ export function hasPositionTracking() {
38
+ return ensureSync().hasPositionTracking();
39
+ }
40
+ export function hasInputStreaming() {
41
+ return ensureSync().hasInputStreaming();
42
+ }
43
+ export function usesVerbatim() {
44
+ return ensureSync().usesVerbatim();
45
+ }
46
+ export function stackOverflowRecoveryAvailable() {
47
+ return ensureSync().stackOverflowRecoveryAvailable();
48
+ }
49
+ export function symbolCount() {
50
+ return ensureSync().symbolCount();
51
+ }
52
+ export function variableCount() {
53
+ return ensureSync().variableCount();
54
+ }
55
+ export function statusString(status) {
56
+ return ensureSync().statusString(status);
57
+ }
58
+ // Preserve original Python naming aliases for docs parity
59
+ export const has_ast = hasAst;
60
+ export const has_procedures = hasProcedures;
61
+ export const has_position_tracking = hasPositionTracking;
62
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,yEAAyE;AACzE,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,CAAC;AACnB,OAAO,EACL,IAAI,EACJ,OAAO,EACP,cAAc,EACd,aAAa,GAId,MAAM,aAAa,CAAC;AAIrB,yCAAyC;AACzC,MAAM,UAAU,OAAO;IACrB,OAAO,UAAU,EAAE,CAAC,OAAO,EAAE,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,OAAO,UAAU,EAAE,CAAC,UAAU,EAAE,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO,UAAU,EAAE,CAAC,iBAAiB,EAAE,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,MAAM;IACpB,OAAO,UAAU,EAAE,CAAC,MAAM,EAAE,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO,UAAU,EAAE,CAAC,aAAa,EAAE,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,yBAAyB;IACvC,OAAO,UAAU,EAAE,CAAC,yBAAyB,EAAE,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,OAAO,UAAU,EAAE,CAAC,sBAAsB,EAAE,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,OAAO,UAAU,EAAE,CAAC,mBAAmB,EAAE,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO,UAAU,EAAE,CAAC,iBAAiB,EAAE,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,OAAO,UAAU,EAAE,CAAC,YAAY,EAAE,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,8BAA8B;IAC5C,OAAO,UAAU,EAAE,CAAC,8BAA8B,EAAE,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,OAAO,UAAU,EAAE,CAAC,WAAW,EAAE,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO,UAAU,EAAE,CAAC,aAAa,EAAE,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,OAAO,UAAU,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;AAC3C,CAAC;AAED,0DAA0D;AAC1D,MAAM,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC;AAC9B,MAAM,CAAC,MAAM,cAAc,GAAG,aAAa,CAAC;AAC5C,MAAM,CAAC,MAAM,qBAAqB,GAAG,mBAAmB,CAAC"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Universal loader for the Galley JavaScript bindings.
3
+ *
4
+ * Binds the runtime-neutral `@sanbus/galley-core` to one of four backends —
5
+ * the Node, Bun, and Deno native adapters, or the WebAssembly adapter —
6
+ * selected per runtime with native-first ordering:
7
+ *
8
+ * - Node: koffi native → wasm → compile error.
9
+ * - Bun: `bun:ffi` native → wasm → compile error.
10
+ * - Deno: `Deno.dlopen` native → wasm → compile error.
11
+ * - Browser: wasm only.
12
+ *
13
+ * Adapters are imported dynamically by specifier string (never resolved
14
+ * statically), so bundlers only ever see the backends that are actually
15
+ * imported, and a backend missing from `node_modules` degrades to
16
+ * "unavailable" instead of failing the load. Each adapter resolves exactly
17
+ * one named artifact or throws `MissingArtifactError`; the loader catches
18
+ * exactly that class to try the next engine. A present-but-broken library
19
+ * throws anything else and fails loudly instead of silently falling back.
20
+ */
21
+ import type { FfiPort } from "@sanbus/galley-core";
22
+ export type Runtime = "node" | "bun" | "deno" | "browser";
23
+ export type Backend = "native" | "wasm";
24
+ export type NativeRuntime = "node" | "bun" | "deno";
25
+ export interface InitOptions {
26
+ /** Grammar artifact path. A `.wasm` suffix forces the wasm backend. */
27
+ libraryPath?: string;
28
+ /** Explicit wasm module path (fallback source when native is missing). */
29
+ wasmPath?: string;
30
+ /** Module URL for `fetch` (browsers). */
31
+ url?: string | URL;
32
+ /** Raw module bytes (browsers, tests). */
33
+ wasmBytes?: Uint8Array;
34
+ /** Suppress the one-time WebAssembly performance notice. */
35
+ quiet?: boolean;
36
+ }
37
+ /** Detect the current JavaScript runtime. Bun and Deno are checked before
38
+ * Node: Bun emulates `process.versions.node`. */
39
+ export declare function detectRuntime(): Runtime;
40
+ /** A resolved backend: the port plus which leg of the chain served it. */
41
+ export interface ResolvedBackend {
42
+ port: FfiPort;
43
+ backend: Backend;
44
+ }
45
+ /**
46
+ * Resolve and initialize the backend for `options`, caching the result.
47
+ * Order: explicit `.wasm` path pins wasm; otherwise native first, then
48
+ * wasm discovery, then a compile-guidance error. Browsers skip native
49
+ * attempts (no FFI); non-Node runtimes without a prior `init()` cannot
50
+ * synchronously initialize wasm and surface the adapter's NeedInitError.
51
+ */
52
+ export declare function init(options?: InitOptions): Promise<ResolvedBackend>;
53
+ /** The initialized backend, or null when `init()` has not completed. */
54
+ export declare function currentBackend(): ResolvedBackend | null;
55
+ /** Backend selected by the last `init()` (`"native"`, `"wasm"`, or null). */
56
+ export declare function backend(): Backend | null;
57
+ /**
58
+ * Synchronous resolution for Node and Bun (dynamic `import()` is async,
59
+ * so other runtimes must `await init()` first). Used by the `Session`
60
+ * constructor and module-level queries.
61
+ */
62
+ export declare function ensureSync(options?: InitOptions): FfiPort;
63
+ /** Test-only: clear cached resolution and the fallback notice. */
64
+ export declare function __resetLoader(): void;
package/dist/loader.js ADDED
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Universal loader for the Galley JavaScript bindings.
3
+ *
4
+ * Binds the runtime-neutral `@sanbus/galley-core` to one of four backends —
5
+ * the Node, Bun, and Deno native adapters, or the WebAssembly adapter —
6
+ * selected per runtime with native-first ordering:
7
+ *
8
+ * - Node: koffi native → wasm → compile error.
9
+ * - Bun: `bun:ffi` native → wasm → compile error.
10
+ * - Deno: `Deno.dlopen` native → wasm → compile error.
11
+ * - Browser: wasm only.
12
+ *
13
+ * Adapters are imported dynamically by specifier string (never resolved
14
+ * statically), so bundlers only ever see the backends that are actually
15
+ * imported, and a backend missing from `node_modules` degrades to
16
+ * "unavailable" instead of failing the load. Each adapter resolves exactly
17
+ * one named artifact or throws `MissingArtifactError`; the loader catches
18
+ * exactly that class to try the next engine. A present-but-broken library
19
+ * throws anything else and fails loudly instead of silently falling back.
20
+ */
21
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
22
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
23
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
24
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
25
+ });
26
+ }
27
+ return path;
28
+ };
29
+ import { createRequire } from "node:module";
30
+ import { MissingArtifactError } from "@sanbus/galley-core";
31
+ /** Detect the current JavaScript runtime. Bun and Deno are checked before
32
+ * Node: Bun emulates `process.versions.node`. */
33
+ export function detectRuntime() {
34
+ const globals = globalThis;
35
+ if (typeof globals.Bun !== "undefined")
36
+ return "bun";
37
+ if (typeof globals.Deno !== "undefined")
38
+ return "deno";
39
+ const processValue = globals.process;
40
+ if (typeof processValue !== "undefined" && typeof processValue.versions?.node === "string") {
41
+ return "node";
42
+ }
43
+ return "browser";
44
+ }
45
+ const NATIVE_ADAPTERS = {
46
+ node: { module: "@sanbus/galley-node", port: "getNodePort" },
47
+ bun: { module: "@sanbus/galley-bun", port: "getBunPort" },
48
+ deno: { module: "@sanbus/galley-deno", port: "getDenoPort" },
49
+ };
50
+ const WASM_MODULE = "@sanbus/galley-wasm";
51
+ function isWasmPath(value) {
52
+ return !!value && value.toLowerCase().endsWith(".wasm");
53
+ }
54
+ async function loadNativeAdapter(runtime) {
55
+ const { module: specifier, port } = NATIVE_ADAPTERS[runtime];
56
+ let loaded;
57
+ try {
58
+ // Specifier is a string on purpose: bundlers must not statically
59
+ // resolve backends that may be absent, and tsc must not require
60
+ // their type declarations to exist yet.
61
+ loaded = (await import(__rewriteRelativeImportExtension(specifier)));
62
+ }
63
+ catch {
64
+ return null;
65
+ }
66
+ const getPort = loaded[port];
67
+ if (typeof getPort !== "function")
68
+ return null;
69
+ return { getPort: getPort };
70
+ }
71
+ async function loadWasmAdapter() {
72
+ try {
73
+ const loaded = (await import(__rewriteRelativeImportExtension(WASM_MODULE)));
74
+ if (typeof loaded["init"] !== "function" ||
75
+ typeof loaded["getWasmPort"] !== "function" ||
76
+ typeof loaded["NeedInitError"] !== "function") {
77
+ return null;
78
+ }
79
+ return loaded;
80
+ }
81
+ catch {
82
+ return null;
83
+ }
84
+ }
85
+ let ready = null;
86
+ let warnedWasm = false;
87
+ function compileGuidance() {
88
+ return new Error("galley: no parser artifact found (tried native library, then WebAssembly).\n" +
89
+ "Build one first: npx galley build <language-dir>\n" +
90
+ "or set GALLEY_LIBRARY_PATH to the built artifact.");
91
+ }
92
+ function noteWasmFallback(quiet) {
93
+ if (quiet || warnedWasm)
94
+ return;
95
+ warnedWasm = true;
96
+ console.warn("galley: using the WebAssembly backend (no native library found); " +
97
+ "throughput trails native codegen (roughly three quarters). " +
98
+ "Build a native library for full speed. Silence with { quiet: true }.");
99
+ }
100
+ /** Try the native leg. A missing artifact yields null; a present-but-broken
101
+ * library throws loudly (an ABI mismatch is a user error, not a
102
+ * fallback case). */
103
+ async function tryNative(runtime, libraryPath) {
104
+ const adapter = await loadNativeAdapter(runtime);
105
+ if (!adapter)
106
+ return null;
107
+ try {
108
+ return adapter.getPort(libraryPath);
109
+ }
110
+ catch (error) {
111
+ if (MissingArtifactError.is(error))
112
+ return null;
113
+ throw error;
114
+ }
115
+ }
116
+ /** Try the wasm leg through the shared adapter. */
117
+ async function tryWasm(options) {
118
+ const wasm = await loadWasmAdapter();
119
+ if (!wasm)
120
+ return null;
121
+ const explicit = options.wasmPath ?? (isWasmPath(options.libraryPath) ? options.libraryPath : undefined);
122
+ try {
123
+ await wasm.init({ libraryPath: explicit, url: options.url, bytes: options.wasmBytes });
124
+ }
125
+ catch (error) {
126
+ if (error instanceof wasm.NeedInitError)
127
+ throw error;
128
+ if (MissingArtifactError.is(error))
129
+ return null;
130
+ throw error;
131
+ }
132
+ return wasm.getWasmPort(explicit);
133
+ }
134
+ /**
135
+ * Resolve and initialize the backend for `options`, caching the result.
136
+ * Order: explicit `.wasm` path pins wasm; otherwise native first, then
137
+ * wasm discovery, then a compile-guidance error. Browsers skip native
138
+ * attempts (no FFI); non-Node runtimes without a prior `init()` cannot
139
+ * synchronously initialize wasm and surface the adapter's NeedInitError.
140
+ */
141
+ export async function init(options = {}) {
142
+ const runtime = detectRuntime();
143
+ if (isWasmPath(options.libraryPath)) {
144
+ const wasm = await loadWasmAdapter();
145
+ if (!wasm)
146
+ throw compileGuidance();
147
+ await wasm.init({ libraryPath: options.libraryPath, url: options.url, bytes: options.wasmBytes });
148
+ noteWasmFallback(options.quiet);
149
+ ready = { port: wasm.getWasmPort(options.libraryPath), backend: "wasm" };
150
+ return ready;
151
+ }
152
+ if (runtime !== "browser") {
153
+ const native = await tryNative(runtime, options.libraryPath);
154
+ if (native) {
155
+ ready = { port: native, backend: "native" };
156
+ return ready;
157
+ }
158
+ }
159
+ const wasmPort = await tryWasm(options);
160
+ if (wasmPort) {
161
+ noteWasmFallback(options.quiet);
162
+ ready = { port: wasmPort, backend: "wasm" };
163
+ return ready;
164
+ }
165
+ throw compileGuidance();
166
+ }
167
+ /** The initialized backend, or null when `init()` has not completed. */
168
+ export function currentBackend() {
169
+ return ready;
170
+ }
171
+ /** Backend selected by the last `init()` (`"native"`, `"wasm"`, or null). */
172
+ export function backend() {
173
+ return ready?.backend ?? null;
174
+ }
175
+ /**
176
+ * Synchronous resolution for Node and Bun (dynamic `import()` is async,
177
+ * so other runtimes must `await init()` first). Used by the `Session`
178
+ * constructor and module-level queries.
179
+ */
180
+ export function ensureSync(options = {}) {
181
+ if (ready && !options.libraryPath && !options.wasmPath && !options.url && !options.wasmBytes) {
182
+ return ready.port;
183
+ }
184
+ const runtime = detectRuntime();
185
+ if (runtime !== "node" && runtime !== "bun") {
186
+ throw new Error("galley: call await init() before using the bindings on this runtime; " +
187
+ "synchronous initialization is only available under Node and Bun.");
188
+ }
189
+ if (!isWasmPath(options.libraryPath)) {
190
+ const native = tryNativeSync(runtime, options.libraryPath);
191
+ if (native) {
192
+ ready = { port: native, backend: "native" };
193
+ return native;
194
+ }
195
+ }
196
+ const wasmPort = tryWasmSync(options);
197
+ if (wasmPort) {
198
+ noteWasmFallback(options.quiet);
199
+ ready = { port: wasmPort, backend: "wasm" };
200
+ return wasmPort;
201
+ }
202
+ throw compileGuidance();
203
+ }
204
+ function requireAdapterModule(specifier) {
205
+ try {
206
+ // Synchronous require: this branch runs under Node and Bun only.
207
+ // Falls back to null when the package is absent.
208
+ const require = createRequire(import.meta.url);
209
+ return require(specifier);
210
+ }
211
+ catch {
212
+ return null;
213
+ }
214
+ }
215
+ /** Synchronous native attempt (Node/Bun only). */
216
+ function tryNativeSync(runtime, libraryPath) {
217
+ const { module: specifier, port } = NATIVE_ADAPTERS[runtime];
218
+ const loaded = requireAdapterModule(specifier);
219
+ if (!loaded)
220
+ return null;
221
+ const getPort = loaded[port];
222
+ if (typeof getPort !== "function")
223
+ return null;
224
+ try {
225
+ return getPort(libraryPath);
226
+ }
227
+ catch (error) {
228
+ if (MissingArtifactError.is(error))
229
+ return null;
230
+ throw error;
231
+ }
232
+ }
233
+ /** Synchronous wasm attempt through the shared adapter (Node/Bun only:
234
+ * the wasm adapter reads files and instantiates synchronously there). */
235
+ function tryWasmSync(options) {
236
+ const loaded = requireAdapterModule(WASM_MODULE);
237
+ if (!loaded || typeof loaded["initSync"] !== "function")
238
+ return null;
239
+ const wasm = loaded;
240
+ const explicit = options.wasmPath ?? (isWasmPath(options.libraryPath) ? options.libraryPath : undefined);
241
+ if (options.url !== undefined && options.wasmBytes === undefined)
242
+ return null;
243
+ try {
244
+ wasm.initSync({ libraryPath: explicit, bytes: options.wasmBytes });
245
+ }
246
+ catch (error) {
247
+ if (MissingArtifactError.is(error))
248
+ return null;
249
+ throw error;
250
+ }
251
+ const getWasmPort = loaded["getWasmPort"];
252
+ if (typeof getWasmPort !== "function")
253
+ return null;
254
+ return getWasmPort(explicit);
255
+ }
256
+ /** Test-only: clear cached resolution and the fallback notice. */
257
+ export function __resetLoader() {
258
+ ready = null;
259
+ warnedWasm = false;
260
+ }
261
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Universal `Session`: the core session bound to the resolved backend.
3
+ *
4
+ * Under Node and Bun the backend resolves synchronously on first use;
5
+ * elsewhere `await init()` must complete first.
6
+ */
7
+ import { Session as CoreSession } from "@sanbus/galley-core";
8
+ import type { SessionOptions } from "@sanbus/galley-core";
9
+ import { type InitOptions } from "./loader.ts";
10
+ export type UniversalSessionOptions = SessionOptions & InitOptions;
11
+ export type { SessionOptions };
12
+ export declare class Session extends CoreSession {
13
+ constructor(options?: UniversalSessionOptions);
14
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Universal `Session`: the core session bound to the resolved backend.
3
+ *
4
+ * Under Node and Bun the backend resolves synchronously on first use;
5
+ * elsewhere `await init()` must complete first.
6
+ */
7
+ import { Session as CoreSession } from "@sanbus/galley-core";
8
+ import { ensureSync } from "./loader.js";
9
+ export class Session extends CoreSession {
10
+ constructor(options = {}) {
11
+ super(ensureSync(options), options);
12
+ }
13
+ }
14
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAE7D,OAAO,EAAE,UAAU,EAAoB,MAAM,aAAa,CAAC;AAK3D,MAAM,OAAO,OAAQ,SAAQ,WAAW;IACtC,YAAY,OAAO,GAA4B,EAAE;QAC/C,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC;IACtC,CAAC;CACF"}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@sanbus/galley",
3
+ "version": "0.0.1",
4
+ "description": "Universal Galley JavaScript bindings: one package for Node, Bun, Deno, and browsers over @sanbus/galley-core, with native-first backend selection and WebAssembly fallback",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "src",
17
+ "build.mjs",
18
+ "README.md"
19
+ ],
20
+ "bin": {
21
+ "galley": "./build.mjs"
22
+ },
23
+ "scripts": {
24
+ "build": "tsc -p tsconfig.json",
25
+ "prepare": "tsc -p tsconfig.json --noCheck --types \"\"",
26
+ "prepublishOnly": "npm run build",
27
+ "test": "node tests/test_loader.mjs"
28
+ },
29
+ "keywords": [
30
+ "galley",
31
+ "parser",
32
+ "javascript",
33
+ "typescript",
34
+ "ffi",
35
+ "wasm"
36
+ ],
37
+ "dependencies": {
38
+ "@sanbus/galley-core": "0.0.1",
39
+ "@sanbus/galley-node": "0.0.1",
40
+ "@sanbus/galley-bun": "0.0.1",
41
+ "@sanbus/galley-deno": "0.0.1",
42
+ "@sanbus/galley-wasm": "0.0.1"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^22.0.0",
46
+ "typescript": "^7.0.2"
47
+ },
48
+ "engines": {
49
+ "node": ">=22"
50
+ }
51
+ }
package/src/index.ts ADDED
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Universal Galley JavaScript bindings — public surface.
3
+ *
4
+ * One package for Node, Bun, Deno, and browsers over `@sanbus/galley-core`,
5
+ * with native-first backend selection and WebAssembly fallback. Call
6
+ * `await init()` once, then use the synchronous `Session` API; under Node
7
+ * and Bun the backend also resolves synchronously on first use.
8
+ */
9
+
10
+ import { ensureSync } from "./loader.ts";
11
+ import { Session } from "./session.ts";
12
+
13
+ // Core surface (Session base is shadowed by the adapter subclass below).
14
+ export * from "@sanbus/galley-core";
15
+ export { Session };
16
+ export {
17
+ init,
18
+ backend,
19
+ currentBackend,
20
+ detectRuntime,
21
+ type InitOptions,
22
+ type Runtime,
23
+ type Backend,
24
+ } from "./loader.ts";
25
+ export type { UniversalSessionOptions } from "./session.ts";
26
+ export type { SessionOptions, WalkStep, Diagnostic, TreeSnapshot } from "@sanbus/galley-core";
27
+
28
+ // Module-level queries (mirror galley.h)
29
+ export function version(): string {
30
+ return ensureSync().version();
31
+ }
32
+
33
+ export function parserType(): number {
34
+ return ensureSync().parserType();
35
+ }
36
+
37
+ export function errorRecoveryMode(): number {
38
+ return ensureSync().errorRecoveryMode();
39
+ }
40
+
41
+ export function hasAst(): boolean {
42
+ return ensureSync().hasAst();
43
+ }
44
+
45
+ export function hasProcedures(): boolean {
46
+ return ensureSync().hasProcedures();
47
+ }
48
+
49
+ export function allowsNoAstTreeProcedures(): boolean {
50
+ return ensureSync().allowsNoAstTreeProcedures();
51
+ }
52
+
53
+ export function sourceRetentionEnabled(): boolean {
54
+ return ensureSync().sourceRetentionEnabled();
55
+ }
56
+
57
+ export function hasPositionTracking(): boolean {
58
+ return ensureSync().hasPositionTracking();
59
+ }
60
+
61
+ export function hasInputStreaming(): boolean {
62
+ return ensureSync().hasInputStreaming();
63
+ }
64
+
65
+ export function usesVerbatim(): boolean {
66
+ return ensureSync().usesVerbatim();
67
+ }
68
+
69
+ export function stackOverflowRecoveryAvailable(): boolean {
70
+ return ensureSync().stackOverflowRecoveryAvailable();
71
+ }
72
+
73
+ export function symbolCount(): number {
74
+ return ensureSync().symbolCount();
75
+ }
76
+
77
+ export function variableCount(): number {
78
+ return ensureSync().variableCount();
79
+ }
80
+
81
+ export function statusString(status: number): string | null {
82
+ return ensureSync().statusString(status);
83
+ }
84
+
85
+ // Preserve original Python naming aliases for docs parity
86
+ export const has_ast = hasAst;
87
+ export const has_procedures = hasProcedures;
88
+ export const has_position_tracking = hasPositionTracking;
package/src/loader.ts ADDED
@@ -0,0 +1,295 @@
1
+ /**
2
+ * Universal loader for the Galley JavaScript bindings.
3
+ *
4
+ * Binds the runtime-neutral `@sanbus/galley-core` to one of four backends —
5
+ * the Node, Bun, and Deno native adapters, or the WebAssembly adapter —
6
+ * selected per runtime with native-first ordering:
7
+ *
8
+ * - Node: koffi native → wasm → compile error.
9
+ * - Bun: `bun:ffi` native → wasm → compile error.
10
+ * - Deno: `Deno.dlopen` native → wasm → compile error.
11
+ * - Browser: wasm only.
12
+ *
13
+ * Adapters are imported dynamically by specifier string (never resolved
14
+ * statically), so bundlers only ever see the backends that are actually
15
+ * imported, and a backend missing from `node_modules` degrades to
16
+ * "unavailable" instead of failing the load. Each adapter resolves exactly
17
+ * one named artifact or throws `MissingArtifactError`; the loader catches
18
+ * exactly that class to try the next engine. A present-but-broken library
19
+ * throws anything else and fails loudly instead of silently falling back.
20
+ */
21
+
22
+ import { createRequire } from "node:module";
23
+ import type { FfiPort } from "@sanbus/galley-core";
24
+ import { MissingArtifactError } from "@sanbus/galley-core";
25
+
26
+ export type Runtime = "node" | "bun" | "deno" | "browser";
27
+ export type Backend = "native" | "wasm";
28
+ export type NativeRuntime = "node" | "bun" | "deno";
29
+
30
+ export interface InitOptions {
31
+ /** Grammar artifact path. A `.wasm` suffix forces the wasm backend. */
32
+ libraryPath?: string;
33
+ /** Explicit wasm module path (fallback source when native is missing). */
34
+ wasmPath?: string;
35
+ /** Module URL for `fetch` (browsers). */
36
+ url?: string | URL;
37
+ /** Raw module bytes (browsers, tests). */
38
+ wasmBytes?: Uint8Array;
39
+ /** Suppress the one-time WebAssembly performance notice. */
40
+ quiet?: boolean;
41
+ }
42
+
43
+ /** Detect the current JavaScript runtime. Bun and Deno are checked before
44
+ * Node: Bun emulates `process.versions.node`. */
45
+ export function detectRuntime(): Runtime {
46
+ const globals = globalThis as Record<string, unknown>;
47
+ if (typeof globals.Bun !== "undefined") return "bun";
48
+ if (typeof globals.Deno !== "undefined") return "deno";
49
+ const processValue = globals.process as { versions?: { node?: unknown } } | undefined;
50
+ if (typeof processValue !== "undefined" && typeof processValue.versions?.node === "string") {
51
+ return "node";
52
+ }
53
+ return "browser";
54
+ }
55
+
56
+ interface NativeAdapter {
57
+ getPort(explicit?: string): FfiPort;
58
+ }
59
+
60
+ interface WasmAdapter {
61
+ init(options?: { libraryPath?: string; url?: string | URL; bytes?: Uint8Array }): Promise<void>;
62
+ initSync(options?: { libraryPath?: string; bytes?: Uint8Array }): void;
63
+ getWasmPort(libraryPath?: string): FfiPort;
64
+ NeedInitError: new (...args: Array<never>) => Error;
65
+ }
66
+
67
+ const NATIVE_ADAPTERS: Record<NativeRuntime, { module: string; port: string }> = {
68
+ node: { module: "@sanbus/galley-node", port: "getNodePort" },
69
+ bun: { module: "@sanbus/galley-bun", port: "getBunPort" },
70
+ deno: { module: "@sanbus/galley-deno", port: "getDenoPort" },
71
+ };
72
+ const WASM_MODULE = "@sanbus/galley-wasm";
73
+
74
+ function isWasmPath(value: string | undefined): boolean {
75
+ return !!value && value.toLowerCase().endsWith(".wasm");
76
+ }
77
+
78
+ async function loadNativeAdapter(runtime: NativeRuntime): Promise<NativeAdapter | null> {
79
+ const { module: specifier, port } = NATIVE_ADAPTERS[runtime];
80
+ let loaded: Record<string, unknown>;
81
+ try {
82
+ // Specifier is a string on purpose: bundlers must not statically
83
+ // resolve backends that may be absent, and tsc must not require
84
+ // their type declarations to exist yet.
85
+ loaded = (await import(specifier)) as Record<string, unknown>;
86
+ } catch {
87
+ return null;
88
+ }
89
+ const getPort = loaded[port];
90
+ if (typeof getPort !== "function") return null;
91
+ return { getPort: getPort as NativeAdapter["getPort"] };
92
+ }
93
+
94
+ async function loadWasmAdapter(): Promise<WasmAdapter | null> {
95
+ try {
96
+ const loaded = (await import(WASM_MODULE)) as Record<string, unknown>;
97
+ if (
98
+ typeof loaded["init"] !== "function" ||
99
+ typeof loaded["getWasmPort"] !== "function" ||
100
+ typeof loaded["NeedInitError"] !== "function"
101
+ ) {
102
+ return null;
103
+ }
104
+ return loaded as unknown as WasmAdapter;
105
+ } catch {
106
+ return null;
107
+ }
108
+ }
109
+
110
+ /** A resolved backend: the port plus which leg of the chain served it. */
111
+ export interface ResolvedBackend {
112
+ port: FfiPort;
113
+ backend: Backend;
114
+ }
115
+
116
+ let ready: ResolvedBackend | null = null;
117
+ let warnedWasm = false;
118
+
119
+ function compileGuidance(): Error {
120
+ return new Error(
121
+ "galley: no parser artifact found (tried native library, then WebAssembly).\n" +
122
+ "Build one first: npx galley build <language-dir>\n" +
123
+ "or set GALLEY_LIBRARY_PATH to the built artifact.",
124
+ );
125
+ }
126
+
127
+ function noteWasmFallback(quiet: boolean | undefined): void {
128
+ if (quiet || warnedWasm) return;
129
+ warnedWasm = true;
130
+ console.warn(
131
+ "galley: using the WebAssembly backend (no native library found); " +
132
+ "throughput trails native codegen (roughly three quarters). " +
133
+ "Build a native library for full speed. Silence with { quiet: true }.",
134
+ );
135
+ }
136
+
137
+ /** Try the native leg. A missing artifact yields null; a present-but-broken
138
+ * library throws loudly (an ABI mismatch is a user error, not a
139
+ * fallback case). */
140
+ async function tryNative(
141
+ runtime: NativeRuntime,
142
+ libraryPath: string | undefined,
143
+ ): Promise<FfiPort | null> {
144
+ const adapter = await loadNativeAdapter(runtime);
145
+ if (!adapter) return null;
146
+ try {
147
+ return adapter.getPort(libraryPath);
148
+ } catch (error) {
149
+ if (MissingArtifactError.is(error)) return null;
150
+ throw error;
151
+ }
152
+ }
153
+
154
+ /** Try the wasm leg through the shared adapter. */
155
+ async function tryWasm(options: InitOptions): Promise<FfiPort | null> {
156
+ const wasm = await loadWasmAdapter();
157
+ if (!wasm) return null;
158
+ const explicit = options.wasmPath ?? (isWasmPath(options.libraryPath) ? options.libraryPath : undefined);
159
+ try {
160
+ await wasm.init({ libraryPath: explicit, url: options.url, bytes: options.wasmBytes });
161
+ } catch (error) {
162
+ if (error instanceof wasm.NeedInitError) throw error;
163
+ if (MissingArtifactError.is(error)) return null;
164
+ throw error;
165
+ }
166
+ return wasm.getWasmPort(explicit);
167
+ }
168
+
169
+ /**
170
+ * Resolve and initialize the backend for `options`, caching the result.
171
+ * Order: explicit `.wasm` path pins wasm; otherwise native first, then
172
+ * wasm discovery, then a compile-guidance error. Browsers skip native
173
+ * attempts (no FFI); non-Node runtimes without a prior `init()` cannot
174
+ * synchronously initialize wasm and surface the adapter's NeedInitError.
175
+ */
176
+ export async function init(options: InitOptions = {}): Promise<ResolvedBackend> {
177
+ const runtime = detectRuntime();
178
+ if (isWasmPath(options.libraryPath)) {
179
+ const wasm = await loadWasmAdapter();
180
+ if (!wasm) throw compileGuidance();
181
+ await wasm.init({ libraryPath: options.libraryPath, url: options.url, bytes: options.wasmBytes });
182
+ noteWasmFallback(options.quiet);
183
+ ready = { port: wasm.getWasmPort(options.libraryPath), backend: "wasm" };
184
+ return ready;
185
+ }
186
+ if (runtime !== "browser") {
187
+ const native = await tryNative(runtime, options.libraryPath);
188
+ if (native) {
189
+ ready = { port: native, backend: "native" };
190
+ return ready;
191
+ }
192
+ }
193
+ const wasmPort = await tryWasm(options);
194
+ if (wasmPort) {
195
+ noteWasmFallback(options.quiet);
196
+ ready = { port: wasmPort, backend: "wasm" };
197
+ return ready;
198
+ }
199
+ throw compileGuidance();
200
+ }
201
+
202
+ /** The initialized backend, or null when `init()` has not completed. */
203
+ export function currentBackend(): ResolvedBackend | null {
204
+ return ready;
205
+ }
206
+
207
+ /** Backend selected by the last `init()` (`"native"`, `"wasm"`, or null). */
208
+ export function backend(): Backend | null {
209
+ return ready?.backend ?? null;
210
+ }
211
+
212
+ /**
213
+ * Synchronous resolution for Node and Bun (dynamic `import()` is async,
214
+ * so other runtimes must `await init()` first). Used by the `Session`
215
+ * constructor and module-level queries.
216
+ */
217
+ export function ensureSync(options: InitOptions = {}): FfiPort {
218
+ if (ready && !options.libraryPath && !options.wasmPath && !options.url && !options.wasmBytes) {
219
+ return ready.port;
220
+ }
221
+ const runtime = detectRuntime();
222
+ if (runtime !== "node" && runtime !== "bun") {
223
+ throw new Error(
224
+ "galley: call await init() before using the bindings on this runtime; " +
225
+ "synchronous initialization is only available under Node and Bun.",
226
+ );
227
+ }
228
+ if (!isWasmPath(options.libraryPath)) {
229
+ const native = tryNativeSync(runtime, options.libraryPath);
230
+ if (native) {
231
+ ready = { port: native, backend: "native" };
232
+ return native;
233
+ }
234
+ }
235
+ const wasmPort = tryWasmSync(options);
236
+ if (wasmPort) {
237
+ noteWasmFallback(options.quiet);
238
+ ready = { port: wasmPort, backend: "wasm" };
239
+ return wasmPort;
240
+ }
241
+ throw compileGuidance();
242
+ }
243
+
244
+ function requireAdapterModule(specifier: string): Record<string, unknown> | null {
245
+ try {
246
+ // Synchronous require: this branch runs under Node and Bun only.
247
+ // Falls back to null when the package is absent.
248
+ const require = createRequire(import.meta.url);
249
+ return require(specifier) as Record<string, unknown>;
250
+ } catch {
251
+ return null;
252
+ }
253
+ }
254
+
255
+ /** Synchronous native attempt (Node/Bun only). */
256
+ function tryNativeSync(runtime: NativeRuntime, libraryPath: string | undefined): FfiPort | null {
257
+ const { module: specifier, port } = NATIVE_ADAPTERS[runtime];
258
+ const loaded = requireAdapterModule(specifier);
259
+ if (!loaded) return null;
260
+ const getPort = loaded[port];
261
+ if (typeof getPort !== "function") return null;
262
+ try {
263
+ return (getPort as NativeAdapter["getPort"])(libraryPath);
264
+ } catch (error) {
265
+ if (MissingArtifactError.is(error)) return null;
266
+ throw error;
267
+ }
268
+ }
269
+
270
+ /** Synchronous wasm attempt through the shared adapter (Node/Bun only:
271
+ * the wasm adapter reads files and instantiates synchronously there). */
272
+ function tryWasmSync(options: InitOptions): FfiPort | null {
273
+ const loaded = requireAdapterModule(WASM_MODULE);
274
+ if (!loaded || typeof loaded["initSync"] !== "function") return null;
275
+ const wasm = loaded as unknown as WasmAdapter & {
276
+ initSync(options?: { libraryPath?: string; bytes?: Uint8Array }): void;
277
+ };
278
+ const explicit = options.wasmPath ?? (isWasmPath(options.libraryPath) ? options.libraryPath : undefined);
279
+ if (options.url !== undefined && options.wasmBytes === undefined) return null;
280
+ try {
281
+ wasm.initSync({ libraryPath: explicit, bytes: options.wasmBytes });
282
+ } catch (error) {
283
+ if (MissingArtifactError.is(error)) return null;
284
+ throw error;
285
+ }
286
+ const getWasmPort = loaded["getWasmPort"];
287
+ if (typeof getWasmPort !== "function") return null;
288
+ return (getWasmPort as WasmAdapter["getWasmPort"])(explicit);
289
+ }
290
+
291
+ /** Test-only: clear cached resolution and the fallback notice. */
292
+ export function __resetLoader(): void {
293
+ ready = null;
294
+ warnedWasm = false;
295
+ }
package/src/session.ts ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Universal `Session`: the core session bound to the resolved backend.
3
+ *
4
+ * Under Node and Bun the backend resolves synchronously on first use;
5
+ * elsewhere `await init()` must complete first.
6
+ */
7
+
8
+ import { Session as CoreSession } from "@sanbus/galley-core";
9
+ import type { SessionOptions } from "@sanbus/galley-core";
10
+ import { ensureSync, type InitOptions } from "./loader.ts";
11
+
12
+ export type UniversalSessionOptions = SessionOptions & InitOptions;
13
+ export type { SessionOptions };
14
+
15
+ export class Session extends CoreSession {
16
+ constructor(options: UniversalSessionOptions = {}) {
17
+ super(ensureSync(options), options);
18
+ }
19
+ }