@fossil-lang/wasm 0.3.0-alpha.2 → 0.3.0-alpha.20

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,119 @@
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, within 60 s (`module/unreachable` when it
8
+ cannot be fetched); a boot that failed is forgotten, so the next call tries again. Its `.wasm` ships in this
9
+ package and the host's bundler emits it as an asset; the host copies nothing.
10
+ - `tokenize(text)` — calls the Rust lexer, returns `TokenRow[]`. The Rust lexer
11
+ is the only lexer: no host reimplements one and drifts from the grammar.
12
+ - `FossilWorkspace` — 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';
11
21
 
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.
22
+ const program = await openProgram('job.fossil', { host, text }); // host: Host
23
+ const extensions = fossil({ ...program, onNavigate });
16
24
 
17
- ## Consumer patterns
25
+ program.registerDescriptor(descriptor); // a host-introspected source, before a check
26
+ const sources = await program.sources(text); // what introspection DESCRIBEs
27
+ ```
18
28
 
19
- ### Vite host
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 `FossilWorkspace` underneath, for a
37
+ question this surface does not ask, and `close()` frees it.
38
+
39
+ ## Documents and sources: fossil resolves, the host reads
40
+
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 `resolveDocuments`
44
+ (`@fossil-lang/storage`) reads it under the credentials the `Host`
45
+ (`@fossil-lang/types`) vends — `connections()` and `credentials(scope, access)`.
46
+ A document under a connection is read by a GET signed with that connection's
47
+ `read` credential; one with no connection only when it is a public `http(s)` URL.
48
+ A host that already holds a document's text calls `registerDocument` itself.
20
49
 
21
50
  ```typescript
22
- import { initFossilWasm, tokenize } from '@fossil-lang/wasm';
23
- import wasmUrl from '@fossil-lang/wasm/pkg/fossil_wasm_bg.wasm?url';
51
+ import { resolveDocuments } from '@fossil-lang/storage';
24
52
 
25
- await initFossilWasm({ wasmUrl });
26
- const tokens = tokenize('prefix ex: <https://example.org/>');
53
+ const ws = new FossilWorkspace();
54
+ const h = ws.openFile('prog.fossil', text); // edited buffers only
55
+ const { unread } = await resolveDocuments(ws.workspace(h), host); // sets the connections too
56
+ const rows = ws.check();
27
57
  ```
28
58
 
29
- ### Next.js host (in a Client Component)
59
+ - `missingDocuments(h)` — `{ key, locator, connection? }` rows. The key is what
60
+ the program wrote, so repointing a connection invalidates nothing; the locator
61
+ is that key through the map, and `connection` is the one it goes through.
62
+ - `registerDocument(key, text)` — what `resolveDocuments` calls for each
63
+ fetched document, until nothing new is missing (a document can name another).
64
+ It is the one loop; a host does not write a second.
65
+ - `openFile` is for buffers the user edits. An open `.shex` is the document
66
+ every program naming it reads; opening a program registers nothing it names.
67
+ - `setConnections(map)` — name → base; re-checks nothing. `resolveDocuments`
68
+ calls it with `host.connections()`, so a host never does.
69
+ - `sources(h)` — the `ProgramSource[]` the program reads, through the map the
70
+ last `resolveDocuments` set: binding, key,
71
+ locator, connection, catalogue row, reader option. Introspection DESCRIBEs these and
72
+ registers each descriptor under `key` with `registerInferredDescriptor`.
73
+
74
+ ## Loading: the `.wasm` is an asset of this package
30
75
 
31
76
  ```typescript
32
- 'use client';
33
77
  import { initFossilWasm, tokenize } from '@fossil-lang/wasm';
