@joycodetech/qmd-ja 2.5.3-ja.3

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +821 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1143 -0
  4. package/bin/qmd-ja +162 -0
  5. package/dist/ast.d.ts +65 -0
  6. package/dist/ast.js +334 -0
  7. package/dist/bench/bench.d.ts +23 -0
  8. package/dist/bench/bench.js +280 -0
  9. package/dist/bench/score.d.ts +33 -0
  10. package/dist/bench/score.js +88 -0
  11. package/dist/bench/types.d.ts +80 -0
  12. package/dist/bench/types.js +8 -0
  13. package/dist/cli/formatter.d.ts +120 -0
  14. package/dist/cli/formatter.js +355 -0
  15. package/dist/cli/qmd.d.ts +43 -0
  16. package/dist/cli/qmd.js +4179 -0
  17. package/dist/collections.d.ts +166 -0
  18. package/dist/collections.js +410 -0
  19. package/dist/db.d.ts +44 -0
  20. package/dist/db.js +75 -0
  21. package/dist/index.d.ts +230 -0
  22. package/dist/index.js +242 -0
  23. package/dist/llm.d.ts +500 -0
  24. package/dist/llm.js +1615 -0
  25. package/dist/maintenance.d.ts +23 -0
  26. package/dist/maintenance.js +37 -0
  27. package/dist/mcp/server.d.ts +24 -0
  28. package/dist/mcp/server.js +702 -0
  29. package/dist/paths.d.ts +1 -0
  30. package/dist/paths.js +4 -0
  31. package/dist/store.d.ts +1002 -0
  32. package/dist/store.js +4208 -0
  33. package/models/vaporetto-bccwj.model +0 -0
  34. package/package.json +130 -0
  35. package/scripts/build.mjs +30 -0
  36. package/scripts/check-package-grammars.mjs +29 -0
  37. package/scripts/package-smoke.mjs +65 -0
  38. package/scripts/test-all.mjs +38 -0
  39. package/skills/qmd/SKILL.md +295 -0
  40. package/skills/qmd/references/mcp-setup.md +102 -0
  41. package/skills/release/SKILL.md +139 -0
  42. package/skills/release/scripts/install-hooks.sh +38 -0
  43. package/vendor/vaporetto-node-wasm/LICENSE +22 -0
  44. package/vendor/vaporetto-node-wasm/package.json +11 -0
  45. package/vendor/vaporetto-node-wasm/vaporetto_node_wasm.d.ts +19 -0
  46. package/vendor/vaporetto-node-wasm/vaporetto_node_wasm.js +202 -0
  47. package/vendor/vaporetto-node-wasm/vaporetto_node_wasm_bg.wasm +0 -0
  48. package/vendor/vaporetto-node-wasm/vaporetto_node_wasm_bg.wasm.d.ts +13 -0
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Collections configuration management
3
+ *
4
+ * This module manages the YAML-based collection configuration at ~/.config/qmd/index.yml.
5
+ * Collections define which directories to index and their associated contexts.
6
+ */
7
+ /**
8
+ * Context definitions for a collection
9
+ * Key is path prefix (e.g., "/", "/2024", "/Board of Directors")
10
+ * Value is the context description
11
+ */
12
+ export type ContextMap = Record<string, string>;
13
+ /**
14
+ * A single collection configuration
15
+ */
16
+ export interface Collection {
17
+ path: string;
18
+ pattern: string;
19
+ ignore?: string[];
20
+ context?: ContextMap;
21
+ update?: string;
22
+ includeByDefault?: boolean;
23
+ }
24
+ /**
25
+ * Model configuration for embedding, reranking, and generation
26
+ */
27
+ export interface ModelsConfig {
28
+ embed?: string;
29
+ rerank?: string;
30
+ generate?: string;
31
+ }
32
+ /**
33
+ * The complete configuration file structure
34
+ */
35
+ export interface CollectionConfig {
36
+ global_context?: string;
37
+ editor_uri?: string;
38
+ editor_uri_template?: string;
39
+ collections: Record<string, Collection>;
40
+ models?: ModelsConfig;
41
+ }
42
+ /**
43
+ * Collection with its name (for return values)
44
+ */
45
+ export interface NamedCollection extends Collection {
46
+ name: string;
47
+ }
48
+ /**
49
+ * Set the config source for SDK mode.
50
+ * - File path: load/save from a specific YAML file
51
+ * - Inline config: use an in-memory CollectionConfig (saveConfig updates in place, no file I/O)
52
+ * - undefined: reset to default file-based config
53
+ */
54
+ export declare function setConfigSource(source?: {
55
+ configPath?: string;
56
+ config?: CollectionConfig;
57
+ }): void;
58
+ /**
59
+ * Set the current index name for config file lookup
60
+ * Config file will be ~/.config/qmd/{indexName}.yml
61
+ */
62
+ export declare function setConfigIndexName(name: string): void;
63
+ /**
64
+ * Find a project-local QMD config by walking upward from startDir.
65
+ * The local config lives at .qmd/index.yaml or .qmd/index.yml and,
66
+ * when used by the CLI, keeps both config and index DB writes inside
67
+ * the project instead of the global ~/.config / ~/.cache locations.
68
+ */
69
+ export declare function findLocalConfigPath(startDir?: string): string | undefined;
70
+ /** Return the local SQLite index path paired with a local .qmd/index.yaml file. */
71
+ export declare function getLocalDbPath(configPath: string): string;
72
+ /**
73
+ * Load configuration from the configured source.
74
+ * - Inline config: returns the in-memory object directly
75
+ * - File-based: reads from YAML file (default ~/.config/qmd/index.yml)
76
+ * Returns empty config if file doesn't exist
77
+ */
78
+ export declare function loadConfig(): CollectionConfig;
79
+ /**
80
+ * Save configuration to the configured source.
81
+ * - Inline config: updates the in-memory object (no file I/O)
82
+ * - File-based: writes to YAML file (default ~/.config/qmd/index.yml)
83
+ */
84
+ export declare function saveConfig(config: CollectionConfig): void;
85
+ /**
86
+ * Get a specific collection by name
87
+ * Returns null if not found
88
+ */
89
+ export declare function getCollection(name: string): NamedCollection | null;
90
+ /**
91
+ * List all collections
92
+ */
93
+ export declare function listCollections(): NamedCollection[];
94
+ /**
95
+ * Get collections that are included by default in queries
96
+ */
97
+ export declare function getDefaultCollections(): NamedCollection[];
98
+ /**
99
+ * Get collection names that are included by default
100
+ */
101
+ export declare function getDefaultCollectionNames(): string[];
102
+ /**
103
+ * Update a collection's settings
104
+ */
105
+ export declare function updateCollectionSettings(name: string, settings: {
106
+ update?: string | null;
107
+ includeByDefault?: boolean;
108
+ }): boolean;
109
+ /**
110
+ * Add or update a collection
111
+ */
112
+ export declare function addCollection(name: string, path: string, pattern?: string): void;
113
+ /**
114
+ * Remove a collection
115
+ */
116
+ export declare function removeCollection(name: string): boolean;
117
+ /**
118
+ * Rename a collection
119
+ */
120
+ export declare function renameCollection(oldName: string, newName: string): boolean;
121
+ /**
122
+ * Get global context
123
+ */
124
+ export declare function getGlobalContext(): string | undefined;
125
+ /**
126
+ * Set global context
127
+ */
128
+ export declare function setGlobalContext(context: string | undefined): void;
129
+ /**
130
+ * Get all contexts for a collection
131
+ */
132
+ export declare function getContexts(collectionName: string): ContextMap | undefined;
133
+ /**
134
+ * Add or update a context for a specific path in a collection
135
+ */
136
+ export declare function addContext(collectionName: string, pathPrefix: string, contextText: string): boolean;
137
+ /**
138
+ * Remove a context from a collection
139
+ */
140
+ export declare function removeContext(collectionName: string, pathPrefix: string): boolean;
141
+ /**
142
+ * List all contexts across all collections
143
+ */
144
+ export declare function listAllContexts(): Array<{
145
+ collection: string;
146
+ path: string;
147
+ context: string;
148
+ }>;
149
+ /**
150
+ * Find best matching context for a given collection and path
151
+ * Returns the most specific matching context (longest path prefix match)
152
+ */
153
+ export declare function findContextForPath(collectionName: string, filePath: string): string | undefined;
154
+ /**
155
+ * Get the config file path (useful for error messages)
156
+ */
157
+ export declare function getConfigPath(): string;
158
+ /**
159
+ * Check if config file exists
160
+ */
161
+ export declare function configExists(): boolean;
162
+ /**
163
+ * Validate a collection name
164
+ * Collection names must be valid and not contain special characters
165
+ */
166
+ export declare function isValidCollectionName(name: string): boolean;
@@ -0,0 +1,410 @@
1
+ /**
2
+ * Collections configuration management
3
+ *
4
+ * This module manages the YAML-based collection configuration at ~/.config/qmd/index.yml.
5
+ * Collections define which directories to index and their associated contexts.
6
+ */
7
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
8
+ import { join, dirname, resolve } from "path";
9
+ import { qmdHomedir } from "./paths.js";
10
+ import YAML from "yaml";
11
+ // ============================================================================
12
+ // Configuration paths
13
+ // ============================================================================
14
+ // Current index name (default: "index")
15
+ let currentIndexName = "index";
16
+ // SDK mode: optional in-memory config or custom config path
17
+ let configSource = { type: 'file' };
18
+ /**
19
+ * Set the config source for SDK mode.
20
+ * - File path: load/save from a specific YAML file
21
+ * - Inline config: use an in-memory CollectionConfig (saveConfig updates in place, no file I/O)
22
+ * - undefined: reset to default file-based config
23
+ */
24
+ export function setConfigSource(source) {
25
+ if (!source) {
26
+ configSource = { type: 'file' };
27
+ return;
28
+ }
29
+ if (source.config) {
30
+ // Ensure collections object exists
31
+ if (!source.config.collections) {
32
+ source.config.collections = {};
33
+ }
34
+ configSource = { type: 'inline', config: source.config };
35
+ }
36
+ else if (source.configPath) {
37
+ configSource = { type: 'file', path: source.configPath };
38
+ }
39
+ else {
40
+ configSource = { type: 'file' };
41
+ }
42
+ }
43
+ /**
44
+ * Set the current index name for config file lookup
45
+ * Config file will be ~/.config/qmd/{indexName}.yml
46
+ */
47
+ export function setConfigIndexName(name) {
48
+ // Resolve relative paths to absolute paths and sanitize for use as filename
49
+ if (name.includes('/')) {
50
+ const absolutePath = resolve(process.cwd(), name);
51
+ // Replace path separators with underscores to create a valid filename
52
+ currentIndexName = absolutePath.replace(/\//g, '_').replace(/^_/, '');
53
+ }
54
+ else {
55
+ currentIndexName = name;
56
+ }
57
+ }
58
+ function getConfigDir() {
59
+ // Allow override via QMD_CONFIG_DIR for testing
60
+ if (process.env.QMD_CONFIG_DIR) {
61
+ return process.env.QMD_CONFIG_DIR;
62
+ }
63
+ // Respect XDG Base Directory specification (consistent with store.ts)
64
+ if (process.env.XDG_CONFIG_HOME) {
65
+ return join(process.env.XDG_CONFIG_HOME, "qmd");
66
+ }
67
+ return join(qmdHomedir(), ".config", "qmd");
68
+ }
69
+ function getConfigFilePath() {
70
+ return join(getConfigDir(), `${currentIndexName}.yml`);
71
+ }
72
+ /**
73
+ * Find a project-local QMD config by walking upward from startDir.
74
+ * The local config lives at .qmd/index.yaml or .qmd/index.yml and,
75
+ * when used by the CLI, keeps both config and index DB writes inside
76
+ * the project instead of the global ~/.config / ~/.cache locations.
77
+ */
78
+ export function findLocalConfigPath(startDir = process.cwd()) {
79
+ let dir = resolve(startDir);
80
+ while (true) {
81
+ const qmdDir = join(dir, ".qmd");
82
+ const yamlPath = join(qmdDir, "index.yaml");
83
+ if (existsSync(yamlPath))
84
+ return yamlPath;
85
+ const ymlPath = join(qmdDir, "index.yml");
86
+ if (existsSync(ymlPath))
87
+ return ymlPath;
88
+ const parent = dirname(dir);
89
+ if (parent === dir)
90
+ return undefined;
91
+ dir = parent;
92
+ }
93
+ }
94
+ /** Return the local SQLite index path paired with a local .qmd/index.yaml file. */
95
+ export function getLocalDbPath(configPath) {
96
+ return join(dirname(configPath), "index.sqlite");
97
+ }
98
+ /**
99
+ * Ensure config directory exists
100
+ */
101
+ function ensureConfigDir() {
102
+ const configDir = getConfigDir();
103
+ if (!existsSync(configDir)) {
104
+ mkdirSync(configDir, { recursive: true });
105
+ }
106
+ }
107
+ // ============================================================================
108
+ // Core functions
109
+ // ============================================================================
110
+ /**
111
+ * Load configuration from the configured source.
112
+ * - Inline config: returns the in-memory object directly
113
+ * - File-based: reads from YAML file (default ~/.config/qmd/index.yml)
114
+ * Returns empty config if file doesn't exist
115
+ */
116
+ export function loadConfig() {
117
+ // SDK inline config mode
118
+ if (configSource.type === 'inline') {
119
+ return configSource.config;
120
+ }
121
+ // File-based config (SDK custom path or default)
122
+ const configPath = configSource.path || getConfigFilePath();
123
+ if (!existsSync(configPath)) {
124
+ return { collections: {} };
125
+ }
126
+ try {
127
+ const content = readFileSync(configPath, "utf-8");
128
+ const parsed = YAML.parse(content);
129
+ const config = parsed ?? { collections: {} };
130
+ // Ensure collections object exists
131
+ if (!config.collections) {
132
+ config.collections = {};
133
+ }
134
+ return config;
135
+ }
136
+ catch (error) {
137
+ throw new Error(`Failed to parse ${configPath}: ${error}`);
138
+ }
139
+ }
140
+ /**
141
+ * Save configuration to the configured source.
142
+ * - Inline config: updates the in-memory object (no file I/O)
143
+ * - File-based: writes to YAML file (default ~/.config/qmd/index.yml)
144
+ */
145
+ export function saveConfig(config) {
146
+ // SDK inline config mode: update in place, no file I/O
147
+ if (configSource.type === 'inline') {
148
+ configSource.config = config;
149
+ return;
150
+ }
151
+ const configPath = configSource.path || getConfigFilePath();
152
+ const configDir = dirname(configPath);
153
+ if (!existsSync(configDir)) {
154
+ mkdirSync(configDir, { recursive: true });
155
+ }
156
+ try {
157
+ const yaml = YAML.stringify(config, {
158
+ indent: 2,
159
+ lineWidth: 0, // Don't wrap lines
160
+ });
161
+ writeFileSync(configPath, yaml, "utf-8");
162
+ }
163
+ catch (error) {
164
+ throw new Error(`Failed to write ${configPath}: ${error}`);
165
+ }
166
+ }
167
+ /**
168
+ * Get a specific collection by name
169
+ * Returns null if not found
170
+ */
171
+ export function getCollection(name) {
172
+ const config = loadConfig();
173
+ const collection = config.collections[name];
174
+ if (!collection) {
175
+ return null;
176
+ }
177
+ return { name, ...collection };
178
+ }
179
+ /**
180
+ * List all collections
181
+ */
182
+ export function listCollections() {
183
+ const config = loadConfig();
184
+ return Object.entries(config.collections).map(([name, collection]) => ({
185
+ name,
186
+ ...collection,
187
+ }));
188
+ }
189
+ /**
190
+ * Get collections that are included by default in queries
191
+ */
192
+ export function getDefaultCollections() {
193
+ return listCollections().filter(c => c.includeByDefault !== false);
194
+ }
195
+ /**
196
+ * Get collection names that are included by default
197
+ */
198
+ export function getDefaultCollectionNames() {
199
+ return getDefaultCollections().map(c => c.name);
200
+ }
201
+ /**
202
+ * Update a collection's settings
203
+ */
204
+ export function updateCollectionSettings(name, settings) {
205
+ const config = loadConfig();
206
+ const collection = config.collections[name];
207
+ if (!collection)
208
+ return false;
209
+ if (settings.update !== undefined) {
210
+ if (settings.update === null) {
211
+ delete collection.update;
212
+ }
213
+ else {
214
+ collection.update = settings.update;
215
+ }
216
+ }
217
+ if (settings.includeByDefault !== undefined) {
218
+ if (settings.includeByDefault === true) {
219
+ // true is default, remove the field
220
+ delete collection.includeByDefault;
221
+ }
222
+ else {
223
+ collection.includeByDefault = settings.includeByDefault;
224
+ }
225
+ }
226
+ saveConfig(config);
227
+ return true;
228
+ }
229
+ /**
230
+ * Add or update a collection
231
+ */
232
+ export function addCollection(name, path, pattern = "**/*.md") {
233
+ const config = loadConfig();
234
+ config.collections[name] = {
235
+ path,
236
+ pattern,
237
+ context: config.collections[name]?.context, // Preserve existing context
238
+ };
239
+ saveConfig(config);
240
+ }
241
+ /**
242
+ * Remove a collection
243
+ */
244
+ export function removeCollection(name) {
245
+ const config = loadConfig();
246
+ if (!config.collections[name]) {
247
+ return false;
248
+ }
249
+ delete config.collections[name];
250
+ saveConfig(config);
251
+ return true;
252
+ }
253
+ /**
254
+ * Rename a collection
255
+ */
256
+ export function renameCollection(oldName, newName) {
257
+ const config = loadConfig();
258
+ if (!config.collections[oldName]) {
259
+ return false;
260
+ }
261
+ if (config.collections[newName]) {
262
+ throw new Error(`Collection '${newName}' already exists`);
263
+ }
264
+ config.collections[newName] = config.collections[oldName];
265
+ delete config.collections[oldName];
266
+ saveConfig(config);
267
+ return true;
268
+ }
269
+ // ============================================================================
270
+ // Context management
271
+ // ============================================================================
272
+ /**
273
+ * Get global context
274
+ */
275
+ export function getGlobalContext() {
276
+ const config = loadConfig();
277
+ return config.global_context;
278
+ }
279
+ /**
280
+ * Set global context
281
+ */
282
+ export function setGlobalContext(context) {
283
+ const config = loadConfig();
284
+ config.global_context = context;
285
+ saveConfig(config);
286
+ }
287
+ /**
288
+ * Get all contexts for a collection
289
+ */
290
+ export function getContexts(collectionName) {
291
+ const collection = getCollection(collectionName);
292
+ return collection?.context;
293
+ }
294
+ /**
295
+ * Add or update a context for a specific path in a collection
296
+ */
297
+ export function addContext(collectionName, pathPrefix, contextText) {
298
+ const config = loadConfig();
299
+ const collection = config.collections[collectionName];
300
+ if (!collection) {
301
+ return false;
302
+ }
303
+ if (!collection.context) {
304
+ collection.context = {};
305
+ }
306
+ collection.context[pathPrefix] = contextText;
307
+ saveConfig(config);
308
+ return true;
309
+ }
310
+ /**
311
+ * Remove a context from a collection
312
+ */
313
+ export function removeContext(collectionName, pathPrefix) {
314
+ const config = loadConfig();
315
+ const collection = config.collections[collectionName];
316
+ if (!collection?.context?.[pathPrefix]) {
317
+ return false;
318
+ }
319
+ delete collection.context[pathPrefix];
320
+ // Remove empty context object
321
+ if (Object.keys(collection.context).length === 0) {
322
+ delete collection.context;
323
+ }
324
+ saveConfig(config);
325
+ return true;
326
+ }
327
+ /**
328
+ * List all contexts across all collections
329
+ */
330
+ export function listAllContexts() {
331
+ const config = loadConfig();
332
+ const results = [];
333
+ // Add global context if present
334
+ if (config.global_context) {
335
+ results.push({
336
+ collection: "*",
337
+ path: "/",
338
+ context: config.global_context,
339
+ });
340
+ }
341
+ // Add collection contexts
342
+ for (const [name, collection] of Object.entries(config.collections)) {
343
+ if (collection.context) {
344
+ for (const [path, context] of Object.entries(collection.context)) {
345
+ results.push({
346
+ collection: name,
347
+ path,
348
+ context,
349
+ });
350
+ }
351
+ }
352
+ }
353
+ return results;
354
+ }
355
+ /**
356
+ * Find best matching context for a given collection and path
357
+ * Returns the most specific matching context (longest path prefix match)
358
+ */
359
+ export function findContextForPath(collectionName, filePath) {
360
+ const config = loadConfig();
361
+ const collection = config.collections[collectionName];
362
+ if (!collection?.context) {
363
+ return config.global_context;
364
+ }
365
+ // Find all matching prefixes
366
+ const matches = [];
367
+ for (const [prefix, context] of Object.entries(collection.context)) {
368
+ // Normalize paths for comparison
369
+ const normalizedPath = filePath.startsWith("/") ? filePath : `/${filePath}`;
370
+ const normalizedPrefix = prefix.startsWith("/") ? prefix : `/${prefix}`;
371
+ if (normalizedPath.startsWith(normalizedPrefix)) {
372
+ matches.push({ prefix: normalizedPrefix, context });
373
+ }
374
+ }
375
+ // Return most specific match (longest prefix)
376
+ if (matches.length > 0) {
377
+ matches.sort((a, b) => b.prefix.length - a.prefix.length);
378
+ return matches[0].context;
379
+ }
380
+ // Fallback to global context
381
+ return config.global_context;
382
+ }
383
+ // ============================================================================
384
+ // Utility functions
385
+ // ============================================================================
386
+ /**
387
+ * Get the config file path (useful for error messages)
388
+ */
389
+ export function getConfigPath() {
390
+ if (configSource.type === 'inline')
391
+ return '<inline>';
392
+ return configSource.path || getConfigFilePath();
393
+ }
394
+ /**
395
+ * Check if config file exists
396
+ */
397
+ export function configExists() {
398
+ if (configSource.type === 'inline')
399
+ return true;
400
+ const path = configSource.path || getConfigFilePath();
401
+ return existsSync(path);
402
+ }
403
+ /**
404
+ * Validate a collection name
405
+ * Collection names must be valid and not contain special characters
406
+ */
407
+ export function isValidCollectionName(name) {
408
+ // Allow alphanumeric, hyphens, underscores
409
+ return /^[a-zA-Z0-9_-]+$/.test(name);
410
+ }
package/dist/db.d.ts ADDED
@@ -0,0 +1,44 @@
1
+ /**
2
+ * db.ts - Cross-runtime SQLite compatibility layer
3
+ *
4
+ * Provides a unified Database export that works under both Bun (bun:sqlite)
5
+ * and Node.js (better-sqlite3). The APIs are nearly identical — the main
6
+ * difference is the import path.
7
+ *
8
+ * On macOS, Apple's system SQLite is compiled with SQLITE_OMIT_LOAD_EXTENSION,
9
+ * which prevents loading native extensions like sqlite-vec. When running under
10
+ * Bun we call Database.setCustomSQLite() to swap in Homebrew's full-featured
11
+ * SQLite build before creating any database instances.
12
+ */
13
+ export declare const isBun: boolean;
14
+ export type SQLiteValue = string | number | bigint | Buffer | Uint8Array | Float32Array | null;
15
+ export type SQLiteParams = readonly SQLiteValue[];
16
+ /**
17
+ * Open a SQLite database. Works with both bun:sqlite and better-sqlite3.
18
+ */
19
+ export declare function openDatabase(path: string): Database;
20
+ /**
21
+ * Common subset of the Database interface used throughout QMD.
22
+ */
23
+ export interface Database {
24
+ exec(sql: string): void;
25
+ prepare(sql: string): Statement;
26
+ loadExtension(path: string): void;
27
+ transaction<T extends (...args: SQLiteValue[]) => unknown>(fn: T): T;
28
+ close(): void;
29
+ }
30
+ export interface Statement {
31
+ run(...params: SQLiteValue[]): {
32
+ changes: number;
33
+ lastInsertRowid: number | bigint;
34
+ };
35
+ get<T = unknown>(...params: SQLiteValue[]): T | undefined;
36
+ all<T = unknown>(...params: SQLiteValue[]): T[];
37
+ }
38
+ /**
39
+ * Load the sqlite-vec extension into a database.
40
+ *
41
+ * Throws with platform-specific fix instructions when the extension is
42
+ * unavailable.
43
+ */
44
+ export declare function loadSqliteVec(db: Database): void;
package/dist/db.js ADDED
@@ -0,0 +1,75 @@
1
+ /**
2
+ * db.ts - Cross-runtime SQLite compatibility layer
3
+ *
4
+ * Provides a unified Database export that works under both Bun (bun:sqlite)
5
+ * and Node.js (better-sqlite3). The APIs are nearly identical — the main
6
+ * difference is the import path.
7
+ *
8
+ * On macOS, Apple's system SQLite is compiled with SQLITE_OMIT_LOAD_EXTENSION,
9
+ * which prevents loading native extensions like sqlite-vec. When running under
10
+ * Bun we call Database.setCustomSQLite() to swap in Homebrew's full-featured
11
+ * SQLite build before creating any database instances.
12
+ */
13
+ export const isBun = "Bun" in globalThis;
14
+ let _Database;
15
+ let _sqliteVecLoad;
16
+ if (isBun) {
17
+ // Dynamic string prevents tsc from resolving bun:sqlite on Node.js builds
18
+ const bunSqlite = "bun:" + "sqlite";
19
+ const BunDatabase = (await import(/* @vite-ignore */ bunSqlite)).Database;
20
+ // See: https://bun.com/docs/runtime/sqlite#setcustomsqlite
21
+ if (process.platform === "darwin") {
22
+ const homebrewPaths = [
23
+ "/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib", // Apple Silicon
24
+ "/usr/local/opt/sqlite/lib/libsqlite3.dylib", // Intel
25
+ ];
26
+ for (const p of homebrewPaths) {
27
+ try {
28
+ BunDatabase.setCustomSQLite(p);
29
+ break;
30
+ }
31
+ catch { }
32
+ }
33
+ }
34
+ _Database = BunDatabase;
35
+ // setCustomSQLite may have silently failed — test that extensions actually work.
36
+ try {
37
+ const { getLoadablePath } = await import("sqlite-vec");
38
+ const vecPath = getLoadablePath();
39
+ const testDb = new BunDatabase(":memory:");
40
+ testDb.loadExtension(vecPath);
41
+ testDb.close();
42
+ _sqliteVecLoad = (db) => db.loadExtension(vecPath);
43
+ }
44
+ catch {
45
+ // Vector search won't work, but BM25 and other operations are unaffected.
46
+ _sqliteVecLoad = null;
47
+ }
48
+ }
49
+ else {
50
+ _Database = (await import("better-sqlite3")).default;
51
+ const sqliteVec = await import("sqlite-vec");
52
+ _sqliteVecLoad = (db) => sqliteVec.load(db);
53
+ }
54
+ /**
55
+ * Open a SQLite database. Works with both bun:sqlite and better-sqlite3.
56
+ */
57
+ export function openDatabase(path) {
58
+ return new _Database(path);
59
+ }
60
+ /**
61
+ * Load the sqlite-vec extension into a database.
62
+ *
63
+ * Throws with platform-specific fix instructions when the extension is
64
+ * unavailable.
65
+ */
66
+ export function loadSqliteVec(db) {
67
+ if (!_sqliteVecLoad) {
68
+ const hint = isBun && process.platform === "darwin"
69
+ ? "On macOS with Bun, install Homebrew SQLite: brew install sqlite\n" +
70
+ "Or install qmd with npm instead: npm install -g @tobilu/qmd"
71
+ : "Ensure the sqlite-vec native module is installed correctly.";
72
+ throw new Error(`sqlite-vec extension is unavailable. ${hint}`);
73
+ }
74
+ _sqliteVecLoad(db);
75
+ }