@fossil-lang/wasm 0.3.0-alpha.1 → 0.3.0-alpha.10

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 CHANGED
@@ -2,51 +2,115 @@
2
2
 
3
3
  JS/TS wrapper around the `fossil-wasm` Rust crate's wasm-bindgen artefacts. Provides:
4
4
 
5
- - `initFossilWasm({ wasmUrl })` — consumer-controlled `.wasm` URL loader (memoised).
6
- - `tokenize(text)` — calls the Rust lexer, returns `TokenRow[]` (ADR-0030).
7
- - `semanticLegend()` — returns the LSP semantic-tokens legend (Phase 6 plan 06-07).
8
- - `FossilPlayground` — the Workspace API class (ADR-0024) for LSP + compile.
5
+ - `openProgram(uri, { host, text })` — one program open for an editor, and the
6
+ call a host with an editor makes. See below.
7
+ - `initFossilWasm()` — boots the module (memoised). Its `.wasm` ships in this
8
+ package and the host's bundler emits it as an asset; the host copies nothing.
9
+ - `tokenize(text)` — calls the Rust lexer, returns `TokenRow[]`. The Rust lexer
10
+ is the only lexer: no host reimplements one and drifts from the grammar.
11
+ - `semanticLegend()` — returns the LSP semantic-tokens legend.
12
+ - `FossilPlayground` — the Workspace API class for LSP + compile. `fossil-lsp`
13
+ itself is native-only (stdio over crossbeam), so the browser gets this
14
+ equivalent dispatch surface over the same `fossil-ide` functions.
15
+
16
+ ## One program in one editor: `openProgram`
9
17
 
10
- ## Why explicit `init({ wasmUrl })` and not auto-load?
18
+ ```typescript
19
+ import { openProgram } from '@fossil-lang/wasm';
20
+ import { fossil } from '@fossil-lang/codemirror-fossil';
21
+
22
+ const program = await openProgram('job.fossil', { host, text }); // host: SourceHost
23
+ const extensions = fossil({ ...program, onNavigate });
24
+
25
+ program.registerDescriptor(descriptor); // a host-introspected source, before a check
26
+ const sources = await program.sources(text); // what introspection DESCRIBEs
27
+ ```
11
28
 
