@hyperfixi/patterns-reference 2.7.1 → 2.8.0
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/dist/api/index.js +25 -1
- package/dist/api/index.mjs +25 -1
- package/dist/index.js +29 -2
- package/dist/index.mjs +29 -2
- package/package.json +7 -7
- package/src/database/connection.ts +47 -2
package/dist/api/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var Database = require('better-sqlite3');
|
|
4
|
+
var fs = require('fs');
|
|
4
5
|
var url = require('url');
|
|
5
6
|
var path = require('path');
|
|
6
7
|
|
|
@@ -16,10 +17,31 @@ var __dirname$1 = path.dirname(__filename2);
|
|
|
16
17
|
var DEFAULT_DB_PATH = process.env.LSP_DB_PATH || process.env.HYPERSCRIPT_LSP_DB || path.join(__dirname$1, "../data/patterns.db");
|
|
17
18
|
var dbInstance = null;
|
|
18
19
|
var currentDbPath = null;
|
|
20
|
+
var openFileId = null;
|
|
21
|
+
var lastIdentityCheckAt = 0;
|
|
22
|
+
var IDENTITY_TTL_MS = 1e3;
|
|
23
|
+
function fileIdentity(path) {
|
|
24
|
+
try {
|
|
25
|
+
const s = fs.statSync(path);
|
|
26
|
+
return `${s.dev}:${s.ino}:${s.size}:${s.mtimeMs}`;
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
19
31
|
function getDatabase(options = {}) {
|
|
20
32
|
const dbPath = options.dbPath ?? DEFAULT_DB_PATH;
|
|
21
33
|
if (dbInstance !== null && currentDbPath === dbPath) {
|
|
22
|
-
|
|
34
|
+
const now = Date.now();
|
|
35
|
+
if (now - lastIdentityCheckAt < IDENTITY_TTL_MS) {
|
|
36
|
+
return dbInstance;
|
|
37
|
+
}
|
|
38
|
+
lastIdentityCheckAt = now;
|
|
39
|
+
const identity = fileIdentity(dbPath);
|
|
40
|
+
if (identity === null || identity === openFileId) {
|
|
41
|
+
return dbInstance;
|
|
42
|
+
}
|
|
43
|
+
dbInstance.close();
|
|
44
|
+
dbInstance = null;
|
|
23
45
|
}
|
|
24
46
|
if (dbInstance !== null) {
|
|
25
47
|
dbInstance.close();
|
|
@@ -29,6 +51,8 @@ function getDatabase(options = {}) {
|
|
|
29
51
|
});
|
|
30
52
|
dbInstance.pragma("foreign_keys = ON");
|
|
31
53
|
currentDbPath = dbPath;
|
|
54
|
+
openFileId = fileIdentity(dbPath);
|
|
55
|
+
lastIdentityCheckAt = Date.now();
|
|
32
56
|
return dbInstance;
|
|
33
57
|
}
|
|
34
58
|
|
package/dist/api/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Database from 'better-sqlite3';
|
|
2
|
+
import { statSync } from 'fs';
|
|
2
3
|
import { fileURLToPath } from 'url';
|
|
3
4
|
import { dirname, join } from 'path';
|
|
4
5
|
|
|
@@ -8,10 +9,31 @@ var __dirname$1 = dirname(__filename$1);
|
|
|
8
9
|
var DEFAULT_DB_PATH = process.env.LSP_DB_PATH || process.env.HYPERSCRIPT_LSP_DB || join(__dirname$1, "../data/patterns.db");
|
|
9
10
|
var dbInstance = null;
|
|
10
11
|
var currentDbPath = null;
|
|
12
|
+
var openFileId = null;
|
|
13
|
+
var lastIdentityCheckAt = 0;
|
|
14
|
+
var IDENTITY_TTL_MS = 1e3;
|
|
15
|
+
function fileIdentity(path) {
|
|
16
|
+
try {
|
|
17
|
+
const s = statSync(path);
|
|
18
|
+
return `${s.dev}:${s.ino}:${s.size}:${s.mtimeMs}`;
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
11
23
|
function getDatabase(options = {}) {
|
|
12
24
|
const dbPath = options.dbPath ?? DEFAULT_DB_PATH;
|
|
13
25
|
if (dbInstance !== null && currentDbPath === dbPath) {
|
|
14
|
-
|
|
26
|
+
const now = Date.now();
|
|
27
|
+
if (now - lastIdentityCheckAt < IDENTITY_TTL_MS) {
|
|
28
|
+
return dbInstance;
|
|
29
|
+
}
|
|
30
|
+
lastIdentityCheckAt = now;
|
|
31
|
+
const identity = fileIdentity(dbPath);
|
|
32
|
+
if (identity === null || identity === openFileId) {
|
|
33
|
+
return dbInstance;
|
|
34
|
+
}
|
|
35
|
+
dbInstance.close();
|
|
36
|
+
dbInstance = null;
|
|
15
37
|
}
|
|
16
38
|
if (dbInstance !== null) {
|
|
17
39
|
dbInstance.close();
|
|
@@ -21,6 +43,8 @@ function getDatabase(options = {}) {
|
|
|
21
43
|
});
|
|
22
44
|
dbInstance.pragma("foreign_keys = ON");
|
|
23
45
|
currentDbPath = dbPath;
|
|
46
|
+
openFileId = fileIdentity(dbPath);
|
|
47
|
+
lastIdentityCheckAt = Date.now();
|
|
24
48
|
return dbInstance;
|
|
25
49
|
}
|
|
26
50
|
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var Database = require('better-sqlite3');
|
|
4
|
+
var fs = require('fs');
|
|
4
5
|
var url = require('url');
|
|
5
6
|
var path = require('path');
|
|
6
7
|
var crypto = require('crypto');
|
|
7
|
-
var fs = require('fs');
|
|
8
8
|
|
|
9
9
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
10
|
|
|
@@ -18,13 +18,34 @@ var __dirname$1 = path.dirname(__filename2);
|
|
|
18
18
|
var DEFAULT_DB_PATH = process.env.LSP_DB_PATH || process.env.HYPERSCRIPT_LSP_DB || path.join(__dirname$1, "../data/patterns.db");
|
|
19
19
|
var dbInstance = null;
|
|
20
20
|
var currentDbPath = null;
|
|
21
|
+
var openFileId = null;
|
|
22
|
+
var lastIdentityCheckAt = 0;
|
|
23
|
+
var IDENTITY_TTL_MS = 1e3;
|
|
24
|
+
function fileIdentity(path) {
|
|
25
|
+
try {
|
|
26
|
+
const s = fs.statSync(path);
|
|
27
|
+
return `${s.dev}:${s.ino}:${s.size}:${s.mtimeMs}`;
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
21
32
|
function getDefaultDbPath() {
|
|
22
33
|
return DEFAULT_DB_PATH;
|
|
23
34
|
}
|
|
24
35
|
function getDatabase(options = {}) {
|
|
25
36
|
const dbPath = options.dbPath ?? DEFAULT_DB_PATH;
|
|
26
37
|
if (dbInstance !== null && currentDbPath === dbPath) {
|
|
27
|
-
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
if (now - lastIdentityCheckAt < IDENTITY_TTL_MS) {
|
|
40
|
+
return dbInstance;
|
|
41
|
+
}
|
|
42
|
+
lastIdentityCheckAt = now;
|
|
43
|
+
const identity = fileIdentity(dbPath);
|
|
44
|
+
if (identity === null || identity === openFileId) {
|
|
45
|
+
return dbInstance;
|
|
46
|
+
}
|
|
47
|
+
dbInstance.close();
|
|
48
|
+
dbInstance = null;
|
|
28
49
|
}
|
|
29
50
|
if (dbInstance !== null) {
|
|
30
51
|
dbInstance.close();
|
|
@@ -34,6 +55,8 @@ function getDatabase(options = {}) {
|
|
|
34
55
|
});
|
|
35
56
|
dbInstance.pragma("foreign_keys = ON");
|
|
36
57
|
currentDbPath = dbPath;
|
|
58
|
+
openFileId = fileIdentity(dbPath);
|
|
59
|
+
lastIdentityCheckAt = Date.now();
|
|
37
60
|
return dbInstance;
|
|
38
61
|
}
|
|
39
62
|
function closeDatabase() {
|
|
@@ -41,11 +64,15 @@ function closeDatabase() {
|
|
|
41
64
|
dbInstance.close();
|
|
42
65
|
dbInstance = null;
|
|
43
66
|
currentDbPath = null;
|
|
67
|
+
openFileId = null;
|
|
68
|
+
lastIdentityCheckAt = 0;
|
|
44
69
|
}
|
|
45
70
|
}
|
|
46
71
|
function resetConnection() {
|
|
47
72
|
dbInstance = null;
|
|
48
73
|
currentDbPath = null;
|
|
74
|
+
openFileId = null;
|
|
75
|
+
lastIdentityCheckAt = 0;
|
|
49
76
|
}
|
|
50
77
|
function isConnected() {
|
|
51
78
|
return dbInstance !== null;
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import Database from 'better-sqlite3';
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, statSync, readdirSync } from 'fs';
|
|
2
3
|
import { fileURLToPath } from 'url';
|
|
3
4
|
import { dirname, join, relative, sep, resolve } from 'path';
|
|
4
5
|
import { createHash } from 'crypto';
|
|
5
|
-
import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from 'fs';
|
|
6
6
|
|
|
7
7
|
// src/database/connection.ts
|
|
8
8
|
var __filename$1 = fileURLToPath(import.meta.url);
|
|
@@ -10,13 +10,34 @@ var __dirname$1 = dirname(__filename$1);
|
|
|
10
10
|
var DEFAULT_DB_PATH = process.env.LSP_DB_PATH || process.env.HYPERSCRIPT_LSP_DB || join(__dirname$1, "../data/patterns.db");
|
|
11
11
|
var dbInstance = null;
|
|
12
12
|
var currentDbPath = null;
|
|
13
|
+
var openFileId = null;
|
|
14
|
+
var lastIdentityCheckAt = 0;
|
|
15
|
+
var IDENTITY_TTL_MS = 1e3;
|
|
16
|
+
function fileIdentity(path) {
|
|
17
|
+
try {
|
|
18
|
+
const s = statSync(path);
|
|
19
|
+
return `${s.dev}:${s.ino}:${s.size}:${s.mtimeMs}`;
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
13
24
|
function getDefaultDbPath() {
|
|
14
25
|
return DEFAULT_DB_PATH;
|
|
15
26
|
}
|
|
16
27
|
function getDatabase(options = {}) {
|
|
17
28
|
const dbPath = options.dbPath ?? DEFAULT_DB_PATH;
|
|
18
29
|
if (dbInstance !== null && currentDbPath === dbPath) {
|
|
19
|
-
|
|
30
|
+
const now = Date.now();
|
|
31
|
+
if (now - lastIdentityCheckAt < IDENTITY_TTL_MS) {
|
|
32
|
+
return dbInstance;
|
|
33
|
+
}
|
|
34
|
+
lastIdentityCheckAt = now;
|
|
35
|
+
const identity = fileIdentity(dbPath);
|
|
36
|
+
if (identity === null || identity === openFileId) {
|
|
37
|
+
return dbInstance;
|
|
38
|
+
}
|
|
39
|
+
dbInstance.close();
|
|
40
|
+
dbInstance = null;
|
|
20
41
|
}
|
|
21
42
|
if (dbInstance !== null) {
|
|
22
43
|
dbInstance.close();
|
|
@@ -26,6 +47,8 @@ function getDatabase(options = {}) {
|
|
|
26
47
|
});
|
|
27
48
|
dbInstance.pragma("foreign_keys = ON");
|
|
28
49
|
currentDbPath = dbPath;
|
|
50
|
+
openFileId = fileIdentity(dbPath);
|
|
51
|
+
lastIdentityCheckAt = Date.now();
|
|
29
52
|
return dbInstance;
|
|
30
53
|
}
|
|
31
54
|
function closeDatabase() {
|
|
@@ -33,11 +56,15 @@ function closeDatabase() {
|
|
|
33
56
|
dbInstance.close();
|
|
34
57
|
dbInstance = null;
|
|
35
58
|
currentDbPath = null;
|
|
59
|
+
openFileId = null;
|
|
60
|
+
lastIdentityCheckAt = 0;
|
|
36
61
|
}
|
|
37
62
|
}
|
|
38
63
|
function resetConnection() {
|
|
39
64
|
dbInstance = null;
|
|
40
65
|
currentDbPath = null;
|
|
66
|
+
openFileId = null;
|
|
67
|
+
lastIdentityCheckAt = 0;
|
|
41
68
|
}
|
|
42
69
|
function isConnected() {
|
|
43
70
|
return dbInstance !== null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperfixi/patterns-reference",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.0",
|
|
4
4
|
"description": "Queryable patterns database for hyperscript with multilingual translations and LLM support",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -66,19 +66,19 @@
|
|
|
66
66
|
},
|
|
67
67
|
"homepage": "https://github.com/codetalcott/hyperfixi/tree/main/packages/patterns-reference#readme",
|
|
68
68
|
"dependencies": {
|
|
69
|
-
"@lokascript/semantic": "^2.
|
|
70
|
-
"better-sqlite3": "^12.
|
|
69
|
+
"@lokascript/semantic": "^2.8.0",
|
|
70
|
+
"better-sqlite3": "^12.11.1"
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
73
|
-
"@hyperfixi/core": "^2.
|
|
74
|
-
"@hyperfixi/reactivity": "^2.
|
|
75
|
-
"@hyperfixi/realtime": "^2.
|
|
73
|
+
"@hyperfixi/core": "^2.8.0",
|
|
74
|
+
"@hyperfixi/reactivity": "^2.8.0",
|
|
75
|
+
"@hyperfixi/realtime": "^2.8.0",
|
|
76
76
|
"@types/better-sqlite3": "^7.6.0",
|
|
77
77
|
"@types/node": "^20.0.0",
|
|
78
78
|
"hyperscript.org": "0.9.93",
|
|
79
79
|
"jsdom": "^26.0.0",
|
|
80
80
|
"tsup": "^8.0.0",
|
|
81
|
-
"tsx": "^4.
|
|
81
|
+
"tsx": "^4.23.1",
|
|
82
82
|
"typescript": "^5.0.0",
|
|
83
83
|
"vitest": "^4.1.5"
|
|
84
84
|
},
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import Database from 'better-sqlite3';
|
|
8
|
+
import { statSync } from 'fs';
|
|
8
9
|
import { fileURLToPath } from 'url';
|
|
9
10
|
import { dirname, join } from 'path';
|
|
10
11
|
import type { ConnectionOptions } from '../types';
|
|
@@ -24,6 +25,30 @@ const DEFAULT_DB_PATH =
|
|
|
24
25
|
let dbInstance: InstanceType<typeof Database> | null = null;
|
|
25
26
|
let currentDbPath: string | null = null;
|
|
26
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Identity of the FILE the open handle is reading, captured when it was opened.
|
|
30
|
+
*
|
|
31
|
+
* `npm run populate` REPLACES patterns.db rather than mutating it, so a long-lived handle
|
|
32
|
+
* (the MCP server's, most notably) keeps reading the old inode: every query answers from
|
|
33
|
+
* the pre-populate database while the file on disk says otherwise. Path equality cannot
|
|
34
|
+
* see this — the path is identical; only the inode moved.
|
|
35
|
+
*/
|
|
36
|
+
let openFileId: string | null = null;
|
|
37
|
+
let lastIdentityCheckAt = 0;
|
|
38
|
+
|
|
39
|
+
/** Re-stat at most this often, so a bulk query loop pays one syscall, not thousands. */
|
|
40
|
+
const IDENTITY_TTL_MS = 1000;
|
|
41
|
+
|
|
42
|
+
/** inode+device+size+mtime — changes iff the file was replaced or rewritten. */
|
|
43
|
+
function fileIdentity(path: string): string | null {
|
|
44
|
+
try {
|
|
45
|
+
const s = statSync(path);
|
|
46
|
+
return `${s.dev}:${s.ino}:${s.size}:${s.mtimeMs}`;
|
|
47
|
+
} catch {
|
|
48
|
+
return null; // mid-replace: treat as unknown, don't thrash the handle
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
27
52
|
/**
|
|
28
53
|
* The resolved default DB path (honouring `LSP_DB_PATH` / `HYPERSCRIPT_LSP_DB`).
|
|
29
54
|
* Exposed so callers (e.g. the multilingual gate's freshness check) can target the
|
|
@@ -39,9 +64,23 @@ export function getDefaultDbPath(): string {
|
|
|
39
64
|
export function getDatabase(options: ConnectionOptions = {}): InstanceType<typeof Database> {
|
|
40
65
|
const dbPath = options.dbPath ?? DEFAULT_DB_PATH;
|
|
41
66
|
|
|
42
|
-
//
|
|
67
|
+
// Reuse the open handle only if it is reading the same PATH *and* the same FILE. The
|
|
68
|
+
// second half matters because `populate` swaps the file underneath us: without it a
|
|
69
|
+
// long-lived consumer serves pre-populate rows indefinitely, which looks like a parser
|
|
70
|
+
// bug and is not one. Behind a TTL so a query loop doesn't stat per row.
|
|
43
71
|
if (dbInstance !== null && currentDbPath === dbPath) {
|
|
44
|
-
|
|
72
|
+
const now = Date.now();
|
|
73
|
+
if (now - lastIdentityCheckAt < IDENTITY_TTL_MS) {
|
|
74
|
+
return dbInstance;
|
|
75
|
+
}
|
|
76
|
+
lastIdentityCheckAt = now;
|
|
77
|
+
const identity = fileIdentity(dbPath);
|
|
78
|
+
if (identity === null || identity === openFileId) {
|
|
79
|
+
return dbInstance;
|
|
80
|
+
}
|
|
81
|
+
// The file was replaced — reopen so callers see the DB that is actually on disk.
|
|
82
|
+
dbInstance.close();
|
|
83
|
+
dbInstance = null;
|
|
45
84
|
}
|
|
46
85
|
|
|
47
86
|
// Close existing connection if path changed
|
|
@@ -57,6 +96,8 @@ export function getDatabase(options: ConnectionOptions = {}): InstanceType<typeo
|
|
|
57
96
|
dbInstance.pragma('foreign_keys = ON');
|
|
58
97
|
|
|
59
98
|
currentDbPath = dbPath;
|
|
99
|
+
openFileId = fileIdentity(dbPath);
|
|
100
|
+
lastIdentityCheckAt = Date.now();
|
|
60
101
|
return dbInstance;
|
|
61
102
|
}
|
|
62
103
|
|
|
@@ -68,6 +109,8 @@ export function closeDatabase(): void {
|
|
|
68
109
|
dbInstance.close();
|
|
69
110
|
dbInstance = null;
|
|
70
111
|
currentDbPath = null;
|
|
112
|
+
openFileId = null;
|
|
113
|
+
lastIdentityCheckAt = 0;
|
|
71
114
|
}
|
|
72
115
|
}
|
|
73
116
|
|
|
@@ -77,6 +120,8 @@ export function closeDatabase(): void {
|
|
|
77
120
|
export function resetConnection(): void {
|
|
78
121
|
dbInstance = null;
|
|
79
122
|
currentDbPath = null;
|
|
123
|
+
openFileId = null;
|
|
124
|
+
lastIdentityCheckAt = 0;
|
|
80
125
|
}
|
|
81
126
|
|
|
82
127
|
/**
|