@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 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
- return dbInstance;
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
 
@@ -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
- return dbInstance;
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
- return dbInstance;
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
- return dbInstance;
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.7.1",
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.7.1",
70
- "better-sqlite3": "^12.6.2"
69
+ "@lokascript/semantic": "^2.8.0",
70
+ "better-sqlite3": "^12.11.1"
71
71
  },
72
72
  "devDependencies": {
73
- "@hyperfixi/core": "^2.7.1",
74
- "@hyperfixi/reactivity": "^2.7.1",
75
- "@hyperfixi/realtime": "^2.7.1",
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.0.0",
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
- // Return existing connection if same path
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
- return dbInstance;
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
  /**