inputlayer-js-dev 0.1.0-dev.ec507e7
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/chunk-S3PNNRWY.mjs +116 -0
- package/dist/functions.d.mts +76 -0
- package/dist/functions.d.ts +76 -0
- package/dist/functions.js +340 -0
- package/dist/functions.mjs +253 -0
- package/dist/index.d.mts +929 -0
- package/dist/index.d.ts +929 -0
- package/dist/index.js +2186 -0
- package/dist/index.mjs +2034 -0
- package/dist/proxy-CPWAPhZZ.d.mts +198 -0
- package/dist/proxy-CPWAPhZZ.d.ts +198 -0
- package/package.json +61 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2034 @@
|
|
|
1
|
+
import {
|
|
2
|
+
aggExpr,
|
|
3
|
+
and,
|
|
4
|
+
arithmetic,
|
|
5
|
+
column,
|
|
6
|
+
comparison,
|
|
7
|
+
inExpr,
|
|
8
|
+
isAggExpr,
|
|
9
|
+
isAnd,
|
|
10
|
+
isArithmetic,
|
|
11
|
+
isColumn,
|
|
12
|
+
isComparison,
|
|
13
|
+
isFuncCall,
|
|
14
|
+
isInExpr,
|
|
15
|
+
isLiteral,
|
|
16
|
+
isMatchExpr,
|
|
17
|
+
isNegatedIn,
|
|
18
|
+
isNot,
|
|
19
|
+
isOr,
|
|
20
|
+
isOrderedColumn,
|
|
21
|
+
literal,
|
|
22
|
+
matchExpr,
|
|
23
|
+
negatedIn,
|
|
24
|
+
not,
|
|
25
|
+
or,
|
|
26
|
+
orderedColumn
|
|
27
|
+
} from "./chunk-S3PNNRWY.mjs";
|
|
28
|
+
|
|
29
|
+
// src/types.ts
|
|
30
|
+
var Timestamp = class _Timestamp {
|
|
31
|
+
constructor(ms) {
|
|
32
|
+
this.ms = Math.floor(ms);
|
|
33
|
+
}
|
|
34
|
+
static now() {
|
|
35
|
+
return new _Timestamp(Date.now());
|
|
36
|
+
}
|
|
37
|
+
static fromDate(date) {
|
|
38
|
+
return new _Timestamp(date.getTime());
|
|
39
|
+
}
|
|
40
|
+
toDate() {
|
|
41
|
+
return new Date(this.ms);
|
|
42
|
+
}
|
|
43
|
+
valueOf() {
|
|
44
|
+
return this.ms;
|
|
45
|
+
}
|
|
46
|
+
toString() {
|
|
47
|
+
return String(this.ms);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// src/naming.ts
|
|
52
|
+
function camelToSnake(name) {
|
|
53
|
+
let s = name.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2");
|
|
54
|
+
s = s.replace(/([a-z0-9])([A-Z])/g, "$1_$2");
|
|
55
|
+
return s.toLowerCase();
|
|
56
|
+
}
|
|
57
|
+
function snakeToCamel(name) {
|
|
58
|
+
return name.split("_").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
59
|
+
}
|
|
60
|
+
function columnToVariable(columnName) {
|
|
61
|
+
return snakeToCamel(columnName);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/proxy.ts
|
|
65
|
+
function wrap(value) {
|
|
66
|
+
if (value instanceof ColumnProxy) {
|
|
67
|
+
return value.toAst();
|
|
68
|
+
}
|
|
69
|
+
if (value !== null && typeof value === "object" && "_tag" in value) {
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
return literal(value);
|
|
73
|
+
}
|
|
74
|
+
var ColumnProxy = class {
|
|
75
|
+
constructor(relation2, name, refAlias) {
|
|
76
|
+
this.relation = relation2;
|
|
77
|
+
this.name = name;
|
|
78
|
+
this.refAlias = refAlias;
|
|
79
|
+
}
|
|
80
|
+
toAst() {
|
|
81
|
+
return column(this.relation, this.name, this.refAlias);
|
|
82
|
+
}
|
|
83
|
+
// ── Comparison operators -> BoolExpr ────────────────────────────
|
|
84
|
+
eq(other) {
|
|
85
|
+
return comparison("=", this.toAst(), wrap(other));
|
|
86
|
+
}
|
|
87
|
+
ne(other) {
|
|
88
|
+
return comparison("!=", this.toAst(), wrap(other));
|
|
89
|
+
}
|
|
90
|
+
lt(other) {
|
|
91
|
+
return comparison("<", this.toAst(), wrap(other));
|
|
92
|
+
}
|
|
93
|
+
le(other) {
|
|
94
|
+
return comparison("<=", this.toAst(), wrap(other));
|
|
95
|
+
}
|
|
96
|
+
gt(other) {
|
|
97
|
+
return comparison(">", this.toAst(), wrap(other));
|
|
98
|
+
}
|
|
99
|
+
ge(other) {
|
|
100
|
+
return comparison(">=", this.toAst(), wrap(other));
|
|
101
|
+
}
|
|
102
|
+
// ── Arithmetic operators -> Expr ────────────────────────────────
|
|
103
|
+
add(other) {
|
|
104
|
+
return arithmetic("+", this.toAst(), wrap(other));
|
|
105
|
+
}
|
|
106
|
+
sub(other) {
|
|
107
|
+
return arithmetic("-", this.toAst(), wrap(other));
|
|
108
|
+
}
|
|
109
|
+
mul(other) {
|
|
110
|
+
return arithmetic("*", this.toAst(), wrap(other));
|
|
111
|
+
}
|
|
112
|
+
div(other) {
|
|
113
|
+
return arithmetic("/", this.toAst(), wrap(other));
|
|
114
|
+
}
|
|
115
|
+
mod(other) {
|
|
116
|
+
return arithmetic("%", this.toAst(), wrap(other));
|
|
117
|
+
}
|
|
118
|
+
// ── Membership ──────────────────────────────────────────────────
|
|
119
|
+
in(other) {
|
|
120
|
+
return inExpr(this.toAst(), other.toAst());
|
|
121
|
+
}
|
|
122
|
+
notIn(other) {
|
|
123
|
+
return negatedIn(this.toAst(), other.toAst());
|
|
124
|
+
}
|
|
125
|
+
// ── Ordering ────────────────────────────────────────────────────
|
|
126
|
+
asc() {
|
|
127
|
+
return orderedColumn(this.toAst(), false);
|
|
128
|
+
}
|
|
129
|
+
desc() {
|
|
130
|
+
return orderedColumn(this.toAst(), true);
|
|
131
|
+
}
|
|
132
|
+
// ── Multi-column match ──────────────────────────────────────────
|
|
133
|
+
matches(relationName, on) {
|
|
134
|
+
const bindings = {};
|
|
135
|
+
for (const [targetCol, sourceColName] of Object.entries(on)) {
|
|
136
|
+
bindings[targetCol] = column(this.relation, sourceColName, this.refAlias);
|
|
137
|
+
}
|
|
138
|
+
return matchExpr(relationName, bindings, false);
|
|
139
|
+
}
|
|
140
|
+
notMatches(relationName, on) {
|
|
141
|
+
const bindings = {};
|
|
142
|
+
for (const [targetCol, sourceColName] of Object.entries(on)) {
|
|
143
|
+
bindings[targetCol] = column(this.relation, sourceColName, this.refAlias);
|
|
144
|
+
}
|
|
145
|
+
return matchExpr(relationName, bindings, true);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
function AND(left, right) {
|
|
149
|
+
return and(left, right);
|
|
150
|
+
}
|
|
151
|
+
function OR(left, right) {
|
|
152
|
+
return or(left, right);
|
|
153
|
+
}
|
|
154
|
+
function NOT(operand) {
|
|
155
|
+
return not(operand);
|
|
156
|
+
}
|
|
157
|
+
var RelationProxy = class {
|
|
158
|
+
constructor(relationName, refAlias) {
|
|
159
|
+
this.relationName = relationName;
|
|
160
|
+
this.refAlias = refAlias;
|
|
161
|
+
}
|
|
162
|
+
/** Get a ColumnProxy for the named column. */
|
|
163
|
+
col(name) {
|
|
164
|
+
return new ColumnProxy(this.relationName, name, this.refAlias);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
var RelationRef = class {
|
|
168
|
+
constructor(schema, alias) {
|
|
169
|
+
this.schema = schema;
|
|
170
|
+
this.alias = alias;
|
|
171
|
+
this.relationName = schema.name ?? camelToSnake(alias);
|
|
172
|
+
}
|
|
173
|
+
/** Get a ColumnProxy for the named column. */
|
|
174
|
+
col(name) {
|
|
175
|
+
return new ColumnProxy(this.relationName, name, this.alias);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// src/relation.ts
|
|
180
|
+
var RelationDef = class {
|
|
181
|
+
constructor(className, columnTypes, name) {
|
|
182
|
+
this.className = className;
|
|
183
|
+
this.relationName = name ?? camelToSnake(className);
|
|
184
|
+
this.columns = Object.keys(columnTypes);
|
|
185
|
+
this.columnTypes = { ...columnTypes };
|
|
186
|
+
}
|
|
187
|
+
/** Get a ColumnProxy for a column (for query building). */
|
|
188
|
+
col(name) {
|
|
189
|
+
if (!(name in this.columnTypes)) {
|
|
190
|
+
throw new Error(
|
|
191
|
+
`Column '${name}' does not exist on relation '${this.relationName}'. Available: ${this.columns.join(", ")}`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
return new ColumnProxy(this.relationName, name);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Create multiple independent references for self-joins.
|
|
198
|
+
*
|
|
199
|
+
* Usage:
|
|
200
|
+
* const [r1, r2] = Follow.refs(2);
|
|
201
|
+
* kg.query({ select: [r1.col("follower"), r2.col("followee")], join: [r1, r2], ... });
|
|
202
|
+
*/
|
|
203
|
+
refs(n) {
|
|
204
|
+
const refs = [];
|
|
205
|
+
for (let i = 1; i <= n; i++) {
|
|
206
|
+
refs.push(
|
|
207
|
+
new RelationRef(
|
|
208
|
+
{ name: this.relationName, columns: this.columns.map((c) => ({ name: c, type: this.columnTypes[c] })) },
|
|
209
|
+
`${this.relationName}_${i}`
|
|
210
|
+
)
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
return refs;
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
function relation(className, columnTypes, opts) {
|
|
217
|
+
return new RelationDef(className, columnTypes, opts?.name);
|
|
218
|
+
}
|
|
219
|
+
function compileValue(value) {
|
|
220
|
+
if (value === null || value === void 0) {
|
|
221
|
+
return "null";
|
|
222
|
+
}
|
|
223
|
+
if (typeof value === "boolean") {
|
|
224
|
+
return value ? "true" : "false";
|
|
225
|
+
}
|
|
226
|
+
if (value instanceof Timestamp) {
|
|
227
|
+
return String(value.ms);
|
|
228
|
+
}
|
|
229
|
+
if (typeof value === "number") {
|
|
230
|
+
if (Number.isInteger(value)) {
|
|
231
|
+
return String(value);
|
|
232
|
+
}
|
|
233
|
+
return String(value);
|
|
234
|
+
}
|
|
235
|
+
if (typeof value === "string") {
|
|
236
|
+
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
237
|
+
return `"${escaped}"`;
|
|
238
|
+
}
|
|
239
|
+
if (Array.isArray(value)) {
|
|
240
|
+
const inner = value.map(compileValue).join(", ");
|
|
241
|
+
return `[${inner}]`;
|
|
242
|
+
}
|
|
243
|
+
throw new TypeError(
|
|
244
|
+
`Cannot compile value of type ${typeof value}: ${String(value)}`
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
function resolveRelationName(r) {
|
|
248
|
+
if (typeof r === "string") return r;
|
|
249
|
+
return r.relationName;
|
|
250
|
+
}
|
|
251
|
+
function getColumns(r) {
|
|
252
|
+
return r.columns;
|
|
253
|
+
}
|
|
254
|
+
function getColumnTypes(r) {
|
|
255
|
+
return r.columnTypes;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// src/derived.ts
|
|
259
|
+
var FromWhere = class {
|
|
260
|
+
constructor(relations, condition) {
|
|
261
|
+
this.relations = relations;
|
|
262
|
+
this.condition = condition;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Map derived columns to body expressions.
|
|
266
|
+
* Keys must match the derived relation's column names.
|
|
267
|
+
*/
|
|
268
|
+
select(columns) {
|
|
269
|
+
const selectMap = {};
|
|
270
|
+
for (const [name, val] of Object.entries(columns)) {
|
|
271
|
+
if (val instanceof ColumnProxy) {
|
|
272
|
+
selectMap[name] = val.toAst();
|
|
273
|
+
} else {
|
|
274
|
+
selectMap[name] = val;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
relations: this.relations,
|
|
279
|
+
selectMap,
|
|
280
|
+
condition: this.condition
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
var FromBuilder = class {
|
|
285
|
+
constructor(rels) {
|
|
286
|
+
this.relations = rels.map((r) => {
|
|
287
|
+
if (r instanceof RelationDef) {
|
|
288
|
+
return { name: r.relationName, def: r, alias: void 0 };
|
|
289
|
+
}
|
|
290
|
+
return {
|
|
291
|
+
name: r.relationName,
|
|
292
|
+
def: {
|
|
293
|
+
relationName: r.relationName,
|
|
294
|
+
columns: r.schema.columns.map((c) => c.name),
|
|
295
|
+
columnTypes: {}
|
|
296
|
+
},
|
|
297
|
+
alias: r.alias
|
|
298
|
+
};
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Add a filter/join condition.
|
|
303
|
+
* Accepts a BoolExpr or a callback receiving RelationProxy objects.
|
|
304
|
+
*/
|
|
305
|
+
where(condition) {
|
|
306
|
+
let resolved;
|
|
307
|
+
if (typeof condition === "function") {
|
|
308
|
+
const proxies = this.relations.map(
|
|
309
|
+
(r) => new RelationProxy(r.name, r.alias)
|
|
310
|
+
);
|
|
311
|
+
resolved = condition(...proxies);
|
|
312
|
+
} else {
|
|
313
|
+
resolved = condition;
|
|
314
|
+
}
|
|
315
|
+
return new FromWhere(this.relations, resolved);
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Map derived columns to body expressions (no filter).
|
|
319
|
+
* Keys must match the derived relation's column names.
|
|
320
|
+
*/
|
|
321
|
+
select(columns) {
|
|
322
|
+
const selectMap = {};
|
|
323
|
+
for (const [name, val] of Object.entries(columns)) {
|
|
324
|
+
if (val instanceof ColumnProxy) {
|
|
325
|
+
selectMap[name] = val.toAst();
|
|
326
|
+
} else {
|
|
327
|
+
selectMap[name] = val;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
relations: this.relations,
|
|
332
|
+
selectMap,
|
|
333
|
+
condition: void 0
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
function from(...relations) {
|
|
338
|
+
return new FromBuilder(relations);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// src/aggregations.ts
|
|
342
|
+
function toExpr(col) {
|
|
343
|
+
if ("toAst" in col && typeof col.toAst === "function") {
|
|
344
|
+
return col.toAst();
|
|
345
|
+
}
|
|
346
|
+
return col;
|
|
347
|
+
}
|
|
348
|
+
function count(column2) {
|
|
349
|
+
if (column2 === void 0) {
|
|
350
|
+
return aggExpr({ func: "count" });
|
|
351
|
+
}
|
|
352
|
+
return aggExpr({ func: "count", column: toExpr(column2) });
|
|
353
|
+
}
|
|
354
|
+
function countDistinct(column2) {
|
|
355
|
+
return aggExpr({ func: "count_distinct", column: toExpr(column2) });
|
|
356
|
+
}
|
|
357
|
+
function sum(column2) {
|
|
358
|
+
return aggExpr({ func: "sum", column: toExpr(column2) });
|
|
359
|
+
}
|
|
360
|
+
function min(column2) {
|
|
361
|
+
return aggExpr({ func: "min", column: toExpr(column2) });
|
|
362
|
+
}
|
|
363
|
+
function max(column2) {
|
|
364
|
+
return aggExpr({ func: "max", column: toExpr(column2) });
|
|
365
|
+
}
|
|
366
|
+
function avg(column2) {
|
|
367
|
+
return aggExpr({ func: "avg", column: toExpr(column2) });
|
|
368
|
+
}
|
|
369
|
+
function topK(opts) {
|
|
370
|
+
return aggExpr({
|
|
371
|
+
func: "top_k",
|
|
372
|
+
params: [opts.k],
|
|
373
|
+
passthrough: (opts.passthrough ?? []).map(toExpr),
|
|
374
|
+
orderColumn: toExpr(opts.orderBy),
|
|
375
|
+
desc: opts.desc ?? true
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
function topKThreshold(opts) {
|
|
379
|
+
return aggExpr({
|
|
380
|
+
func: "top_k_threshold",
|
|
381
|
+
params: [opts.k, opts.threshold],
|
|
382
|
+
passthrough: (opts.passthrough ?? []).map(toExpr),
|
|
383
|
+
orderColumn: toExpr(opts.orderBy),
|
|
384
|
+
desc: opts.desc ?? true
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
function withinRadius(opts) {
|
|
388
|
+
return aggExpr({
|
|
389
|
+
func: "within_radius",
|
|
390
|
+
params: [opts.maxDistance],
|
|
391
|
+
passthrough: (opts.passthrough ?? []).map(toExpr),
|
|
392
|
+
orderColumn: toExpr(opts.distance),
|
|
393
|
+
desc: !(opts.asc ?? true)
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// src/index-def.ts
|
|
398
|
+
var HnswIndex = class {
|
|
399
|
+
constructor(opts) {
|
|
400
|
+
this.name = opts.name;
|
|
401
|
+
this.relation = opts.relation;
|
|
402
|
+
this.column = opts.column;
|
|
403
|
+
this.metric = opts.metric ?? "cosine";
|
|
404
|
+
this.m = opts.m ?? 16;
|
|
405
|
+
this.efConstruction = opts.efConstruction ?? 100;
|
|
406
|
+
this.efSearch = opts.efSearch ?? 50;
|
|
407
|
+
}
|
|
408
|
+
/** Compile this index definition to an IQL meta command. */
|
|
409
|
+
toIQL() {
|
|
410
|
+
const relName = resolveRelationName(this.relation);
|
|
411
|
+
return `.index create ${this.name} on ${relName}(${this.column}) type hnsw metric ${this.metric} m ${this.m} ef_construction ${this.efConstruction} ef_search ${this.efSearch}`;
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
// src/errors.ts
|
|
416
|
+
var InputLayerError = class extends Error {
|
|
417
|
+
constructor(message) {
|
|
418
|
+
super(message);
|
|
419
|
+
this.name = "InputLayerError";
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
var ConnectionError = class extends InputLayerError {
|
|
423
|
+
constructor(message) {
|
|
424
|
+
super(message);
|
|
425
|
+
this.name = "ConnectionError";
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
var AuthenticationError = class extends InputLayerError {
|
|
429
|
+
constructor(message) {
|
|
430
|
+
super(message);
|
|
431
|
+
this.name = "AuthenticationError";
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
var SchemaConflictError = class extends InputLayerError {
|
|
435
|
+
constructor(message, opts) {
|
|
436
|
+
super(message);
|
|
437
|
+
this.name = "SchemaConflictError";
|
|
438
|
+
this.existingSchema = opts?.existingSchema;
|
|
439
|
+
this.proposedSchema = opts?.proposedSchema;
|
|
440
|
+
this.conflicts = opts?.conflicts ?? [];
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
var ValidationError = class extends InputLayerError {
|
|
444
|
+
constructor(message, opts) {
|
|
445
|
+
super(message);
|
|
446
|
+
this.name = "ValidationError";
|
|
447
|
+
this.details = opts?.details ?? [];
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
var QueryTimeoutError = class extends InputLayerError {
|
|
451
|
+
constructor(message) {
|
|
452
|
+
super(message);
|
|
453
|
+
this.name = "QueryTimeoutError";
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
var PermissionError = class extends InputLayerError {
|
|
457
|
+
constructor(message) {
|
|
458
|
+
super(message);
|
|
459
|
+
this.name = "PermissionError";
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
var KnowledgeGraphNotFoundError = class extends InputLayerError {
|
|
463
|
+
constructor(message) {
|
|
464
|
+
super(message);
|
|
465
|
+
this.name = "KnowledgeGraphNotFoundError";
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
var KnowledgeGraphExistsError = class extends InputLayerError {
|
|
469
|
+
constructor(message) {
|
|
470
|
+
super(message);
|
|
471
|
+
this.name = "KnowledgeGraphExistsError";
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
var CannotDropError = class extends InputLayerError {
|
|
475
|
+
constructor(message) {
|
|
476
|
+
super(message);
|
|
477
|
+
this.name = "CannotDropError";
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
var RelationNotFoundError = class extends InputLayerError {
|
|
481
|
+
constructor(message) {
|
|
482
|
+
super(message);
|
|
483
|
+
this.name = "RelationNotFoundError";
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
var RuleNotFoundError = class extends InputLayerError {
|
|
487
|
+
constructor(message) {
|
|
488
|
+
super(message);
|
|
489
|
+
this.name = "RuleNotFoundError";
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
var IndexNotFoundError = class extends InputLayerError {
|
|
493
|
+
constructor(message) {
|
|
494
|
+
super(message);
|
|
495
|
+
this.name = "IndexNotFoundError";
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
var InternalError = class extends InputLayerError {
|
|
499
|
+
constructor(message) {
|
|
500
|
+
super(message);
|
|
501
|
+
this.name = "InternalError";
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
// src/result.ts
|
|
506
|
+
var ResultSet = class {
|
|
507
|
+
constructor(opts) {
|
|
508
|
+
this.columns = opts.columns;
|
|
509
|
+
this.rows = opts.rows;
|
|
510
|
+
this.rowCount = opts.rowCount ?? opts.rows.length;
|
|
511
|
+
this.totalCount = opts.totalCount ?? this.rowCount;
|
|
512
|
+
this.truncated = opts.truncated ?? false;
|
|
513
|
+
this.executionTimeMs = opts.executionTimeMs ?? 0;
|
|
514
|
+
this.rowProvenance = opts.rowProvenance;
|
|
515
|
+
this.hasEphemeral = opts.hasEphemeral ?? false;
|
|
516
|
+
this.ephemeralSources = opts.ephemeralSources ?? [];
|
|
517
|
+
this.warnings = opts.warnings ?? [];
|
|
518
|
+
}
|
|
519
|
+
/** Number of result rows. */
|
|
520
|
+
get length() {
|
|
521
|
+
return this.rowCount;
|
|
522
|
+
}
|
|
523
|
+
/** True if there are any results. */
|
|
524
|
+
get isEmpty() {
|
|
525
|
+
return this.rowCount === 0;
|
|
526
|
+
}
|
|
527
|
+
/** Get a single row as a keyed object. */
|
|
528
|
+
get(index) {
|
|
529
|
+
return this.rowToObj(this.rows[index]);
|
|
530
|
+
}
|
|
531
|
+
/** Get the first row or undefined if empty. */
|
|
532
|
+
first() {
|
|
533
|
+
if (this.rows.length === 0) return void 0;
|
|
534
|
+
return this.rowToObj(this.rows[0]);
|
|
535
|
+
}
|
|
536
|
+
/** Return the single value from a 1x1 result. */
|
|
537
|
+
scalar() {
|
|
538
|
+
if (this.rows.length === 0 || this.rows[0].length === 0) {
|
|
539
|
+
throw new Error("No results to extract scalar from");
|
|
540
|
+
}
|
|
541
|
+
return this.rows[0][0];
|
|
542
|
+
}
|
|
543
|
+
/** Convert all rows to a list of keyed objects. */
|
|
544
|
+
toDicts() {
|
|
545
|
+
return this.rows.map((row) => this.rowToObj(row));
|
|
546
|
+
}
|
|
547
|
+
/** Convert all rows to a list of tuples (arrays). */
|
|
548
|
+
toTuples() {
|
|
549
|
+
return this.rows.map((row) => [...row]);
|
|
550
|
+
}
|
|
551
|
+
/** Iterate over rows as keyed objects. */
|
|
552
|
+
[Symbol.iterator]() {
|
|
553
|
+
let i = 0;
|
|
554
|
+
const self = this;
|
|
555
|
+
return {
|
|
556
|
+
next() {
|
|
557
|
+
if (i < self.rows.length) {
|
|
558
|
+
return { value: self.rowToObj(self.rows[i++]), done: false };
|
|
559
|
+
}
|
|
560
|
+
return { value: void 0, done: true };
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
rowToObj(row) {
|
|
565
|
+
const obj = {};
|
|
566
|
+
for (let i = 0; i < this.columns.length && i < row.length; i++) {
|
|
567
|
+
obj[this.columns[i]] = row[i];
|
|
568
|
+
}
|
|
569
|
+
return obj;
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
// src/connection.ts
|
|
574
|
+
import WebSocket from "ws";
|
|
575
|
+
|
|
576
|
+
// src/protocol.ts
|
|
577
|
+
function serializeMessage(msg) {
|
|
578
|
+
return JSON.stringify(msg);
|
|
579
|
+
}
|
|
580
|
+
function deserializeMessage(data) {
|
|
581
|
+
const obj = JSON.parse(data);
|
|
582
|
+
const type = obj.type;
|
|
583
|
+
if (type === "authenticated") {
|
|
584
|
+
return obj;
|
|
585
|
+
}
|
|
586
|
+
if (type === "auth_error") {
|
|
587
|
+
return obj;
|
|
588
|
+
}
|
|
589
|
+
if (type === "result") {
|
|
590
|
+
return obj;
|
|
591
|
+
}
|
|
592
|
+
if (type === "error") {
|
|
593
|
+
return obj;
|
|
594
|
+
}
|
|
595
|
+
if (type === "result_start") {
|
|
596
|
+
return obj;
|
|
597
|
+
}
|
|
598
|
+
if (type === "result_chunk") {
|
|
599
|
+
return obj;
|
|
600
|
+
}
|
|
601
|
+
if (type === "result_end") {
|
|
602
|
+
return obj;
|
|
603
|
+
}
|
|
604
|
+
if (type === "pong") {
|
|
605
|
+
return obj;
|
|
606
|
+
}
|
|
607
|
+
if (type === "persistent_update" || type === "rule_change" || type === "kg_change" || type === "schema_change") {
|
|
608
|
+
return obj;
|
|
609
|
+
}
|
|
610
|
+
throw new Error(`Unknown message type: ${type}`);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// src/notifications.ts
|
|
614
|
+
var NotificationDispatcher = class {
|
|
615
|
+
constructor() {
|
|
616
|
+
this.callbacks = [];
|
|
617
|
+
this._lastSeq = 0;
|
|
618
|
+
this.waiters = [];
|
|
619
|
+
}
|
|
620
|
+
get lastSeq() {
|
|
621
|
+
return this._lastSeq;
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Register a callback for notifications.
|
|
625
|
+
*
|
|
626
|
+
* @param eventType - Filter by event type (e.g. "persistent_update")
|
|
627
|
+
* @param opts - Additional filters
|
|
628
|
+
* @param callback - Function to call when a matching event arrives
|
|
629
|
+
*/
|
|
630
|
+
on(eventType, opts, callback) {
|
|
631
|
+
this.callbacks.push({
|
|
632
|
+
eventType,
|
|
633
|
+
relation: opts.relation,
|
|
634
|
+
knowledgeGraph: opts.knowledgeGraph,
|
|
635
|
+
callback
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
/** Remove a previously registered callback. */
|
|
639
|
+
off(callback) {
|
|
640
|
+
this.callbacks = this.callbacks.filter((e) => e.callback !== callback);
|
|
641
|
+
}
|
|
642
|
+
/** Dispatch a notification to matching callbacks. */
|
|
643
|
+
dispatch(event) {
|
|
644
|
+
this._lastSeq = Math.max(this._lastSeq, event.seq);
|
|
645
|
+
for (const waiter of this.waiters) {
|
|
646
|
+
waiter(event);
|
|
647
|
+
}
|
|
648
|
+
this.waiters = [];
|
|
649
|
+
for (const entry of this.callbacks) {
|
|
650
|
+
if (entry.eventType !== void 0 && event.type !== entry.eventType) continue;
|
|
651
|
+
if (entry.relation !== void 0 && event.relation !== entry.relation) continue;
|
|
652
|
+
if (entry.knowledgeGraph !== void 0 && event.knowledgeGraph !== entry.knowledgeGraph) continue;
|
|
653
|
+
try {
|
|
654
|
+
entry.callback(event);
|
|
655
|
+
} catch {
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
/** Wait for the next notification event. */
|
|
660
|
+
next() {
|
|
661
|
+
return new Promise((resolve) => {
|
|
662
|
+
this.waiters.push(resolve);
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Create an async iterable of notification events.
|
|
667
|
+
*
|
|
668
|
+
* Usage:
|
|
669
|
+
* for await (const event of dispatcher.events()) { ... }
|
|
670
|
+
*/
|
|
671
|
+
async *events() {
|
|
672
|
+
while (true) {
|
|
673
|
+
yield await this.next();
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
// src/connection.ts
|
|
679
|
+
var Connection = class {
|
|
680
|
+
constructor(opts) {
|
|
681
|
+
this.ws = null;
|
|
682
|
+
this._connected = false;
|
|
683
|
+
this._dispatcher = new NotificationDispatcher();
|
|
684
|
+
this.url = opts.url;
|
|
685
|
+
this.username = opts.username;
|
|
686
|
+
this.password = opts.password;
|
|
687
|
+
this.apiKey = opts.apiKey;
|
|
688
|
+
this.autoReconnect = opts.autoReconnect ?? true;
|
|
689
|
+
this.reconnectDelay = opts.reconnectDelay ?? 1;
|
|
690
|
+
this.maxReconnectAttempts = opts.maxReconnectAttempts ?? 10;
|
|
691
|
+
this.initialKg = opts.initialKg;
|
|
692
|
+
this._lastSeq = opts.lastSeq;
|
|
693
|
+
}
|
|
694
|
+
// ── Properties ──────────────────────────────────────────────────
|
|
695
|
+
get connected() {
|
|
696
|
+
return this._connected;
|
|
697
|
+
}
|
|
698
|
+
get sessionId() {
|
|
699
|
+
return this._sessionId;
|
|
700
|
+
}
|
|
701
|
+
get serverVersion() {
|
|
702
|
+
return this._serverVersion;
|
|
703
|
+
}
|
|
704
|
+
get role() {
|
|
705
|
+
return this._role;
|
|
706
|
+
}
|
|
707
|
+
get currentKg() {
|
|
708
|
+
return this._currentKg;
|
|
709
|
+
}
|
|
710
|
+
/** Force-set the current KG (used by KnowledgeGraph after .kg use). */
|
|
711
|
+
setCurrentKg(name) {
|
|
712
|
+
this._currentKg = name;
|
|
713
|
+
}
|
|
714
|
+
get dispatcher() {
|
|
715
|
+
return this._dispatcher;
|
|
716
|
+
}
|
|
717
|
+
get lastSeq() {
|
|
718
|
+
return this._dispatcher.lastSeq;
|
|
719
|
+
}
|
|
720
|
+
// ── Connection lifecycle ────────────────────────────────────────
|
|
721
|
+
async connect() {
|
|
722
|
+
let wsUrl = this.url;
|
|
723
|
+
const params = [];
|
|
724
|
+
if (this.initialKg) {
|
|
725
|
+
params.push(`kg=${this.initialKg}`);
|
|
726
|
+
}
|
|
727
|
+
if (this._lastSeq !== void 0) {
|
|
728
|
+
params.push(`last_seq=${this._lastSeq}`);
|
|
729
|
+
}
|
|
730
|
+
if (params.length > 0) {
|
|
731
|
+
const separator = wsUrl.includes("?") ? "&" : "?";
|
|
732
|
+
wsUrl = `${wsUrl}${separator}${params.join("&")}`;
|
|
733
|
+
}
|
|
734
|
+
try {
|
|
735
|
+
this.ws = await this.createWebSocket(wsUrl);
|
|
736
|
+
} catch (e) {
|
|
737
|
+
throw new ConnectionError(`Failed to connect to ${wsUrl}: ${e}`);
|
|
738
|
+
}
|
|
739
|
+
await this.authenticate();
|
|
740
|
+
this._connected = true;
|
|
741
|
+
this.ws.on("message", (data) => {
|
|
742
|
+
try {
|
|
743
|
+
const msg = deserializeMessage(String(data));
|
|
744
|
+
if (this.pendingResolve) {
|
|
745
|
+
this.pendingResolve(msg);
|
|
746
|
+
this.pendingResolve = void 0;
|
|
747
|
+
} else if (this.isNotification(msg)) {
|
|
748
|
+
this.dispatchNotification(msg);
|
|
749
|
+
}
|
|
750
|
+
} catch {
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
this.ws.on("close", () => {
|
|
754
|
+
this._connected = false;
|
|
755
|
+
if (this.autoReconnect) {
|
|
756
|
+
this.reconnect().catch(() => {
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
});
|
|
760
|
+
this.ws.on("error", () => {
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
async close() {
|
|
764
|
+
this._connected = false;
|
|
765
|
+
if (this.ws) {
|
|
766
|
+
this.ws.removeAllListeners();
|
|
767
|
+
this.ws.close();
|
|
768
|
+
this.ws = null;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
// ── Authentication ──────────────────────────────────────────────
|
|
772
|
+
async authenticate() {
|
|
773
|
+
if (!this.ws) throw new ConnectionError("Not connected");
|
|
774
|
+
let msg;
|
|
775
|
+
if (this.apiKey) {
|
|
776
|
+
msg = { type: "authenticate", api_key: this.apiKey };
|
|
777
|
+
} else if (this.username && this.password) {
|
|
778
|
+
msg = { type: "login", username: this.username, password: this.password };
|
|
779
|
+
} else {
|
|
780
|
+
throw new AuthenticationError(
|
|
781
|
+
"No credentials provided (need username/password or apiKey)"
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
this.ws.send(serializeMessage(msg));
|
|
785
|
+
const response = await this.receiveOne();
|
|
786
|
+
if (response.type === "auth_error") {
|
|
787
|
+
throw new AuthenticationError(response.message);
|
|
788
|
+
}
|
|
789
|
+
if (response.type === "authenticated") {
|
|
790
|
+
this._sessionId = response.session_id;
|
|
791
|
+
this._serverVersion = response.version;
|
|
792
|
+
this._role = response.role;
|
|
793
|
+
this._currentKg = response.knowledge_graph;
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
throw new AuthenticationError(`Unexpected auth response: ${JSON.stringify(response)}`);
|
|
797
|
+
}
|
|
798
|
+
// ── Command execution ───────────────────────────────────────────
|
|
799
|
+
/**
|
|
800
|
+
* Send a program/command and wait for the result.
|
|
801
|
+
* Transparently assembles streamed results (result_start -> chunks -> result_end).
|
|
802
|
+
*/
|
|
803
|
+
async execute(program) {
|
|
804
|
+
if (!this._connected || !this.ws) {
|
|
805
|
+
throw new ConnectionError("Not connected");
|
|
806
|
+
}
|
|
807
|
+
const msg = { type: "execute", program };
|
|
808
|
+
this.ws.send(serializeMessage(msg));
|
|
809
|
+
return this.readResult();
|
|
810
|
+
}
|
|
811
|
+
async readResult() {
|
|
812
|
+
while (true) {
|
|
813
|
+
const response = await this.receiveMessage();
|
|
814
|
+
if (this.isNotification(response)) {
|
|
815
|
+
this.dispatchNotification(response);
|
|
816
|
+
continue;
|
|
817
|
+
}
|
|
818
|
+
if (response.type === "pong") {
|
|
819
|
+
continue;
|
|
820
|
+
}
|
|
821
|
+
if (response.type === "result") {
|
|
822
|
+
if (response.switched_kg) {
|
|
823
|
+
this._currentKg = response.switched_kg;
|
|
824
|
+
}
|
|
825
|
+
return response;
|
|
826
|
+
}
|
|
827
|
+
if (response.type === "error") {
|
|
828
|
+
return {
|
|
829
|
+
type: "result",
|
|
830
|
+
columns: ["error"],
|
|
831
|
+
rows: [[response.message]],
|
|
832
|
+
row_count: 1,
|
|
833
|
+
total_count: 1,
|
|
834
|
+
truncated: false,
|
|
835
|
+
execution_time_ms: 0
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
if (response.type === "result_start") {
|
|
839
|
+
return this.assembleStream(response);
|
|
840
|
+
}
|
|
841
|
+
throw new InternalError(
|
|
842
|
+
`Unexpected message during result read: ${JSON.stringify(response)}`
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
async assembleStream(start) {
|
|
847
|
+
const allRows = [];
|
|
848
|
+
const allProvenance = [];
|
|
849
|
+
while (true) {
|
|
850
|
+
const response = await this.receiveMessage();
|
|
851
|
+
if (this.isNotification(response)) {
|
|
852
|
+
this.dispatchNotification(response);
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
855
|
+
if (response.type === "result_chunk") {
|
|
856
|
+
allRows.push(...response.rows);
|
|
857
|
+
if (response.row_provenance) {
|
|
858
|
+
allProvenance.push(...response.row_provenance);
|
|
859
|
+
}
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
862
|
+
if (response.type === "result_end") {
|
|
863
|
+
if (start.switched_kg) {
|
|
864
|
+
this._currentKg = start.switched_kg;
|
|
865
|
+
}
|
|
866
|
+
return {
|
|
867
|
+
type: "result",
|
|
868
|
+
columns: start.columns,
|
|
869
|
+
rows: allRows,
|
|
870
|
+
row_count: response.row_count,
|
|
871
|
+
total_count: start.total_count,
|
|
872
|
+
truncated: start.truncated,
|
|
873
|
+
execution_time_ms: start.execution_time_ms,
|
|
874
|
+
row_provenance: allProvenance.length > 0 ? allProvenance : void 0,
|
|
875
|
+
metadata: start.metadata,
|
|
876
|
+
switched_kg: start.switched_kg
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
throw new InternalError(
|
|
880
|
+
`Unexpected message during streaming: ${JSON.stringify(response)}`
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
// ── Notification handling ───────────────────────────────────────
|
|
885
|
+
isNotification(msg) {
|
|
886
|
+
return msg.type === "persistent_update" || msg.type === "rule_change" || msg.type === "kg_change" || msg.type === "schema_change";
|
|
887
|
+
}
|
|
888
|
+
dispatchNotification(notif) {
|
|
889
|
+
const event = {
|
|
890
|
+
type: notif.type,
|
|
891
|
+
seq: notif.seq,
|
|
892
|
+
timestampMs: notif.timestamp_ms,
|
|
893
|
+
sessionId: notif.session_id,
|
|
894
|
+
knowledgeGraph: notif.knowledge_graph,
|
|
895
|
+
relation: notif.relation,
|
|
896
|
+
operation: notif.operation,
|
|
897
|
+
count: notif.count,
|
|
898
|
+
ruleName: notif.rule_name,
|
|
899
|
+
entity: notif.entity
|
|
900
|
+
};
|
|
901
|
+
this._dispatcher.dispatch(event);
|
|
902
|
+
}
|
|
903
|
+
// ── Reconnection ────────────────────────────────────────────────
|
|
904
|
+
async reconnect() {
|
|
905
|
+
let delay = this.reconnectDelay;
|
|
906
|
+
for (let attempt = 0; attempt < this.maxReconnectAttempts; attempt++) {
|
|
907
|
+
await sleep(delay * 1e3);
|
|
908
|
+
try {
|
|
909
|
+
this._lastSeq = this._dispatcher.lastSeq;
|
|
910
|
+
await this.connect();
|
|
911
|
+
return;
|
|
912
|
+
} catch {
|
|
913
|
+
delay = Math.min(delay * 2, 60);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
throw new ConnectionError(
|
|
917
|
+
`Failed to reconnect after ${this.maxReconnectAttempts} attempts`
|
|
918
|
+
);
|
|
919
|
+
}
|
|
920
|
+
// ── Keep-alive ──────────────────────────────────────────────────
|
|
921
|
+
async ping() {
|
|
922
|
+
if (!this.ws) throw new ConnectionError("Not connected");
|
|
923
|
+
this.ws.send(serializeMessage({ type: "ping" }));
|
|
924
|
+
}
|
|
925
|
+
// ── WebSocket helpers ───────────────────────────────────────────
|
|
926
|
+
createWebSocket(url) {
|
|
927
|
+
return new Promise((resolve, reject) => {
|
|
928
|
+
const ws = new WebSocket(url);
|
|
929
|
+
ws.once("open", () => resolve(ws));
|
|
930
|
+
ws.once("error", (err) => reject(err));
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
/** Receive exactly one message (used during auth before handler is set up). */
|
|
934
|
+
receiveOne() {
|
|
935
|
+
return new Promise((resolve, reject) => {
|
|
936
|
+
if (!this.ws) return reject(new ConnectionError("Not connected"));
|
|
937
|
+
const handler = (data) => {
|
|
938
|
+
this.ws?.removeListener("message", handler);
|
|
939
|
+
try {
|
|
940
|
+
resolve(deserializeMessage(String(data)));
|
|
941
|
+
} catch (e) {
|
|
942
|
+
reject(e);
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
this.ws.on("message", handler);
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
/** Receive the next message via the pendingResolve mechanism. */
|
|
949
|
+
receiveMessage() {
|
|
950
|
+
return new Promise((resolve) => {
|
|
951
|
+
this.pendingResolve = resolve;
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
};
|
|
955
|
+
function sleep(ms) {
|
|
956
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// src/compiler.ts
|
|
960
|
+
var VarEnv = class {
|
|
961
|
+
constructor() {
|
|
962
|
+
this.map = /* @__PURE__ */ new Map();
|
|
963
|
+
this.counter = 0;
|
|
964
|
+
this.parent = /* @__PURE__ */ new Map();
|
|
965
|
+
}
|
|
966
|
+
find(key) {
|
|
967
|
+
let current = key;
|
|
968
|
+
while (true) {
|
|
969
|
+
const p = this.parent.get(current) ?? current;
|
|
970
|
+
if (p === current) return current;
|
|
971
|
+
const gp = this.parent.get(p) ?? p;
|
|
972
|
+
this.parent.set(current, gp);
|
|
973
|
+
current = gp;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
union(a, b) {
|
|
977
|
+
const ra = this.find(a);
|
|
978
|
+
const rb = this.find(b);
|
|
979
|
+
if (ra !== rb) {
|
|
980
|
+
this.parent.set(rb, ra);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
getVar(col) {
|
|
984
|
+
const key = `${col.refAlias ?? col.relation}.${col.name}`;
|
|
985
|
+
const root = this.find(key);
|
|
986
|
+
const existing = this.map.get(root);
|
|
987
|
+
if (existing !== void 0) return existing;
|
|
988
|
+
let varName = columnToVariable(col.name);
|
|
989
|
+
const usedVars = new Set(this.map.values());
|
|
990
|
+
if (usedVars.has(varName)) {
|
|
991
|
+
this.counter++;
|
|
992
|
+
varName = `${varName}_${this.counter}`;
|
|
993
|
+
}
|
|
994
|
+
this.map.set(root, varName);
|
|
995
|
+
return varName;
|
|
996
|
+
}
|
|
997
|
+
unify(colA, colB) {
|
|
998
|
+
const keyA = `${colA.refAlias ?? colA.relation}.${colA.name}`;
|
|
999
|
+
const keyB = `${colB.refAlias ?? colB.relation}.${colB.name}`;
|
|
1000
|
+
this.union(keyA, keyB);
|
|
1001
|
+
const root = this.find(keyA);
|
|
1002
|
+
const existing = this.map.get(root);
|
|
1003
|
+
if (existing !== void 0) return existing;
|
|
1004
|
+
let varName = columnToVariable(colA.name);
|
|
1005
|
+
const usedVars = new Set(this.map.values());
|
|
1006
|
+
if (usedVars.has(varName)) {
|
|
1007
|
+
this.counter++;
|
|
1008
|
+
varName = `${varName}_${this.counter}`;
|
|
1009
|
+
}
|
|
1010
|
+
this.map.set(root, varName);
|
|
1011
|
+
return varName;
|
|
1012
|
+
}
|
|
1013
|
+
lookup(col) {
|
|
1014
|
+
const key = `${col.refAlias ?? col.relation}.${col.name}`;
|
|
1015
|
+
const root = this.find(key);
|
|
1016
|
+
return this.map.get(root);
|
|
1017
|
+
}
|
|
1018
|
+
/** Direct-set a variable for conditional delete setup. */
|
|
1019
|
+
set(key, varName) {
|
|
1020
|
+
this.map.set(key, varName);
|
|
1021
|
+
}
|
|
1022
|
+
};
|
|
1023
|
+
function compileExpr(expr, env) {
|
|
1024
|
+
if (isColumn(expr)) {
|
|
1025
|
+
return env.getVar(expr);
|
|
1026
|
+
}
|
|
1027
|
+
if (isLiteral(expr)) {
|
|
1028
|
+
return compileValue(expr.value);
|
|
1029
|
+
}
|
|
1030
|
+
if (isArithmetic(expr)) {
|
|
1031
|
+
const left = compileExpr(expr.left, env);
|
|
1032
|
+
const right = compileExpr(expr.right, env);
|
|
1033
|
+
return `${left} ${expr.op} ${right}`;
|
|
1034
|
+
}
|
|
1035
|
+
if (isFuncCall(expr)) {
|
|
1036
|
+
const args = expr.args.map((a) => compileExpr(a, env)).join(", ");
|
|
1037
|
+
return `${expr.name}(${args})`;
|
|
1038
|
+
}
|
|
1039
|
+
if (isOrderedColumn(expr)) {
|
|
1040
|
+
const varStr = compileExpr(expr.column, env);
|
|
1041
|
+
const suffix = expr.descending ? ":desc" : ":asc";
|
|
1042
|
+
return `${varStr}${suffix}`;
|
|
1043
|
+
}
|
|
1044
|
+
if (isAggExpr(expr)) {
|
|
1045
|
+
return compileAggExpr(expr, env);
|
|
1046
|
+
}
|
|
1047
|
+
throw new TypeError(`Cannot compile expression: ${JSON.stringify(expr)}`);
|
|
1048
|
+
}
|
|
1049
|
+
function compileAggExpr(agg, env) {
|
|
1050
|
+
const parts = [];
|
|
1051
|
+
for (const p of agg.params) {
|
|
1052
|
+
parts.push(compileValue(p));
|
|
1053
|
+
}
|
|
1054
|
+
for (const pt of agg.passthrough) {
|
|
1055
|
+
parts.push(compileExpr(pt, env));
|
|
1056
|
+
}
|
|
1057
|
+
if (agg.orderColumn !== void 0) {
|
|
1058
|
+
const orderVar = compileExpr(agg.orderColumn, env);
|
|
1059
|
+
const suffix = agg.desc ? ":desc" : ":asc";
|
|
1060
|
+
parts.push(`${orderVar}${suffix}`);
|
|
1061
|
+
} else if (agg.column !== void 0) {
|
|
1062
|
+
parts.push(compileExpr(agg.column, env));
|
|
1063
|
+
}
|
|
1064
|
+
const inner = parts.join(", ");
|
|
1065
|
+
return `${agg.func}<${inner}>`;
|
|
1066
|
+
}
|
|
1067
|
+
function compileBoolExpr(expr, env) {
|
|
1068
|
+
if (isComparison(expr)) {
|
|
1069
|
+
return [compileComparison(expr, env)];
|
|
1070
|
+
}
|
|
1071
|
+
if (isAnd(expr)) {
|
|
1072
|
+
return [...compileBoolExpr(expr.left, env), ...compileBoolExpr(expr.right, env)];
|
|
1073
|
+
}
|
|
1074
|
+
if (isOr(expr)) {
|
|
1075
|
+
throw new Error(
|
|
1076
|
+
"OR conditions require query splitting. Use compileOrBranches() instead."
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
1079
|
+
if (isNot(expr)) {
|
|
1080
|
+
const innerParts = compileBoolExpr(expr.operand, env);
|
|
1081
|
+
return [`!(${innerParts.join(", ")})`];
|
|
1082
|
+
}
|
|
1083
|
+
if (isInExpr(expr)) {
|
|
1084
|
+
return [compileIn(expr.column, expr.targetColumn, false, env)];
|
|
1085
|
+
}
|
|
1086
|
+
if (isNegatedIn(expr)) {
|
|
1087
|
+
return [compileIn(expr.column, expr.targetColumn, true, env)];
|
|
1088
|
+
}
|
|
1089
|
+
if (isMatchExpr(expr)) {
|
|
1090
|
+
return [compileMatch(expr, env)];
|
|
1091
|
+
}
|
|
1092
|
+
throw new TypeError(`Cannot compile boolean expression: ${JSON.stringify(expr)}`);
|
|
1093
|
+
}
|
|
1094
|
+
function compileComparison(comp, env) {
|
|
1095
|
+
if (comp.op === "=" && isColumn(comp.left) && isColumn(comp.right)) {
|
|
1096
|
+
env.unify(comp.left, comp.right);
|
|
1097
|
+
return "";
|
|
1098
|
+
}
|
|
1099
|
+
const left = compileExpr(comp.left, env);
|
|
1100
|
+
const right = compileExpr(comp.right, env);
|
|
1101
|
+
return `${left} ${comp.op} ${right}`;
|
|
1102
|
+
}
|
|
1103
|
+
function compileIn(col, target, negated, env) {
|
|
1104
|
+
if (isColumn(col) && isColumn(target)) {
|
|
1105
|
+
env.unify(col, target);
|
|
1106
|
+
const tgtVar = env.getVar(target);
|
|
1107
|
+
const prefix2 = negated ? "!" : "";
|
|
1108
|
+
return `${prefix2}${target.relation}(..., ${tgtVar}, ...)`;
|
|
1109
|
+
}
|
|
1110
|
+
const srcVar = compileExpr(col, env);
|
|
1111
|
+
const prefix = negated ? "!" : "";
|
|
1112
|
+
return `${prefix}(..., ${srcVar}, ...)`;
|
|
1113
|
+
}
|
|
1114
|
+
function compileMatch(match, env) {
|
|
1115
|
+
const parts = [];
|
|
1116
|
+
for (const [, sourceExpr] of Object.entries(match.bindings)) {
|
|
1117
|
+
parts.push(compileExpr(sourceExpr, env));
|
|
1118
|
+
}
|
|
1119
|
+
const prefix = match.negated ? "!" : "";
|
|
1120
|
+
return `${prefix}${match.relation}(${parts.join(", ")})`;
|
|
1121
|
+
}
|
|
1122
|
+
function compileOrBranches(expr, env) {
|
|
1123
|
+
if (isOr(expr)) {
|
|
1124
|
+
return [
|
|
1125
|
+
...compileOrBranches(expr.left, env),
|
|
1126
|
+
...compileOrBranches(expr.right, env)
|
|
1127
|
+
];
|
|
1128
|
+
}
|
|
1129
|
+
return [compileBoolExpr(expr, env)];
|
|
1130
|
+
}
|
|
1131
|
+
function compileSchema(rel) {
|
|
1132
|
+
const name = rel.relationName;
|
|
1133
|
+
const cols = rel.columns;
|
|
1134
|
+
const colTypes = rel.columnTypes;
|
|
1135
|
+
const parts = cols.map((c) => {
|
|
1136
|
+
const type = colTypes[c].replace(/\[(\d+)\]/, "($1)");
|
|
1137
|
+
return `${c}: ${type}`;
|
|
1138
|
+
});
|
|
1139
|
+
return `+${name}(${parts.join(", ")})`;
|
|
1140
|
+
}
|
|
1141
|
+
function compileInsert(rel, fact, persistent = true) {
|
|
1142
|
+
const name = rel.relationName;
|
|
1143
|
+
const values = rel.columns.map((c) => compileValue(fact[c]));
|
|
1144
|
+
const prefix = persistent ? "+" : "";
|
|
1145
|
+
return `${prefix}${name}(${values.join(", ")})`;
|
|
1146
|
+
}
|
|
1147
|
+
function compileBulkInsert(rel, facts, persistent = true) {
|
|
1148
|
+
const name = rel.relationName;
|
|
1149
|
+
const tuples = facts.map((fact) => {
|
|
1150
|
+
const values = rel.columns.map((c) => compileValue(fact[c]));
|
|
1151
|
+
return `(${values.join(", ")})`;
|
|
1152
|
+
});
|
|
1153
|
+
const prefix = persistent ? "+" : "";
|
|
1154
|
+
return `${prefix}${name}[${tuples.join(", ")}]`;
|
|
1155
|
+
}
|
|
1156
|
+
function compileDelete(rel, fact) {
|
|
1157
|
+
const name = rel.relationName;
|
|
1158
|
+
const values = rel.columns.map((c) => compileValue(fact[c]));
|
|
1159
|
+
return `-${name}(${values.join(", ")})`;
|
|
1160
|
+
}
|
|
1161
|
+
function compileConditionalDelete(rel, condition) {
|
|
1162
|
+
const name = rel.relationName;
|
|
1163
|
+
const cols = rel.columns;
|
|
1164
|
+
const vars = cols.map((_, i) => `X${i}`);
|
|
1165
|
+
const head = `-${name}(${vars.join(", ")})`;
|
|
1166
|
+
const env = new VarEnv();
|
|
1167
|
+
for (let i = 0; i < cols.length; i++) {
|
|
1168
|
+
env.set(`${name}.${cols[i]}`, vars[i]);
|
|
1169
|
+
}
|
|
1170
|
+
const bodyRel = `${name}(${vars.join(", ")})`;
|
|
1171
|
+
const condParts = compileBoolExpr(condition, env).filter((p) => p !== "");
|
|
1172
|
+
const allBody = [bodyRel, ...condParts];
|
|
1173
|
+
return `${head} <- ${allBody.join(", ")}`;
|
|
1174
|
+
}
|
|
1175
|
+
function hasOr(expr) {
|
|
1176
|
+
if (isOr(expr)) return true;
|
|
1177
|
+
if (isAnd(expr)) return hasOr(expr.left) || hasOr(expr.right);
|
|
1178
|
+
if (isNot(expr)) return hasOr(expr.operand);
|
|
1179
|
+
return false;
|
|
1180
|
+
}
|
|
1181
|
+
function processJoinCondition(condition, env) {
|
|
1182
|
+
if (isComparison(condition) && condition.op === "=") {
|
|
1183
|
+
if (isColumn(condition.left) && isColumn(condition.right)) {
|
|
1184
|
+
env.unify(condition.left, condition.right);
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
if (isAnd(condition)) {
|
|
1189
|
+
processJoinCondition(condition.left, env);
|
|
1190
|
+
processJoinCondition(condition.right, env);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
function compileQuery(opts) {
|
|
1194
|
+
const env = new VarEnv();
|
|
1195
|
+
const allRelations = [];
|
|
1196
|
+
if (opts.join) {
|
|
1197
|
+
for (const r of opts.join) {
|
|
1198
|
+
if (r instanceof RelationDef) {
|
|
1199
|
+
allRelations.push({ name: r.relationName, def: r, alias: void 0 });
|
|
1200
|
+
} else {
|
|
1201
|
+
allRelations.push({
|
|
1202
|
+
name: r.relationName,
|
|
1203
|
+
def: {
|
|
1204
|
+
relationName: r.relationName,
|
|
1205
|
+
columns: r.schema.columns.map((c) => c.name),
|
|
1206
|
+
columnTypes: {}
|
|
1207
|
+
},
|
|
1208
|
+
alias: r.alias
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
if (opts.on) {
|
|
1214
|
+
processJoinCondition(opts.on, env);
|
|
1215
|
+
}
|
|
1216
|
+
let whereParts = [];
|
|
1217
|
+
let orBranches;
|
|
1218
|
+
if (opts.where) {
|
|
1219
|
+
if (hasOr(opts.where)) {
|
|
1220
|
+
orBranches = compileOrBranches(opts.where, env);
|
|
1221
|
+
} else {
|
|
1222
|
+
whereParts = compileBoolExpr(opts.where, env).filter((p) => p !== "");
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
const hasAgg = opts.select.some((s) => isAggExpr(s));
|
|
1226
|
+
const computed = opts.computed ?? {};
|
|
1227
|
+
const hasComputedAgg = Object.values(computed).some((v) => isAggExpr(v));
|
|
1228
|
+
if (hasAgg || hasComputedAgg) {
|
|
1229
|
+
return compileAggQuery(
|
|
1230
|
+
opts.select,
|
|
1231
|
+
env,
|
|
1232
|
+
allRelations,
|
|
1233
|
+
whereParts,
|
|
1234
|
+
orBranches,
|
|
1235
|
+
opts.orderBy,
|
|
1236
|
+
opts.limit,
|
|
1237
|
+
opts.offset,
|
|
1238
|
+
computed
|
|
1239
|
+
);
|
|
1240
|
+
}
|
|
1241
|
+
const headParts = [];
|
|
1242
|
+
const bodyAtoms = [];
|
|
1243
|
+
const fullRelations = [];
|
|
1244
|
+
for (const s of opts.select) {
|
|
1245
|
+
if (s instanceof RelationDef) {
|
|
1246
|
+
fullRelations.push({ name: s.relationName, def: s, alias: void 0 });
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
if (fullRelations.length > 0) {
|
|
1250
|
+
for (const { name, def, alias } of fullRelations) {
|
|
1251
|
+
for (const col of def.columns) {
|
|
1252
|
+
const astCol = column(name, col, alias);
|
|
1253
|
+
headParts.push(env.getVar(astCol));
|
|
1254
|
+
}
|
|
1255
|
+
if (!allRelations.some((r) => r.name === name && r.alias === alias)) {
|
|
1256
|
+
allRelations.push({ name, def, alias });
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
for (const s of opts.select) {
|
|
1261
|
+
if (!(s instanceof RelationDef) && isColumn(s)) {
|
|
1262
|
+
headParts.push(env.getVar(s));
|
|
1263
|
+
} else if (!(s instanceof RelationDef) && !isColumn(s) && "_tag" in s) {
|
|
1264
|
+
headParts.push(compileExpr(s, env));
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
for (const [, expr] of Object.entries(computed)) {
|
|
1268
|
+
headParts.push(compileExpr(expr, env));
|
|
1269
|
+
}
|
|
1270
|
+
if (opts.orderBy !== void 0) {
|
|
1271
|
+
if (isOrderedColumn(opts.orderBy)) {
|
|
1272
|
+
const orderVar = compileExpr(opts.orderBy.column, env);
|
|
1273
|
+
const suffix = opts.orderBy.descending ? ":desc" : ":asc";
|
|
1274
|
+
for (let i = 0; i < headParts.length; i++) {
|
|
1275
|
+
if (headParts[i] === orderVar) {
|
|
1276
|
+
headParts[i] = `${orderVar}${suffix}`;
|
|
1277
|
+
break;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
} else if (isColumn(opts.orderBy)) {
|
|
1281
|
+
const orderVar = env.getVar(opts.orderBy);
|
|
1282
|
+
for (let i = 0; i < headParts.length; i++) {
|
|
1283
|
+
if (headParts[i] === orderVar) {
|
|
1284
|
+
headParts[i] = `${orderVar}:asc`;
|
|
1285
|
+
break;
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
for (const { name, def, alias } of allRelations) {
|
|
1291
|
+
const cols = def.columns;
|
|
1292
|
+
const atomParts = cols.map((col) => {
|
|
1293
|
+
const astCol = column(name, col, alias);
|
|
1294
|
+
return env.lookup(astCol) ?? "_";
|
|
1295
|
+
});
|
|
1296
|
+
bodyAtoms.push(`${name}(${atomParts.join(", ")})`);
|
|
1297
|
+
}
|
|
1298
|
+
const allBody = [...bodyAtoms, ...whereParts];
|
|
1299
|
+
if (opts.limit !== void 0) {
|
|
1300
|
+
if (opts.offset !== void 0) {
|
|
1301
|
+
allBody.push(`limit(${opts.limit}, ${opts.offset})`);
|
|
1302
|
+
} else {
|
|
1303
|
+
allBody.push(`limit(${opts.limit})`);
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
const headStr = headParts.join(", ");
|
|
1307
|
+
if (orBranches !== void 0) {
|
|
1308
|
+
return orBranches.map((branchParts) => {
|
|
1309
|
+
const filtered = branchParts.filter((p) => p !== "");
|
|
1310
|
+
const branchBody = [...bodyAtoms, ...filtered];
|
|
1311
|
+
if (opts.limit !== void 0) {
|
|
1312
|
+
if (opts.offset !== void 0) {
|
|
1313
|
+
branchBody.push(`limit(${opts.limit}, ${opts.offset})`);
|
|
1314
|
+
} else {
|
|
1315
|
+
branchBody.push(`limit(${opts.limit})`);
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
return `?${headStr} <- ${branchBody.join(", ")}`;
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
if (allBody.length > 0) {
|
|
1322
|
+
return `?${headStr} <- ${allBody.join(", ")}`;
|
|
1323
|
+
}
|
|
1324
|
+
return `?${headStr}`;
|
|
1325
|
+
}
|
|
1326
|
+
function compileAggQuery(select, env, allRelations, whereParts, orBranches, orderBy, limit, offset, computed) {
|
|
1327
|
+
const headParts = [];
|
|
1328
|
+
const aggParts = [];
|
|
1329
|
+
for (const s of select) {
|
|
1330
|
+
if (s instanceof RelationDef) {
|
|
1331
|
+
for (const col of s.columns) {
|
|
1332
|
+
const astCol = column(s.relationName, col);
|
|
1333
|
+
headParts.push(env.getVar(astCol));
|
|
1334
|
+
}
|
|
1335
|
+
} else if (isAggExpr(s)) {
|
|
1336
|
+
aggParts.push(compileExpr(s, env));
|
|
1337
|
+
} else if (isColumn(s)) {
|
|
1338
|
+
headParts.push(env.getVar(s));
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
for (const [, expr] of Object.entries(computed)) {
|
|
1342
|
+
if (isAggExpr(expr)) {
|
|
1343
|
+
aggParts.push(compileExpr(expr, env));
|
|
1344
|
+
} else {
|
|
1345
|
+
headParts.push(compileExpr(expr, env));
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
const bodyAtoms = [];
|
|
1349
|
+
for (const { name, def, alias } of allRelations) {
|
|
1350
|
+
const cols = def.columns;
|
|
1351
|
+
const atomParts = cols.map((col) => {
|
|
1352
|
+
const astCol = column(name, col, alias);
|
|
1353
|
+
return env.lookup(astCol) ?? "_";
|
|
1354
|
+
});
|
|
1355
|
+
bodyAtoms.push(`${name}(${atomParts.join(", ")})`);
|
|
1356
|
+
}
|
|
1357
|
+
const allBody = [...bodyAtoms, ...whereParts];
|
|
1358
|
+
if (limit !== void 0) {
|
|
1359
|
+
if (offset !== void 0) {
|
|
1360
|
+
allBody.push(`limit(${limit}, ${offset})`);
|
|
1361
|
+
} else {
|
|
1362
|
+
allBody.push(`limit(${limit})`);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
const allHead = [...headParts, ...aggParts];
|
|
1366
|
+
const headStr = allHead.join(", ");
|
|
1367
|
+
if (allBody.length > 0) {
|
|
1368
|
+
return `?${headStr} <- ${allBody.join(", ")}`;
|
|
1369
|
+
}
|
|
1370
|
+
return `?${headStr}`;
|
|
1371
|
+
}
|
|
1372
|
+
function compileRule(headName, headColumns, clause, persistent = true) {
|
|
1373
|
+
const env = new VarEnv();
|
|
1374
|
+
if (clause.condition) {
|
|
1375
|
+
processJoinCondition(clause.condition, env);
|
|
1376
|
+
}
|
|
1377
|
+
const headParts = headColumns.map((col) => {
|
|
1378
|
+
const expr = clause.selectMap[col];
|
|
1379
|
+
if (expr !== void 0) {
|
|
1380
|
+
return compileExpr(expr, env);
|
|
1381
|
+
}
|
|
1382
|
+
return columnToVariable(col);
|
|
1383
|
+
});
|
|
1384
|
+
const bodyAtoms = [];
|
|
1385
|
+
for (const { name, def, alias } of clause.relations) {
|
|
1386
|
+
const cols = def.columns;
|
|
1387
|
+
const atomParts = cols.map((col) => {
|
|
1388
|
+
const astCol = column(name, col, alias);
|
|
1389
|
+
return env.lookup(astCol) ?? "_";
|
|
1390
|
+
});
|
|
1391
|
+
bodyAtoms.push(`${name}(${atomParts.join(", ")})`);
|
|
1392
|
+
}
|
|
1393
|
+
let condParts = [];
|
|
1394
|
+
if (clause.condition) {
|
|
1395
|
+
condParts = compileBoolExpr(clause.condition, env).filter((p) => p !== "");
|
|
1396
|
+
}
|
|
1397
|
+
const allBody = [...bodyAtoms, ...condParts];
|
|
1398
|
+
const prefix = persistent ? "+" : "";
|
|
1399
|
+
const headStr = `${prefix}${headName}(${headParts.join(", ")})`;
|
|
1400
|
+
return `${headStr} <- ${allBody.join(", ")}`;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
// src/session.ts
|
|
1404
|
+
var Session = class {
|
|
1405
|
+
constructor(connection) {
|
|
1406
|
+
this.conn = connection;
|
|
1407
|
+
}
|
|
1408
|
+
/** Insert ephemeral session facts (no + prefix). */
|
|
1409
|
+
async insert(rel, facts) {
|
|
1410
|
+
const factList = Array.isArray(facts) ? facts : [facts];
|
|
1411
|
+
if (factList.length === 0) return;
|
|
1412
|
+
let iql;
|
|
1413
|
+
if (factList.length === 1) {
|
|
1414
|
+
iql = compileInsert(rel, factList[0], false);
|
|
1415
|
+
} else {
|
|
1416
|
+
iql = compileBulkInsert(rel, factList, false);
|
|
1417
|
+
}
|
|
1418
|
+
await this.conn.execute(iql);
|
|
1419
|
+
}
|
|
1420
|
+
/** Define session-scoped rules (no + prefix). */
|
|
1421
|
+
async defineRules(headName, headColumns, clauses) {
|
|
1422
|
+
for (const clause of clauses) {
|
|
1423
|
+
const iql = compileRule(headName, headColumns, clause, false);
|
|
1424
|
+
await this.conn.execute(iql);
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
/** List session rules. */
|
|
1428
|
+
async listRules() {
|
|
1429
|
+
const result = await this.conn.execute(".session list");
|
|
1430
|
+
return result.rows.length > 0 ? result.rows.map((row) => String(row[0])) : [];
|
|
1431
|
+
}
|
|
1432
|
+
/** Drop a session rule by name, or a specific clause by index. */
|
|
1433
|
+
async dropRule(name, index) {
|
|
1434
|
+
if (index !== void 0) {
|
|
1435
|
+
await this.conn.execute(`.session remove ${name} ${index}`);
|
|
1436
|
+
} else {
|
|
1437
|
+
await this.conn.execute(`.session drop ${name}`);
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
/** Clear all session facts and rules. */
|
|
1441
|
+
async clear() {
|
|
1442
|
+
await this.conn.execute(".session clear");
|
|
1443
|
+
}
|
|
1444
|
+
};
|
|
1445
|
+
|
|
1446
|
+
// src/knowledge-graph.ts
|
|
1447
|
+
var KnowledgeGraph = class {
|
|
1448
|
+
constructor(name, connection) {
|
|
1449
|
+
this.kgActive = false;
|
|
1450
|
+
this._name = name;
|
|
1451
|
+
this.conn = connection;
|
|
1452
|
+
this._session = new Session(connection);
|
|
1453
|
+
}
|
|
1454
|
+
get name() {
|
|
1455
|
+
return this._name;
|
|
1456
|
+
}
|
|
1457
|
+
get session() {
|
|
1458
|
+
return this._session;
|
|
1459
|
+
}
|
|
1460
|
+
/** Ensure the connection is using this knowledge graph. */
|
|
1461
|
+
async ensureKg() {
|
|
1462
|
+
if (this.conn.currentKg === this._name) return;
|
|
1463
|
+
await this.conn.execute(`.kg create ${this._name}`);
|
|
1464
|
+
await this.conn.execute(`.kg use ${this._name}`);
|
|
1465
|
+
this.conn.setCurrentKg(this._name);
|
|
1466
|
+
}
|
|
1467
|
+
// ── Schema ──────────────────────────────────────────────────────
|
|
1468
|
+
/** Deploy schema definitions. Idempotent. */
|
|
1469
|
+
async define(...relations) {
|
|
1470
|
+
await this.ensureKg();
|
|
1471
|
+
for (const rel of relations) {
|
|
1472
|
+
const iql = compileSchema(rel);
|
|
1473
|
+
await this.conn.execute(iql);
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
/** List all relations in this KG. */
|
|
1477
|
+
async relations() {
|
|
1478
|
+
await this.ensureKg();
|
|
1479
|
+
const result = await this.conn.execute(".rel");
|
|
1480
|
+
return result.rows.map((row) => ({
|
|
1481
|
+
name: String(row[0]),
|
|
1482
|
+
rowCount: row.length > 1 ? Number(row[1]) : 0
|
|
1483
|
+
}));
|
|
1484
|
+
}
|
|
1485
|
+
/** Describe a relation's schema. */
|
|
1486
|
+
async describe(relation2) {
|
|
1487
|
+
await this.ensureKg();
|
|
1488
|
+
const name = typeof relation2 === "string" ? relation2 : relation2.relationName;
|
|
1489
|
+
const result = await this.conn.execute(`.rel ${name}`);
|
|
1490
|
+
const columns = result.rows.map((row) => ({
|
|
1491
|
+
name: String(row[0]),
|
|
1492
|
+
type: String(row[1])
|
|
1493
|
+
}));
|
|
1494
|
+
return { name, columns, rowCount: 0, sample: [] };
|
|
1495
|
+
}
|
|
1496
|
+
/** Drop a relation and all its data. */
|
|
1497
|
+
async dropRelation(relation2) {
|
|
1498
|
+
await this.ensureKg();
|
|
1499
|
+
const name = typeof relation2 === "string" ? relation2 : relation2.relationName;
|
|
1500
|
+
await this.conn.execute(`.rel drop ${name}`);
|
|
1501
|
+
}
|
|
1502
|
+
// ── Insert ──────────────────────────────────────────────────────
|
|
1503
|
+
/** Insert facts into the knowledge graph. */
|
|
1504
|
+
async insert(rel, facts) {
|
|
1505
|
+
await this.ensureKg();
|
|
1506
|
+
const factList = Array.isArray(facts) ? facts : [facts];
|
|
1507
|
+
if (factList.length === 0) return { count: 0 };
|
|
1508
|
+
let iql;
|
|
1509
|
+
if (factList.length === 1) {
|
|
1510
|
+
iql = compileInsert(rel, factList[0]);
|
|
1511
|
+
} else {
|
|
1512
|
+
iql = compileBulkInsert(rel, factList);
|
|
1513
|
+
}
|
|
1514
|
+
const result = await this.conn.execute(iql);
|
|
1515
|
+
return { count: result.rows.length };
|
|
1516
|
+
}
|
|
1517
|
+
// ── Delete ──────────────────────────────────────────────────────
|
|
1518
|
+
/**
|
|
1519
|
+
* Delete facts from the knowledge graph.
|
|
1520
|
+
*
|
|
1521
|
+
* @param rel - The relation definition
|
|
1522
|
+
* @param factsOrCondition - Either specific facts to delete, or a BoolExpr condition
|
|
1523
|
+
*/
|
|
1524
|
+
async delete(rel, factsOrCondition) {
|
|
1525
|
+
await this.ensureKg();
|
|
1526
|
+
if (typeof factsOrCondition === "object" && factsOrCondition !== null && "_tag" in factsOrCondition) {
|
|
1527
|
+
const iql = compileConditionalDelete(rel, factsOrCondition);
|
|
1528
|
+
const result = await this.conn.execute(iql);
|
|
1529
|
+
return { count: result.rows.length };
|
|
1530
|
+
}
|
|
1531
|
+
const facts = Array.isArray(factsOrCondition) ? factsOrCondition : [factsOrCondition];
|
|
1532
|
+
for (const fact of facts) {
|
|
1533
|
+
const iql = compileDelete(rel, fact);
|
|
1534
|
+
await this.conn.execute(iql);
|
|
1535
|
+
}
|
|
1536
|
+
return { count: facts.length };
|
|
1537
|
+
}
|
|
1538
|
+
// ── Query ───────────────────────────────────────────────────────
|
|
1539
|
+
/**
|
|
1540
|
+
* Query the knowledge graph.
|
|
1541
|
+
*
|
|
1542
|
+
* @example
|
|
1543
|
+
* // Simple query
|
|
1544
|
+
* const result = await kg.query({ select: [Employee] });
|
|
1545
|
+
*
|
|
1546
|
+
* // Filter
|
|
1547
|
+
* const result = await kg.query({
|
|
1548
|
+
* select: [Employee.col("name"), Employee.col("salary")],
|
|
1549
|
+
* join: [Employee],
|
|
1550
|
+
* where: Employee.col("department").eq("eng"),
|
|
1551
|
+
* });
|
|
1552
|
+
*
|
|
1553
|
+
* // Join
|
|
1554
|
+
* const result = await kg.query({
|
|
1555
|
+
* select: [Employee.col("name"), Department.col("budget")],
|
|
1556
|
+
* join: [Employee, Department],
|
|
1557
|
+
* on: Employee.col("department").eq(Department.col("name")),
|
|
1558
|
+
* });
|
|
1559
|
+
*/
|
|
1560
|
+
async query(opts) {
|
|
1561
|
+
await this.ensureKg();
|
|
1562
|
+
const iql = compileQuery(opts);
|
|
1563
|
+
if (Array.isArray(iql)) {
|
|
1564
|
+
const allRows = [];
|
|
1565
|
+
let columns = [];
|
|
1566
|
+
for (const q of iql) {
|
|
1567
|
+
const result2 = await this.conn.execute(q);
|
|
1568
|
+
if (columns.length === 0) {
|
|
1569
|
+
columns = result2.columns;
|
|
1570
|
+
}
|
|
1571
|
+
allRows.push(...result2.rows);
|
|
1572
|
+
}
|
|
1573
|
+
return new ResultSet({ columns, rows: allRows });
|
|
1574
|
+
}
|
|
1575
|
+
const result = await this.conn.execute(iql);
|
|
1576
|
+
const rs = new ResultSet({
|
|
1577
|
+
columns: result.columns,
|
|
1578
|
+
rows: result.rows,
|
|
1579
|
+
rowCount: result.row_count,
|
|
1580
|
+
totalCount: result.total_count,
|
|
1581
|
+
truncated: result.truncated,
|
|
1582
|
+
executionTimeMs: result.execution_time_ms,
|
|
1583
|
+
rowProvenance: result.row_provenance
|
|
1584
|
+
});
|
|
1585
|
+
if (result.metadata) {
|
|
1586
|
+
rs.hasEphemeral = result.metadata.has_ephemeral ?? false;
|
|
1587
|
+
rs.ephemeralSources = result.metadata.ephemeral_sources ?? [];
|
|
1588
|
+
rs.warnings = result.metadata.warnings ?? [];
|
|
1589
|
+
}
|
|
1590
|
+
return rs;
|
|
1591
|
+
}
|
|
1592
|
+
/**
|
|
1593
|
+
* Stream query results in batches.
|
|
1594
|
+
*
|
|
1595
|
+
* Returns an async generator yielding arrays of rows.
|
|
1596
|
+
*/
|
|
1597
|
+
async *queryStream(opts) {
|
|
1598
|
+
const batchSize = opts.batchSize ?? 1e3;
|
|
1599
|
+
const result = await this.query(opts);
|
|
1600
|
+
for (let i = 0; i < result.rows.length; i += batchSize) {
|
|
1601
|
+
const batch = result.rows.slice(i, i + batchSize);
|
|
1602
|
+
yield batch.map((row) => {
|
|
1603
|
+
const obj = {};
|
|
1604
|
+
for (let j = 0; j < result.columns.length && j < row.length; j++) {
|
|
1605
|
+
obj[result.columns[j]] = row[j];
|
|
1606
|
+
}
|
|
1607
|
+
return obj;
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
// ── Vector search ───────────────────────────────────────────────
|
|
1612
|
+
/**
|
|
1613
|
+
* Perform a vector similarity search.
|
|
1614
|
+
*/
|
|
1615
|
+
async vectorSearch(opts) {
|
|
1616
|
+
await this.ensureKg();
|
|
1617
|
+
const rel = opts.relation;
|
|
1618
|
+
const relName = rel.relationName;
|
|
1619
|
+
const cols = rel.columns;
|
|
1620
|
+
let vecColumn = opts.column;
|
|
1621
|
+
if (!vecColumn) {
|
|
1622
|
+
for (const [name, type] of Object.entries(rel.columnTypes)) {
|
|
1623
|
+
if (type === "vector" || type.startsWith("vector[")) {
|
|
1624
|
+
vecColumn = name;
|
|
1625
|
+
break;
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
if (!vecColumn) {
|
|
1629
|
+
throw new Error(`No vector column found in ${relName}`);
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
const vecStr = `[${opts.queryVec.join(", ")}]`;
|
|
1633
|
+
const distFn = {
|
|
1634
|
+
cosine: "cosine",
|
|
1635
|
+
euclidean: "euclidean",
|
|
1636
|
+
manhattan: "manhattan",
|
|
1637
|
+
dot_product: "dot"
|
|
1638
|
+
};
|
|
1639
|
+
const fnName = distFn[opts.metric ?? "cosine"] ?? "cosine";
|
|
1640
|
+
const colVars = cols.map((_, i) => `X${i}`).join(", ");
|
|
1641
|
+
const vecVar = `X${cols.indexOf(vecColumn)}`;
|
|
1642
|
+
const distAssign = `Dist = ${fnName}(${vecVar}, ${vecStr})`;
|
|
1643
|
+
let query;
|
|
1644
|
+
if (opts.k !== void 0) {
|
|
1645
|
+
query = `?top_k<${opts.k}, ${colVars}, Dist:asc> <- ${relName}(${colVars}), ${distAssign}`;
|
|
1646
|
+
} else if (opts.radius !== void 0) {
|
|
1647
|
+
query = `?within_radius<${opts.radius}, ${colVars}, Dist:asc> <- ${relName}(${colVars}), ${distAssign}`;
|
|
1648
|
+
} else {
|
|
1649
|
+
throw new Error("Must specify either k or radius");
|
|
1650
|
+
}
|
|
1651
|
+
const result = await this.conn.execute(query);
|
|
1652
|
+
return new ResultSet({
|
|
1653
|
+
columns: result.columns,
|
|
1654
|
+
rows: result.rows,
|
|
1655
|
+
rowCount: result.row_count,
|
|
1656
|
+
totalCount: result.total_count,
|
|
1657
|
+
truncated: result.truncated,
|
|
1658
|
+
executionTimeMs: result.execution_time_ms
|
|
1659
|
+
});
|
|
1660
|
+
}
|
|
1661
|
+
// ── Rules ───────────────────────────────────────────────────────
|
|
1662
|
+
/** Deploy persistent rule definitions. */
|
|
1663
|
+
async defineRules(headName, headColumns, clauses) {
|
|
1664
|
+
await this.ensureKg();
|
|
1665
|
+
for (const clause of clauses) {
|
|
1666
|
+
const iql = compileRule(headName, headColumns, clause, true);
|
|
1667
|
+
await this.conn.execute(iql);
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
/** List all rules in this KG. */
|
|
1671
|
+
async listRules() {
|
|
1672
|
+
await this.ensureKg();
|
|
1673
|
+
const result = await this.conn.execute(".rule list");
|
|
1674
|
+
return result.rows.map((row) => ({
|
|
1675
|
+
name: String(row[0]),
|
|
1676
|
+
clauseCount: row.length > 1 ? Number(row[1]) : 1
|
|
1677
|
+
}));
|
|
1678
|
+
}
|
|
1679
|
+
/** Get the IQL definition of a rule. */
|
|
1680
|
+
async ruleDefinition(name) {
|
|
1681
|
+
await this.ensureKg();
|
|
1682
|
+
const result = await this.conn.execute(`.rule show ${name}`);
|
|
1683
|
+
return result.rows.map((row) => String(row[0]));
|
|
1684
|
+
}
|
|
1685
|
+
/** Drop all clauses of a rule. */
|
|
1686
|
+
async dropRule(name) {
|
|
1687
|
+
await this.ensureKg();
|
|
1688
|
+
await this.conn.execute(`.rule drop ${name}`);
|
|
1689
|
+
}
|
|
1690
|
+
/** Remove a specific clause from a rule (1-based index). */
|
|
1691
|
+
async dropRuleClause(name, index) {
|
|
1692
|
+
await this.ensureKg();
|
|
1693
|
+
await this.conn.execute(`.rule remove ${name} ${index}`);
|
|
1694
|
+
}
|
|
1695
|
+
/** Replace a specific rule clause (remove + re-add). */
|
|
1696
|
+
async editRuleClause(name, index, headColumns, clause) {
|
|
1697
|
+
await this.dropRuleClause(name, index);
|
|
1698
|
+
const iql = compileRule(name, headColumns, clause, true);
|
|
1699
|
+
await this.conn.execute(iql);
|
|
1700
|
+
}
|
|
1701
|
+
/** Clear a rule's materialized data. */
|
|
1702
|
+
async clearRule(name) {
|
|
1703
|
+
await this.ensureKg();
|
|
1704
|
+
await this.conn.execute(`.rule clear ${name}`);
|
|
1705
|
+
}
|
|
1706
|
+
/** Drop all rules whose names start with prefix. */
|
|
1707
|
+
async dropRulesByPrefix(prefix) {
|
|
1708
|
+
await this.ensureKg();
|
|
1709
|
+
await this.conn.execute(`.rule drop prefix ${prefix}`);
|
|
1710
|
+
}
|
|
1711
|
+
// ── Indexes ─────────────────────────────────────────────────────
|
|
1712
|
+
/** Create an HNSW vector index. */
|
|
1713
|
+
async createIndex(index) {
|
|
1714
|
+
await this.ensureKg();
|
|
1715
|
+
await this.conn.execute(index.toIQL());
|
|
1716
|
+
}
|
|
1717
|
+
/** List all indexes. */
|
|
1718
|
+
async listIndexes() {
|
|
1719
|
+
await this.ensureKg();
|
|
1720
|
+
const result = await this.conn.execute(".index list");
|
|
1721
|
+
return result.rows.map((row) => ({
|
|
1722
|
+
name: String(row[0]),
|
|
1723
|
+
relation: row.length > 1 ? String(row[1]) : "",
|
|
1724
|
+
column: row.length > 2 ? String(row[2]) : "",
|
|
1725
|
+
metric: row.length > 3 ? String(row[3]) : "",
|
|
1726
|
+
rowCount: row.length > 4 ? Number(row[4]) : 0
|
|
1727
|
+
}));
|
|
1728
|
+
}
|
|
1729
|
+
/** Get statistics for an index. */
|
|
1730
|
+
async indexStats(name) {
|
|
1731
|
+
await this.ensureKg();
|
|
1732
|
+
const result = await this.conn.execute(`.index stats ${name}`);
|
|
1733
|
+
const row = result.rows[0] ?? [name, 0, 0, 0];
|
|
1734
|
+
return {
|
|
1735
|
+
name: String(row[0]),
|
|
1736
|
+
rowCount: row.length > 1 ? Number(row[1]) : 0,
|
|
1737
|
+
layers: row.length > 2 ? Number(row[2]) : 0,
|
|
1738
|
+
memoryBytes: row.length > 3 ? Number(row[3]) : 0
|
|
1739
|
+
};
|
|
1740
|
+
}
|
|
1741
|
+
/** Drop an index. */
|
|
1742
|
+
async dropIndex(name) {
|
|
1743
|
+
await this.ensureKg();
|
|
1744
|
+
await this.conn.execute(`.index drop ${name}`);
|
|
1745
|
+
}
|
|
1746
|
+
/** Rebuild an index. */
|
|
1747
|
+
async rebuildIndex(name) {
|
|
1748
|
+
await this.ensureKg();
|
|
1749
|
+
await this.conn.execute(`.index rebuild ${name}`);
|
|
1750
|
+
}
|
|
1751
|
+
// ── ACL ─────────────────────────────────────────────────────────
|
|
1752
|
+
/** Grant per-KG access. */
|
|
1753
|
+
async grantAccess(username, role) {
|
|
1754
|
+
await this.conn.execute(`.kg acl grant ${this._name} ${username} ${role}`);
|
|
1755
|
+
}
|
|
1756
|
+
/** Revoke per-KG access. */
|
|
1757
|
+
async revokeAccess(username) {
|
|
1758
|
+
await this.conn.execute(`.kg acl revoke ${this._name} ${username}`);
|
|
1759
|
+
}
|
|
1760
|
+
/** List ACL entries. */
|
|
1761
|
+
async listAcl() {
|
|
1762
|
+
const result = await this.conn.execute(`.kg acl list ${this._name}`);
|
|
1763
|
+
return result.rows.filter((row) => row.length >= 2).map((row) => ({
|
|
1764
|
+
username: String(row[0]),
|
|
1765
|
+
role: String(row[1])
|
|
1766
|
+
}));
|
|
1767
|
+
}
|
|
1768
|
+
// ── Meta ────────────────────────────────────────────────────────
|
|
1769
|
+
/** Show the query plan without executing. */
|
|
1770
|
+
async explain(opts) {
|
|
1771
|
+
await this.ensureKg();
|
|
1772
|
+
let iql = compileQuery(opts);
|
|
1773
|
+
if (Array.isArray(iql)) {
|
|
1774
|
+
iql = iql[0];
|
|
1775
|
+
}
|
|
1776
|
+
const result = await this.conn.execute(`.explain ${iql}`);
|
|
1777
|
+
const planText = result.rows.map((row) => String(row[0])).join("\n");
|
|
1778
|
+
return { iql, plan: planText };
|
|
1779
|
+
}
|
|
1780
|
+
/** Trigger storage compaction. */
|
|
1781
|
+
async compact() {
|
|
1782
|
+
await this.ensureKg();
|
|
1783
|
+
await this.conn.execute(".compact");
|
|
1784
|
+
}
|
|
1785
|
+
/** Get server status. */
|
|
1786
|
+
async status() {
|
|
1787
|
+
await this.ensureKg();
|
|
1788
|
+
const result = await this.conn.execute(".status");
|
|
1789
|
+
const row = result.rows[0] ?? ["unknown", "unknown"];
|
|
1790
|
+
return {
|
|
1791
|
+
version: row.length > 0 ? String(row[0]) : "unknown",
|
|
1792
|
+
knowledgeGraph: row.length > 1 ? String(row[1]) : this._name
|
|
1793
|
+
};
|
|
1794
|
+
}
|
|
1795
|
+
/** Load data from a file on the server. */
|
|
1796
|
+
async load(path, mode) {
|
|
1797
|
+
await this.ensureKg();
|
|
1798
|
+
let cmd = `.load ${path}`;
|
|
1799
|
+
if (mode) cmd += ` ${mode}`;
|
|
1800
|
+
await this.conn.execute(cmd);
|
|
1801
|
+
}
|
|
1802
|
+
/** Clear all relations matching a prefix. */
|
|
1803
|
+
async clearPrefix(prefix) {
|
|
1804
|
+
await this.ensureKg();
|
|
1805
|
+
const result = await this.conn.execute(`.clear prefix ${prefix}`);
|
|
1806
|
+
const details = result.rows.filter((row) => row.length > 1).map((row) => [String(row[0]), Number(row[1])]);
|
|
1807
|
+
return {
|
|
1808
|
+
relationsCleared: result.rows.length,
|
|
1809
|
+
factsCleared: details.reduce((sum2, [, count2]) => sum2 + count2, 0),
|
|
1810
|
+
details
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1813
|
+
/** Execute raw IQL. */
|
|
1814
|
+
async execute(iql) {
|
|
1815
|
+
await this.ensureKg();
|
|
1816
|
+
const result = await this.conn.execute(iql);
|
|
1817
|
+
return new ResultSet({
|
|
1818
|
+
columns: result.columns,
|
|
1819
|
+
rows: result.rows,
|
|
1820
|
+
rowCount: result.row_count,
|
|
1821
|
+
totalCount: result.total_count,
|
|
1822
|
+
truncated: result.truncated,
|
|
1823
|
+
executionTimeMs: result.execution_time_ms
|
|
1824
|
+
});
|
|
1825
|
+
}
|
|
1826
|
+
};
|
|
1827
|
+
|
|
1828
|
+
// src/auth.ts
|
|
1829
|
+
function compileCreateUser(username, password, role = "viewer") {
|
|
1830
|
+
return `.user create ${username} ${password} ${role}`;
|
|
1831
|
+
}
|
|
1832
|
+
function compileDropUser(username) {
|
|
1833
|
+
return `.user drop ${username}`;
|
|
1834
|
+
}
|
|
1835
|
+
function compileSetPassword(username, newPassword) {
|
|
1836
|
+
return `.user password ${username} ${newPassword}`;
|
|
1837
|
+
}
|
|
1838
|
+
function compileSetRole(username, role) {
|
|
1839
|
+
return `.user role ${username} ${role}`;
|
|
1840
|
+
}
|
|
1841
|
+
function compileListUsers() {
|
|
1842
|
+
return ".user list";
|
|
1843
|
+
}
|
|
1844
|
+
function compileCreateApiKey(label) {
|
|
1845
|
+
return `.apikey create ${label}`;
|
|
1846
|
+
}
|
|
1847
|
+
function compileListApiKeys() {
|
|
1848
|
+
return ".apikey list";
|
|
1849
|
+
}
|
|
1850
|
+
function compileRevokeApiKey(label) {
|
|
1851
|
+
return `.apikey revoke ${label}`;
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
// src/client.ts
|
|
1855
|
+
var InputLayer = class {
|
|
1856
|
+
constructor(opts) {
|
|
1857
|
+
this.kgs = /* @__PURE__ */ new Map();
|
|
1858
|
+
this.conn = new Connection({
|
|
1859
|
+
url: opts.url,
|
|
1860
|
+
username: opts.username,
|
|
1861
|
+
password: opts.password,
|
|
1862
|
+
apiKey: opts.apiKey,
|
|
1863
|
+
autoReconnect: opts.autoReconnect,
|
|
1864
|
+
reconnectDelay: opts.reconnectDelay,
|
|
1865
|
+
maxReconnectAttempts: opts.maxReconnectAttempts,
|
|
1866
|
+
initialKg: opts.initialKg,
|
|
1867
|
+
lastSeq: opts.lastSeq
|
|
1868
|
+
});
|
|
1869
|
+
}
|
|
1870
|
+
// ── Connection lifecycle ────────────────────────────────────────
|
|
1871
|
+
/** Connect and authenticate. */
|
|
1872
|
+
async connect() {
|
|
1873
|
+
await this.conn.connect();
|
|
1874
|
+
}
|
|
1875
|
+
/** Close the connection. */
|
|
1876
|
+
async close() {
|
|
1877
|
+
await this.conn.close();
|
|
1878
|
+
}
|
|
1879
|
+
// ── Properties ──────────────────────────────────────────────────
|
|
1880
|
+
get connected() {
|
|
1881
|
+
return this.conn.connected;
|
|
1882
|
+
}
|
|
1883
|
+
get sessionId() {
|
|
1884
|
+
return this.conn.sessionId;
|
|
1885
|
+
}
|
|
1886
|
+
get serverVersion() {
|
|
1887
|
+
return this.conn.serverVersion;
|
|
1888
|
+
}
|
|
1889
|
+
get role() {
|
|
1890
|
+
return this.conn.role;
|
|
1891
|
+
}
|
|
1892
|
+
get lastSeq() {
|
|
1893
|
+
return this.conn.lastSeq;
|
|
1894
|
+
}
|
|
1895
|
+
// ── KG management ───────────────────────────────────────────────
|
|
1896
|
+
/** Get a KnowledgeGraph handle. Switches the session's active KG. */
|
|
1897
|
+
knowledgeGraph(name) {
|
|
1898
|
+
let kg = this.kgs.get(name);
|
|
1899
|
+
if (!kg) {
|
|
1900
|
+
kg = new KnowledgeGraph(name, this.conn);
|
|
1901
|
+
this.kgs.set(name, kg);
|
|
1902
|
+
}
|
|
1903
|
+
return kg;
|
|
1904
|
+
}
|
|
1905
|
+
/** List all knowledge graphs. */
|
|
1906
|
+
async listKnowledgeGraphs() {
|
|
1907
|
+
const result = await this.conn.execute(".kg list");
|
|
1908
|
+
return result.rows.length > 0 ? result.rows.map((row) => String(row[0])) : [];
|
|
1909
|
+
}
|
|
1910
|
+
/** Drop a knowledge graph. */
|
|
1911
|
+
async dropKnowledgeGraph(name) {
|
|
1912
|
+
await this.conn.execute(`.kg drop ${name}`);
|
|
1913
|
+
this.kgs.delete(name);
|
|
1914
|
+
}
|
|
1915
|
+
// ── User management ─────────────────────────────────────────────
|
|
1916
|
+
async createUser(username, password, role = "viewer") {
|
|
1917
|
+
await this.conn.execute(compileCreateUser(username, password, role));
|
|
1918
|
+
}
|
|
1919
|
+
async dropUser(username) {
|
|
1920
|
+
await this.conn.execute(compileDropUser(username));
|
|
1921
|
+
}
|
|
1922
|
+
async setPassword(username, newPassword) {
|
|
1923
|
+
await this.conn.execute(compileSetPassword(username, newPassword));
|
|
1924
|
+
}
|
|
1925
|
+
async setRole(username, role) {
|
|
1926
|
+
await this.conn.execute(compileSetRole(username, role));
|
|
1927
|
+
}
|
|
1928
|
+
async listUsers() {
|
|
1929
|
+
const result = await this.conn.execute(compileListUsers());
|
|
1930
|
+
return result.rows.filter((row) => row.length >= 2).map((row) => ({
|
|
1931
|
+
username: String(row[0]),
|
|
1932
|
+
role: String(row[1])
|
|
1933
|
+
}));
|
|
1934
|
+
}
|
|
1935
|
+
// ── API key management ──────────────────────────────────────────
|
|
1936
|
+
/** Create an API key. Returns the key string. */
|
|
1937
|
+
async createApiKey(label) {
|
|
1938
|
+
const result = await this.conn.execute(compileCreateApiKey(label));
|
|
1939
|
+
if (result.rows.length > 0 && result.rows[0].length > 0) {
|
|
1940
|
+
return String(result.rows[0][0]);
|
|
1941
|
+
}
|
|
1942
|
+
return "";
|
|
1943
|
+
}
|
|
1944
|
+
async listApiKeys() {
|
|
1945
|
+
const result = await this.conn.execute(compileListApiKeys());
|
|
1946
|
+
return result.rows.map((row) => ({
|
|
1947
|
+
label: String(row[0]),
|
|
1948
|
+
createdAt: row.length > 1 ? String(row[1]) : ""
|
|
1949
|
+
}));
|
|
1950
|
+
}
|
|
1951
|
+
async revokeApiKey(label) {
|
|
1952
|
+
await this.conn.execute(compileRevokeApiKey(label));
|
|
1953
|
+
}
|
|
1954
|
+
// ── Notifications ───────────────────────────────────────────────
|
|
1955
|
+
/**
|
|
1956
|
+
* Register a notification callback.
|
|
1957
|
+
*
|
|
1958
|
+
* @param eventType - Filter by event type (e.g. "persistent_update")
|
|
1959
|
+
* @param callback - Function to call when event arrives
|
|
1960
|
+
* @param opts - Additional filters
|
|
1961
|
+
*/
|
|
1962
|
+
on(eventType, callback, opts) {
|
|
1963
|
+
this.conn.dispatcher.on(eventType, opts ?? {}, callback);
|
|
1964
|
+
}
|
|
1965
|
+
/** Remove a notification callback. */
|
|
1966
|
+
off(callback) {
|
|
1967
|
+
this.conn.dispatcher.off(callback);
|
|
1968
|
+
}
|
|
1969
|
+
/** Async iterator yielding notification events. */
|
|
1970
|
+
async *notifications() {
|
|
1971
|
+
yield* this.conn.dispatcher.events();
|
|
1972
|
+
}
|
|
1973
|
+
};
|
|
1974
|
+
export {
|
|
1975
|
+
AND,
|
|
1976
|
+
AuthenticationError,
|
|
1977
|
+
CannotDropError,
|
|
1978
|
+
ColumnProxy,
|
|
1979
|
+
Connection,
|
|
1980
|
+
ConnectionError,
|
|
1981
|
+
HnswIndex,
|
|
1982
|
+
IndexNotFoundError,
|
|
1983
|
+
InputLayer,
|
|
1984
|
+
InputLayerError,
|
|
1985
|
+
InternalError,
|
|
1986
|
+
KnowledgeGraph,
|
|
1987
|
+
KnowledgeGraphExistsError,
|
|
1988
|
+
KnowledgeGraphNotFoundError,
|
|
1989
|
+
NOT,
|
|
1990
|
+
NotificationDispatcher,
|
|
1991
|
+
OR,
|
|
1992
|
+
PermissionError,
|
|
1993
|
+
QueryTimeoutError,
|
|
1994
|
+
RelationDef,
|
|
1995
|
+
RelationNotFoundError,
|
|
1996
|
+
RelationProxy,
|
|
1997
|
+
RelationRef,
|
|
1998
|
+
ResultSet,
|
|
1999
|
+
RuleNotFoundError,
|
|
2000
|
+
SchemaConflictError,
|
|
2001
|
+
Session,
|
|
2002
|
+
Timestamp,
|
|
2003
|
+
ValidationError,
|
|
2004
|
+
avg,
|
|
2005
|
+
camelToSnake,
|
|
2006
|
+
columnToVariable,
|
|
2007
|
+
compileBoolExpr,
|
|
2008
|
+
compileBulkInsert,
|
|
2009
|
+
compileConditionalDelete,
|
|
2010
|
+
compileDelete,
|
|
2011
|
+
compileExpr,
|
|
2012
|
+
compileInsert,
|
|
2013
|
+
compileQuery,
|
|
2014
|
+
compileRule,
|
|
2015
|
+
compileSchema,
|
|
2016
|
+
compileValue,
|
|
2017
|
+
count,
|
|
2018
|
+
countDistinct,
|
|
2019
|
+
deserializeMessage,
|
|
2020
|
+
from,
|
|
2021
|
+
getColumnTypes,
|
|
2022
|
+
getColumns,
|
|
2023
|
+
max,
|
|
2024
|
+
min,
|
|
2025
|
+
relation,
|
|
2026
|
+
resolveRelationName,
|
|
2027
|
+
serializeMessage,
|
|
2028
|
+
snakeToCamel,
|
|
2029
|
+
sum,
|
|
2030
|
+
topK,
|
|
2031
|
+
topKThreshold,
|
|
2032
|
+
withinRadius,
|
|
2033
|
+
wrap
|
|
2034
|
+
};
|