sensemaking 0.24.4 → 0.24.6
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 +16 -12
- package/dist/cjs/cli/status.js +14 -24
- package/dist/cjs/cli/status.js.map +1 -1
- package/dist/cjs/commands/search.js +22 -0
- package/dist/cjs/commands/search.js.map +1 -1
- package/dist/cjs/config/types.js.map +1 -1
- package/dist/cjs/errors.d.cts +1 -1
- package/dist/cjs/errors.d.ts +1 -1
- package/dist/cjs/errors.js.map +1 -1
- package/dist/cjs/lib/watch-notification.d.cts +1 -0
- package/dist/cjs/lib/watch-notification.d.ts +1 -0
- package/dist/cjs/lib/watch-notification.js +15 -0
- package/dist/cjs/lib/watch-notification.js.map +1 -0
- package/dist/cjs/lib/worker-file.d.cts +1 -0
- package/dist/cjs/lib/worker-file.d.ts +1 -0
- package/dist/cjs/lib/worker-file.js +34 -0
- package/dist/cjs/lib/worker-file.js.map +1 -0
- package/dist/cjs/output/search-error.js +38 -0
- package/dist/cjs/output/search-error.js.map +1 -1
- package/dist/cjs/scan/pool.js +2 -23
- package/dist/cjs/scan/pool.js.map +1 -1
- package/dist/cjs/store/builder.d.cts +2 -1
- package/dist/cjs/store/builder.d.ts +2 -1
- package/dist/cjs/store/builder.js +4 -4
- package/dist/cjs/store/builder.js.map +1 -1
- package/dist/cjs/store/duckdb/lexical.d.cts +1 -0
- package/dist/cjs/store/duckdb/lexical.d.ts +1 -0
- package/dist/cjs/store/duckdb/lexical.js +28 -9
- package/dist/cjs/store/duckdb/lexical.js.map +1 -1
- package/dist/cjs/store/duckdb/native.d.cts +2 -0
- package/dist/cjs/store/duckdb/native.d.ts +2 -0
- package/dist/cjs/store/duckdb/native.js +11 -1
- package/dist/cjs/store/duckdb/native.js.map +1 -1
- package/dist/cjs/store/duckdb/ordered-bm25.d.cts +17 -0
- package/dist/cjs/store/duckdb/ordered-bm25.d.ts +17 -0
- package/dist/cjs/store/duckdb/ordered-bm25.js +238 -0
- package/dist/cjs/store/duckdb/ordered-bm25.js.map +1 -0
- package/dist/cjs/store/lock-wait.d.cts +2 -2
- package/dist/cjs/store/lock-wait.d.ts +2 -2
- package/dist/cjs/store/lock-wait.js +14 -11
- package/dist/cjs/store/lock-wait.js.map +1 -1
- package/dist/cjs/store/native.d.cts +4 -0
- package/dist/cjs/store/native.d.ts +4 -0
- package/dist/cjs/store/native.js +64 -9
- package/dist/cjs/store/native.js.map +1 -1
- package/dist/cjs/store/open.js +11 -7
- package/dist/cjs/store/open.js.map +1 -1
- package/dist/cjs/store/reconcile.d.cts +5 -1
- package/dist/cjs/store/reconcile.d.ts +5 -1
- package/dist/cjs/store/reconcile.js +5 -4
- package/dist/cjs/store/reconcile.js.map +1 -1
- package/dist/cjs/store/turso/lexical.js +5 -1
- package/dist/cjs/store/turso/lexical.js.map +1 -1
- package/dist/cjs/text/segment.js +168 -3
- package/dist/cjs/text/segment.js.map +1 -1
- package/dist/cjs/watch-claim.d.cts +39 -0
- package/dist/cjs/watch-claim.d.ts +39 -0
- package/dist/cjs/watch-claim.js +446 -0
- package/dist/cjs/watch-claim.js.map +1 -0
- package/dist/cjs/watch.js +246 -147
- package/dist/cjs/watch.js.map +1 -1
- package/dist/cjs/workers/watch-heartbeat.d.cts +1 -0
- package/dist/cjs/workers/watch-heartbeat.d.ts +1 -0
- package/dist/cjs/workers/watch-heartbeat.js +308 -0
- package/dist/cjs/workers/watch-heartbeat.js.map +1 -0
- package/dist/esm/cli/status.js +6 -5
- package/dist/esm/cli/status.js.map +1 -1
- package/dist/esm/commands/search.js +21 -0
- package/dist/esm/commands/search.js.map +1 -1
- package/dist/esm/config/types.js.map +1 -1
- package/dist/esm/errors.d.ts +1 -1
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/lib/watch-notification.d.ts +1 -0
- package/dist/esm/lib/watch-notification.js +4 -0
- package/dist/esm/lib/watch-notification.js.map +1 -0
- package/dist/esm/lib/worker-file.d.ts +1 -0
- package/dist/esm/lib/worker-file.js +24 -0
- package/dist/esm/lib/worker-file.js.map +1 -0
- package/dist/esm/output/search-error.js +21 -0
- package/dist/esm/output/search-error.js.map +1 -1
- package/dist/esm/scan/pool.js +2 -22
- package/dist/esm/scan/pool.js.map +1 -1
- package/dist/esm/store/builder.d.ts +2 -1
- package/dist/esm/store/builder.js +4 -4
- package/dist/esm/store/builder.js.map +1 -1
- package/dist/esm/store/duckdb/lexical.d.ts +1 -0
- package/dist/esm/store/duckdb/lexical.js +16 -7
- package/dist/esm/store/duckdb/lexical.js.map +1 -1
- package/dist/esm/store/duckdb/native.d.ts +2 -0
- package/dist/esm/store/duckdb/native.js +5 -1
- package/dist/esm/store/duckdb/native.js.map +1 -1
- package/dist/esm/store/duckdb/ordered-bm25.d.ts +17 -0
- package/dist/esm/store/duckdb/ordered-bm25.js +56 -0
- package/dist/esm/store/duckdb/ordered-bm25.js.map +1 -0
- package/dist/esm/store/lock-wait.d.ts +2 -2
- package/dist/esm/store/lock-wait.js +17 -14
- package/dist/esm/store/lock-wait.js.map +1 -1
- package/dist/esm/store/native.d.ts +4 -0
- package/dist/esm/store/native.js +60 -6
- package/dist/esm/store/native.js.map +1 -1
- package/dist/esm/store/open.js +9 -6
- package/dist/esm/store/open.js.map +1 -1
- package/dist/esm/store/reconcile.d.ts +5 -1
- package/dist/esm/store/reconcile.js +4 -3
- package/dist/esm/store/reconcile.js.map +1 -1
- package/dist/esm/store/turso/lexical.js +5 -1
- package/dist/esm/store/turso/lexical.js.map +1 -1
- package/dist/esm/text/segment.js +130 -4
- package/dist/esm/text/segment.js.map +1 -1
- package/dist/esm/watch-claim.d.ts +39 -0
- package/dist/esm/watch-claim.js +176 -0
- package/dist/esm/watch-claim.js.map +1 -0
- package/dist/esm/watch.js +169 -118
- package/dist/esm/watch.js.map +1 -1
- package/dist/esm/workers/watch-heartbeat.d.ts +1 -0
- package/dist/esm/workers/watch-heartbeat.js +106 -0
- package/dist/esm/workers/watch-heartbeat.js.map +1 -0
- package/package.json +2 -2
- package/skills/sense/SKILL.md +105 -104
- package/skills/sense/references/search.md +47 -0
- package/skills/sense/references/sql.md +120 -0
- package/skills/sense/references/stores/duckdb.md +35 -0
- package/skills/sense/references/stores/sqlite.md +66 -0
- package/skills/sense/references/stores/turso.md +36 -0
- package/skills/sense-setup/SKILL.md +93 -34
- package/skills/sense-setup/references/embeddings.md +51 -0
- package/skills/sense-setup/references/store-benchmarks.md +16 -0
- package/skills/sense-setup/references/stores/duckdb.md +28 -0
- package/skills/sense-setup/references/stores/sqlite.md +21 -0
- package/skills/sense-setup/references/stores/turso.md +25 -0
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
function _export(target, all) {
|
|
6
|
+
for(var name in all)Object.defineProperty(target, name, {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: Object.getOwnPropertyDescriptor(all, name).get
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
_export(exports, {
|
|
12
|
+
get WATCH_CLAIM_FILENAME () {
|
|
13
|
+
return WATCH_CLAIM_FILENAME;
|
|
14
|
+
},
|
|
15
|
+
get WATCH_HEARTBEAT_INTERVAL_MS () {
|
|
16
|
+
return WATCH_HEARTBEAT_INTERVAL_MS;
|
|
17
|
+
},
|
|
18
|
+
get WATCH_STALE_HEARTBEAT_MS () {
|
|
19
|
+
return WATCH_STALE_HEARTBEAT_MS;
|
|
20
|
+
},
|
|
21
|
+
get WatchClaimDatabase () {
|
|
22
|
+
return WatchClaimDatabase;
|
|
23
|
+
},
|
|
24
|
+
get readWatchClaim () {
|
|
25
|
+
return readWatchClaim;
|
|
26
|
+
},
|
|
27
|
+
get startWatchClaim () {
|
|
28
|
+
return startWatchClaim;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
var _nodecrypto = require("node:crypto");
|
|
32
|
+
var _nodefs = require("node:fs");
|
|
33
|
+
var _nodepath = require("node:path");
|
|
34
|
+
var _nodesqlite = require("node:sqlite");
|
|
35
|
+
var _nodeworker_threads = require("node:worker_threads");
|
|
36
|
+
var _errorsts = require("./errors.js");
|
|
37
|
+
var _workerfilets = require("./lib/worker-file.js");
|
|
38
|
+
var _workererrorts = require("./scan/worker-error.js");
|
|
39
|
+
var _connectionts = require("./store/sqlite/connection.js");
|
|
40
|
+
var _transactionts = require("./store/transaction.js");
|
|
41
|
+
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
|
|
42
|
+
try {
|
|
43
|
+
var info = gen[key](arg);
|
|
44
|
+
var value = info.value;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
reject(error);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (info.done) resolve(value);
|
|
50
|
+
else Promise.resolve(value).then(_next, _throw);
|
|
51
|
+
}
|
|
52
|
+
function _async_to_generator(fn) {
|
|
53
|
+
return function() {
|
|
54
|
+
var self = this, args = arguments;
|
|
55
|
+
return new Promise(function(resolve, reject) {
|
|
56
|
+
var gen = fn.apply(self, args);
|
|
57
|
+
function _next(value) {
|
|
58
|
+
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
|
|
59
|
+
}
|
|
60
|
+
function _throw(err) {
|
|
61
|
+
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
|
|
62
|
+
}
|
|
63
|
+
_next(undefined);
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function _class_call_check(instance, Constructor) {
|
|
68
|
+
if (!(instance instanceof Constructor)) throw new TypeError("Cannot call a class as a function");
|
|
69
|
+
}
|
|
70
|
+
function _instanceof(left, right) {
|
|
71
|
+
"@swc/helpers - instanceof";
|
|
72
|
+
if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
|
|
73
|
+
return !!right[Symbol.hasInstance](left);
|
|
74
|
+
} else return left instanceof right;
|
|
75
|
+
}
|
|
76
|
+
function _ts_generator(thisArg, body) {
|
|
77
|
+
var f, y, t, _ = {
|
|
78
|
+
label: 0,
|
|
79
|
+
sent: function() {
|
|
80
|
+
if (t[0] & 1) throw t[1];
|
|
81
|
+
return t[1];
|
|
82
|
+
},
|
|
83
|
+
trys: [],
|
|
84
|
+
ops: []
|
|
85
|
+
}, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype), d = Object.defineProperty;
|
|
86
|
+
return d(g, "next", {
|
|
87
|
+
value: verb(0)
|
|
88
|
+
}), d(g, "throw", {
|
|
89
|
+
value: verb(1)
|
|
90
|
+
}), d(g, "return", {
|
|
91
|
+
value: verb(2)
|
|
92
|
+
}), typeof Symbol === "function" && d(g, Symbol.iterator, {
|
|
93
|
+
value: function() {
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
}), g;
|
|
97
|
+
function verb(n) {
|
|
98
|
+
return function(v) {
|
|
99
|
+
return step([
|
|
100
|
+
n,
|
|
101
|
+
v
|
|
102
|
+
]);
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function step(op) {
|
|
106
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
107
|
+
while(g && (g = 0, op[0] && (_ = 0)), _)try {
|
|
108
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
109
|
+
if (y = 0, t) op = [
|
|
110
|
+
op[0] & 2,
|
|
111
|
+
t.value
|
|
112
|
+
];
|
|
113
|
+
switch(op[0]){
|
|
114
|
+
case 0:
|
|
115
|
+
case 1:
|
|
116
|
+
t = op;
|
|
117
|
+
break;
|
|
118
|
+
case 4:
|
|
119
|
+
_.label++;
|
|
120
|
+
return {
|
|
121
|
+
value: op[1],
|
|
122
|
+
done: false
|
|
123
|
+
};
|
|
124
|
+
case 5:
|
|
125
|
+
_.label++;
|
|
126
|
+
y = op[1];
|
|
127
|
+
op = [
|
|
128
|
+
0
|
|
129
|
+
];
|
|
130
|
+
continue;
|
|
131
|
+
case 7:
|
|
132
|
+
op = _.ops.pop();
|
|
133
|
+
_.trys.pop();
|
|
134
|
+
continue;
|
|
135
|
+
default:
|
|
136
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
|
|
137
|
+
_ = 0;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {
|
|
141
|
+
_.label = op[1];
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
if (op[0] === 6 && _.label < t[1]) {
|
|
145
|
+
_.label = t[1];
|
|
146
|
+
t = op;
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
if (t && _.label < t[2]) {
|
|
150
|
+
_.label = t[2];
|
|
151
|
+
_.ops.push(op);
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
if (t[2]) _.ops.pop();
|
|
155
|
+
_.trys.pop();
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
op = body.call(thisArg, _);
|
|
159
|
+
} catch (e) {
|
|
160
|
+
op = [
|
|
161
|
+
6,
|
|
162
|
+
e
|
|
163
|
+
];
|
|
164
|
+
y = 0;
|
|
165
|
+
} finally{
|
|
166
|
+
f = t = 0;
|
|
167
|
+
}
|
|
168
|
+
if (op[0] & 5) throw op[1];
|
|
169
|
+
return {
|
|
170
|
+
value: op[0] ? op[1] : void 0,
|
|
171
|
+
done: true
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
var WATCH_CLAIM_FILENAME = '.sense-watch.db';
|
|
176
|
+
var WATCH_HEARTBEAT_INTERVAL_MS = 5000;
|
|
177
|
+
var WATCH_STALE_HEARTBEAT_MS = 15000;
|
|
178
|
+
var CLAIM_BUSY_TIMEOUT_MS = 30000;
|
|
179
|
+
function claimPath(configDir) {
|
|
180
|
+
return (0, _nodepath.join)(configDir, WATCH_CLAIM_FILENAME);
|
|
181
|
+
}
|
|
182
|
+
function changedOne(changes) {
|
|
183
|
+
return changes === 1 || changes === BigInt(1);
|
|
184
|
+
}
|
|
185
|
+
var WatchClaimDatabase = /*#__PURE__*/ function() {
|
|
186
|
+
"use strict";
|
|
187
|
+
function WatchClaimDatabase(configDir) {
|
|
188
|
+
_class_call_check(this, WatchClaimDatabase);
|
|
189
|
+
var db = new _nodesqlite.DatabaseSync(claimPath(configDir));
|
|
190
|
+
try {
|
|
191
|
+
db.exec("PRAGMA busy_timeout = ".concat(CLAIM_BUSY_TIMEOUT_MS));
|
|
192
|
+
db.exec('CREATE TABLE IF NOT EXISTS watch_claim (singleton INTEGER PRIMARY KEY CHECK (singleton = 1), token TEXT NOT NULL, pid INTEGER NOT NULL, heartbeat_ms INTEGER NOT NULL)');
|
|
193
|
+
} catch (err) {
|
|
194
|
+
try {
|
|
195
|
+
db.close();
|
|
196
|
+
} catch (closeError) {
|
|
197
|
+
throw new AggregateError([
|
|
198
|
+
err,
|
|
199
|
+
closeError
|
|
200
|
+
], 'watch claim setup and database cleanup both failed');
|
|
201
|
+
}
|
|
202
|
+
throw err;
|
|
203
|
+
}
|
|
204
|
+
this.db = db;
|
|
205
|
+
this.conn = (0, _connectionts.createConnection)(db);
|
|
206
|
+
}
|
|
207
|
+
var _proto = WatchClaimDatabase.prototype;
|
|
208
|
+
_proto.acquire = function acquire(token, pid, force) {
|
|
209
|
+
return _async_to_generator(function() {
|
|
210
|
+
var _this;
|
|
211
|
+
return _ts_generator(this, function(_state) {
|
|
212
|
+
switch(_state.label){
|
|
213
|
+
case 0:
|
|
214
|
+
_this = this;
|
|
215
|
+
return [
|
|
216
|
+
4,
|
|
217
|
+
(0, _transactionts.withTransaction)(this.conn, function() {
|
|
218
|
+
return _async_to_generator(function() {
|
|
219
|
+
var current, age;
|
|
220
|
+
return _ts_generator(this, function(_state) {
|
|
221
|
+
current = this.db.prepare('SELECT token, pid, heartbeat_ms FROM watch_claim WHERE singleton = 1').get();
|
|
222
|
+
if (current && !force) {
|
|
223
|
+
age = Date.now() - current.heartbeat_ms;
|
|
224
|
+
if (age >= 0 && age < WATCH_STALE_HEARTBEAT_MS) throw new _errorsts.SenseError('WATCH_ACTIVE', "another watcher appears active (heartbeat ".concat(Math.round(age / 1000), "s ago); use --force to override"));
|
|
225
|
+
}
|
|
226
|
+
this.db.prepare('INSERT INTO watch_claim (singleton, token, pid, heartbeat_ms) VALUES (1, ?, ?, ?) ON CONFLICT(singleton) DO UPDATE SET token = excluded.token, pid = excluded.pid, heartbeat_ms = excluded.heartbeat_ms').run(token, pid, Date.now());
|
|
227
|
+
return [
|
|
228
|
+
2
|
|
229
|
+
];
|
|
230
|
+
});
|
|
231
|
+
}).call(_this);
|
|
232
|
+
}, _transactionts.BEGIN_WRITE)
|
|
233
|
+
];
|
|
234
|
+
case 1:
|
|
235
|
+
_state.sent();
|
|
236
|
+
return [
|
|
237
|
+
2
|
|
238
|
+
];
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
}).call(this);
|
|
242
|
+
};
|
|
243
|
+
_proto.renew = function renew(token) {
|
|
244
|
+
return changedOne(this.db.prepare('UPDATE watch_claim SET heartbeat_ms = ? WHERE singleton = 1 AND token = ?').run(Date.now(), token).changes);
|
|
245
|
+
};
|
|
246
|
+
_proto.release = function release(token) {
|
|
247
|
+
return changedOne(this.db.prepare('DELETE FROM watch_claim WHERE singleton = 1 AND token = ?').run(token).changes);
|
|
248
|
+
};
|
|
249
|
+
_proto.read = function read() {
|
|
250
|
+
var row = this.db.prepare('SELECT token, pid, heartbeat_ms FROM watch_claim WHERE singleton = 1').get();
|
|
251
|
+
return row ? {
|
|
252
|
+
token: row.token,
|
|
253
|
+
pid: row.pid,
|
|
254
|
+
heartbeatMs: row.heartbeat_ms
|
|
255
|
+
} : null;
|
|
256
|
+
};
|
|
257
|
+
_proto.close = function close() {
|
|
258
|
+
this.db.close();
|
|
259
|
+
};
|
|
260
|
+
return WatchClaimDatabase;
|
|
261
|
+
}();
|
|
262
|
+
function readWatchClaim(configDir) {
|
|
263
|
+
var path = claimPath(configDir);
|
|
264
|
+
if (!(0, _nodefs.existsSync)(path)) return null;
|
|
265
|
+
var db;
|
|
266
|
+
try {
|
|
267
|
+
db = new _nodesqlite.DatabaseSync(path, {
|
|
268
|
+
readOnly: true
|
|
269
|
+
});
|
|
270
|
+
} catch (err) {
|
|
271
|
+
if (!(0, _nodefs.existsSync)(path)) return null;
|
|
272
|
+
throw err;
|
|
273
|
+
}
|
|
274
|
+
try {
|
|
275
|
+
db.exec("PRAGMA busy_timeout = ".concat(CLAIM_BUSY_TIMEOUT_MS));
|
|
276
|
+
var row = db.prepare('SELECT token, pid, heartbeat_ms FROM watch_claim WHERE singleton = 1').get();
|
|
277
|
+
return row ? {
|
|
278
|
+
token: row.token,
|
|
279
|
+
pid: row.pid,
|
|
280
|
+
heartbeatMs: row.heartbeat_ms
|
|
281
|
+
} : null;
|
|
282
|
+
} catch (err) {
|
|
283
|
+
if (/no such table: watch_claim/.test(err.message)) return null;
|
|
284
|
+
throw err;
|
|
285
|
+
} finally{
|
|
286
|
+
db.close();
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
function startWatchClaim(configDir, force, heartbeatIntervalMs, onFailure) {
|
|
290
|
+
return _async_to_generator(function() {
|
|
291
|
+
var workerData, worker, acquired, closing, exited, failureReported, closeError, closePromise, resolveExit, exit, resolveStart, rejectStart, start, fail, err;
|
|
292
|
+
return _ts_generator(this, function(_state) {
|
|
293
|
+
switch(_state.label){
|
|
294
|
+
case 0:
|
|
295
|
+
workerData = {
|
|
296
|
+
configDir: configDir,
|
|
297
|
+
force: force,
|
|
298
|
+
heartbeatIntervalMs: heartbeatIntervalMs,
|
|
299
|
+
pid: process.pid,
|
|
300
|
+
token: (0, _nodecrypto.randomUUID)()
|
|
301
|
+
};
|
|
302
|
+
worker = new _nodeworker_threads.Worker((0, _workerfilets.resolveWorkerFile)('watch-heartbeat'), {
|
|
303
|
+
workerData: workerData
|
|
304
|
+
});
|
|
305
|
+
acquired = false;
|
|
306
|
+
closing = false;
|
|
307
|
+
exited = false;
|
|
308
|
+
failureReported = false;
|
|
309
|
+
closeError = null;
|
|
310
|
+
exit = new Promise(function(resolve) {
|
|
311
|
+
resolveExit = resolve;
|
|
312
|
+
});
|
|
313
|
+
start = new Promise(function(resolve, reject) {
|
|
314
|
+
resolveStart = resolve;
|
|
315
|
+
rejectStart = reject;
|
|
316
|
+
});
|
|
317
|
+
fail = function fail(err) {
|
|
318
|
+
if (failureReported) return;
|
|
319
|
+
failureReported = true;
|
|
320
|
+
if (!acquired) rejectStart(err);
|
|
321
|
+
else if (closing) closeError !== null && closeError !== void 0 ? closeError : closeError = err;
|
|
322
|
+
else onFailure(err);
|
|
323
|
+
};
|
|
324
|
+
worker.on('message', function(message) {
|
|
325
|
+
if (message.type === 'acquired') {
|
|
326
|
+
acquired = true;
|
|
327
|
+
resolveStart();
|
|
328
|
+
} else if (message.type === 'failure') {
|
|
329
|
+
fail((0, _workererrorts.reviveError)(message.error));
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
worker.on('error', fail);
|
|
333
|
+
worker.on('exit', function(code) {
|
|
334
|
+
exited = true;
|
|
335
|
+
resolveExit();
|
|
336
|
+
if (closing && code !== 0) closeError !== null && closeError !== void 0 ? closeError : closeError = new Error("watch heartbeat worker stopped with exit code ".concat(code));
|
|
337
|
+
else if (!closing && !failureReported) fail(new Error("watch heartbeat worker stopped unexpectedly with exit code ".concat(code)));
|
|
338
|
+
});
|
|
339
|
+
_state.label = 1;
|
|
340
|
+
case 1:
|
|
341
|
+
_state.trys.push([
|
|
342
|
+
1,
|
|
343
|
+
3,
|
|
344
|
+
,
|
|
345
|
+
7
|
|
346
|
+
]);
|
|
347
|
+
return [
|
|
348
|
+
4,
|
|
349
|
+
start
|
|
350
|
+
];
|
|
351
|
+
case 2:
|
|
352
|
+
_state.sent();
|
|
353
|
+
return [
|
|
354
|
+
3,
|
|
355
|
+
7
|
|
356
|
+
];
|
|
357
|
+
case 3:
|
|
358
|
+
err = _state.sent();
|
|
359
|
+
closing = true;
|
|
360
|
+
if (!!exited) return [
|
|
361
|
+
3,
|
|
362
|
+
5
|
|
363
|
+
];
|
|
364
|
+
return [
|
|
365
|
+
4,
|
|
366
|
+
worker.terminate()
|
|
367
|
+
];
|
|
368
|
+
case 4:
|
|
369
|
+
_state.sent();
|
|
370
|
+
_state.label = 5;
|
|
371
|
+
case 5:
|
|
372
|
+
return [
|
|
373
|
+
4,
|
|
374
|
+
exit
|
|
375
|
+
];
|
|
376
|
+
case 6:
|
|
377
|
+
_state.sent();
|
|
378
|
+
throw err;
|
|
379
|
+
case 7:
|
|
380
|
+
return [
|
|
381
|
+
2,
|
|
382
|
+
{
|
|
383
|
+
close: function close() {
|
|
384
|
+
closePromise !== null && closePromise !== void 0 ? closePromise : closePromise = function() {
|
|
385
|
+
return _async_to_generator(function() {
|
|
386
|
+
var err;
|
|
387
|
+
return _ts_generator(this, function(_state) {
|
|
388
|
+
switch(_state.label){
|
|
389
|
+
case 0:
|
|
390
|
+
closing = true;
|
|
391
|
+
if (!!exited) return [
|
|
392
|
+
3,
|
|
393
|
+
4
|
|
394
|
+
];
|
|
395
|
+
_state.label = 1;
|
|
396
|
+
case 1:
|
|
397
|
+
_state.trys.push([
|
|
398
|
+
1,
|
|
399
|
+
2,
|
|
400
|
+
,
|
|
401
|
+
4
|
|
402
|
+
]);
|
|
403
|
+
worker.postMessage({
|
|
404
|
+
type: 'stop'
|
|
405
|
+
});
|
|
406
|
+
return [
|
|
407
|
+
3,
|
|
408
|
+
4
|
|
409
|
+
];
|
|
410
|
+
case 2:
|
|
411
|
+
err = _state.sent();
|
|
412
|
+
closeError !== null && closeError !== void 0 ? closeError : closeError = _instanceof(err, Error) ? err : new Error(String(err));
|
|
413
|
+
return [
|
|
414
|
+
4,
|
|
415
|
+
worker.terminate()
|
|
416
|
+
];
|
|
417
|
+
case 3:
|
|
418
|
+
_state.sent();
|
|
419
|
+
return [
|
|
420
|
+
3,
|
|
421
|
+
4
|
|
422
|
+
];
|
|
423
|
+
case 4:
|
|
424
|
+
return [
|
|
425
|
+
4,
|
|
426
|
+
exit
|
|
427
|
+
];
|
|
428
|
+
case 5:
|
|
429
|
+
_state.sent();
|
|
430
|
+
if (closeError) throw closeError;
|
|
431
|
+
return [
|
|
432
|
+
2
|
|
433
|
+
];
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
})();
|
|
437
|
+
}();
|
|
438
|
+
return closePromise;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
];
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
})();
|
|
445
|
+
}
|
|
446
|
+
/* CJS INTEROP */ if (exports.__esModule && exports.default) { try { Object.defineProperty(exports.default, '__esModule', { value: true }); for (var key in exports) { exports.default[key] = exports[key]; } } catch (_) {}; module.exports = exports.default; }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/watch-claim.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { DatabaseSync } from 'node:sqlite';\nimport { Worker } from 'node:worker_threads';\nimport { SenseError } from './errors.ts';\nimport { resolveWorkerFile } from './lib/worker-file.ts';\nimport { reviveError, type WorkerErrorPayload } from './scan/worker-error.ts';\nimport { createConnection } from './store/sqlite/connection.ts';\nimport { BEGIN_WRITE, withTransaction } from './store/transaction.ts';\n\nexport const WATCH_CLAIM_FILENAME = '.sense-watch.db';\nexport const WATCH_HEARTBEAT_INTERVAL_MS = 5_000;\nexport const WATCH_STALE_HEARTBEAT_MS = 15_000;\nconst CLAIM_BUSY_TIMEOUT_MS = 30_000;\n\nexport interface WatchClaimRecord {\n token: string;\n pid: number;\n heartbeatMs: number;\n}\n\ninterface ClaimRow {\n token: string;\n pid: number;\n heartbeat_ms: number;\n}\n\nexport interface WatchClaimWorkerData {\n configDir: string;\n force: boolean;\n heartbeatIntervalMs: number;\n pid: number;\n token: string;\n}\n\nexport type WatchClaimWorkerMessage = { type: 'acquired' } | { type: 'stopped' } | { type: 'failure'; error: WorkerErrorPayload };\n\nexport interface WatchClaimController {\n close(): Promise<void>;\n}\n\nfunction claimPath(configDir: string): string {\n return join(configDir, WATCH_CLAIM_FILENAME);\n}\n\nfunction changedOne(changes: number | bigint): boolean {\n return changes === 1 || changes === BigInt(1);\n}\n\n// This database is separate from the selected search store so its heartbeat remains writable while\n// a DuckDB, Turso, or SQLite reconciliation owns the cache connection or blocks the main thread.\nexport class WatchClaimDatabase {\n private readonly db: DatabaseSync;\n private readonly conn: ReturnType<typeof createConnection>;\n\n constructor(configDir: string) {\n const db = new DatabaseSync(claimPath(configDir));\n try {\n db.exec(`PRAGMA busy_timeout = ${CLAIM_BUSY_TIMEOUT_MS}`);\n db.exec('CREATE TABLE IF NOT EXISTS watch_claim (singleton INTEGER PRIMARY KEY CHECK (singleton = 1), token TEXT NOT NULL, pid INTEGER NOT NULL, heartbeat_ms INTEGER NOT NULL)');\n } catch (err) {\n try {\n db.close();\n } catch (closeError) {\n throw new AggregateError([err, closeError], 'watch claim setup and database cleanup both failed');\n }\n throw err;\n }\n this.db = db;\n this.conn = createConnection(db);\n }\n\n async acquire(token: string, pid: number, force: boolean): Promise<void> {\n await withTransaction(\n this.conn,\n async () => {\n const current = this.db.prepare('SELECT token, pid, heartbeat_ms FROM watch_claim WHERE singleton = 1').get() as ClaimRow | undefined;\n if (current && !force) {\n const age = Date.now() - current.heartbeat_ms;\n if (age >= 0 && age < WATCH_STALE_HEARTBEAT_MS) throw new SenseError('WATCH_ACTIVE', `another watcher appears active (heartbeat ${Math.round(age / 1000)}s ago); use --force to override`);\n }\n this.db.prepare('INSERT INTO watch_claim (singleton, token, pid, heartbeat_ms) VALUES (1, ?, ?, ?) ON CONFLICT(singleton) DO UPDATE SET token = excluded.token, pid = excluded.pid, heartbeat_ms = excluded.heartbeat_ms').run(token, pid, Date.now());\n },\n BEGIN_WRITE\n );\n }\n\n renew(token: string): boolean {\n return changedOne(this.db.prepare('UPDATE watch_claim SET heartbeat_ms = ? WHERE singleton = 1 AND token = ?').run(Date.now(), token).changes);\n }\n\n release(token: string): boolean {\n return changedOne(this.db.prepare('DELETE FROM watch_claim WHERE singleton = 1 AND token = ?').run(token).changes);\n }\n\n read(): WatchClaimRecord | null {\n const row = this.db.prepare('SELECT token, pid, heartbeat_ms FROM watch_claim WHERE singleton = 1').get() as ClaimRow | undefined;\n return row ? { token: row.token, pid: row.pid, heartbeatMs: row.heartbeat_ms } : null;\n }\n\n close(): void {\n this.db.close();\n }\n}\n\n// Status opens the coordinator read-only and therefore cannot create either the file or its schema.\nexport function readWatchClaim(configDir: string): WatchClaimRecord | null {\n const path = claimPath(configDir);\n if (!existsSync(path)) return null;\n let db: DatabaseSync;\n try {\n db = new DatabaseSync(path, { readOnly: true });\n } catch (err) {\n if (!existsSync(path)) return null;\n throw err;\n }\n try {\n db.exec(`PRAGMA busy_timeout = ${CLAIM_BUSY_TIMEOUT_MS}`);\n const row = db.prepare('SELECT token, pid, heartbeat_ms FROM watch_claim WHERE singleton = 1').get() as ClaimRow | undefined;\n return row ? { token: row.token, pid: row.pid, heartbeatMs: row.heartbeat_ms } : null;\n } catch (err) {\n if (/no such table: watch_claim/.test((err as Error).message)) return null;\n throw err;\n } finally {\n db.close();\n }\n}\n\nexport async function startWatchClaim(configDir: string, force: boolean, heartbeatIntervalMs: number, onFailure: (err: Error) => void): Promise<WatchClaimController> {\n const workerData: WatchClaimWorkerData = { configDir, force, heartbeatIntervalMs, pid: process.pid, token: randomUUID() };\n const worker = new Worker(resolveWorkerFile('watch-heartbeat'), { workerData });\n let acquired = false;\n let closing = false;\n let exited = false;\n let failureReported = false;\n let closeError: Error | null = null;\n let closePromise: Promise<void> | undefined;\n let resolveExit!: () => void;\n const exit = new Promise<void>((resolve) => {\n resolveExit = resolve;\n });\n let resolveStart!: () => void;\n let rejectStart!: (err: Error) => void;\n const start = new Promise<void>((resolve, reject) => {\n resolveStart = resolve;\n rejectStart = reject;\n });\n\n const fail = (err: Error): void => {\n if (failureReported) return;\n failureReported = true;\n if (!acquired) rejectStart(err);\n else if (closing) closeError ??= err;\n else onFailure(err);\n };\n worker.on('message', (message: WatchClaimWorkerMessage) => {\n if (message.type === 'acquired') {\n acquired = true;\n resolveStart();\n } else if (message.type === 'failure') {\n fail(reviveError(message.error));\n }\n });\n worker.on('error', fail);\n worker.on('exit', (code) => {\n exited = true;\n resolveExit();\n if (closing && code !== 0) closeError ??= new Error(`watch heartbeat worker stopped with exit code ${code}`);\n else if (!closing && !failureReported) fail(new Error(`watch heartbeat worker stopped unexpectedly with exit code ${code}`));\n });\n\n try {\n await start;\n } catch (err) {\n closing = true;\n if (!exited) await worker.terminate();\n await exit;\n throw err;\n }\n\n return {\n close(): Promise<void> {\n closePromise ??= (async () => {\n closing = true;\n if (!exited) {\n try {\n worker.postMessage({ type: 'stop' });\n } catch (err) {\n closeError ??= err instanceof Error ? err : new Error(String(err));\n await worker.terminate();\n }\n }\n await exit;\n if (closeError) throw closeError;\n })();\n return closePromise;\n },\n };\n}\n"],"names":["WATCH_CLAIM_FILENAME","WATCH_HEARTBEAT_INTERVAL_MS","WATCH_STALE_HEARTBEAT_MS","WatchClaimDatabase","readWatchClaim","startWatchClaim","CLAIM_BUSY_TIMEOUT_MS","claimPath","configDir","join","changedOne","changes","BigInt","db","DatabaseSync","exec","err","close","closeError","AggregateError","conn","createConnection","acquire","token","pid","force","withTransaction","current","age","prepare","get","Date","now","heartbeat_ms","SenseError","Math","round","run","BEGIN_WRITE","renew","release","read","row","heartbeatMs","path","existsSync","readOnly","test","message","heartbeatIntervalMs","onFailure","workerData","worker","acquired","closing","exited","failureReported","closePromise","resolveExit","exit","resolveStart","rejectStart","start","fail","process","randomUUID","Worker","resolveWorkerFile","Promise","resolve","reject","on","type","reviveError","error","code","Error","terminate","postMessage","String"],"mappings":";;;;;;;;;;;QAWaA;eAAAA;;QACAC;eAAAA;;QACAC;eAAAA;;QAuCAC;eAAAA;;QAuDGC;eAAAA;;QAsBMC;eAAAA;;;0BAjIK;sBACA;wBACN;0BACQ;kCACN;wBACI;4BACO;6BACmB;4BACpB;6BACY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEtC,IAAML,uBAAuB;AAC7B,IAAMC,8BAA8B;AACpC,IAAMC,2BAA2B;AACxC,IAAMI,wBAAwB;AA4B9B,SAASC,UAAUC,SAAiB;IAClC,OAAOC,IAAAA,cAAI,EAACD,WAAWR;AACzB;AAEA,SAASU,WAAWC,OAAwB;IAC1C,OAAOA,YAAY,KAAKA,YAAYC,OAAO;AAC7C;AAIO,IAAA,AAAMT,mCAAN;;aAAMA,mBAICK,SAAiB;gCAJlBL;QAKT,IAAMU,KAAK,IAAIC,wBAAY,CAACP,UAAUC;QACtC,IAAI;YACFK,GAAGE,IAAI,CAAC,AAAC,yBAA8C,OAAtBT;YACjCO,GAAGE,IAAI,CAAC;QACV,EAAE,OAAOC,KAAK;YACZ,IAAI;gBACFH,GAAGI,KAAK;YACV,EAAE,OAAOC,YAAY;gBACnB,MAAM,IAAIC,eAAe;oBAACH;oBAAKE;iBAAW,EAAE;YAC9C;YACA,MAAMF;QACR;QACA,IAAI,CAACH,EAAE,GAAGA;QACV,IAAI,CAACO,IAAI,GAAGC,IAAAA,8BAAgB,EAACR;;iBAlBpBV;IAqBX,OAAMmB,OAaL,GAbD,SAAMA,QAAQC,KAAa,EAAEC,GAAW,EAAEC,KAAc;;;;;;;wBACtD;;4BAAMC,IAAAA,8BAAe,EACnB,IAAI,CAACN,IAAI,EACT;;wCACQO,SAEEC;;wCAFFD,UAAU,IAAI,CAACd,EAAE,CAACgB,OAAO,CAAC,wEAAwEC,GAAG;wCAC3G,IAAIH,WAAW,CAACF,OAAO;4CACfG,MAAMG,KAAKC,GAAG,KAAKL,QAAQM,YAAY;4CAC7C,IAAIL,OAAO,KAAKA,MAAM1B,0BAA0B,MAAM,IAAIgC,oBAAU,CAAC,gBAAgB,AAAC,6CAAmE,OAAvBC,KAAKC,KAAK,CAACR,MAAM,OAAM;wCAC3J;wCACA,IAAI,CAACf,EAAE,CAACgB,OAAO,CAAC,2MAA2MQ,GAAG,CAACd,OAAOC,KAAKO,KAAKC,GAAG;;;;;gCACrP;+BACAM,0BAAW;;;wBAVb;;;;;;QAYF;;IAEAC,OAAAA,KAEC,GAFDA,SAAAA,MAAMhB,KAAa;QACjB,OAAOb,WAAW,IAAI,CAACG,EAAE,CAACgB,OAAO,CAAC,6EAA6EQ,GAAG,CAACN,KAAKC,GAAG,IAAIT,OAAOZ,OAAO;IAC/I;IAEA6B,OAAAA,OAEC,GAFDA,SAAAA,QAAQjB,KAAa;QACnB,OAAOb,WAAW,IAAI,CAACG,EAAE,CAACgB,OAAO,CAAC,6DAA6DQ,GAAG,CAACd,OAAOZ,OAAO;IACnH;IAEA8B,OAAAA,IAGC,GAHDA,SAAAA;QACE,IAAMC,MAAM,IAAI,CAAC7B,EAAE,CAACgB,OAAO,CAAC,wEAAwEC,GAAG;QACvG,OAAOY,MAAM;YAAEnB,OAAOmB,IAAInB,KAAK;YAAEC,KAAKkB,IAAIlB,GAAG;YAAEmB,aAAaD,IAAIT,YAAY;QAAC,IAAI;IACnF;IAEAhB,OAAAA,KAEC,GAFDA,SAAAA;QACE,IAAI,CAACJ,EAAE,CAACI,KAAK;IACf;WAnDWd;;AAuDN,SAASC,eAAeI,SAAiB;IAC9C,IAAMoC,OAAOrC,UAAUC;IACvB,IAAI,CAACqC,IAAAA,kBAAU,EAACD,OAAO,OAAO;IAC9B,IAAI/B;IACJ,IAAI;QACFA,KAAK,IAAIC,wBAAY,CAAC8B,MAAM;YAAEE,UAAU;QAAK;IAC/C,EAAE,OAAO9B,KAAK;QACZ,IAAI,CAAC6B,IAAAA,kBAAU,EAACD,OAAO,OAAO;QAC9B,MAAM5B;IACR;IACA,IAAI;QACFH,GAAGE,IAAI,CAAC,AAAC,yBAA8C,OAAtBT;QACjC,IAAMoC,MAAM7B,GAAGgB,OAAO,CAAC,wEAAwEC,GAAG;QAClG,OAAOY,MAAM;YAAEnB,OAAOmB,IAAInB,KAAK;YAAEC,KAAKkB,IAAIlB,GAAG;YAAEmB,aAAaD,IAAIT,YAAY;QAAC,IAAI;IACnF,EAAE,OAAOjB,KAAK;QACZ,IAAI,6BAA6B+B,IAAI,CAAC,AAAC/B,IAAcgC,OAAO,GAAG,OAAO;QACtE,MAAMhC;IACR,SAAU;QACRH,GAAGI,KAAK;IACV;AACF;AAEO,SAAeZ,gBAAgBG,SAAiB,EAAEiB,KAAc,EAAEwB,mBAA2B,EAAEC,SAA+B;;YAC7HC,YACAC,QACFC,UACAC,SACAC,QACAC,iBACAtC,YACAuC,cACAC,aACEC,MAGFC,cACAC,aACEC,OAKAC,MAyBG/C;;;;oBA5CHmC,aAAmC;wBAAE3C,WAAAA;wBAAWiB,OAAAA;wBAAOwB,qBAAAA;wBAAqBzB,KAAKwC,QAAQxC,GAAG;wBAAED,OAAO0C,IAAAA,sBAAU;oBAAG;oBAClHb,SAAS,IAAIc,0BAAM,CAACC,IAAAA,+BAAiB,EAAC,oBAAoB;wBAAEhB,YAAAA;oBAAW;oBACzEE,WAAW;oBACXC,UAAU;oBACVC,SAAS;oBACTC,kBAAkB;oBAClBtC,aAA2B;oBAGzByC,OAAO,IAAIS,QAAc,SAACC;wBAC9BX,cAAcW;oBAChB;oBAGMP,QAAQ,IAAIM,QAAc,SAACC,SAASC;wBACxCV,eAAeS;wBACfR,cAAcS;oBAChB;oBAEMP,OAAO,cAAC/C;wBACZ,IAAIwC,iBAAiB;wBACrBA,kBAAkB;wBAClB,IAAI,CAACH,UAAUQ,YAAY7C;6BACtB,IAAIsC,SAASpC,uBAAAA,wBAAAA,aAAAA,aAAeF;6BAC5BkC,UAAUlC;oBACjB;oBACAoC,OAAOmB,EAAE,CAAC,WAAW,SAACvB;wBACpB,IAAIA,QAAQwB,IAAI,KAAK,YAAY;4BAC/BnB,WAAW;4BACXO;wBACF,OAAO,IAAIZ,QAAQwB,IAAI,KAAK,WAAW;4BACrCT,KAAKU,IAAAA,0BAAW,EAACzB,QAAQ0B,KAAK;wBAChC;oBACF;oBACAtB,OAAOmB,EAAE,CAAC,SAASR;oBACnBX,OAAOmB,EAAE,CAAC,QAAQ,SAACI;wBACjBpB,SAAS;wBACTG;wBACA,IAAIJ,WAAWqB,SAAS,GAAGzD,uBAAAA,wBAAAA,aAAAA,aAAe,IAAI0D,MAAM,AAAC,iDAAqD,OAALD;6BAChG,IAAI,CAACrB,WAAW,CAACE,iBAAiBO,KAAK,IAAIa,MAAM,AAAC,8DAAkE,OAALD;oBACtH;;;;;;;;;oBAGE;;wBAAMb;;;oBAAN;;;;;;oBACO9C;oBACPsC,UAAU;yBACN,CAACC,QAAD;;;;oBAAS;;wBAAMH,OAAOyB,SAAS;;;oBAAtB;;;oBACb;;wBAAMlB;;;oBAAN;oBACA,MAAM3C;;oBAGR;;wBAAO;4BACLC,OAAAA,SAAAA;gCACEwC,yBAAAA,0BAAAA,eAAAA,eAAiB,AAAC;;4CAKLzC;;;;oDAJXsC,UAAU;yDACN,CAACC,QAAD;;;;;;;;;;;;oDAEAH,OAAO0B,WAAW,CAAC;wDAAEN,MAAM;oDAAO;;;;;;oDAC3BxD;oDACPE,uBAAAA,wBAAAA,aAAAA,aAAeF,AAAG,YAAHA,KAAe4D,SAAQ5D,MAAM,IAAI4D,MAAMG,OAAO/D;oDAC7D;;wDAAMoC,OAAOyB,SAAS;;;oDAAtB;;;;;;oDAGJ;;wDAAMlB;;;oDAAN;oDACA,IAAIzC,YAAY,MAAMA;;;;;;oCACxB;;gCACA,OAAOuC;4BACT;wBACF;;;;IACF"}
|