34
78
 
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
- }, []);
79
+ await initFossilWasm();
80
+ const tokens = tokenize('User := io.csv("data/people.csv")');
39
81
  ```
40
82
 
41
- ### Web Worker
83
+ That is the whole host flow, in Vite, Next.js (webpack or Turbopack), a Web
84
+ Worker or any bundler that understands `new URL('…', import.meta.url)`. The glue
85
+ (`wasm-bindgen --target web`) locates `fossil_wasm_bg.wasm` with exactly that
86
+ expression, the bundler copies the file into its output under a hashed name and
87
+ rewrites the URL, and the browser fetches it from there. No copy script, no
88
+ `public/` directory, no URL to keep in sync with a version.
89
+
90
+ **Vite dev server, package installed from npm:** Vite's dependency optimizer
91
+ pre-bundles the package into `node_modules/.vite/deps/` without its `.wasm`, and
92
+ the URL then answers with `index.html`. Keep the three packages out of it —
93
+ `vite build` needs nothing:
94
+
95
+ ```js
96
+ // vite.config.js
97
+ export default { optimizeDeps: { exclude: ['@fossil-lang/wasm', '@fossil-lang/executor', '@fossil-lang/corpus'] } };
98
+ ```
99
+
100
+ A workspace-linked package is never pre-bundled, which is why a host inside this
101
+ repository would need no such line. Next.js needs none in either `next dev` or `next build`, webpack or
102
+ Turbopack.
103
+
104
+ `--target bundler` was rejected because it emits `import … from '*.wasm'` (the
105
+ ESM-integration proposal), which each bundler gates behind its own experimental
106
+ flag. `new URL(…, import.meta.url)` is the pattern they all support by default.
107
+
108
+ **Without a bundler**, pass the module yourself — `initFossilWasm(wasm)` takes the
109
+ glue's `InitInput` (`BufferSource`, `Response`, `URL`, `WebAssembly.Module`). Node
110
+ is the case: its `fetch` rejects `file://`, so hand it the bytes:
42
111
 
43
112
  ```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';
113
+ import { readFile } from 'node:fs/promises';
114
+ import { createRequire } from 'node:module';
47
115
 
48
- await initFossilWasm({ wasmUrl });
49
- const pg = new FossilPlayground();
116
+ const path = createRequire(import.meta.url).resolve('@fossil-lang/wasm/pkg/fossil_wasm_bg.wasm');
117
+ await initFossilWasm(await readFile(path));
50
118
  ```
51
119
 
52
120
  ## Build
@@ -63,8 +131,6 @@ Optionally `wasm-opt` (binaryen) for size reduction.
63
131
 
64
132
  ## Source of truth
