@mindstudio-ai/agent 0.1.163 → 0.1.165
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/CHANGELOG.md +53 -0
- package/dist/cli.js +88 -7
- package/dist/index.js +33 -1
- package/dist/index.js.map +1 -1
- package/dist/postinstall.js +88 -7
- package/package.json +3 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
What changed in `@mindstudio-ai/agent`, newest first, each version under its own heading. An entry says what's new, what's fixed, and what changed behavior, with what an app's code should be checked for. An entry whose version npm doesn't have yet is still open: a later change adds to it.
|
|
4
|
+
|
|
5
|
+
Versions before 0.1.163 aren't recorded here.
|
|
6
|
+
|
|
7
|
+
## 0.1.165
|
|
8
|
+
|
|
9
|
+
### New
|
|
10
|
+
|
|
11
|
+
- The CLI can log what each call costs. When `MINDSTUDIO_USAGE_LOG` names a file, every billed call from an action, `batch` (a line per step) or `ask` appends one JSON line to it: `{ ts, action, billingCost, billingEvents?, durationMs }`, with `billingCost` in nano-dollars. It's for a tool that runs the CLI and keeps its own record of spend, whatever happens to the command's output. App code isn't affected.
|
|
12
|
+
|
|
13
|
+
## 0.1.164
|
|
14
|
+
|
|
15
|
+
### New
|
|
16
|
+
|
|
17
|
+
- The package carries this changelog, `CHANGELOG.md`, from this version on. Its entries start at 0.1.163.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- Filters on a column typed with a named type compile again. A column like `category: DocumentCategory` is stored as a json column. In 0.1.163, `===`, `!==` and `[...].includes(o.category)` with strings on it read the whole table into JS, and `o.category.includes('x')` failed the query with `malformed JSON` once a row held a plain string.
|
|
22
|
+
- `o.field == null` on a json column also matches a row whose text is `null`, which reads back as null. That happens when the string `'null'` was written.
|
|
23
|
+
|
|
24
|
+
## 0.1.163
|
|
25
|
+
|
|
26
|
+
Published by release v1.0.2832.
|
|
27
|
+
|
|
28
|
+
### Changed behavior
|
|
29
|
+
|
|
30
|
+
A filter that compiles to SQL now selects exactly the rows its function would in JavaScript, NULL columns included. Before, some compiled to SQL that answered differently, and which answer an app got depended on whether the filter compiled.
|
|
31
|
+
|
|
32
|
+
- `!==` and `!=` keep rows where the column is NULL, as in JS (`null !== 'archived'` is true). They used to drop them. That includes `removeAll`, which now deletes those rows too. Check: a `filter`, `count` or `removeAll` with `!==` on a column that can be empty, where empty rows should be left out. Add `o.status != null &&`.
|
|
33
|
+
- `<`, `<=`, `>` and `>=` give JS's answer for a NULL column: `o.n < 5` keeps a row with no `n`, since `null < 5` is true in JS. So does `!(…)`: `!(o.status === 'a')` keeps empty rows. Check: comparisons on a nullable number column where empty rows should be left out.
|
|
34
|
+
- `o.text.includes('x')` is case-sensitive, as JS's `includes` is, and `_` and `%` in the needle are literal characters. It used to be a case-insensitive SQL `LIKE`, in which a needle holding `_` or `%` matched nothing. Check: a search box written as `o.name.includes(q)` that relies on case-insensitive matching. Keep a lowercased copy of the field and compare `o.nameLower.includes(q.toLowerCase())` with `q` passed in bindings, or use `db.sql()` with `LIKE` (and `ESCAPE`).
|
|
35
|
+
- `o.tags.includes('x')` on a json array column is array membership, as in JS. It used to be a substring of the column's JSON text, so `'x'` also matched `'xy'`.
|
|
36
|
+
- A bare field reads by JS truthiness for its type: `o.name` is false for `''`, `o.count` for `0`, `!o.flag` is true for false and for an empty boolean. They used to compile to `= 1` and `= 0`, which was wrong for text and number columns.
|
|
37
|
+
- A computed `sortBy` (anything but `o => o.field`, such as `o => o.meta.date` or `o => o.name.toLowerCase()`) sorts. It used to be ignored whenever `take()` or `skip()` applied, so `.sortBy(computed).take(10)` returned 10 arbitrary rows. The rows are now sorted in JS after the filter runs in SQL, so `take()` reads every matching row first, and on a large table that can reach `result_too_large`. Check: a computed `sortBy` with `take()` on a table that grows. Sort by a column (`o => o.createdAt`) where you can.
|
|
38
|
+
- With no `sortBy`, `reverse()` and `last()` order by the reverse of the order rows were written in. They used to do nothing, so `last()` returned the first row.
|
|
39
|
+
- A filter SQL can't answer exactly runs in JS over every row instead, with a warning in the logs: a value of another type than its column (`o.amount > '100'`), a relational or loose comparison inside a JSON field (`o.stats.score > 50`, `o.meta.kind == 'x'`), and `.length`. The answer is right; the cost is reading the whole table.
|
|
40
|
+
|
|
41
|
+
### New
|
|
42
|
+
|
|
43
|
+
- A comparison with a constant expression compiles: `o.createdAt > db.ago(db.days(30))`, `o.createdAt > Date.now() - 90 * 24 * 60 * 60 * 1000`, `o.amount > 50 * 100`. These used to read the whole table into JS.
|
|
44
|
+
- Comparisons compile either way round: `5000 < o.amount`.
|
|
45
|
+
- `.select(...columns)` on a query or table returns only those columns (`SELECT id, name`). Filters and sorts still see the whole row.
|
|
46
|
+
- Reads a method starts together against one database, such as a `Promise.all` of queries, go to the platform as one round trip. Each gets its own results, and identical reads in flight share one answer. `db.batch()` is still the way to run reads and writes together, in order, as one transaction.
|
|
47
|
+
- Every value in a compiled filter is a bound parameter, an `IN` list included, so a query's statement doesn't change with its values.
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
|
|
51
|
+
- Filters in a deployed method's bundled code compile. esbuild prints `5000` as `5e3` and `'Zürich'` as `"Z\xFCrich"`, which the compiler couldn't read: a round number of 1,000 or more read the whole table into JS, and a string with a non-ASCII character or an escape matched nothing at all.
|
|
52
|
+
- `skip()` without `take()` failed with a SQL error.
|
|
53
|
+
- `[null, 'a'].includes(o.x)` didn't match rows where the field is empty.
|
package/dist/cli.js
CHANGED
|
@@ -9,6 +9,31 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
// src/usageLog.ts
|
|
13
|
+
import { appendFileSync } from "fs";
|
|
14
|
+
function logUsage(entry) {
|
|
15
|
+
const file = process.env.MINDSTUDIO_USAGE_LOG;
|
|
16
|
+
if (!file || typeof entry.billingCost !== "number") {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const line = {
|
|
20
|
+
ts: Date.now(),
|
|
21
|
+
action: entry.action,
|
|
22
|
+
billingCost: entry.billingCost,
|
|
23
|
+
...entry.billingEvents?.length && { billingEvents: entry.billingEvents },
|
|
24
|
+
durationMs: Date.now() - entry.startedAt
|
|
25
|
+
};
|
|
26
|
+
try {
|
|
27
|
+
appendFileSync(file, JSON.stringify(line) + "\n");
|
|
28
|
+
} catch {
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
var init_usageLog = __esm({
|
|
32
|
+
"src/usageLog.ts"() {
|
|
33
|
+
"use strict";
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
12
37
|
// src/generated/metadata.ts
|
|
13
38
|
var metadata_exports = {};
|
|
14
39
|
__export(metadata_exports, {
|
|
@@ -2413,6 +2438,25 @@ function nestedComparison(node, negated, params) {
|
|
|
2413
2438
|
function withNulls(core, field, nullsMatch) {
|
|
2414
2439
|
return nullsMatch ? `(${core} OR ${field.sql} IS NULL)` : core;
|
|
2415
2440
|
}
|
|
2441
|
+
function readsBackAsWritten(value) {
|
|
2442
|
+
if (typeof value !== "string") {
|
|
2443
|
+
return false;
|
|
2444
|
+
}
|
|
2445
|
+
try {
|
|
2446
|
+
JSON.parse(value);
|
|
2447
|
+
return false;
|
|
2448
|
+
} catch {
|
|
2449
|
+
return true;
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
function jsonColumnIn(field, values, negated, params) {
|
|
2453
|
+
params.push(JSON.stringify(values.flatMap((v) => [v, JSON.stringify(v)])));
|
|
2454
|
+
return withNulls(
|
|
2455
|
+
`${field.sql} ${negated ? "NOT IN" : "IN"} (SELECT value FROM json_each(?))`,
|
|
2456
|
+
field,
|
|
2457
|
+
negated
|
|
2458
|
+
);
|
|
2459
|
+
}
|
|
2416
2460
|
function compileNode(node, negated, columns, params) {
|
|
2417
2461
|
const column = (field) => field.nested ? void 0 : columns.find((c) => c.name === field.column);
|
|
2418
2462
|
const stored = (field, value) => field.nested ? serializeColumnParam(value, "", []) : serializeColumnParam(value, field.column, columns);
|
|
@@ -2430,6 +2474,10 @@ function compileNode(node, negated, columns, params) {
|
|
|
2430
2474
|
return nestedComparison(node, negated, params);
|
|
2431
2475
|
}
|
|
2432
2476
|
const type = column(node.field)?.type;
|
|
2477
|
+
if (type === "json") {
|
|
2478
|
+
const strictEquals = node.op === "===" ? !negated : node.op === "!==" ? negated : null;
|
|
2479
|
+
return strictEquals !== null && readsBackAsWritten(node.value) ? jsonColumnIn(node.field, [node.value], !strictEquals, params) : null;
|
|
2480
|
+
}
|
|
2433
2481
|
if (!type || typeof node.value !== VALUE_KIND[type]) {
|
|
2434
2482
|
return null;
|
|
2435
2483
|
}
|
|
@@ -2450,10 +2498,16 @@ function compileNode(node, negated, columns, params) {
|
|
|
2450
2498
|
return null;
|
|
2451
2499
|
}
|
|
2452
2500
|
const isNull = negated ? !node.isNull : node.isNull;
|
|
2501
|
+
if (column(node.field)?.type === "json") {
|
|
2502
|
+
return isNull ? `(${node.field.sql} IS NULL OR ${node.field.sql} = 'null')` : `(${node.field.sql} IS NOT NULL AND ${node.field.sql} <> 'null')`;
|
|
2503
|
+
}
|
|
2453
2504
|
return `${node.field.sql} ${isNull ? "IS NULL" : "IS NOT NULL"}`;
|
|
2454
2505
|
}
|
|
2455
2506
|
case "in": {
|
|
2456
2507
|
const type = column(node.field)?.type;
|
|
2508
|
+
if (type === "json") {
|
|
2509
|
+
return node.values.every(readsBackAsWritten) ? jsonColumnIn(node.field, node.values, negated, params) : null;
|
|
2510
|
+
}
|
|
2457
2511
|
if (!type || node.values.some(
|
|
2458
2512
|
(v) => v !== null && v !== void 0 && typeof v !== VALUE_KIND[type]
|
|
2459
2513
|
)) {
|
|
@@ -2473,6 +2527,7 @@ function compileNode(node, negated, columns, params) {
|
|
|
2473
2527
|
case "includes": {
|
|
2474
2528
|
const type = column(node.field)?.type;
|
|
2475
2529
|
if (type === "json") {
|
|
2530
|
+
const col = node.field.sql;
|
|
2476
2531
|
let element;
|
|
2477
2532
|
if (typeof node.needle === "boolean") {
|
|
2478
2533
|
element = `type = '${node.needle}'`;
|
|
@@ -2480,7 +2535,9 @@ function compileNode(node, negated, columns, params) {
|
|
|
2480
2535
|
params.push(node.needle);
|
|
2481
2536
|
element = `type IN ${JSON_TYPES[typeof node.needle]} AND value = ?`;
|
|
2482
2537
|
}
|
|
2483
|
-
|
|
2538
|
+
const substring = `${negated ? "=" : ">"} 0`;
|
|
2539
|
+
params.push(String(node.needle), String(node.needle));
|
|
2540
|
+
return `(CASE WHEN json_valid(${col}) THEN CASE json_type(${col}) WHEN 'array' THEN ${negated ? "NOT EXISTS" : "EXISTS"} (SELECT 1 FROM json_each(${col}) WHERE ${element}) WHEN 'text' THEN instr(${col} ->> '$', ?) ${substring} ELSE 0 END ELSE instr(${col}, ?) ${substring} END)`;
|
|
2484
2541
|
}
|
|
2485
2542
|
if ((type === "text" || type === "user") && typeof node.needle === "string") {
|
|
2486
2543
|
params.push(node.needle);
|
|
@@ -9275,6 +9332,7 @@ function summarizeInput(input) {
|
|
|
9275
9332
|
async function cmdAsk(question, options) {
|
|
9276
9333
|
try {
|
|
9277
9334
|
let lineBuffer = "";
|
|
9335
|
+
const startedAt = Date.now();
|
|
9278
9336
|
const result = await runAsk(question, options, (event) => {
|
|
9279
9337
|
switch (event.type) {
|
|
9280
9338
|
case "text":
|
|
@@ -9304,6 +9362,12 @@ async function cmdAsk(question, options) {
|
|
|
9304
9362
|
if (lineBuffer) {
|
|
9305
9363
|
process.stderr.write(lineBuffer);
|
|
9306
9364
|
}
|
|
9365
|
+
logUsage({
|
|
9366
|
+
action: "ask",
|
|
9367
|
+
billingCost: result.billingCost,
|
|
9368
|
+
billingEvents: result.billingEvents,
|
|
9369
|
+
startedAt
|
|
9370
|
+
});
|
|
9307
9371
|
if (options.json) {
|
|
9308
9372
|
process.stdout.write(
|
|
9309
9373
|
JSON.stringify({
|
|
@@ -9328,6 +9392,7 @@ var init_ask = __esm({
|
|
|
9328
9392
|
async "src/ask/index.ts"() {
|
|
9329
9393
|
"use strict";
|
|
9330
9394
|
await init_client();
|
|
9395
|
+
init_usageLog();
|
|
9331
9396
|
init_context2();
|
|
9332
9397
|
init_sse();
|
|
9333
9398
|
init_tools();
|
|
@@ -9402,7 +9467,7 @@ async function startMcpServer(options) {
|
|
|
9402
9467
|
capabilities: { tools: {} },
|
|
9403
9468
|
serverInfo: {
|
|
9404
9469
|
name: "mindstudio-agent",
|
|
9405
|
-
version: "0.1.
|
|
9470
|
+
version: "0.1.165"
|
|
9406
9471
|
},
|
|
9407
9472
|
instructions: 'Welcome to MindStudio \u2014 a platform with 200+ AI models, 850+ third-party integrations, and pre-built agents.\n\nGetting started:\n1. Call `ask` with any question about the SDK \u2014 it knows every action, model, and connector and returns working code with real model IDs and config options. Examples: ask("generate an image with FLUX"), ask("what models support vision?"), ask("how do I send a Slack message?").\n2. Call `changeName` to set your display name \u2014 use your name or whatever your user calls you. This is how you\'ll appear in MindStudio request logs.\n3. If you have a profile picture, pass its public image URL to `changeProfilePicture`.\n4. For manual browsing, call `listActions` to discover all available actions.\n\nThen use the tools to generate text, images, video, audio, search the web, work with data sources, run agents, and more.\n\nImportant:\n- AI-powered actions (text generation, image generation, video, audio, etc.) cost money. Before running these, call `estimateActionCost` and confirm with the user before proceeding \u2014 unless they\'ve explicitly told you to go ahead.\n- Not all agents from `listAgents` are configured for API use. Do not try to run an agent just because it appears in the list \u2014 it will likely fail. Only run agents the user specifically asks you to run.'
|
|
9408
9473
|
});
|
|
@@ -9853,6 +9918,7 @@ var init_mcp = __esm({
|
|
|
9853
9918
|
});
|
|
9854
9919
|
|
|
9855
9920
|
// src/cli.ts
|
|
9921
|
+
init_usageLog();
|
|
9856
9922
|
import { parseArgs } from "util";
|
|
9857
9923
|
import { execSync } from "child_process";
|
|
9858
9924
|
import { readFileSync as readFileSync3 } from "fs";
|
|
@@ -10172,11 +10238,18 @@ async function cmdExec(method, input, options) {
|
|
|
10172
10238
|
`);
|
|
10173
10239
|
};
|
|
10174
10240
|
}
|
|
10241
|
+
const startedAt = Date.now();
|
|
10175
10242
|
const result = await agent.executeStep(meta.stepType, input, {
|
|
10176
10243
|
appId: options.appId,
|
|
10177
10244
|
threadId: options.threadId,
|
|
10178
10245
|
onLog
|
|
10179
10246
|
});
|
|
10247
|
+
logUsage({
|
|
10248
|
+
action: camelToKebab(method),
|
|
10249
|
+
billingCost: result.$billingCost,
|
|
10250
|
+
billingEvents: result.$billingEvents,
|
|
10251
|
+
startedAt
|
|
10252
|
+
});
|
|
10180
10253
|
if (options.outputKey) {
|
|
10181
10254
|
const val = result[options.outputKey];
|
|
10182
10255
|
if (typeof val === "string") {
|
|
@@ -10352,10 +10425,18 @@ Example: mindstudio batch '[{"stepType":"generateImage","step":{"prompt":"a cat"
|
|
|
10352
10425
|
return { stepType: item.stepType, step: item.step };
|
|
10353
10426
|
});
|
|
10354
10427
|
const agent = await createAgent(options);
|
|
10428
|
+
const startedAt = Date.now();
|
|
10355
10429
|
const result = await agent.executeStepBatch(steps, {
|
|
10356
10430
|
appId: options.appId,
|
|
10357
10431
|
threadId: options.threadId
|
|
10358
10432
|
});
|
|
10433
|
+
for (const step of result.results) {
|
|
10434
|
+
logUsage({
|
|
10435
|
+
action: `batch:${step.stepType}`,
|
|
10436
|
+
billingCost: step.billingCost,
|
|
10437
|
+
startedAt
|
|
10438
|
+
});
|
|
10439
|
+
}
|
|
10359
10440
|
if (options.noMeta) {
|
|
10360
10441
|
process.stdout.write(JSON.stringify(result.results, null, 2) + "\n");
|
|
10361
10442
|
} else {
|
|
@@ -10426,7 +10507,7 @@ function isNewerVersion(current, latest) {
|
|
|
10426
10507
|
return false;
|
|
10427
10508
|
}
|
|
10428
10509
|
async function checkForUpdate() {
|
|
10429
|
-
const currentVersion = "0.1.
|
|
10510
|
+
const currentVersion = "0.1.165";
|
|
10430
10511
|
if (!currentVersion || process.env.MINDSTUDIO_NO_UPDATE_CHECK) {
|
|
10431
10512
|
return null;
|
|
10432
10513
|
}
|
|
@@ -10461,7 +10542,7 @@ async function checkForUpdate() {
|
|
|
10461
10542
|
}
|
|
10462
10543
|
}
|
|
10463
10544
|
function printUpdateNotice(latestVersion) {
|
|
10464
|
-
const currentVersion = "0.1.
|
|
10545
|
+
const currentVersion = "0.1.165";
|
|
10465
10546
|
process.stderr.write(
|
|
10466
10547
|
`
|
|
10467
10548
|
${ansi2.cyanBright("Update available")} ${ansi2.gray(currentVersion + " \u2192")} ${ansi2.cyanBold(latestVersion)}
|
|
@@ -10474,7 +10555,7 @@ function isStandaloneBinary() {
|
|
|
10474
10555
|
return !argv1.includes("node_modules");
|
|
10475
10556
|
}
|
|
10476
10557
|
async function cmdUpdate() {
|
|
10477
|
-
const currentVersion = "0.1.
|
|
10558
|
+
const currentVersion = "0.1.165";
|
|
10478
10559
|
process.stderr.write(
|
|
10479
10560
|
` ${ansi2.gray("Current version:")} ${currentVersion}
|
|
10480
10561
|
`
|
|
@@ -10615,7 +10696,7 @@ async function cmdLogin(options) {
|
|
|
10615
10696
|
process.stderr.write("\n");
|
|
10616
10697
|
printLogo();
|
|
10617
10698
|
process.stderr.write("\n");
|
|
10618
|
-
const ver = "0.1.
|
|
10699
|
+
const ver = "0.1.165";
|
|
10619
10700
|
process.stderr.write(
|
|
10620
10701
|
` ${ansi2.bold("MindStudio Agent")} ${ver ? " " + ansi2.gray("v" + ver) : ""}
|
|
10621
10702
|
`
|
|
@@ -10943,7 +11024,7 @@ async function main() {
|
|
|
10943
11024
|
const updatePromise = command !== "mcp" && command !== "login" && command !== "update" ? checkForUpdate() : Promise.resolve(null);
|
|
10944
11025
|
try {
|
|
10945
11026
|
if (command === "version" || command === "-v") {
|
|
10946
|
-
process.stdout.write("0.1.
|
|
11027
|
+
process.stdout.write("0.1.165\n");
|
|
10947
11028
|
return;
|
|
10948
11029
|
}
|
|
10949
11030
|
if (command === "login") {
|
package/dist/index.js
CHANGED
|
@@ -1594,6 +1594,25 @@ function nestedComparison(node, negated, params) {
|
|
|
1594
1594
|
function withNulls(core, field, nullsMatch) {
|
|
1595
1595
|
return nullsMatch ? `(${core} OR ${field.sql} IS NULL)` : core;
|
|
1596
1596
|
}
|
|
1597
|
+
function readsBackAsWritten(value) {
|
|
1598
|
+
if (typeof value !== "string") {
|
|
1599
|
+
return false;
|
|
1600
|
+
}
|
|
1601
|
+
try {
|
|
1602
|
+
JSON.parse(value);
|
|
1603
|
+
return false;
|
|
1604
|
+
} catch {
|
|
1605
|
+
return true;
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
function jsonColumnIn(field, values, negated, params) {
|
|
1609
|
+
params.push(JSON.stringify(values.flatMap((v) => [v, JSON.stringify(v)])));
|
|
1610
|
+
return withNulls(
|
|
1611
|
+
`${field.sql} ${negated ? "NOT IN" : "IN"} (SELECT value FROM json_each(?))`,
|
|
1612
|
+
field,
|
|
1613
|
+
negated
|
|
1614
|
+
);
|
|
1615
|
+
}
|
|
1597
1616
|
function compileNode(node, negated, columns, params) {
|
|
1598
1617
|
const column = (field) => field.nested ? void 0 : columns.find((c) => c.name === field.column);
|
|
1599
1618
|
const stored = (field, value) => field.nested ? serializeColumnParam(value, "", []) : serializeColumnParam(value, field.column, columns);
|
|
@@ -1611,6 +1630,10 @@ function compileNode(node, negated, columns, params) {
|
|
|
1611
1630
|
return nestedComparison(node, negated, params);
|
|
1612
1631
|
}
|
|
1613
1632
|
const type = column(node.field)?.type;
|
|
1633
|
+
if (type === "json") {
|
|
1634
|
+
const strictEquals = node.op === "===" ? !negated : node.op === "!==" ? negated : null;
|
|
1635
|
+
return strictEquals !== null && readsBackAsWritten(node.value) ? jsonColumnIn(node.field, [node.value], !strictEquals, params) : null;
|
|
1636
|
+
}
|
|
1614
1637
|
if (!type || typeof node.value !== VALUE_KIND[type]) {
|
|
1615
1638
|
return null;
|
|
1616
1639
|
}
|
|
@@ -1631,10 +1654,16 @@ function compileNode(node, negated, columns, params) {
|
|
|
1631
1654
|
return null;
|
|
1632
1655
|
}
|
|
1633
1656
|
const isNull = negated ? !node.isNull : node.isNull;
|
|
1657
|
+
if (column(node.field)?.type === "json") {
|
|
1658
|
+
return isNull ? `(${node.field.sql} IS NULL OR ${node.field.sql} = 'null')` : `(${node.field.sql} IS NOT NULL AND ${node.field.sql} <> 'null')`;
|
|
1659
|
+
}
|
|
1634
1660
|
return `${node.field.sql} ${isNull ? "IS NULL" : "IS NOT NULL"}`;
|
|
1635
1661
|
}
|
|
1636
1662
|
case "in": {
|
|
1637
1663
|
const type = column(node.field)?.type;
|
|
1664
|
+
if (type === "json") {
|
|
1665
|
+
return node.values.every(readsBackAsWritten) ? jsonColumnIn(node.field, node.values, negated, params) : null;
|
|
1666
|
+
}
|
|
1638
1667
|
if (!type || node.values.some(
|
|
1639
1668
|
(v) => v !== null && v !== void 0 && typeof v !== VALUE_KIND[type]
|
|
1640
1669
|
)) {
|
|
@@ -1654,6 +1683,7 @@ function compileNode(node, negated, columns, params) {
|
|
|
1654
1683
|
case "includes": {
|
|
1655
1684
|
const type = column(node.field)?.type;
|
|
1656
1685
|
if (type === "json") {
|
|
1686
|
+
const col = node.field.sql;
|
|
1657
1687
|
let element;
|
|
1658
1688
|
if (typeof node.needle === "boolean") {
|
|
1659
1689
|
element = `type = '${node.needle}'`;
|
|
@@ -1661,7 +1691,9 @@ function compileNode(node, negated, columns, params) {
|
|
|
1661
1691
|
params.push(node.needle);
|
|
1662
1692
|
element = `type IN ${JSON_TYPES[typeof node.needle]} AND value = ?`;
|
|
1663
1693
|
}
|
|
1664
|
-
|
|
1694
|
+
const substring = `${negated ? "=" : ">"} 0`;
|
|
1695
|
+
params.push(String(node.needle), String(node.needle));
|
|
1696
|
+
return `(CASE WHEN json_valid(${col}) THEN CASE json_type(${col}) WHEN 'array' THEN ${negated ? "NOT EXISTS" : "EXISTS"} (SELECT 1 FROM json_each(${col}) WHERE ${element}) WHEN 'text' THEN instr(${col} ->> '$', ?) ${substring} ELSE 0 END ELSE instr(${col}, ?) ${substring} END)`;
|
|
1665
1697
|
}
|
|
1666
1698
|
if ((type === "text" || type === "user") && typeof node.needle === "string") {
|
|
1667
1699
|
params.push(node.needle);
|