@rejot-dev/thalo 0.1.0 → 0.2.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.
@@ -31,14 +31,25 @@ interface MergeOptions {
31
31
  * It parses all three versions, matches entries, detects conflicts,
32
32
  * and produces a merged result.
33
33
  *
34
+ * This function is synchronous but requires the parser to be initialized first.
35
+ * Call `await initParser()` from `@rejot-dev/thalo/node` before using this function.
36
+ * Internally uses `parseDocument` which depends on the initialized parser.
37
+ *
34
38
  * @param base - Base version content (common ancestor)
35
39
  * @param ours - Our version content (local changes)
36
40
  * @param theirs - Their version content (incoming changes)
37
41
  * @param options - Merge options
38
42
  * @returns MergeResult with merged content and conflict information
43
+ * @throws Error if the parser has not been initialized
39
44
  *
40
45
  * @example
41
46
  * ```typescript
47
+ * import { initParser } from "@rejot-dev/thalo/node";
48
+ * import { mergeThaloFiles } from "@rejot-dev/thalo";
49
+ *
50
+ * // Initialize the parser first (required)
51
+ * await initParser();
52
+ *
42
53
  * const base = '2026-01-01T00:00Z define-entity lore "Lore"';
43
54
  * const ours = base + '\n2026-01-02T00:00Z create lore "My entry" ^entry1';
44
55
  * const theirs = base + '\n2026-01-03T00:00Z create lore "Their entry" ^entry2';
@@ -1 +1 @@
1
- {"version":3,"file":"driver.d.ts","names":[],"sources":["../../src/merge/driver.ts"],"sourcesContent":[],"mappings":";;;;;;;AAWA;AA6CgB,UA7CC,YAAA,CA6Cc;;;;;;;;;;;;;;;;kBA3Bb;;;;;;;;;;;;;;;;;;;;;;;;;;iBA2BF,eAAA,uDAIL,eACR"}
1
+ {"version":3,"file":"driver.d.ts","names":[],"sources":["../../src/merge/driver.ts"],"sourcesContent":[],"mappings":";;;;;;;AAWA;AAwDgB,UAxDC,YAAA,CAwDc;;;;;;;;;;;;;;;;kBAtCb;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAsCF,eAAA,uDAIL,eACR"}
@@ -1,5 +1,5 @@
1
1
  import { extractSourceFile } from "../ast/extract.js";
2
- import { parseDocument } from "../parser.js";
2
+ import { parseDocument } from "../parser.node.js";
3
3
  import { matchEntries } from "./entry-matcher.js";
4
4
  import { detectConflicts } from "./conflict-detector.js";
5
5
  import { buildMergedResult } from "./merge-result-builder.js";
@@ -12,14 +12,25 @@ import { buildMergedResult } from "./merge-result-builder.js";
12
12
  * It parses all three versions, matches entries, detects conflicts,
13
13
  * and produces a merged result.
14
14
  *
15
+ * This function is synchronous but requires the parser to be initialized first.
16
+ * Call `await initParser()` from `@rejot-dev/thalo/node` before using this function.
17
+ * Internally uses `parseDocument` which depends on the initialized parser.
18
+ *
15
19
  * @param base - Base version content (common ancestor)
16
20
  * @param ours - Our version content (local changes)
17
21
  * @param theirs - Their version content (incoming changes)
18
22
  * @param options - Merge options
19
23
  * @returns MergeResult with merged content and conflict information
24
+ * @throws Error if the parser has not been initialized
20
25
  *
21
26
  * @example
22
27
  * ```typescript
28
+ * import { initParser } from "@rejot-dev/thalo/node";
29
+ * import { mergeThaloFiles } from "@rejot-dev/thalo";
30
+ *
31
+ * // Initialize the parser first (required)
32
+ * await initParser();
33
+ *
23
34
  * const base = '2026-01-01T00:00Z define-entity lore "Lore"';
24
35
  * const ours = base + '\n2026-01-02T00:00Z create lore "My entry" ^entry1';
25
36
  * const theirs = base + '\n2026-01-03T00:00Z create lore "Their entry" ^entry2';
@@ -1 +1 @@
1
- {"version":3,"file":"driver.js","names":["syntaxErrorConflicts: MergeConflict[]"],"sources":["../../src/merge/driver.ts"],"sourcesContent":["import { parseDocument } from \"../parser.js\";\nimport { extractSourceFile } from \"../ast/extract.js\";\nimport type { MergeResult } from \"./merge-result-builder.js\";\nimport type { MergeConflict, ConflictRule } from \"./conflict-detector.js\";\nimport { matchEntries } from \"./entry-matcher.js\";\nimport { detectConflicts } from \"./conflict-detector.js\";\nimport { buildMergedResult } from \"./merge-result-builder.js\";\n\n/**\n * Options for the merge driver\n */\nexport interface MergeOptions {\n /**\n * Conflict marker style\n * - \"git\": Standard Git style (ours/theirs)\n * - \"diff3\": Include base section\n */\n markerStyle?: \"git\" | \"diff3\";\n\n /**\n * Whether to include base in markers (diff3 style)\n * Deprecated: Use markerStyle: \"diff3\" instead\n */\n showBase?: boolean;\n\n /**\n * Custom conflict detection rules\n * Applied after default rules\n */\n conflictRules?: ConflictRule[];\n}\n\n/**\n * Perform three-way merge of thalo files\n *\n * This is the main entry point for the merge driver.\n * It parses all three versions, matches entries, detects conflicts,\n * and produces a merged result.\n *\n * @param base - Base version content (common ancestor)\n * @param ours - Our version content (local changes)\n * @param theirs - Their version content (incoming changes)\n * @param options - Merge options\n * @returns MergeResult with merged content and conflict information\n *\n * @example\n * ```typescript\n * const base = '2026-01-01T00:00Z define-entity lore \"Lore\"';\n * const ours = base + '\\n2026-01-02T00:00Z create lore \"My entry\" ^entry1';\n * const theirs = base + '\\n2026-01-03T00:00Z create lore \"Their entry\" ^entry2';\n *\n * const result = mergeThaloFiles(base, ours, theirs);\n * console.log(result.success); // true\n * console.log(result.content); // Merged content with both entries\n * ```\n */\nexport function mergeThaloFiles(\n base: string,\n ours: string,\n theirs: string,\n options: MergeOptions = {},\n): MergeResult {\n try {\n const baseDoc = parseDocument(base, { fileType: \"thalo\" });\n const oursDoc = parseDocument(ours, { fileType: \"thalo\" });\n const theirsDoc = parseDocument(theirs, { fileType: \"thalo\" });\n\n const baseAst =\n baseDoc.blocks.length > 0\n ? extractSourceFile(baseDoc.blocks[0].tree.rootNode)\n : { entries: [], syntaxErrors: [] };\n const oursAst =\n oursDoc.blocks.length > 0\n ? extractSourceFile(oursDoc.blocks[0].tree.rootNode)\n : { entries: [], syntaxErrors: [] };\n const theirsAst =\n theirsDoc.blocks.length > 0\n ? extractSourceFile(theirsDoc.blocks[0].tree.rootNode)\n : { entries: [], syntaxErrors: [] };\n\n // Surface syntax errors as parse-error conflicts\n const syntaxErrorConflicts: MergeConflict[] = [];\n if (baseAst.syntaxErrors.length > 0) {\n syntaxErrorConflicts.push({\n type: \"parse-error\",\n message: `Parse error in base: ${baseAst.syntaxErrors.map((e) => e.message || \"syntax error\").join(\", \")}`,\n location: 0,\n identity: { entryType: \"parse-error\" },\n context: { errorMessage: \"base version has syntax errors\" },\n });\n }\n if (oursAst.syntaxErrors.length > 0) {\n syntaxErrorConflicts.push({\n type: \"parse-error\",\n message: `Parse error in ours: ${oursAst.syntaxErrors.map((e) => e.message || \"syntax error\").join(\", \")}`,\n location: 0,\n identity: { entryType: \"parse-error\" },\n context: { errorMessage: \"ours version has syntax errors\" },\n });\n }\n if (theirsAst.syntaxErrors.length > 0) {\n syntaxErrorConflicts.push({\n type: \"parse-error\",\n message: `Parse error in theirs: ${theirsAst.syntaxErrors.map((e) => e.message || \"syntax error\").join(\", \")}`,\n location: 0,\n identity: { entryType: \"parse-error\" },\n context: { errorMessage: \"theirs version has syntax errors\" },\n });\n }\n\n if (syntaxErrorConflicts.length > 0) {\n return {\n success: false,\n content: ours,\n conflicts: syntaxErrorConflicts,\n stats: {\n totalEntries: 0,\n oursOnly: 0,\n theirsOnly: 0,\n common: 0,\n autoMerged: 0,\n conflicts: syntaxErrorConflicts.length,\n },\n };\n }\n\n const matches = matchEntries(baseAst.entries, oursAst.entries, theirsAst.entries);\n\n const conflicts = detectConflicts(matches, options);\n\n const result = buildMergedResult(matches, conflicts, options);\n\n return result;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n return {\n success: false,\n content: ours,\n conflicts: [\n {\n type: \"merge-error\",\n message: `Merge failed: ${errorMessage}`,\n location: 0,\n identity: { entryType: \"error\" },\n context: { errorMessage },\n },\n ],\n stats: {\n totalEntries: 0,\n oursOnly: 0,\n theirsOnly: 0,\n common: 0,\n autoMerged: 0,\n conflicts: 1,\n },\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwDA,SAAgB,gBACd,MACA,MACA,QACA,UAAwB,EAAE,EACb;AACb,KAAI;EACF,MAAM,UAAU,cAAc,MAAM,EAAE,UAAU,SAAS,CAAC;EAC1D,MAAM,UAAU,cAAc,MAAM,EAAE,UAAU,SAAS,CAAC;EAC1D,MAAM,YAAY,cAAc,QAAQ,EAAE,UAAU,SAAS,CAAC;EAE9D,MAAM,UACJ,QAAQ,OAAO,SAAS,IACpB,kBAAkB,QAAQ,OAAO,GAAG,KAAK,SAAS,GAClD;GAAE,SAAS,EAAE;GAAE,cAAc,EAAE;GAAE;EACvC,MAAM,UACJ,QAAQ,OAAO,SAAS,IACpB,kBAAkB,QAAQ,OAAO,GAAG,KAAK,SAAS,GAClD;GAAE,SAAS,EAAE;GAAE,cAAc,EAAE;GAAE;EACvC,MAAM,YACJ,UAAU,OAAO,SAAS,IACtB,kBAAkB,UAAU,OAAO,GAAG,KAAK,SAAS,GACpD;GAAE,SAAS,EAAE;GAAE,cAAc,EAAE;GAAE;EAGvC,MAAMA,uBAAwC,EAAE;AAChD,MAAI,QAAQ,aAAa,SAAS,EAChC,sBAAqB,KAAK;GACxB,MAAM;GACN,SAAS,wBAAwB,QAAQ,aAAa,KAAK,MAAM,EAAE,WAAW,eAAe,CAAC,KAAK,KAAK;GACxG,UAAU;GACV,UAAU,EAAE,WAAW,eAAe;GACtC,SAAS,EAAE,cAAc,kCAAkC;GAC5D,CAAC;AAEJ,MAAI,QAAQ,aAAa,SAAS,EAChC,sBAAqB,KAAK;GACxB,MAAM;GACN,SAAS,wBAAwB,QAAQ,aAAa,KAAK,MAAM,EAAE,WAAW,eAAe,CAAC,KAAK,KAAK;GACxG,UAAU;GACV,UAAU,EAAE,WAAW,eAAe;GACtC,SAAS,EAAE,cAAc,kCAAkC;GAC5D,CAAC;AAEJ,MAAI,UAAU,aAAa,SAAS,EAClC,sBAAqB,KAAK;GACxB,MAAM;GACN,SAAS,0BAA0B,UAAU,aAAa,KAAK,MAAM,EAAE,WAAW,eAAe,CAAC,KAAK,KAAK;GAC5G,UAAU;GACV,UAAU,EAAE,WAAW,eAAe;GACtC,SAAS,EAAE,cAAc,oCAAoC;GAC9D,CAAC;AAGJ,MAAI,qBAAqB,SAAS,EAChC,QAAO;GACL,SAAS;GACT,SAAS;GACT,WAAW;GACX,OAAO;IACL,cAAc;IACd,UAAU;IACV,YAAY;IACZ,QAAQ;IACR,YAAY;IACZ,WAAW,qBAAqB;IACjC;GACF;EAGH,MAAM,UAAU,aAAa,QAAQ,SAAS,QAAQ,SAAS,UAAU,QAAQ;AAMjF,SAFe,kBAAkB,SAFf,gBAAgB,SAAS,QAAQ,EAEE,QAAQ;UAGtD,OAAO;EACd,MAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AAC3E,SAAO;GACL,SAAS;GACT,SAAS;GACT,WAAW,CACT;IACE,MAAM;IACN,SAAS,iBAAiB;IAC1B,UAAU;IACV,UAAU,EAAE,WAAW,SAAS;IAChC,SAAS,EAAE,cAAc;IAC1B,CACF;GACD,OAAO;IACL,cAAc;IACd,UAAU;IACV,YAAY;IACZ,QAAQ;IACR,YAAY;IACZ,WAAW;IACZ;GACF"}
1
+ {"version":3,"file":"driver.js","names":["syntaxErrorConflicts: MergeConflict[]"],"sources":["../../src/merge/driver.ts"],"sourcesContent":["import { parseDocument } from \"../parser.node.js\";\nimport { extractSourceFile } from \"../ast/extract.js\";\nimport type { MergeResult } from \"./merge-result-builder.js\";\nimport type { MergeConflict, ConflictRule } from \"./conflict-detector.js\";\nimport { matchEntries } from \"./entry-matcher.js\";\nimport { detectConflicts } from \"./conflict-detector.js\";\nimport { buildMergedResult } from \"./merge-result-builder.js\";\n\n/**\n * Options for the merge driver\n */\nexport interface MergeOptions {\n /**\n * Conflict marker style\n * - \"git\": Standard Git style (ours/theirs)\n * - \"diff3\": Include base section\n */\n markerStyle?: \"git\" | \"diff3\";\n\n /**\n * Whether to include base in markers (diff3 style)\n * Deprecated: Use markerStyle: \"diff3\" instead\n */\n showBase?: boolean;\n\n /**\n * Custom conflict detection rules\n * Applied after default rules\n */\n conflictRules?: ConflictRule[];\n}\n\n/**\n * Perform three-way merge of thalo files\n *\n * This is the main entry point for the merge driver.\n * It parses all three versions, matches entries, detects conflicts,\n * and produces a merged result.\n *\n * This function is synchronous but requires the parser to be initialized first.\n * Call `await initParser()` from `@rejot-dev/thalo/node` before using this function.\n * Internally uses `parseDocument` which depends on the initialized parser.\n *\n * @param base - Base version content (common ancestor)\n * @param ours - Our version content (local changes)\n * @param theirs - Their version content (incoming changes)\n * @param options - Merge options\n * @returns MergeResult with merged content and conflict information\n * @throws Error if the parser has not been initialized\n *\n * @example\n * ```typescript\n * import { initParser } from \"@rejot-dev/thalo/node\";\n * import { mergeThaloFiles } from \"@rejot-dev/thalo\";\n *\n * // Initialize the parser first (required)\n * await initParser();\n *\n * const base = '2026-01-01T00:00Z define-entity lore \"Lore\"';\n * const ours = base + '\\n2026-01-02T00:00Z create lore \"My entry\" ^entry1';\n * const theirs = base + '\\n2026-01-03T00:00Z create lore \"Their entry\" ^entry2';\n *\n * const result = mergeThaloFiles(base, ours, theirs);\n * console.log(result.success); // true\n * console.log(result.content); // Merged content with both entries\n * ```\n */\nexport function mergeThaloFiles(\n base: string,\n ours: string,\n theirs: string,\n options: MergeOptions = {},\n): MergeResult {\n try {\n const baseDoc = parseDocument(base, { fileType: \"thalo\" });\n const oursDoc = parseDocument(ours, { fileType: \"thalo\" });\n const theirsDoc = parseDocument(theirs, { fileType: \"thalo\" });\n\n const baseAst =\n baseDoc.blocks.length > 0\n ? extractSourceFile(baseDoc.blocks[0].tree.rootNode)\n : { entries: [], syntaxErrors: [] };\n const oursAst =\n oursDoc.blocks.length > 0\n ? extractSourceFile(oursDoc.blocks[0].tree.rootNode)\n : { entries: [], syntaxErrors: [] };\n const theirsAst =\n theirsDoc.blocks.length > 0\n ? extractSourceFile(theirsDoc.blocks[0].tree.rootNode)\n : { entries: [], syntaxErrors: [] };\n\n // Surface syntax errors as parse-error conflicts\n const syntaxErrorConflicts: MergeConflict[] = [];\n if (baseAst.syntaxErrors.length > 0) {\n syntaxErrorConflicts.push({\n type: \"parse-error\",\n message: `Parse error in base: ${baseAst.syntaxErrors.map((e) => e.message || \"syntax error\").join(\", \")}`,\n location: 0,\n identity: { entryType: \"parse-error\" },\n context: { errorMessage: \"base version has syntax errors\" },\n });\n }\n if (oursAst.syntaxErrors.length > 0) {\n syntaxErrorConflicts.push({\n type: \"parse-error\",\n message: `Parse error in ours: ${oursAst.syntaxErrors.map((e) => e.message || \"syntax error\").join(\", \")}`,\n location: 0,\n identity: { entryType: \"parse-error\" },\n context: { errorMessage: \"ours version has syntax errors\" },\n });\n }\n if (theirsAst.syntaxErrors.length > 0) {\n syntaxErrorConflicts.push({\n type: \"parse-error\",\n message: `Parse error in theirs: ${theirsAst.syntaxErrors.map((e) => e.message || \"syntax error\").join(\", \")}`,\n location: 0,\n identity: { entryType: \"parse-error\" },\n context: { errorMessage: \"theirs version has syntax errors\" },\n });\n }\n\n if (syntaxErrorConflicts.length > 0) {\n return {\n success: false,\n content: ours,\n conflicts: syntaxErrorConflicts,\n stats: {\n totalEntries: 0,\n oursOnly: 0,\n theirsOnly: 0,\n common: 0,\n autoMerged: 0,\n conflicts: syntaxErrorConflicts.length,\n },\n };\n }\n\n const matches = matchEntries(baseAst.entries, oursAst.entries, theirsAst.entries);\n\n const conflicts = detectConflicts(matches, options);\n\n const result = buildMergedResult(matches, conflicts, options);\n\n return result;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n return {\n success: false,\n content: ours,\n conflicts: [\n {\n type: \"merge-error\",\n message: `Merge failed: ${errorMessage}`,\n location: 0,\n identity: { entryType: \"error\" },\n context: { errorMessage },\n },\n ],\n stats: {\n totalEntries: 0,\n oursOnly: 0,\n theirsOnly: 0,\n common: 0,\n autoMerged: 0,\n conflicts: 1,\n },\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmEA,SAAgB,gBACd,MACA,MACA,QACA,UAAwB,EAAE,EACb;AACb,KAAI;EACF,MAAM,UAAU,cAAc,MAAM,EAAE,UAAU,SAAS,CAAC;EAC1D,MAAM,UAAU,cAAc,MAAM,EAAE,UAAU,SAAS,CAAC;EAC1D,MAAM,YAAY,cAAc,QAAQ,EAAE,UAAU,SAAS,CAAC;EAE9D,MAAM,UACJ,QAAQ,OAAO,SAAS,IACpB,kBAAkB,QAAQ,OAAO,GAAG,KAAK,SAAS,GAClD;GAAE,SAAS,EAAE;GAAE,cAAc,EAAE;GAAE;EACvC,MAAM,UACJ,QAAQ,OAAO,SAAS,IACpB,kBAAkB,QAAQ,OAAO,GAAG,KAAK,SAAS,GAClD;GAAE,SAAS,EAAE;GAAE,cAAc,EAAE;GAAE;EACvC,MAAM,YACJ,UAAU,OAAO,SAAS,IACtB,kBAAkB,UAAU,OAAO,GAAG,KAAK,SAAS,GACpD;GAAE,SAAS,EAAE;GAAE,cAAc,EAAE;GAAE;EAGvC,MAAMA,uBAAwC,EAAE;AAChD,MAAI,QAAQ,aAAa,SAAS,EAChC,sBAAqB,KAAK;GACxB,MAAM;GACN,SAAS,wBAAwB,QAAQ,aAAa,KAAK,MAAM,EAAE,WAAW,eAAe,CAAC,KAAK,KAAK;GACxG,UAAU;GACV,UAAU,EAAE,WAAW,eAAe;GACtC,SAAS,EAAE,cAAc,kCAAkC;GAC5D,CAAC;AAEJ,MAAI,QAAQ,aAAa,SAAS,EAChC,sBAAqB,KAAK;GACxB,MAAM;GACN,SAAS,wBAAwB,QAAQ,aAAa,KAAK,MAAM,EAAE,WAAW,eAAe,CAAC,KAAK,KAAK;GACxG,UAAU;GACV,UAAU,EAAE,WAAW,eAAe;GACtC,SAAS,EAAE,cAAc,kCAAkC;GAC5D,CAAC;AAEJ,MAAI,UAAU,aAAa,SAAS,EAClC,sBAAqB,KAAK;GACxB,MAAM;GACN,SAAS,0BAA0B,UAAU,aAAa,KAAK,MAAM,EAAE,WAAW,eAAe,CAAC,KAAK,KAAK;GAC5G,UAAU;GACV,UAAU,EAAE,WAAW,eAAe;GACtC,SAAS,EAAE,cAAc,oCAAoC;GAC9D,CAAC;AAGJ,MAAI,qBAAqB,SAAS,EAChC,QAAO;GACL,SAAS;GACT,SAAS;GACT,WAAW;GACX,OAAO;IACL,cAAc;IACd,UAAU;IACV,YAAY;IACZ,QAAQ;IACR,YAAY;IACZ,WAAW,qBAAqB;IACjC;GACF;EAGH,MAAM,UAAU,aAAa,QAAQ,SAAS,QAAQ,SAAS,UAAU,QAAQ;AAMjF,SAFe,kBAAkB,SAFf,gBAAgB,SAAS,QAAQ,EAEE,QAAQ;UAGtD,OAAO;EACd,MAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AAC3E,SAAO;GACL,SAAS;GACT,SAAS;GACT,WAAW,CACT;IACE,MAAM;IACN,SAAS,iBAAiB;IAC1B,UAAU;IACV,UAAU,EAAE,WAAW,SAAS;IAChC,SAAS,EAAE,cAAc;IAC1B,CACF;GACD,OAAO;IACL,cAAc;IACd,UAAU;IACV,YAAY;IACZ,QAAQ;IACR,YAAY;IACZ,WAAW;IACZ;GACF"}
@@ -0,0 +1,66 @@
1
+ import { FileType, GenericTree, ParseOptions, ParsedBlock as ParsedBlock$1, ParsedDocument as ParsedDocument$1, ThaloParser } from "./parser.shared.js";
2
+ import { Workspace } from "./model/workspace.js";
3
+
4
+ //#region src/parser.node.d.ts
5
+
6
+ type ParsedBlock = ParsedBlock$1<GenericTree>;
7
+ type ParsedDocument = ParsedDocument$1<GenericTree>;
8
+ /**
9
+ * Check if the parser has been initialized.
10
+ */
11
+ declare function isInitialized(): boolean;
12
+ /**
13
+ * Check if using native bindings (vs WASM fallback).
14
+ * Only valid after `initParser()` has been called.
15
+ */
16
+ declare function isUsingNative(): boolean;
17
+ /**
18
+ * Initialize the parser, trying native first then falling back to WASM.
19
+ *
20
+ * This must be called (and awaited) once before using `createParser()` or
21
+ * `createWorkspace()`. Multiple calls are safe and will return immediately
22
+ * if already initialized.
23
+ *
24
+ * @returns Promise that resolves when parser is ready
25
+ * @throws Error if both native and WASM initialization fail
26
+ */
27
+ declare function initParser(): Promise<void>;
28
+ /**
29
+ * Create a ThaloParser instance.
30
+ *
31
+ * Note: `initParser()` must be called first.
32
+ *
33
+ * @throws Error if `initParser()` has not been called
34
+ */
35
+ declare function createParser(): ThaloParser<GenericTree>;
36
+ /**
37
+ * Create a Workspace with the initialized parser.
38
+ *
39
+ * Note: `initParser()` must be called first.
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * import { initParser, createWorkspace } from "@rejot-dev/thalo/node";
44
+ *
45
+ * await initParser();
46
+ * const workspace = createWorkspace();
47
+ * workspace.addDocument(source, { filename: "test.thalo" });
48
+ * ```
49
+ *
50
+ * @throws Error if `initParser()` has not been called
51
+ */
52
+ declare function createWorkspace(): Workspace;
53
+ /**
54
+ * Parse a document using the initialized parser.
55
+ *
56
+ * Note: `initParser()` must be called first.
57
+ *
58
+ * @param source - The source code to parse
59
+ * @param options - Parse options
60
+ * @returns A parsed document
61
+ * @throws Error if `initParser()` has not been called
62
+ */
63
+ declare function parseDocument(source: string, options?: ParseOptions): ParsedDocument$1<GenericTree>;
64
+ //#endregion
65
+ export { type FileType, type ParseOptions, ParsedBlock, ParsedDocument, type ThaloParser, Workspace, createParser, createWorkspace, initParser, isInitialized, isUsingNative, parseDocument };
66
+ //# sourceMappingURL=parser.node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.node.d.ts","names":[],"sources":["../src/parser.node.ts"],"sourcesContent":[],"mappings":";;;;;KAqCY,WAAA,GAAc,cAAmB;KACjC,cAAA,GAAiB,iBAAsB;;;;iBAenC,aAAA,CAAA;;;;;iBAQA,aAAA,CAAA;;;;;;;;;;;iBAcM,UAAA,CAAA,GAAc;;;;;;;;iBAkFpB,YAAA,CAAA,GAAgB,YAAY;;;;;;;;;;;;;;;;;iBAyB5B,eAAA,CAAA,GAAmB;;;;;;;;;;;iBAsBnB,aAAA,2BAEJ,eACT,iBAAsB"}
@@ -0,0 +1,118 @@
1
+ import { createThaloParser } from "./parser.shared.js";
2
+ import { Workspace } from "./model/workspace.js";
3
+
4
+ //#region src/parser.node.ts
5
+ let parserFactory = null;
6
+ let usingNative = false;
7
+ /**
8
+ * Check if the parser has been initialized.
9
+ */
10
+ function isInitialized() {
11
+ return parserFactory !== null;
12
+ }
13
+ /**
14
+ * Check if using native bindings (vs WASM fallback).
15
+ * Only valid after `initParser()` has been called.
16
+ */
17
+ function isUsingNative() {
18
+ return usingNative;
19
+ }
20
+ /**
21
+ * Initialize the parser, trying native first then falling back to WASM.
22
+ *
23
+ * This must be called (and awaited) once before using `createParser()` or
24
+ * `createWorkspace()`. Multiple calls are safe and will return immediately
25
+ * if already initialized.
26
+ *
27
+ * @returns Promise that resolves when parser is ready
28
+ * @throws Error if both native and WASM initialization fail
29
+ */
30
+ async function initParser() {
31
+ if (parserFactory) return;
32
+ try {
33
+ const { default: Parser } = await import("tree-sitter");
34
+ const { default: thalo } = await import("@rejot-dev/tree-sitter-thalo");
35
+ thalo.nodeTypeInfo ??= [];
36
+ new Parser().setLanguage(thalo);
37
+ parserFactory = () => {
38
+ const parser = new Parser();
39
+ parser.setLanguage(thalo);
40
+ return createThaloParser(parser);
41
+ };
42
+ usingNative = true;
43
+ return;
44
+ } catch {}
45
+ try {
46
+ const { Parser, Language } = await import("web-tree-sitter");
47
+ const { readFileSync } = await import("node:fs");
48
+ const { createRequire } = await import("node:module");
49
+ const require = createRequire(import.meta.url);
50
+ const treeSitterWasmPath = require.resolve("web-tree-sitter/tree-sitter.wasm");
51
+ const languageWasmPath = require.resolve("@rejot-dev/tree-sitter-thalo/tree-sitter-thalo.wasm");
52
+ const treeSitterWasm = readFileSync(treeSitterWasmPath);
53
+ const languageWasm = readFileSync(languageWasmPath);
54
+ await Parser.init({ wasmBinary: treeSitterWasm });
55
+ const language = await Language.load(languageWasm);
56
+ parserFactory = () => {
57
+ const parser = new Parser();
58
+ parser.setLanguage(language);
59
+ return createThaloParser(parser);
60
+ };
61
+ usingNative = false;
62
+ return;
63
+ } catch (wasmError) {
64
+ throw new Error(`Failed to initialize thalo parser. Native tree-sitter bindings are not available for your platform, and WASM fallback also failed: ${wasmError instanceof Error ? wasmError.message : wasmError}`);
65
+ }
66
+ }
67
+ let cachedParser = null;
68
+ /**
69
+ * Create a ThaloParser instance.
70
+ *
71
+ * Note: `initParser()` must be called first.
72
+ *
73
+ * @throws Error if `initParser()` has not been called
74
+ */
75
+ function createParser() {
76
+ if (!parserFactory) throw new Error("Parser not initialized. Call `await initParser()` before using createParser().");
77
+ return parserFactory();
78
+ }
79
+ /**
80
+ * Create a Workspace with the initialized parser.
81
+ *
82
+ * Note: `initParser()` must be called first.
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * import { initParser, createWorkspace } from "@rejot-dev/thalo/node";
87
+ *
88
+ * await initParser();
89
+ * const workspace = createWorkspace();
90
+ * workspace.addDocument(source, { filename: "test.thalo" });
91
+ * ```
92
+ *
93
+ * @throws Error if `initParser()` has not been called
94
+ */
95
+ function createWorkspace() {
96
+ if (!parserFactory) throw new Error("Parser not initialized. Call `await initParser()` before using createWorkspace().");
97
+ if (!cachedParser) cachedParser = parserFactory();
98
+ return new Workspace(cachedParser);
99
+ }
100
+ /**
101
+ * Parse a document using the initialized parser.
102
+ *
103
+ * Note: `initParser()` must be called first.
104
+ *
105
+ * @param source - The source code to parse
106
+ * @param options - Parse options
107
+ * @returns A parsed document
108
+ * @throws Error if `initParser()` has not been called
109
+ */
110
+ function parseDocument(source, options) {
111
+ if (!parserFactory) throw new Error("Parser not initialized. Call `await initParser()` before using parseDocument().");
112
+ if (!cachedParser) cachedParser = parserFactory();
113
+ return cachedParser.parseDocument(source, options);
114
+ }
115
+
116
+ //#endregion
117
+ export { Workspace, createParser, createWorkspace, initParser, isInitialized, isUsingNative, parseDocument };
118
+ //# sourceMappingURL=parser.node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.node.js","names":["parserFactory: (() => ThaloParser<GenericTree>) | null","cachedParser: ThaloParser<GenericTree> | null"],"sources":["../src/parser.node.ts"],"sourcesContent":["/**\n * Node.js parser with automatic fallback from native to WASM.\n *\n * This module provides a unified API for Node.js environments that:\n * 1. Tries to use native tree-sitter bindings (fastest)\n * 2. Falls back to web-tree-sitter WASM if native bindings aren't available\n *\n * The fallback is transparent - callers don't need to know which implementation\n * is being used. The only requirement is that `initParser()` must be called\n * (and awaited) before using `createParser()` or `createWorkspace()`.\n *\n * @example\n * ```typescript\n * import { initParser, createWorkspace } from \"@rejot-dev/thalo/node\";\n *\n * // Initialize once at startup (required)\n * await initParser();\n *\n * // Then use synchronously\n * const workspace = createWorkspace();\n * workspace.addDocument(source, { filename: \"test.thalo\" });\n * ```\n */\n\nimport {\n createThaloParser,\n type ThaloParser,\n type ParsedBlock as GenericParsedBlock,\n type ParsedDocument as GenericParsedDocument,\n type FileType,\n type ParseOptions,\n type GenericTree,\n} from \"./parser.shared.js\";\nimport type { Language as NativeLanguage } from \"tree-sitter\";\nimport { Workspace } from \"./model/workspace.js\";\n\n// Re-export shared types\nexport type ParsedBlock = GenericParsedBlock<GenericTree>;\nexport type ParsedDocument = GenericParsedDocument<GenericTree>;\nexport type { FileType, ParseOptions, ThaloParser };\n\n// Re-export Workspace for convenience\nexport { Workspace } from \"./model/workspace.js\";\n\n// Parser factory function - set after initialization\nlet parserFactory: (() => ThaloParser<GenericTree>) | null = null;\n\n// Track which implementation is being used\nlet usingNative = false;\n\n/**\n * Check if the parser has been initialized.\n */\nexport function isInitialized(): boolean {\n return parserFactory !== null;\n}\n\n/**\n * Check if using native bindings (vs WASM fallback).\n * Only valid after `initParser()` has been called.\n */\nexport function isUsingNative(): boolean {\n return usingNative;\n}\n\n/**\n * Initialize the parser, trying native first then falling back to WASM.\n *\n * This must be called (and awaited) once before using `createParser()` or\n * `createWorkspace()`. Multiple calls are safe and will return immediately\n * if already initialized.\n *\n * @returns Promise that resolves when parser is ready\n * @throws Error if both native and WASM initialization fail\n */\nexport async function initParser(): Promise<void> {\n // Already initialized\n if (parserFactory) {\n return;\n }\n\n // Try native first\n try {\n const { default: Parser } = await import(\"tree-sitter\");\n const { default: thalo } = await import(\"@rejot-dev/tree-sitter-thalo\");\n\n // Ensure nodeTypeInfo is an array\n thalo.nodeTypeInfo ??= [];\n\n // Test that we can actually create a parser (this will fail if native bindings are broken)\n const testParser = new Parser();\n testParser.setLanguage(thalo as unknown as NativeLanguage);\n\n // Native works! Set up the factory\n parserFactory = () => {\n const parser = new Parser();\n parser.setLanguage(thalo as unknown as NativeLanguage);\n return createThaloParser(parser);\n };\n usingNative = true;\n return;\n } catch {\n // Native failed, try WASM fallback\n }\n\n // Fall back to WASM\n try {\n const { Parser, Language } = await import(\"web-tree-sitter\");\n\n // Dynamically import Node.js APIs for WASM file loading\n const { readFileSync } = await import(\"node:fs\");\n const { createRequire } = await import(\"node:module\");\n const require = createRequire(import.meta.url);\n\n // Resolve WASM file paths from installed dependencies\n const treeSitterWasmPath = require.resolve(\"web-tree-sitter/tree-sitter.wasm\");\n const languageWasmPath = require.resolve(\"@rejot-dev/tree-sitter-thalo/tree-sitter-thalo.wasm\");\n\n // Load WASM files\n const treeSitterWasm = readFileSync(treeSitterWasmPath);\n const languageWasm = readFileSync(languageWasmPath);\n\n // Initialize web-tree-sitter\n await Parser.init({\n wasmBinary: treeSitterWasm,\n });\n\n // Load the thalo language\n const language = await Language.load(languageWasm);\n\n // WASM works! Set up the factory\n parserFactory = () => {\n const parser = new Parser();\n parser.setLanguage(language);\n return createThaloParser(parser);\n };\n usingNative = false;\n return;\n } catch (wasmError) {\n throw new Error(\n `Failed to initialize thalo parser. ` +\n `Native tree-sitter bindings are not available for your platform, ` +\n `and WASM fallback also failed: ${wasmError instanceof Error ? wasmError.message : wasmError}`,\n );\n }\n}\n\n// Cached parser instance for createWorkspace\nlet cachedParser: ThaloParser<GenericTree> | null = null;\n\n/**\n * Create a ThaloParser instance.\n *\n * Note: `initParser()` must be called first.\n *\n * @throws Error if `initParser()` has not been called\n */\nexport function createParser(): ThaloParser<GenericTree> {\n if (!parserFactory) {\n throw new Error(\n \"Parser not initialized. Call `await initParser()` before using createParser().\",\n );\n }\n return parserFactory();\n}\n\n/**\n * Create a Workspace with the initialized parser.\n *\n * Note: `initParser()` must be called first.\n *\n * @example\n * ```typescript\n * import { initParser, createWorkspace } from \"@rejot-dev/thalo/node\";\n *\n * await initParser();\n * const workspace = createWorkspace();\n * workspace.addDocument(source, { filename: \"test.thalo\" });\n * ```\n *\n * @throws Error if `initParser()` has not been called\n */\nexport function createWorkspace(): Workspace {\n if (!parserFactory) {\n throw new Error(\n \"Parser not initialized. Call `await initParser()` before using createWorkspace().\",\n );\n }\n if (!cachedParser) {\n cachedParser = parserFactory();\n }\n return new Workspace(cachedParser);\n}\n\n/**\n * Parse a document using the initialized parser.\n *\n * Note: `initParser()` must be called first.\n *\n * @param source - The source code to parse\n * @param options - Parse options\n * @returns A parsed document\n * @throws Error if `initParser()` has not been called\n */\nexport function parseDocument(\n source: string,\n options?: ParseOptions,\n): GenericParsedDocument<GenericTree> {\n if (!parserFactory) {\n throw new Error(\n \"Parser not initialized. Call `await initParser()` before using parseDocument().\",\n );\n }\n if (!cachedParser) {\n cachedParser = parserFactory();\n }\n return cachedParser.parseDocument(source, options);\n}\n"],"mappings":";;;;AA6CA,IAAIA,gBAAyD;AAG7D,IAAI,cAAc;;;;AAKlB,SAAgB,gBAAyB;AACvC,QAAO,kBAAkB;;;;;;AAO3B,SAAgB,gBAAyB;AACvC,QAAO;;;;;;;;;;;;AAaT,eAAsB,aAA4B;AAEhD,KAAI,cACF;AAIF,KAAI;EACF,MAAM,EAAE,SAAS,WAAW,MAAM,OAAO;EACzC,MAAM,EAAE,SAAS,UAAU,MAAM,OAAO;AAGxC,QAAM,iBAAiB,EAAE;AAIzB,EADmB,IAAI,QAAQ,CACpB,YAAY,MAAmC;AAG1D,wBAAsB;GACpB,MAAM,SAAS,IAAI,QAAQ;AAC3B,UAAO,YAAY,MAAmC;AACtD,UAAO,kBAAkB,OAAO;;AAElC,gBAAc;AACd;SACM;AAKR,KAAI;EACF,MAAM,EAAE,QAAQ,aAAa,MAAM,OAAO;EAG1C,MAAM,EAAE,iBAAiB,MAAM,OAAO;EACtC,MAAM,EAAE,kBAAkB,MAAM,OAAO;EACvC,MAAM,UAAU,cAAc,OAAO,KAAK,IAAI;EAG9C,MAAM,qBAAqB,QAAQ,QAAQ,mCAAmC;EAC9E,MAAM,mBAAmB,QAAQ,QAAQ,sDAAsD;EAG/F,MAAM,iBAAiB,aAAa,mBAAmB;EACvD,MAAM,eAAe,aAAa,iBAAiB;AAGnD,QAAM,OAAO,KAAK,EAChB,YAAY,gBACb,CAAC;EAGF,MAAM,WAAW,MAAM,SAAS,KAAK,aAAa;AAGlD,wBAAsB;GACpB,MAAM,SAAS,IAAI,QAAQ;AAC3B,UAAO,YAAY,SAAS;AAC5B,UAAO,kBAAkB,OAAO;;AAElC,gBAAc;AACd;UACO,WAAW;AAClB,QAAM,IAAI,MACR,sIAEoC,qBAAqB,QAAQ,UAAU,UAAU,YACtF;;;AAKL,IAAIC,eAAgD;;;;;;;;AASpD,SAAgB,eAAyC;AACvD,KAAI,CAAC,cACH,OAAM,IAAI,MACR,iFACD;AAEH,QAAO,eAAe;;;;;;;;;;;;;;;;;;AAmBxB,SAAgB,kBAA6B;AAC3C,KAAI,CAAC,cACH,OAAM,IAAI,MACR,oFACD;AAEH,KAAI,CAAC,aACH,gBAAe,eAAe;AAEhC,QAAO,IAAI,UAAU,aAAa;;;;;;;;;;;;AAapC,SAAgB,cACd,QACA,SACoC;AACpC,KAAI,CAAC,cACH,OAAM,IAAI,MACR,kFACD;AAEH,KAAI,CAAC,aACH,gBAAe,eAAe;AAEhC,QAAO,aAAa,cAAc,QAAQ,QAAQ"}
@@ -1,10 +1,10 @@
1
1
  import { entryMatchesQuery } from "../query.js";
2
2
  import { UncommittedChangesError } from "./change-tracker.js";
3
3
  import { extractSourceFile } from "../../ast/extract.js";
4
- import { parseDocument } from "../../parser.js";
5
4
  import { getEntryIdentity, serializeIdentity } from "../../merge/entry-matcher.js";
6
5
  import { entriesEqual } from "../../merge/entry-merger.js";
7
6
  import { commitExists, detectGitContext, getBlameCommitsForLineRange, getBlameIgnoreRevs, getCurrentCommit, getFileAtCommit, getFilesChangedSince, getUncommittedFiles, isCommitAncestorOf } from "../../git/git.js";
7
+ import { parseDocument } from "../../parser.js";
8
8
 
9
9
  //#region src/services/change-tracker/git-tracker.ts
10
10
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rejot-dev/thalo",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -29,6 +29,11 @@
29
29
  "types": "./dist/parser.web.d.ts",
30
30
  "default": "./dist/parser.web.js"
31
31
  },
32
+ "./node": {
33
+ "development": "./src/parser.node.ts",
34
+ "types": "./dist/parser.node.d.ts",
35
+ "default": "./dist/parser.node.js"
36
+ },
32
37
  "./services/semantic-tokens": {
33
38
  "development": "./src/services/semantic-tokens.ts",
34
39
  "types": "./dist/services/semantic-tokens.d.ts",
@@ -88,12 +93,17 @@
88
93
  "development": "./src/files.ts",
89
94
  "types": "./dist/files.d.ts",
90
95
  "default": "./dist/files.js"
96
+ },
97
+ "./api": {
98
+ "development": "./src/api.ts",
99
+ "types": "./dist/api.d.ts",
100
+ "default": "./dist/api.js"
91
101
  }
92
102
  },
93
103
  "main": "./dist/mod.js",
94
104
  "types": "./dist/mod.d.ts",
95
105
  "dependencies": {
96
- "@rejot-dev/tree-sitter-thalo": "0.1.0"
106
+ "@rejot-dev/tree-sitter-thalo": "0.2.1"
97
107
  },
98
108
  "peerDependencies": {
99
109
  "tree-sitter": "^0.25.0",