65
133
 
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)
134
+ - Rust crate: `crates/fossil-wasm/` — everything here is a wrapper over its
135
+ wasm-bindgen exports, and nothing in this package reimplements it.
136
+ - The grammar the tokenizer follows: `grammar.bnf` at the repo root.
@@ -0,0 +1,206 @@
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, TokenKindLegend, TokenRow } from '@fossil-lang/types';
16
+ import type { CheckRow, CompletionRow, DefinitionRow, HoverRow, InferredDescriptorJson, SemanticTokenRow, SourceRefInfo, ProviderInfo } from './index.js';
17
+ /**
18
+ * Opaque file-handle returned by {@link FossilWorkspace.openFile}. Pass it
19
+ * back into the matching `updateFile` / `closeFile` / `diagnosticsFor`
20
+ * calls. JS code cannot construct one directly (the wasm-bindgen class has a
21
+ * private constructor) — handles are minted only by `openFile` on the Rust
22
+ * side, where they index a `HashMap<FileHandle, SourceFile>` keyed by a `u32`.
23
+ */
24
+ export type FileHandle = RawFileHandle;
25
+ /**
26
+ * Tokenize a Fossil source string. Returns the byte-range tokens from the
27
+ * canonical Rust lexer (`fossil_syntax::lexer::raw_lex`) — the single grammar
28
+ * source of truth, so no editor ever reimplements the lexer in TS and drifts.
29
+ *
30
+ * MUST be called after {@link initFossilWasm} has resolved; otherwise the
31
+ * underlying wasm-bindgen function throws (the wasm module is not yet
32
+ * instantiated).
33
+ */
34
+ export declare function tokenize(text: string): TokenRow[];
35
+ /**
36
+ * The legend for {@link TokenRow.kind}: every lexer variant NAME, indexed by the
37
+ * discriminant a row carries. `tokenKinds()[row.kind]` is `"Comment"`,
38
+ * `"KwFrom"`, `"String"`, …
39
+ *
40
+ * **This is the contract, and the numbers are not.** `kind` is a variant
41
+ * discriminant of `fossil_syntax::lexer::Token`, so any reorder of that enum
42
+ * remaps every value with nothing going red. The predecessor of this package
43
+ * hard-coded the table (`enum FossilKind { Whitespace = 0, … }`) under a comment
44
+ * saying it had to be updated in lockstep; it was wrong in nine places by the
45
+ * time it was deleted. Keying on the name is what makes a reorder a non-event.
46
+ *
47
+ * An index past the end of the legend is a variant appended by a compiler newer
48
+ * than this host: `undefined`, and a host styles it as plain text.
49
+ *
50
+ * MUST be called after {@link initFossilWasm} has resolved.
51
+ */
52
+ export declare function tokenKinds(): TokenKindLegend;
53
+ /**
54
+ * Parse a Fossil program and return its external references — the typed lineage
55
+ * (every data URI + `schema =` argument, each tagged with its `@conn` alias and
56
+ * role). keasy's client-compute job runner reads this to derive a job's
57
+ * connections WITHOUT subprocessing `fossil` / a server round-trip. Identical
58
+ * shape to the native `fossil refs` (the SAME `fossil_lineage::SourceRefInfo`
59
+ * struct), so the browser and the CLI never diverge.
60
+ *
61
+ * MUST be called after {@link initFossilWasm} has resolved.
62
+ */
63
+ export declare function refs(program: string): SourceRefInfo[];
64
+ /**
65
+ * List the data-source providers fossil supports (`io.csv`, `io.rdf`, …) — the
66
+ * provider name, the extensions it reads, and how it can be used. Identical
67
+ * shape to the native `fossil providers`.
68
+ *
69
+ * MUST be called after {@link initFossilWasm} has resolved.
70
+ */
71
+ export declare function providers(): ProviderInfo[];
72
+ /**
73
+ * Workspace API class. Thin TS wrapper around the wasm-bindgen
74
+ * `FossilWorkspace` that exposes camelCase method names for JS idiom + better
75
+ * TS inference (the raw bindings use snake_case from the Rust impl block).
76
+ *
77
+ * One instance per browser tab / Node process — the instance owns the Salsa
78
+ * store + `Arc<OutputDescriptorKind>` for the schema slot.
79
+ *
80
+ * NOTE: {@link FileHandle} is opaque — JS cannot construct one. Callers receive
81
+ * a handle from `openFile` and pass it back to subsequent operations.
82
+ */
83
+ export declare class FossilWorkspace {
84
+ private _inner;
85
+ constructor();
86
+ /**
87
+ * Free the underlying WASM-side Salsa store. Call when the workspace is no
88
+ * longer needed (e.g. component unmount).
89
+ */
90
+ free(): void;
91
+ /**
92
+ * Open a file in the workspace. Returns the {@link FileHandle} subsequent
93
+ * `updateFile` / `closeFile` / `diagnosticsFor` calls key on.
94
+ */
95
+ openFile(path: string, contents: string): FileHandle;
96
+ /**
97
+ * Apply an edit to an open file. Mutates the SAME `SourceFile` via the Salsa
98
+ * `Setter` (`set_text`) — bumps the revision for incremental
99
+ * invalidation rather than a full recompute, and that revision bump is also
100
+ * what cancels any analysis still running on an older snapshot.
101
+ */
102
+ updateFile(handle: FileHandle, contents: string): void;
103
+ /**
104
+ * Close a file in the workspace. Strict: closing an unknown / already-closed
105
+ * handle throws so JS-side bugs surface loudly.
106
+ */
107
+ closeFile(handle: FileHandle): void;
108
+ /**
109
+ * The connection map `@name/…` expands against — what
110
+ * `Host.connections()` answers. It reaches locators only, so setting it
111
+ * re-checks nothing.
112
+ */
113
+ setConnections(connections: Record<string, string>): void;
114
+ /**
115
+ * The documents the file at `handle` names and nothing has registered, each
116
+ * with the key to register it under and the locator to read it from.
117
+ */
118
+ missingDocuments(handle: FileHandle): MissingDocument[];
119
+ /** Register a fetched document's text under the key {@link missingDocuments} reported. */
120
+ registerDocument(key: string, text: string): void;
121
+ /**
122
+ * The data sources the file at `handle` reads, as fossil resolved them: the
123
+ * binding, the URI as written, its locator through the connection map, the
124
+ * catalogue row and the reader option.
125
+ */
126
+ sources(handle: FileHandle): ProgramSource[];
127
+ /**
128
+ * The file at `handle` as a {@link DocumentWorkspace}, for
129
+ * `resolveDocuments(workspace.workspace(handle), host)`.
130
+ */
131
+ workspace(handle: FileHandle): DocumentWorkspace;
132
+ /**
133
+ * Workspace-wide diagnostic drain. Runs `parse → def_map → typecheck_mapping`
134
+ * across every open file and returns a flat array of {@link CheckRow}.
135
+ */
136
+ check(): CheckRow[];
137
+ /**
138
+ * Per-file diagnostic drain: one file's rows of {@link check}.
139
+ */
140
+ diagnosticsFor(handle: FileHandle): CheckRow[];
141
+ /**
142
+ * What is under the cursor — `{ markdown, range }`, or `null` when nothing
143
+ * there has a type.
144
+ *
145
+ * `line` / `character` are LSP: zero-based, `character` in UTF-16 code units.
146
+ * A CodeMirror or Monaco host already counts in those units, so a document
147
+ * offset converts with `doc.lineAt(pos)` and no byte arithmetic — unlike
148
+ * {@link tokenize}, whose offsets ARE bytes.
149
+ *
150
+ * ## Push the buffer before you ask
151
+ *
152
+ * This reads the text of the last {@link updateFile}. Hover fires on
153
+ * mouse-move and the checker is debounced, so a hover mid-debounce answers
154
+ * about text one keystroke old and its range lands one keystroke wrong. The
155
+ * three read-only methods take a SHARED borrow on the Rust side and cannot
156
+ * poison the workspace the way a re-entered `updateFile` once could — see the
157
+ * `ide` module in `crates/fossil-wasm` — but staleness is not a borrow
158
+ * problem and nothing here can fix it for you.
159
+ */
160
+ hover(handle: FileHandle, line: number, character: number): HoverRow | null;
161
+ /**
162
+ * The completion candidates at a position, already narrowed by the receiver:
163
+ * `str.` offers string members and no reader, a property-key position offers
164
+ * the target shape's predicates and no catalogue row at all.
165
+ *
166
+ * `kind` is the LSP `CompletionItemKind` **by name** (`"function"`,
167
+ * `"field"`) rather than by number. The numbers never cross this boundary —
168
+ * `packages/codemirror-fossil`'s deleted predecessor is what happens when
169
+ * they do.
170
+ *
171
+ * The same staleness note as {@link hover} applies, and harder: completion
172
+ * fires on nearly every keystroke.
173
+ */
174
+ completions(handle: FileHandle, line: number, character: number): CompletionRow[];
175
+ /**
176
+ * Where the name under the cursor is defined. Empty when nothing there has a
177
+ * definition.
178
+ *
179
+ * `uri` is the key the buffer was opened under, verbatim — and two of the
180
+ * four positions this recognises resolve into the **shape document**, so a
181
+ * host with a single editor pane has to read `uri` before it moves a cursor.
182
+ */
183
+ gotoDefinition(handle: FileHandle, line: number, character: number): DefinitionRow[];
184
+ /**
185
+ * Every classified span of the file at `handle`, in source order — the
186
+ * semantic layer an editor lays over {@link tokenize}'s lexical one. Ranges
187
+ * are LSP (UTF-16), like {@link hover}'s, and the same staleness note
188
+ * applies: push the buffer before you ask.
189
+ */
190
+ semanticTokens(handle: FileHandle): SemanticTokenRow[];
191
+ /**
192
+ * Register an {@link InferredDescriptorJson} under the source URI the program
193
+ * wrote, BEFORE invoking {@link check}. The Rust compiler reads from this
194
+ * during forward type propagation.
195
+ *
196
+ * The compiler never introspects a source itself: it performs no network or
197
+ * file IO — that would break the WASM gate and Salsa's determinism alike —
198
+ * so a host that wants column types must run the `DESCRIBE` and push the
199
+ * result in here.
200
+ *
201
+ * @throws {FossilError} `api/invalid-argument` if the descriptor fails to deserialise on the Rust
202
+ * side, the `serde_json` error as its cause.
203
+ */
204
+ registerInferredDescriptor(descriptor: InferredDescriptorJson): void;
205
+ }
206
+ //# 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,EAK5B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EACV,iBAAiB,EACjB,eAAe,EACf,aAAa,EACb,eAAe,EACf,QAAQ,EACT,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EACV,QAAQ,EACR,aAAa,EACb,aAAa,EACb,QAAQ,EACR,sBAAsB,EACtB,gBAAgB,EAChB,aAAa,EACb,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB;;;;;;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;;;;;;;;;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,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAqB;;IAMnC;;;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;;OAEG;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;;;;;OAKG;IACH,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,gBAAgB,EAAE;IAItD;;;;;;;;;;;;OAYG;IACH,0BAA0B,CAAC,UAAU,EAAE,sBAAsB,GAAG,IAAI;CAKrE"}
package/dist/client.js ADDED
@@ -0,0 +1,250 @@
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 { FossilWorkspace as RawFossilWorkspace, FileHandle as RawFileHandle, tokenize as rawTokenize, tokenKinds as rawTokenKinds, refs as rawRefs, providers as rawProviders, } from '../pkg/fossil_wasm.js';
15
+ /**
16
+ * Tokenize a Fossil source string. Returns the byte-range tokens from the
17
+ * canonical Rust lexer (`fossil_syntax::lexer::raw_lex`) — the single grammar
18
+ * source of truth, so no editor ever reimplements the lexer in TS and drifts.
19
+ *
20
+ * MUST be called after {@link initFossilWasm} has resolved; otherwise the
21
+ * underlying wasm-bindgen function throws (the wasm module is not yet
22
+ * instantiated).
23
+ */
24
+ export function tokenize(text) {
25
+ // The wasm-bindgen wrapper returns a `JsValue` typed as `any`; the Rust side
26
+ // (crates/fossil-wasm/src/tokenize.rs) serializes `Vec<TokenRow>` via
27
+ // `serde_wasm_bindgen::to_value`, so the shape matches `{ kind, start, end }`
28
+ // exactly. Cast is safe because the Rust ↔ JS contract is enforced upstream.
29
+ return rawTokenize(text);
30
+ }
31
+ /**
32
+ * The legend for {@link TokenRow.kind}: every lexer variant NAME, indexed by the
33
+ * discriminant a row carries. `tokenKinds()[row.kind]` is `"Comment"`,
34
+ * `"KwFrom"`, `"String"`, …
35
+ *
36
+ * **This is the contract, and the numbers are not.** `kind` is a variant
37
+ * discriminant of `fossil_syntax::lexer::Token`, so any reorder of that enum
38
+ * remaps every value with nothing going red. The predecessor of this package
39
+ * hard-coded the table (`enum FossilKind { Whitespace = 0, … }`) under a comment
40
+ * saying it had to be updated in lockstep; it was wrong in nine places by the
41
+ * time it was deleted. Keying on the name is what makes a reorder a non-event.
42
+ *
43
+ * An index past the end of the legend is a variant appended by a compiler newer
44
+ * than this host: `undefined`, and a host styles it as plain text.
45
+ *
46
+ * MUST be called after {@link initFossilWasm} has resolved.
47
+ */
48
+ export function tokenKinds() {
49
+ return rawTokenKinds();
50
+ }
51
+ /**
52
+ * Parse a Fossil program and return its external references — the typed lineage
53
+ * (every data URI + `schema =` argument, each tagged with its `@conn` alias and
54
+ * role). keasy's client-compute job runner reads this to derive a job's
55
+ * connections WITHOUT subprocessing `fossil` / a server round-trip. Identical
56
+ * shape to the native `fossil refs` (the SAME `fossil_lineage::SourceRefInfo`
57
+ * struct), so the browser and the CLI never diverge.
58
+ *
59
+ * MUST be called after {@link initFossilWasm} has resolved.
60
+ */
61
+ export function refs(program) {
62
+ return rawRefs(program);
63
+ }
64
+ /**
65
+ * List the data-source providers fossil supports (`io.csv`, `io.rdf`, …) — the
66
+ * provider name, the extensions it reads, and how it can be used. Identical
67
+ * shape to the native `fossil providers`.
68
+ *
69
+ * MUST be called after {@link initFossilWasm} has resolved.
70
+ */
71
+ export function providers() {
72
+ return rawProviders();
73
+ }
74
+ /**
75
+ * Workspace API class. Thin TS wrapper around the wasm-bindgen
76
+ * `FossilWorkspace` that exposes camelCase method names for JS idiom + better
77
+ * TS inference (the raw bindings use snake_case from the Rust impl block).
78
+ *
79
+ * One instance per browser tab / Node process — the instance owns the Salsa
80
+ * store + `Arc<OutputDescriptorKind>` for the schema slot.
81
+ *
82
+ * NOTE: {@link FileHandle} is opaque — JS cannot construct one. Callers receive
83
+ * a handle from `openFile` and pass it back to subsequent operations.
84
+ */
85
+ export class FossilWorkspace {
86
+ _inner;
87
+ constructor() {
88
+ this._inner = new RawFossilWorkspace();
89
+ }
90
+ /**
91
+ * Free the underlying WASM-side Salsa store. Call when the workspace is no
92
+ * longer needed (e.g. component unmount).
93
+ */
94
+ free() {
95
+ this._inner.free();
96
+ }
97
+ /**
98
+ * Open a file in the workspace. Returns the {@link FileHandle} subsequent
99
+ * `updateFile` / `closeFile` / `diagnosticsFor` calls key on.
100
+ */
101
+ openFile(path, contents) {
102
+ return this._inner.open_file(path, contents);
103
+ }
104
+ /**
105
+ * Apply an edit to an open file. Mutates the SAME `SourceFile` via the Salsa
106
+ * `Setter` (`set_text`) — bumps the revision for incremental
107
+ * invalidation rather than a full recompute, and that revision bump is also
108
+ * what cancels any analysis still running on an older snapshot.
109
+ */
110
+ updateFile(handle, contents) {
111
+ this._inner.update_file(handle, contents);
112
+ }
113
+ /**
114
+ * Close a file in the workspace. Strict: closing an unknown / already-closed
115
+ * handle throws so JS-side bugs surface loudly.
116
+ */
117
+ closeFile(handle) {
118
+ this._inner.close_file(handle);
119
+ }
120
+ /**
121
+ * The connection map `@name/…` expands against — what
122
+ * `Host.connections()` answers. It reaches locators only, so setting it
123
+ * re-checks nothing.
124
+ */
125
+ setConnections(connections) {
126
+ this._inner.setConnections(connections);
127
+ }
128
+ /**
129
+ * The documents the file at `handle` names and nothing has registered, each
130
+ * with the key to register it under and the locator to read it from.
131
+ */
132
+ missingDocuments(handle) {
133
+ return this._inner.missingDocuments(handle);
134
+ }
135
+ /** Register a fetched document's text under the key {@link missingDocuments} reported. */
136
+ registerDocument(key, text) {
137
+ this._inner.registerDocument(key, text);
138
+ }
139
+ /**
140
+ * The data sources the file at `handle` reads, as fossil resolved them: the
141
+ * binding, the URI as written, its locator through the connection map, the
142
+ * catalogue row and the reader option.
143
+ */
144
+ sources(handle) {
145
+ return this._inner.sources(handle);
146
+ }
147
+ /**
148
+ * The file at `handle` as a {@link DocumentWorkspace}, for
149
+ * `resolveDocuments(workspace.workspace(handle), host)`.
150
+ */
151
+ workspace(handle) {
152
+ return {
153
+ setConnections: (connections) => this.setConnections(connections),
154
+ missingDocuments: () => this.missingDocuments(handle),
155
+ registerDocument: (key, text) => this.registerDocument(key, text),
156
+ };
157
+ }
158
+ /**
159
+ * Workspace-wide diagnostic drain. Runs `parse → def_map → typecheck_mapping`
160
+ * across every open file and returns a flat array of {@link CheckRow}.
161
+ */
162
+ check() {
163
+ return this._inner.check();
164
+ }
165
+ /**
166
+ * Per-file diagnostic drain: one file's rows of {@link check}.
167
+ */
168
+ diagnosticsFor(handle) {
169
+ return this._inner.diagnostics_for(handle);
170
+ }
171
+ /**
172
+ * What is under the cursor — `{ markdown, range }`, or `null` when nothing
173
+ * there has a type.
174
+ *
175
+ * `line` / `character` are LSP: zero-based, `character` in UTF-16 code units.
176
+ * A CodeMirror or Monaco host already counts in those units, so a document
177
+ * offset converts with `doc.lineAt(pos)` and no byte arithmetic — unlike
178
+ * {@link tokenize}, whose offsets ARE bytes.
179
+ *
180
+ * ## Push the buffer before you ask
181
+ *
182
+ * This reads the text of the last {@link updateFile}. Hover fires on
183
+ * mouse-move and the checker is debounced, so a hover mid-debounce answers
184
+ * about text one keystroke old and its range lands one keystroke wrong. The
185
+ * three read-only methods take a SHARED borrow on the Rust side and cannot
186
+ * poison the workspace the way a re-entered `updateFile` once could — see the
187
+ * `ide` module in `crates/fossil-wasm` — but staleness is not a borrow
188
+ * problem and nothing here can fix it for you.
189
+ */
190
+ hover(handle, line, character) {
191
+ // `serde_wasm_bindgen` writes `None` as `undefined`; a host reading this
192
+ // should have one falsy answer to check, not two.
193
+ return this._inner.hover(handle, line, character) ?? null;
194
+ }
195
+ /**
196
+ * The completion candidates at a position, already narrowed by the receiver:
197
+ * `str.` offers string members and no reader, a property-key position offers
198
+ * the target shape's predicates and no catalogue row at all.
199
+ *
200
+ * `kind` is the LSP `CompletionItemKind` **by name** (`"function"`,
201
+ * `"field"`) rather than by number. The numbers never cross this boundary —
202
+ * `packages/codemirror-fossil`'s deleted predecessor is what happens when
203
+ * they do.
204
+ *
205
+ * The same staleness note as {@link hover} applies, and harder: completion
206
+ * fires on nearly every keystroke.
207
+ */
208
+ completions(handle, line, character) {
209
+ return this._inner.completions(handle, line, character);
210
+ }
211
+ /**
212
+ * Where the name under the cursor is defined. Empty when nothing there has a
213
+ * definition.
214
+ *
215
+ * `uri` is the key the buffer was opened under, verbatim — and two of the
216
+ * four positions this recognises resolve into the **shape document**, so a
217
+ * host with a single editor pane has to read `uri` before it moves a cursor.
218
+ */
219
+ gotoDefinition(handle, line, character) {
220
+ return this._inner.gotoDefinition(handle, line, character);
221
+ }
222
+ /**
223
+ * Every classified span of the file at `handle`, in source order — the
224
+ * semantic layer an editor lays over {@link tokenize}'s lexical one. Ranges
225
+ * are LSP (UTF-16), like {@link hover}'s, and the same staleness note
226
+ * applies: push the buffer before you ask.
227
+ */
228
+ semanticTokens(handle) {
229
+ return this._inner.semanticTokens(handle);
230
+ }
231
+ /**
232
+ * Register an {@link InferredDescriptorJson} under the source URI the program
233
+ * wrote, BEFORE invoking {@link check}. The Rust compiler reads from this
234
+ * during forward type propagation.
235
+ *
236
+ * The compiler never introspects a source itself: it performs no network or
237
+ * file IO — that would break the WASM gate and Salsa's determinism alike —
238
+ * so a host that wants column types must run the `DESCRIBE` and push the
239
+ * result in here.
240
+ *
241
+ * @throws {FossilError} `api/invalid-argument` if the descriptor fails to deserialise on the Rust
242
+ * side, the `serde_json` error as its cause.
243
+ */
244
+ registerInferredDescriptor(descriptor) {
245
+ // The wasm-bindgen wrapper accepts a JSON string; serialise here so callers
246
+ // pass a typed object.
247
+ this._inner.registerInferredDescriptor(JSON.stringify(descriptor));
248
+ }
249
+ }
250
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}