12
- We use `wasm-bindgen --target web` (NOT `--target bundler`). This means consumers
13
- control the `.wasm` URL resolution — works in Vite, Next.js, Webpack, Rspack, or
14
- plain `new URL(...)` in a Web Worker context. See `08-RESEARCH.md` Pitfall 1
15
- (this monorepo's phase-8 research) for why `--target bundler` was rejected.
29
+ It boots the module, opens `uri` in its own workspace, and reads the documents
30
+ the text names through `host`. The answer carries `fossil()`'s option names —
31
+ `uri`, `tokenize`, `tokenKinds`, `check`, `hover`, `complete`, `definition` —
32
+ and every one that answers about the program takes the text and pushes it
33
+ first, comparing against what it last pushed so the common case costs a string
34
+ comparison. `check` and `sources` also run `resolveDocuments`, which reads
35
+ nothing when nothing is missing. That is the whole protocol an editor host used
36
+ to write by hand; `workspace` is the `FossilPlayground` underneath, for a
37
+ question this surface does not ask, and `close()` frees it.
16
38
 
17
- ## Consumer patterns
39
+ ## Documents and sources: fossil resolves, the host reads
18
40
 
19
- ### Vite host
41
+ A program names shape documents (`io.shex("@vocab/person.shex")`) and data
42
+ sources (`io.csv("@lake/users.csv")`). The checker reads no file and no
43
+ network: it reports what it is missing, and the host hands back text through a
44
+ `SourceHost` (`@fossil-lang/types`) — the connection map, and `sign(locators)`.
20
45
 
21
46
  ```typescript
22
- import { initFossilWasm, tokenize } from '@fossil-lang/wasm';
23
- import wasmUrl from '@fossil-lang/wasm/pkg/fossil_wasm_bg.wasm?url';
47
+ import { resolveDocuments } from '@fossil-lang/types';
24
48
 
25
- await initFossilWasm({ wasmUrl });
26
- const tokens = tokenize('prefix ex: <https://example.org/>');
49
+ const pg = new FossilPlayground();
50
+ const h = pg.openFile('prog.fossil', text); // edited buffers only
51
+ const { unread } = await resolveDocuments(pg.workspace(h), host); // sets the connections too
52
+ const rows = pg.check();
27
53
  ```
28
54
 
29
- ### Next.js host (in a Client Component)
55
+ - `missingDocuments(h)` — `{ key, locator }` rows. The key is what the program
56
+ wrote, so repointing a connection invalidates nothing; the locator is that
57
+ key through the map, and it is what `sign` receives.
58
+ - `registerDocument(key, text)` — what `resolveDocuments` calls for each
59
+ fetched document, until nothing new is missing (a document can name another).
60
+ It is the one loop; a host does not write a second.
61
+ - `openFile` is for buffers the user edits. An open `.shex` is the document
62
+ every program naming it reads; opening a program registers nothing it names.
63
+ - `setConnections(map)` — name → base; re-checks nothing. `resolveDocuments`
64
+ calls it with `host.connections()`, so a host never does.
65
+ - `sources(h)` — the `ProgramSource[]` the program reads, through the map the
66
+ last `resolveDocuments` set: binding, key,
67
+ locator, catalogue row, reader option. Introspection DESCRIBEs these and
68
+ registers each descriptor under `key` with `registerInferredDescriptor`.
69
+
70
+ ## Loading: the `.wasm` is an asset of this package
30
71
 
31
72
  ```typescript
32
- 'use client';
33
73
  import { initFossilWasm, tokenize } from '@fossil-lang/wasm';
34
74
 
35
- // Put fossil_wasm_bg.wasm in public/wasm/ at build time (next.config.mjs copies it)
36
- useEffect(() => {
37
- void initFossilWasm({ wasmUrl: '/wasm/fossil_wasm_bg.wasm' });
38
- }, []);
75
+ await initFossilWasm();
76
+ const tokens = tokenize('User := io.csv("data/people.csv")');
39
77
  ```
40
78
 
41
- ### Web Worker
79
+ That is the whole host flow, in Vite, Next.js (webpack or Turbopack), a Web
80
+ Worker or any bundler that understands `new URL('…', import.meta.url)`. The glue
81
+ (`wasm-bindgen --target web`) locates `fossil_wasm_bg.wasm` with exactly that
82
+ expression, the bundler copies the file into its output under a hashed name and
83
+ rewrites the URL, and the browser fetches it from there. No copy script, no
84
+ `public/` directory, no URL to keep in sync with a version.
85
+
86
+ **Vite dev server, package installed from npm:** Vite's dependency optimizer
87
+ pre-bundles the package into `node_modules/.vite/deps/` without its `.wasm`, and
88
+ the URL then answers with `index.html`. Keep the three packages out of it —
89
+ `vite build` needs nothing:
90
+
91
+ ```js
92
+ // vite.config.js
93
+ export default { optimizeDeps: { exclude: ['@fossil-lang/wasm', '@fossil-lang/executor', '@fossil-lang/corpus'] } };
94
+ ```
95
+
96
+ A workspace-linked package is never pre-bundled, which is why the playground needs
97
+ no such line. Next.js needs none in either `next dev` or `next build`, webpack or
98
+ Turbopack.
99
+
100
+ `--target bundler` was rejected because it emits `import … from '*.wasm'` (the
101
+ ESM-integration proposal), which each bundler gates behind its own experimental
102
+ flag. `new URL(…, import.meta.url)` is the pattern they all support by default.
103
+
104
+ **Without a bundler**, pass the module yourself — `initFossilWasm(wasm)` takes the
105
+ glue's `InitInput` (`BufferSource`, `Response`, `URL`, `WebAssembly.Module`). Node
106
+ is the case: its `fetch` rejects `file://`, so hand it the bytes:
42
107
 
43
108
  ```typescript
44
- // inside a Worker module:
45
- import { initFossilWasm, FossilPlayground } from '@fossil-lang/wasm';
46
- import wasmUrl from '@fossil-lang/wasm/pkg/fossil_wasm_bg.wasm?url';
109
+ import { readFile } from 'node:fs/promises';
110
+ import { createRequire } from 'node:module';
47
111
 
48
- await initFossilWasm({ wasmUrl });
49
- const pg = new FossilPlayground();
112
+ const path = createRequire(import.meta.url).resolve('@fossil-lang/wasm/pkg/fossil_wasm_bg.wasm');
113
+ await initFossilWasm(await readFile(path));
50
114
  ```
51
115
 
52
116
  ## Build
@@ -63,8 +127,6 @@ Optionally `wasm-opt` (binaryen) for size reduction.
63
127
 
64
128
  ## Source of truth
65
129
 
66
- - Rust crate: `crates/fossil-wasm/`
67
- - tokenize ADR: `decisions/0030-wasm-exported-tokenizer.md`
68
- - Workspace API ADR: `decisions/0024-fossil-wasm-workspace-api.md`
69
- - Distribution pattern: `decisions/0028-playground-as-react-library.md`
70
- (this package is one of the six in the `@fossil-lang/*` family)
130
+ - Rust crate: `crates/fossil-wasm/` — everything here is a wrapper over its
131
+ wasm-bindgen exports, and nothing in this package reimplements it.
132
+ - The grammar the tokenizer follows: `grammar.bnf` at the repo root.
@@ -0,0 +1,223 @@
1
+ /**
2
+ * The wasm-bindgen-backed runtime surface (lexer, LSP worker, Workspace API) of
3
+ * @fossil-lang/wasm.
4
+ *
5
+ * Kept in its OWN module (NOT the package entry) so the wasm-bindgen glue
6
+ * (`../pkg/fossil_wasm.js`) is imported only from leaf modules — `load.ts` (for
7
+ * `init`) and here. Under a bundler with `"sideEffects": false`, importing the
8
+ * stateful glue from the entry chunk can duplicate it: `init()` then populates
9
+ * the `wasm` binding in one instance while these functions read `undefined` from
10
+ * another (→ `Cannot read properties of undefined (reading '__wbindgen_malloc…')`,
11
+ * seen when the codemirror tokenizer calls `tokenize` on the main thread).
12
+ * Mirrors `@fossil-lang/corpus`'s `client.ts` split, the known-good shape.
13
+ */
14
+ import { FileHandle as RawFileHandle } from '../pkg/fossil_wasm.js';
15
+ import type { DocumentWorkspace, MissingDocument, ProgramSource, SemanticTokensLegend, TokenKindLegend, TokenRow } from '@fossil-lang/types';
16
+ import type { CheckRow, CompletionRow, DefinitionRow, HoverRow, InferredDescriptorJson, SourceRefInfo, ProviderInfo } from './index.js';
17
+ /**
18
+ * Install the LSP-over-postMessage dispatcher on the current Worker scope.
19
+ *
20
+ * `fossil-wasm` IS the LSP server-side in the browser: `fossil-lsp` is
21
+ * native-only (it speaks stdio over crossbeam and does not compile to
22
+ * `wasm32`), so the Worker gets an equivalent 16-route dispatch loop over the
23
+ * same `fossil-ide` free functions. The Rust function (re-exported from
24
+ * `crates/fossil-wasm/src/lsp_worker.rs`) installs `self.onmessage` on the
25
+ * Worker scope and owns LSP JSON-RPC dispatch from that point forward.
26
+ *
27
+ * MUST be called inside a Web Worker scope, AFTER {@link initFossilWasm} has
28
+ * resolved. Calling it on the main thread is a no-op (the dispatcher needs
29
+ * `DedicatedWorkerGlobalScope.onmessage`).
30
+ */
31
+ export declare function start_lsp_worker(): void;
32
+ /**
33
+ * Opaque file-handle returned by {@link FossilPlayground.openFile}. Pass it
34
+ * back into the matching `updateFile` / `closeFile` / `diagnosticsFor`
35
+ * calls. JS code cannot construct one directly (the wasm-bindgen class has a
36
+ * private constructor) — handles are minted only by `openFile` on the Rust
37
+ * side, where they index a `HashMap<FileHandle, SourceFile>` keyed by a `u32`.
38
+ */
39
+ export type FileHandle = RawFileHandle;
40
+ /**
41
+ * Tokenize a Fossil source string. Returns the byte-range tokens from the
42
+ * canonical Rust lexer (`fossil_syntax::lexer::raw_lex`) — the single grammar
43
+ * source of truth, so no editor ever reimplements the lexer in TS and drifts.
44
+ *
45
+ * MUST be called after {@link initFossilWasm} has resolved; otherwise the
46
+ * underlying wasm-bindgen function throws (the wasm module is not yet
47
+ * instantiated).
48
+ */
49
+ export declare function tokenize(text: string): TokenRow[];
50
+ /**
51
+ * The legend for {@link TokenRow.kind}: every lexer variant NAME, indexed by the
52
+ * discriminant a row carries. `tokenKinds()[row.kind]` is `"Comment"`,
53
+ * `"KwFrom"`, `"String"`, …
54
+ *
55
+ * **This is the contract, and the numbers are not.** `kind` is a variant
56
+ * discriminant of `fossil_syntax::lexer::Token`, so any reorder of that enum
57
+ * remaps every value with nothing going red. The predecessor of this package
58
+ * hard-coded the table (`enum FossilKind { Whitespace = 0, … }`) under a comment
59
+ * saying it had to be updated in lockstep; it was wrong in nine places by the
60
+ * time it was deleted. Keying on the name is what makes a reorder a non-event.
61
+ *
62
+ * An index past the end of the legend is a variant appended by a compiler newer
63
+ * than this host: `undefined`, and a host styles it as plain text.
64
+ *
65
+ * MUST be called after {@link initFossilWasm} has resolved.
66
+ */
67
+ export declare function tokenKinds(): TokenKindLegend;
68
+ /**
69
+ * Returns the LSP `SemanticTokensLegend` (token types + modifiers list) the
70
+ * editor uses to map LSP `semanticTokens/full` response indices to highlight
71
+ * categories. Re-export of `fossil_ide::semantic_legend` over the wasm-bindgen
72
+ * boundary — NO duplicate legend definition lives here.
73
+ *
74
+ * MUST be called after {@link initFossilWasm} has resolved.
75
+ */
76
+ export declare function semanticLegend(): SemanticTokensLegend;
77
+ /**
78
+ * Parse a Fossil program and return its external references — the typed lineage
79
+ * (every data URI + `schema =` argument, each tagged with its `@conn` alias and
80
+ * role). keasy's client-compute job runner reads this to derive a job's
81
+ * connections WITHOUT subprocessing `fossil` / a server round-trip. Identical
82
+ * shape to the native `fossil refs` (the SAME `fossil_lineage::SourceRefInfo`
83
+ * struct), so the browser and the CLI never diverge.
84
+ *
85
+ * MUST be called after {@link initFossilWasm} has resolved.
86
+ */
87
+ export declare function refs(program: string): SourceRefInfo[];
88
+ /**
89
+ * List the data-source providers fossil supports (`io.csv`, `io.rdf`, …) — the
90
+ * provider name, the extensions it reads, and how it can be used. Identical
91
+ * shape to the native `fossil providers`.
92
+ *
93
+ * MUST be called after {@link initFossilWasm} has resolved.
94
+ */
95
+ export declare function providers(): ProviderInfo[];
96
+ /**
97
+ * Workspace API class. Thin TS wrapper around the wasm-bindgen
98
+ * `FossilPlayground` that exposes camelCase method names for JS idiom + better
99
+ * TS inference (the raw bindings use snake_case from the Rust impl block).
100
+ *
101
+ * One instance per browser tab / Node process — the instance owns the Salsa
102
+ * store + `Arc<OutputDescriptorKind>` for the schema slot.
103
+ *
104
+ * NOTE: {@link FileHandle} is opaque — JS cannot construct one. Callers receive
105
+ * a handle from `openFile` and pass it back to subsequent operations.
106
+ */
107
+ export declare class FossilPlayground {
108
+ private _inner;
109
+ constructor();
110
+ /**
111
+ * Free the underlying WASM-side Salsa store. Call when the playground is no
112
+ * longer needed (e.g. component unmount).
113
+ */
114
+ free(): void;
115
+ /**
116
+ * Open a file in the workspace. Returns the {@link FileHandle} subsequent
117
+ * `updateFile` / `closeFile` / `diagnosticsFor` calls key on.
118
+ */
119
+ openFile(path: string, contents: string): FileHandle;
120
+ /**
121
+ * Apply an edit to an open file. Mutates the SAME `SourceFile` via the Salsa
122
+ * `Setter` (`set_text`) — bumps the revision for incremental
123
+ * invalidation rather than a full recompute, and that revision bump is also
124
+ * what cancels any analysis still running on an older snapshot.
125
+ */
126
+ updateFile(handle: FileHandle, contents: string): void;
127
+ /**
128
+ * Close a file in the workspace. Strict: closing an unknown / already-closed
129
+ * handle throws so JS-side bugs surface loudly.
130
+ */
131
+ closeFile(handle: FileHandle): void;
132
+ /**
133
+ * The connection map `@name/…` expands against — what
134
+ * `SourceHost.connections()` answers. It reaches locators only, so setting it
135
+ * re-checks nothing.
136
+ */
137
+ setConnections(connections: Record<string, string>): void;
138
+ /**
139
+ * The documents the file at `handle` names and nothing has registered, each
140
+ * with the key to register it under and the locator to read it from.
141
+ */
142
+ missingDocuments(handle: FileHandle): MissingDocument[];
143
+ /** Register a fetched document's text under the key {@link missingDocuments} reported. */
144
+ registerDocument(key: string, text: string): void;
145
+ /**
146
+ * The data sources the file at `handle` reads, as fossil resolved them: the
147
+ * binding, the URI as written, its locator through the connection map, the
148
+ * catalogue row and the reader option.
149
+ */
150
+ sources(handle: FileHandle): ProgramSource[];
151
+ /**
152
+ * The file at `handle` as a {@link DocumentWorkspace}, for
153
+ * `resolveDocuments(playground.workspace(handle), host)`.
154
+ */
155
+ workspace(handle: FileHandle): DocumentWorkspace;
156
+ /**
157
+ * Workspace-wide diagnostic drain. Runs `parse → def_map → typecheck_mapping`
158
+ * across every open file and returns a flat array of {@link CheckRow}.
159
+ */
160
+ check(): CheckRow[];
161
+ /**
162
+ * Per-file diagnostic drain — the accessor the LSP Worker uses for its
163
+ * per-file `publishDiagnostics` notifications.
164
+ */
165
+ diagnosticsFor(handle: FileHandle): CheckRow[];
166
+ /**
167
+ * What is under the cursor — `{ markdown, range }`, or `null` when nothing
168
+ * there has a type.
169
+ *
170
+ * `line` / `character` are LSP: zero-based, `character` in UTF-16 code units.
171
+ * A CodeMirror or Monaco host already counts in those units, so a document
172
+ * offset converts with `doc.lineAt(pos)` and no byte arithmetic — unlike
173
+ * {@link tokenize}, whose offsets ARE bytes.
174
+ *
175
+ * ## Push the buffer before you ask
176
+ *
177
+ * This reads the text of the last {@link updateFile}. Hover fires on
178
+ * mouse-move and the checker is debounced, so a hover mid-debounce answers
179
+ * about text one keystroke old and its range lands one keystroke wrong. The
180
+ * three read-only methods take a SHARED borrow on the Rust side and cannot
181
+ * poison the workspace the way a re-entered `updateFile` once could — see the
182
+ * `ide` module in `crates/fossil-wasm` — but staleness is not a borrow
183
+ * problem and nothing here can fix it for you.
184
+ */
185
+ hover(handle: FileHandle, line: number, character: number): HoverRow | null;
186
+ /**
187
+ * The completion candidates at a position, already narrowed by the receiver:
188
+ * `str.` offers string members and no reader, a property-key position offers
189
+ * the target shape's predicates and no catalogue row at all.
190
+ *
191
+ * `kind` is the LSP `CompletionItemKind` **by name** (`"function"`,
192
+ * `"field"`) rather than by number. The numbers never cross this boundary —
193
+ * `packages/codemirror-fossil`'s deleted predecessor is what happens when
194
+ * they do.
195
+ *
196
+ * The same staleness note as {@link hover} applies, and harder: completion
197
+ * fires on nearly every keystroke.
198
+ */
199
+ completions(handle: FileHandle, line: number, character: number): CompletionRow[];
200
+ /**
201
+ * Where the name under the cursor is defined. Empty when nothing there has a
202
+ * definition.
203
+ *
204
+ * `uri` is the key the buffer was opened under, verbatim — and two of the
205
+ * four positions this recognises resolve into the **shape document**, so a
206
+ * host with a single editor pane has to read `uri` before it moves a cursor.
207
+ */
208
+ gotoDefinition(handle: FileHandle, line: number, character: number): DefinitionRow[];
209
+ /**
210
+ * Register an {@link InferredDescriptorJson} under the source URI the program
211
+ * wrote, BEFORE invoking {@link check}. The Rust compiler reads from this
212
+ * during forward type propagation.
213
+ *
214
+ * The compiler never introspects a source itself: it performs no network or
215
+ * file IO — that would break the WASM gate and Salsa's determinism alike —
216
+ * so a host that wants column types must run the `DESCRIBE` and push the
217
+ * result in here.
218
+ *
219
+ * @throws Error if the descriptor JSON fails to deserialise on the Rust side.
220
+ */
221
+ registerInferredDescriptor(descriptor: InferredDescriptorJson): void;
222
+ }
223
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAEL,UAAU,IAAI,aAAa,EAO5B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EACV,iBAAiB,EACjB,eAAe,EACf,aAAa,EACb,oBAAoB,EACpB,eAAe,EACf,QAAQ,EACT,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EACV,QAAQ,EACR,aAAa,EACb,aAAa,EACb,QAAQ,EACR,sBAAsB,EACtB,aAAa,EACb,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC;AAED;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG,aAAa,CAAC;AAEvC;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,EAAE,CAMjD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,UAAU,IAAI,eAAe,CAE5C;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,IAAI,oBAAoB,CAErD;AAED;;;;;;;;;GASG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,EAAE,CAErD;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,IAAI,YAAY,EAAE,CAE1C;AAED;;;;;;;;;;GAUG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAsB;;IAMpC;;;OAGG;IACH,IAAI,IAAI,IAAI;IAIZ;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU;IAIpD;;;;;OAKG;IACH,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAItD;;;OAGG;IACH,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAInC;;;;OAIG;IACH,cAAc,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI;IAIzD;;;OAGG;IACH,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,eAAe,EAAE;IAIvD,0FAA0F;IAC1F,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAIjD;;;;OAIG;IACH,OAAO,CAAC,MAAM,EAAE,UAAU,GAAG,aAAa,EAAE;IAI5C;;;OAGG;IACH,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,iBAAiB;IAQhD;;;OAGG;IACH,KAAK,IAAI,QAAQ,EAAE;IAInB;;;OAGG;IACH,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,QAAQ,EAAE;IAI9C;;;;;;;;;;;;;;;;;;OAkBG;IACH,KAAK,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI;IAM3E;;;;;;;;;;;;OAYG;IACH,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,aAAa,EAAE;IAIjF;;;;;;;OAOG;IACH,cAAc,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,aAAa,EAAE;IAIpF;;;;;;;;;;;OAWG;IACH,0BAA0B,CAAC,UAAU,EAAE,sBAAsB,GAAG,IAAI;CAKrE"}
package/dist/client.js ADDED
@@ -0,0 +1,269 @@
1
+ /**
2
+ * The wasm-bindgen-backed runtime surface (lexer, LSP worker, Workspace API) of
3
+ * @fossil-lang/wasm.
4
+ *
5
+ * Kept in its OWN module (NOT the package entry) so the wasm-bindgen glue
6
+ * (`../pkg/fossil_wasm.js`) is imported only from leaf modules — `load.ts` (for
7
+ * `init`) and here. Under a bundler with `"sideEffects": false`, importing the
8
+ * stateful glue from the entry chunk can duplicate it: `init()` then populates
9
+ * the `wasm` binding in one instance while these functions read `undefined` from
10
+ * another (→ `Cannot read properties of undefined (reading '__wbindgen_malloc…')`,
11
+ * seen when the codemirror tokenizer calls `tokenize` on the main thread).
12
+ * Mirrors `@fossil-lang/corpus`'s `client.ts` split, the known-good shape.
13
+ */
14
+ import { FossilPlayground as RawFossilPlayground, FileHandle as RawFileHandle, tokenize as rawTokenize, semantic_legend as rawSemanticLegend, tokenKinds as rawTokenKinds, start_lsp_worker as rawStartLspWorker, refs as rawRefs, providers as rawProviders, } from '../pkg/fossil_wasm.js';
15
+ /**
16
+ * Install the LSP-over-postMessage dispatcher on the current Worker scope.
17
+ *
18
+ * `fossil-wasm` IS the LSP server-side in the browser: `fossil-lsp` is
19
+ * native-only (it speaks stdio over crossbeam and does not compile to
20
+ * `wasm32`), so the Worker gets an equivalent 16-route dispatch loop over the
21
+ * same `fossil-ide` free functions. The Rust function (re-exported from
22
+ * `crates/fossil-wasm/src/lsp_worker.rs`) installs `self.onmessage` on the
23
+ * Worker scope and owns LSP JSON-RPC dispatch from that point forward.
24
+ *
25
+ * MUST be called inside a Web Worker scope, AFTER {@link initFossilWasm} has
26
+ * resolved. Calling it on the main thread is a no-op (the dispatcher needs
27
+ * `DedicatedWorkerGlobalScope.onmessage`).
28
+ */
29
+ export function start_lsp_worker() {
30
+ rawStartLspWorker();
31
+ }
32
+ /**
33
+ * Tokenize a Fossil source string. Returns the byte-range tokens from the
34
+ * canonical Rust lexer (`fossil_syntax::lexer::raw_lex`) — the single grammar
35
+ * source of truth, so no editor ever reimplements the lexer in TS and drifts.
36
+ *
37
+ * MUST be called after {@link initFossilWasm} has resolved; otherwise the
38
+ * underlying wasm-bindgen function throws (the wasm module is not yet
39
+ * instantiated).
40
+ */
41
+ export function tokenize(text) {
42
+ // The wasm-bindgen wrapper returns a `JsValue` typed as `any`; the Rust side
43
+ // (crates/fossil-wasm/src/tokenize.rs) serializes `Vec<TokenRow>` via
44
+ // `serde_wasm_bindgen::to_value`, so the shape matches `{ kind, start, end }`
45
+ // exactly. Cast is safe because the Rust ↔ JS contract is enforced upstream.
46
+ return rawTokenize(text);
47
+ }
48
+ /**
49
+ * The legend for {@link TokenRow.kind}: every lexer variant NAME, indexed by the
50
+ * discriminant a row carries. `tokenKinds()[row.kind]` is `"Comment"`,
51
+ * `"KwFrom"`, `"String"`, …
52
+ *
53
+ * **This is the contract, and the numbers are not.** `kind` is a variant
54
+ * discriminant of `fossil_syntax::lexer::Token`, so any reorder of that enum
55
+ * remaps every value with nothing going red. The predecessor of this package
56
+ * hard-coded the table (`enum FossilKind { Whitespace = 0, … }`) under a comment
57
+ * saying it had to be updated in lockstep; it was wrong in nine places by the
58
+ * time it was deleted. Keying on the name is what makes a reorder a non-event.
59
+ *
60
+ * An index past the end of the legend is a variant appended by a compiler newer
61
+ * than this host: `undefined`, and a host styles it as plain text.
62
+ *
63
+ * MUST be called after {@link initFossilWasm} has resolved.
64
+ */
65
+ export function tokenKinds() {
66
+ return rawTokenKinds();
67
+ }
68
+ /**
69
+ * Returns the LSP `SemanticTokensLegend` (token types + modifiers list) the
70
+ * editor uses to map LSP `semanticTokens/full` response indices to highlight
71
+ * categories. Re-export of `fossil_ide::semantic_legend` over the wasm-bindgen
72
+ * boundary — NO duplicate legend definition lives here.
73
+ *
74
+ * MUST be called after {@link initFossilWasm} has resolved.
75
+ */
76
+ export function semanticLegend() {
77
+ return rawSemanticLegend();
78
+ }
79
+ /**
80
+ * Parse a Fossil program and return its external references — the typed lineage
81
+ * (every data URI + `schema =` argument, each tagged with its `@conn` alias and
82
+ * role). keasy's client-compute job runner reads this to derive a job's
83
+ * connections WITHOUT subprocessing `fossil` / a server round-trip. Identical
84
+ * shape to the native `fossil refs` (the SAME `fossil_lineage::SourceRefInfo`
85
+ * struct), so the browser and the CLI never diverge.
86
+ *
87
+ * MUST be called after {@link initFossilWasm} has resolved.
88
+ */
89
+ export function refs(program) {
90
+ return rawRefs(program);
91
+ }
92
+ /**
93
+ * List the data-source providers fossil supports (`io.csv`, `io.rdf`, …) — the
94
+ * provider name, the extensions it reads, and how it can be used. Identical
95
+ * shape to the native `fossil providers`.
96
+ *
97
+ * MUST be called after {@link initFossilWasm} has resolved.
98
+ */
99
+ export function providers() {
100
+ return rawProviders();
101
+ }
102
+ /**
103
+ * Workspace API class. Thin TS wrapper around the wasm-bindgen
104
+ * `FossilPlayground` that exposes camelCase method names for JS idiom + better
105
+ * TS inference (the raw bindings use snake_case from the Rust impl block).
106
+ *
107
+ * One instance per browser tab / Node process — the instance owns the Salsa
108
+ * store + `Arc<OutputDescriptorKind>` for the schema slot.
109
+ *
110
+ * NOTE: {@link FileHandle} is opaque — JS cannot construct one. Callers receive
111
+ * a handle from `openFile` and pass it back to subsequent operations.
112
+ */
113
+ export class FossilPlayground {
114
+ _inner;
115
+ constructor() {
116
+ this._inner = new RawFossilPlayground();
117
+ }
118
+ /**
119
+ * Free the underlying WASM-side Salsa store. Call when the playground is no
120
+ * longer needed (e.g. component unmount).
121
+ */
122
+ free() {
123
+ this._inner.free();
124
+ }
125
+ /**
126
+ * Open a file in the workspace. Returns the {@link FileHandle} subsequent
127
+ * `updateFile` / `closeFile` / `diagnosticsFor` calls key on.
128
+ */
129
+ openFile(path, contents) {
130
+ return this._inner.open_file(path, contents);
131
+ }
132
+ /**
133
+ * Apply an edit to an open file. Mutates the SAME `SourceFile` via the Salsa
134
+ * `Setter` (`set_text`) — bumps the revision for incremental
135
+ * invalidation rather than a full recompute, and that revision bump is also
136
+ * what cancels any analysis still running on an older snapshot.
137
+ */
138
+ updateFile(handle, contents) {
139
+ this._inner.update_file(handle, contents);
140
+ }
141
+ /**
142
+ * Close a file in the workspace. Strict: closing an unknown / already-closed
143
+ * handle throws so JS-side bugs surface loudly.
144
+ */
145
+ closeFile(handle) {
146
+ this._inner.close_file(handle);
147
+ }
148
+ /**
149
+ * The connection map `@name/…` expands against — what
150
+ * `SourceHost.connections()` answers. It reaches locators only, so setting it
151
+ * re-checks nothing.
152
+ */
153
+ setConnections(connections) {
154
+ this._inner.setConnections(connections);
155
+ }
156
+ /**
157
+ * The documents the file at `handle` names and nothing has registered, each
158
+ * with the key to register it under and the locator to read it from.
159
+ */
160
+ missingDocuments(handle) {
161
+ return this._inner.missingDocuments(handle);
162
+ }
163
+ /** Register a fetched document's text under the key {@link missingDocuments} reported. */
164
+ registerDocument(key, text) {
165
+ this._inner.registerDocument(key, text);
166
+ }
167
+ /**
168
+ * The data sources the file at `handle` reads, as fossil resolved them: the
169
+ * binding, the URI as written, its locator through the connection map, the
170
+ * catalogue row and the reader option.
171
+ */
172
+ sources(handle) {
173
+ return this._inner.sources(handle);
174
+ }
175
+ /**
176
+ * The file at `handle` as a {@link DocumentWorkspace}, for
177
+ * `resolveDocuments(playground.workspace(handle), host)`.
178
+ */
179
+ workspace(handle) {
180
+ return {
181
+ setConnections: (connections) => this.setConnections(connections),
182
+ missingDocuments: () => this.missingDocuments(handle),
183
+ registerDocument: (key, text) => this.registerDocument(key, text),
184
+ };
185
+ }
186
+ /**
187
+ * Workspace-wide diagnostic drain. Runs `parse → def_map → typecheck_mapping`
188
+ * across every open file and returns a flat array of {@link CheckRow}.
189
+ */
190
+ check() {
191
+ return this._inner.check();
192
+ }
193
+ /**
194
+ * Per-file diagnostic drain — the accessor the LSP Worker uses for its
195
+ * per-file `publishDiagnostics` notifications.
196
+ */
197
+ diagnosticsFor(handle) {
198
+ return this._inner.diagnostics_for(handle);
199
+ }
200
+ /**
201
+ * What is under the cursor — `{ markdown, range }`, or `null` when nothing
202
+ * there has a type.
203
+ *
204
+ * `line` / `character` are LSP: zero-based, `character` in UTF-16 code units.
205
+ * A CodeMirror or Monaco host already counts in those units, so a document
206
+ * offset converts with `doc.lineAt(pos)` and no byte arithmetic — unlike
207
+ * {@link tokenize}, whose offsets ARE bytes.
208
+ *
209
+ * ## Push the buffer before you ask
210
+ *
211
+ * This reads the text of the last {@link updateFile}. Hover fires on
212
+ * mouse-move and the checker is debounced, so a hover mid-debounce answers
213
+ * about text one keystroke old and its range lands one keystroke wrong. The
214
+ * three read-only methods take a SHARED borrow on the Rust side and cannot
215
+ * poison the workspace the way a re-entered `updateFile` once could — see the
216
+ * `ide` module in `crates/fossil-wasm` — but staleness is not a borrow
217
+ * problem and nothing here can fix it for you.
218
+ */
219
+ hover(handle, line, character) {
220
+ // `serde_wasm_bindgen` writes `None` as `undefined`; a host reading this
221
+ // should have one falsy answer to check, not two.
222
+ return this._inner.hover(handle, line, character) ?? null;
223
+ }
224
+ /**
225
+ * The completion candidates at a position, already narrowed by the receiver:
226
+ * `str.` offers string members and no reader, a property-key position offers
227
+ * the target shape's predicates and no catalogue row at all.
228
+ *
229
+ * `kind` is the LSP `CompletionItemKind` **by name** (`"function"`,
230
+ * `"field"`) rather than by number. The numbers never cross this boundary —
231
+ * `packages/codemirror-fossil`'s deleted predecessor is what happens when
232
+ * they do.
233
+ *
234
+ * The same staleness note as {@link hover} applies, and harder: completion
235
+ * fires on nearly every keystroke.
236
+ */
237
+ completions(handle, line, character) {
238
+ return this._inner.completions(handle, line, character);
239
+ }
240
+ /**
241
+ * Where the name under the cursor is defined. Empty when nothing there has a
242
+ * definition.
243
+ *
244
+ * `uri` is the key the buffer was opened under, verbatim — and two of the
245
+ * four positions this recognises resolve into the **shape document**, so a
246
+ * host with a single editor pane has to read `uri` before it moves a cursor.
247
+ */
248
+ gotoDefinition(handle, line, character) {
249
+ return this._inner.gotoDefinition(handle, line, character);
250
+ }
251
+ /**
252
+ * Register an {@link InferredDescriptorJson} under the source URI the program
253
+ * wrote, BEFORE invoking {@link check}. The Rust compiler reads from this
254
+ * during forward type propagation.
255
+ *
256
+ * The compiler never introspects a source itself: it performs no network or
257
+ * file IO — that would break the WASM gate and Salsa's determinism alike —
258
+ * so a host that wants column types must run the `DESCRIBE` and push the
259
+ * result in here.
260
+ *
261
+ * @throws Error if the descriptor JSON fails to deserialise on the Rust side.
262
+ */
263
+ registerInferredDescriptor(descriptor) {
264
+ // The wasm-bindgen wrapper accepts a JSON string; serialise here so callers
265
+ // pass a typed object.
266
+ this._inner.registerInferredDescriptor(JSON.stringify(descriptor));
267
+ }
268
+ }
269
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EACL,gBAAgB,IAAI,mBAAmB,EACvC,UAAU,IAAI,aAAa,EAC3B,QAAQ,IAAI,WAAW,EACvB,eAAe,IAAI,iBAAiB,EACpC,UAAU,IAAI,aAAa,EAC3B,gBAAgB,IAAI,iBAAiB,EACrC,IAAI,IAAI,OAAO,EACf,SAAS,IAAI,YAAY,GAC1B,MAAM,uBAAuB,CAAC;AAmB/B;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,gBAAgB;IAC9B,iBAAiB,EAAE,CAAC;AACtB,CAAC;AAWD;;;;;;;;GAQG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,6EAA6E;IAC7E,sEAAsE;IACtE,8EAA8E;IAC9E,6EAA6E;IAC7E,OAAO,WAAW,CAAC,IAAI,CAAe,CAAC;AACzC,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,UAAU;IACxB,OAAO,aAAa,EAAc,CAAC;AACrC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc;IAC5B,OAAO,iBAAiB,EAA0B,CAAC;AACrD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,IAAI,CAAC,OAAe;IAClC,OAAO,OAAO,CAAC,OAAO,CAAoB,CAAC;AAC7C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,SAAS;IACvB,OAAO,YAAY,EAAoB,CAAC;AAC1C,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,OAAO,gBAAgB;IACnB,MAAM,CAAsB;IAEpC;QACE,IAAI,CAAC,MAAM,GAAG,IAAI,mBAAmB,EAAE,CAAC;IAC1C,CAAC;IAED;;;OAGG;IACH,IAAI;QACF,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IACrB,CAAC;IAED;;;OAGG;IACH,QAAQ,CAAC,IAAY,EAAE,QAAgB;QACrC,OAAO,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC/C,CAAC;IAED;;;;;OAKG;IACH,UAAU,CAAC,MAAkB,EAAE,QAAgB;QAC7C,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC5C,CAAC;IAED;;;OAGG;IACH,SAAS,CAAC,MAAkB;QAC1B,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IACjC,CAAC;IAED;;;;OAIG;IACH,cAAc,CAAC,WAAmC;QAChD,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;IAC1C,CAAC;IAED;;;OAGG;IACH,gBAAgB,CAAC,MAAkB;QACjC,OAAO,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAsB,CAAC;IACnE,CAAC;IAED,0FAA0F;IAC1F,gBAAgB,CAAC,GAAW,EAAE,IAAY;QACxC,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAC1C,CAAC;IAED;;;;OAIG;IACH,OAAO,CAAC,MAAkB;QACxB,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAoB,CAAC;IACxD,CAAC;IAED;;;OAGG;IACH,SAAS,CAAC,MAAkB;QAC1B,OAAO;YACL,cAAc,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,WAAW,CAAC;YACjE,gBAAgB,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC;YACrD,gBAAgB,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC;SAClE,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,KAAK;QACH,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,EAAgB,CAAC;IAC3C,CAAC;IAED;;;OAGG;IACH,cAAc,CAAC,MAAkB;QAC/B,OAAO,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,MAAM,CAAe,CAAC;IAC3D,CAAC;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACH,KAAK,CAAC,MAAkB,EAAE,IAAY,EAAE,SAAiB;QACvD,yEAAyE;QACzE,kDAAkD;QAClD,OAAQ,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,CAAiC,IAAI,IAAI,CAAC;IAC7F,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,WAAW,CAAC,MAAkB,EAAE,IAAY,EAAE,SAAiB;QAC7D,OAAO,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,CAAoB,CAAC;IAC7E,CAAC;IAED;;;;;;;OAOG;IACH,cAAc,CAAC,MAAkB,EAAE,IAAY,EAAE,SAAiB;QAChE,OAAO,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,CAAoB,CAAC;IAChF,CAAC;IAED;;;;;;;;;;;OAWG;IACH,0BAA0B,CAAC,UAAkC;QAC3D,4EAA4E;QAC5E,uBAAuB;QACvB,IAAI,CAAC,MAAM,CAAC,0BAA0B,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC;IACrE,CAAC;CACF"}