yaml-flow 2.5.0 → 2.6.1
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/browser/card-compute.js +546 -0
- package/browser/live-cards.js +1381 -0
- package/browser/live-cards.schema.json +246 -0
- package/dist/card-compute/index.cjs +456 -0
- package/dist/card-compute/index.cjs.map +1 -0
- package/dist/card-compute/index.d.cts +85 -0
- package/dist/card-compute/index.d.ts +85 -0
- package/dist/card-compute/index.js +451 -0
- package/dist/card-compute/index.js.map +1 -0
- package/dist/index.cjs +454 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +454 -7
- package/dist/index.js.map +1 -1
- package/package.json +8 -2
- package/schema/live-cards.schema.json +246 -0
package/dist/index.cjs
CHANGED
|
@@ -255,7 +255,7 @@ var StepMachine = class {
|
|
|
255
255
|
}
|
|
256
256
|
}
|
|
257
257
|
sleep(ms) {
|
|
258
|
-
return new Promise((
|
|
258
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
259
259
|
}
|
|
260
260
|
async run(initialData) {
|
|
261
261
|
const runId = generateRunId();
|
|
@@ -2157,7 +2157,7 @@ async function batch(items, options) {
|
|
|
2157
2157
|
if (total === 0) {
|
|
2158
2158
|
return { items: [], completed: 0, failed: 0, total: 0, durationMs: 0 };
|
|
2159
2159
|
}
|
|
2160
|
-
return new Promise((
|
|
2160
|
+
return new Promise((resolve2) => {
|
|
2161
2161
|
let active = 0;
|
|
2162
2162
|
function tryStartNext() {
|
|
2163
2163
|
while (active < concurrency && nextIndex < total) {
|
|
@@ -2174,7 +2174,7 @@ async function batch(items, options) {
|
|
|
2174
2174
|
failed++;
|
|
2175
2175
|
}
|
|
2176
2176
|
if (active === 0 && completed + failed === total) {
|
|
2177
|
-
|
|
2177
|
+
resolve2({
|
|
2178
2178
|
items: results,
|
|
2179
2179
|
completed,
|
|
2180
2180
|
failed,
|
|
@@ -2213,7 +2213,7 @@ async function batch(items, options) {
|
|
|
2213
2213
|
active--;
|
|
2214
2214
|
onProgress?.(makeProgress(active));
|
|
2215
2215
|
if (completed + failed === total) {
|
|
2216
|
-
|
|
2216
|
+
resolve2({
|
|
2217
2217
|
items: results,
|
|
2218
2218
|
completed,
|
|
2219
2219
|
failed,
|
|
@@ -3214,7 +3214,7 @@ function createCliAdapter(opts) {
|
|
|
3214
3214
|
const timeout = opts.timeout ?? 6e4;
|
|
3215
3215
|
return {
|
|
3216
3216
|
analyze: (prompt) => {
|
|
3217
|
-
return new Promise((
|
|
3217
|
+
return new Promise((resolve2, reject) => {
|
|
3218
3218
|
const args = opts.args(prompt);
|
|
3219
3219
|
const child = child_process.execFile(
|
|
3220
3220
|
opts.command,
|
|
@@ -3234,7 +3234,7 @@ stderr: ${stderr.slice(0, 500)}` : "") + `
|
|
|
3234
3234
|
${error.message}`
|
|
3235
3235
|
));
|
|
3236
3236
|
} else {
|
|
3237
|
-
|
|
3237
|
+
resolve2(stdout);
|
|
3238
3238
|
}
|
|
3239
3239
|
}
|
|
3240
3240
|
);
|
|
@@ -3282,8 +3282,456 @@ function createHttpAdapter(opts) {
|
|
|
3282
3282
|
};
|
|
3283
3283
|
}
|
|
3284
3284
|
|
|
3285
|
+
// src/card-compute/index.ts
|
|
3286
|
+
function deepGet(obj, path) {
|
|
3287
|
+
if (!path || !obj) return void 0;
|
|
3288
|
+
const parts = path.split(".");
|
|
3289
|
+
let cur = obj;
|
|
3290
|
+
for (let i = 0; i < parts.length; i++) {
|
|
3291
|
+
if (cur == null) return void 0;
|
|
3292
|
+
cur = cur[parts[i]];
|
|
3293
|
+
}
|
|
3294
|
+
return cur;
|
|
3295
|
+
}
|
|
3296
|
+
function deepSet(obj, path, value) {
|
|
3297
|
+
const parts = path.split(".");
|
|
3298
|
+
let cur = obj;
|
|
3299
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
3300
|
+
if (cur[parts[i]] == null || typeof cur[parts[i]] !== "object") cur[parts[i]] = {};
|
|
3301
|
+
cur = cur[parts[i]];
|
|
3302
|
+
}
|
|
3303
|
+
cur[parts[parts.length - 1]] = value;
|
|
3304
|
+
}
|
|
3305
|
+
var _fns = {};
|
|
3306
|
+
_fns.sum = (input, _e, opts) => {
|
|
3307
|
+
const a = Array.isArray(input) ? input : [];
|
|
3308
|
+
return opts.field ? a.reduce((s, r) => s + (Number(r[opts.field]) || 0), 0) : a.reduce((s, v) => s + (Number(v) || 0), 0);
|
|
3309
|
+
};
|
|
3310
|
+
_fns.avg = (input, _e, opts) => {
|
|
3311
|
+
const s = _fns.sum(input, _e, opts);
|
|
3312
|
+
const n = Array.isArray(input) ? input.length : 1;
|
|
3313
|
+
return n ? s / n : 0;
|
|
3314
|
+
};
|
|
3315
|
+
_fns.min = (input, _e, opts) => {
|
|
3316
|
+
const a = Array.isArray(input) ? input : [];
|
|
3317
|
+
const vals = opts.field ? a.map((r) => Number(r[opts.field])) : a.map(Number);
|
|
3318
|
+
return vals.length ? Math.min(...vals) : 0;
|
|
3319
|
+
};
|
|
3320
|
+
_fns.max = (input, _e, opts) => {
|
|
3321
|
+
const a = Array.isArray(input) ? input : [];
|
|
3322
|
+
const vals = opts.field ? a.map((r) => Number(r[opts.field])) : a.map(Number);
|
|
3323
|
+
return vals.length ? Math.max(...vals) : 0;
|
|
3324
|
+
};
|
|
3325
|
+
_fns.count = (input) => Array.isArray(input) ? input.length : input != null ? 1 : 0;
|
|
3326
|
+
_fns.first = (input) => Array.isArray(input) ? input[0] : input;
|
|
3327
|
+
_fns.last = (input) => Array.isArray(input) ? input[input.length - 1] : input;
|
|
3328
|
+
_fns.add = (input) => {
|
|
3329
|
+
const a = Array.isArray(input) ? input : [];
|
|
3330
|
+
return a.reduce((s, v) => s + Number(v), 0);
|
|
3331
|
+
};
|
|
3332
|
+
_fns.sub = (input) => {
|
|
3333
|
+
const a = Array.isArray(input) ? input : [];
|
|
3334
|
+
return a.length >= 2 ? Number(a[0]) - Number(a[1]) : 0;
|
|
3335
|
+
};
|
|
3336
|
+
_fns.mul = (input) => {
|
|
3337
|
+
const a = Array.isArray(input) ? input : [];
|
|
3338
|
+
return a.reduce((s, v) => s * Number(v), 1);
|
|
3339
|
+
};
|
|
3340
|
+
_fns.div = (input) => {
|
|
3341
|
+
const a = Array.isArray(input) ? input : [];
|
|
3342
|
+
return a.length >= 2 && Number(a[1]) !== 0 ? Number(a[0]) / Number(a[1]) : 0;
|
|
3343
|
+
};
|
|
3344
|
+
_fns.round = (input, _e, opts) => {
|
|
3345
|
+
const decimals = opts.decimals != null ? opts.decimals : 0;
|
|
3346
|
+
const factor = Math.pow(10, decimals);
|
|
3347
|
+
return Math.round(Number(input) * factor) / factor;
|
|
3348
|
+
};
|
|
3349
|
+
_fns.abs = (input) => Math.abs(Number(input));
|
|
3350
|
+
_fns.mod = (input) => {
|
|
3351
|
+
const a = Array.isArray(input) ? input : [];
|
|
3352
|
+
return a.length >= 2 ? Number(a[0]) % Number(a[1]) : 0;
|
|
3353
|
+
};
|
|
3354
|
+
_fns.gt = (input) => {
|
|
3355
|
+
const a = Array.isArray(input) ? input : [];
|
|
3356
|
+
return a.length >= 2 && Number(a[0]) > Number(a[1]);
|
|
3357
|
+
};
|
|
3358
|
+
_fns.gte = (input) => {
|
|
3359
|
+
const a = Array.isArray(input) ? input : [];
|
|
3360
|
+
return a.length >= 2 && Number(a[0]) >= Number(a[1]);
|
|
3361
|
+
};
|
|
3362
|
+
_fns.lt = (input) => {
|
|
3363
|
+
const a = Array.isArray(input) ? input : [];
|
|
3364
|
+
return a.length >= 2 && Number(a[0]) < Number(a[1]);
|
|
3365
|
+
};
|
|
3366
|
+
_fns.lte = (input) => {
|
|
3367
|
+
const a = Array.isArray(input) ? input : [];
|
|
3368
|
+
return a.length >= 2 && Number(a[0]) <= Number(a[1]);
|
|
3369
|
+
};
|
|
3370
|
+
_fns.eq = (input) => {
|
|
3371
|
+
const a = Array.isArray(input) ? input : [];
|
|
3372
|
+
return a.length >= 2 && a[0] === a[1];
|
|
3373
|
+
};
|
|
3374
|
+
_fns.neq = (input) => {
|
|
3375
|
+
const a = Array.isArray(input) ? input : [];
|
|
3376
|
+
return a.length >= 2 && a[0] !== a[1];
|
|
3377
|
+
};
|
|
3378
|
+
_fns.and = (input) => {
|
|
3379
|
+
const a = Array.isArray(input) ? input : [];
|
|
3380
|
+
return a.every(Boolean);
|
|
3381
|
+
};
|
|
3382
|
+
_fns.or = (input) => {
|
|
3383
|
+
const a = Array.isArray(input) ? input : [];
|
|
3384
|
+
return a.some(Boolean);
|
|
3385
|
+
};
|
|
3386
|
+
_fns.not = (input) => !input;
|
|
3387
|
+
_fns.concat = (input) => {
|
|
3388
|
+
const a = Array.isArray(input) ? input : [];
|
|
3389
|
+
return a.map((v) => v != null ? String(v) : "").join("");
|
|
3390
|
+
};
|
|
3391
|
+
_fns.upper = (input) => String(input || "").toUpperCase();
|
|
3392
|
+
_fns.lower = (input) => String(input || "").toLowerCase();
|
|
3393
|
+
_fns.template = (input, _e, opts) => {
|
|
3394
|
+
let t = String(opts.format || "");
|
|
3395
|
+
if (input && typeof input === "object" && !Array.isArray(input)) {
|
|
3396
|
+
for (const k of Object.keys(input)) {
|
|
3397
|
+
const v = input[k];
|
|
3398
|
+
t = t.split("{{" + k + "}}").join(v != null ? String(v) : "");
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
return t;
|
|
3402
|
+
};
|
|
3403
|
+
_fns.join = (input, _e, opts) => {
|
|
3404
|
+
const a = Array.isArray(input) ? input : [];
|
|
3405
|
+
const sep = opts.separator != null ? String(opts.separator) : ", ";
|
|
3406
|
+
return a.map((v) => v != null ? String(v) : "").join(sep);
|
|
3407
|
+
};
|
|
3408
|
+
_fns.split = (input, _e, opts) => {
|
|
3409
|
+
const sep = opts.separator != null ? String(opts.separator) : ",";
|
|
3410
|
+
return String(input || "").split(sep).map((s) => s.trim());
|
|
3411
|
+
};
|
|
3412
|
+
_fns.trim = (input) => String(input || "").trim();
|
|
3413
|
+
_fns.pluck = (input, _e, opts) => Array.isArray(input) ? input.map((r) => r[opts.field]) : [];
|
|
3414
|
+
_fns.filter = (input, _e, opts) => {
|
|
3415
|
+
if (!Array.isArray(input)) return [];
|
|
3416
|
+
if (opts.field) return input.filter((r) => !!r[opts.field]);
|
|
3417
|
+
return input.filter(Boolean);
|
|
3418
|
+
};
|
|
3419
|
+
_fns.map = (input) => Array.isArray(input) ? input.slice() : [];
|
|
3420
|
+
_fns.sort = (input, _e, opts) => {
|
|
3421
|
+
const a = Array.isArray(input) ? input.slice() : [];
|
|
3422
|
+
const f = opts.field;
|
|
3423
|
+
const dir = opts.direction === "desc" ? -1 : 1;
|
|
3424
|
+
if (f) return a.sort((x, y) => x[f] > y[f] ? dir : x[f] < y[f] ? -dir : 0);
|
|
3425
|
+
return a.sort((x, y) => x > y ? dir : x < y ? -dir : 0);
|
|
3426
|
+
};
|
|
3427
|
+
_fns.slice = (input, _e, opts) => Array.isArray(input) ? input.slice(opts.start || 0, opts.end) : input;
|
|
3428
|
+
_fns.flat = (input, _e, opts) => {
|
|
3429
|
+
const depth = opts.depth != null ? opts.depth : 1;
|
|
3430
|
+
return Array.isArray(input) ? input.flat(depth) : [input];
|
|
3431
|
+
};
|
|
3432
|
+
_fns.unique = (input) => {
|
|
3433
|
+
if (!Array.isArray(input)) return [input];
|
|
3434
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3435
|
+
return input.filter((v) => {
|
|
3436
|
+
const key = typeof v === "object" ? JSON.stringify(v) : v;
|
|
3437
|
+
if (seen.has(key)) return false;
|
|
3438
|
+
seen.add(key);
|
|
3439
|
+
return true;
|
|
3440
|
+
});
|
|
3441
|
+
};
|
|
3442
|
+
_fns.group = (input, _e, opts) => {
|
|
3443
|
+
const a = Array.isArray(input) ? input : [];
|
|
3444
|
+
const g = {};
|
|
3445
|
+
a.forEach((r) => {
|
|
3446
|
+
const k = String(r[opts.field] || "");
|
|
3447
|
+
if (!g[k]) g[k] = [];
|
|
3448
|
+
g[k].push(r);
|
|
3449
|
+
});
|
|
3450
|
+
return g;
|
|
3451
|
+
};
|
|
3452
|
+
_fns.flatten_keys = (input) => {
|
|
3453
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return [];
|
|
3454
|
+
const result = [];
|
|
3455
|
+
for (const k of Object.keys(input)) {
|
|
3456
|
+
const vals = Array.isArray(input[k]) ? input[k] : [input[k]];
|
|
3457
|
+
vals.forEach((v) => result.push({ key: k, value: v }));
|
|
3458
|
+
}
|
|
3459
|
+
return result;
|
|
3460
|
+
};
|
|
3461
|
+
_fns.entries = (input) => {
|
|
3462
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return [];
|
|
3463
|
+
return Object.keys(input).map((k) => ({ key: k, value: input[k] }));
|
|
3464
|
+
};
|
|
3465
|
+
_fns.from_entries = (input) => {
|
|
3466
|
+
if (!Array.isArray(input)) return {};
|
|
3467
|
+
const obj = {};
|
|
3468
|
+
input.forEach((item) => {
|
|
3469
|
+
if (item.key != null) obj[item.key] = item.value;
|
|
3470
|
+
});
|
|
3471
|
+
return obj;
|
|
3472
|
+
};
|
|
3473
|
+
_fns.length = (input) => {
|
|
3474
|
+
if (Array.isArray(input)) return input.length;
|
|
3475
|
+
if (typeof input === "string") return input.length;
|
|
3476
|
+
if (input && typeof input === "object") return Object.keys(input).length;
|
|
3477
|
+
return 0;
|
|
3478
|
+
};
|
|
3479
|
+
_fns.get = (input, _e, opts) => deepGet(input, opts.field || opts.path || "");
|
|
3480
|
+
_fns.default = (input, _e, opts) => input != null ? input : opts.value;
|
|
3481
|
+
_fns.coalesce = (input) => {
|
|
3482
|
+
const a = Array.isArray(input) ? input : [];
|
|
3483
|
+
for (let i = 0; i < a.length; i++) {
|
|
3484
|
+
if (a[i] != null) return a[i];
|
|
3485
|
+
}
|
|
3486
|
+
return null;
|
|
3487
|
+
};
|
|
3488
|
+
_fns.now = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
3489
|
+
_fns.diff_days = (input) => {
|
|
3490
|
+
const a = Array.isArray(input) ? input : [];
|
|
3491
|
+
return a.length >= 2 ? Math.floor((new Date(a[0]).getTime() - new Date(a[1]).getTime()) / 864e5) : 0;
|
|
3492
|
+
};
|
|
3493
|
+
_fns.format_date = (input, _e, opts) => {
|
|
3494
|
+
try {
|
|
3495
|
+
const d = new Date(input);
|
|
3496
|
+
if (opts.format === "iso") return d.toISOString();
|
|
3497
|
+
if (opts.format === "date") return d.toLocaleDateString();
|
|
3498
|
+
if (opts.format === "time") return d.toLocaleTimeString();
|
|
3499
|
+
return d.toLocaleDateString();
|
|
3500
|
+
} catch {
|
|
3501
|
+
return String(input);
|
|
3502
|
+
}
|
|
3503
|
+
};
|
|
3504
|
+
_fns.parse_date = (input) => {
|
|
3505
|
+
try {
|
|
3506
|
+
return new Date(input).toISOString();
|
|
3507
|
+
} catch {
|
|
3508
|
+
return null;
|
|
3509
|
+
}
|
|
3510
|
+
};
|
|
3511
|
+
_fns.to_number = (input) => Number(input) || 0;
|
|
3512
|
+
_fns.to_string = (input) => input != null ? String(input) : "";
|
|
3513
|
+
_fns.to_bool = (input) => !!input;
|
|
3514
|
+
_fns.type_of = (input) => Array.isArray(input) ? "array" : typeof input;
|
|
3515
|
+
_fns.is_null = (input) => input == null;
|
|
3516
|
+
_fns.is_empty = (input) => {
|
|
3517
|
+
if (input == null) return true;
|
|
3518
|
+
if (Array.isArray(input)) return input.length === 0;
|
|
3519
|
+
if (typeof input === "string") return input.length === 0;
|
|
3520
|
+
if (typeof input === "object") return Object.keys(input).length === 0;
|
|
3521
|
+
return false;
|
|
3522
|
+
};
|
|
3523
|
+
var _customFns = {};
|
|
3524
|
+
function evalExpr(expr, node) {
|
|
3525
|
+
if (expr == null) return expr;
|
|
3526
|
+
if (typeof expr !== "object" || Array.isArray(expr)) return expr;
|
|
3527
|
+
const e = expr;
|
|
3528
|
+
if (!e.fn) return expr;
|
|
3529
|
+
let input = e.input;
|
|
3530
|
+
if (typeof input === "string" && input.startsWith("state.")) {
|
|
3531
|
+
input = deepGet(node, input);
|
|
3532
|
+
} else if (Array.isArray(input)) {
|
|
3533
|
+
input = input.map((v) => {
|
|
3534
|
+
if (typeof v === "string" && v.startsWith("state.")) return deepGet(node, v);
|
|
3535
|
+
if (v && typeof v === "object" && v.fn) return evalExpr(v, node);
|
|
3536
|
+
return v;
|
|
3537
|
+
});
|
|
3538
|
+
} else if (input && typeof input === "object" && input.fn) {
|
|
3539
|
+
input = evalExpr(input, node);
|
|
3540
|
+
}
|
|
3541
|
+
if (e.fn === "if") {
|
|
3542
|
+
const cond = evalExpr(e.cond, node);
|
|
3543
|
+
if (cond) {
|
|
3544
|
+
return e.then && typeof e.then === "object" && e.then.fn ? evalExpr(e.then, node) : e.then;
|
|
3545
|
+
} else {
|
|
3546
|
+
return e.else && typeof e.else === "object" && e.else.fn ? evalExpr(e.else, node) : e.else;
|
|
3547
|
+
}
|
|
3548
|
+
}
|
|
3549
|
+
if (e.fn === "filter" && Array.isArray(input) && e.where) {
|
|
3550
|
+
return input.filter((item) => {
|
|
3551
|
+
const tmp = { state: { ...node.state, $: item } };
|
|
3552
|
+
return evalExpr(e.where, tmp);
|
|
3553
|
+
});
|
|
3554
|
+
}
|
|
3555
|
+
if (e.fn === "map" && Array.isArray(input) && e.apply) {
|
|
3556
|
+
return input.map((item) => {
|
|
3557
|
+
const tmp = { state: { ...node.state, $: item } };
|
|
3558
|
+
return evalExpr(e.apply, tmp);
|
|
3559
|
+
});
|
|
3560
|
+
}
|
|
3561
|
+
const fn = _customFns[e.fn] || _fns[e.fn];
|
|
3562
|
+
if (!fn) {
|
|
3563
|
+
console.warn('CardCompute: unknown function "' + e.fn + '"');
|
|
3564
|
+
return void 0;
|
|
3565
|
+
}
|
|
3566
|
+
return fn(input, evalExpr, e);
|
|
3567
|
+
}
|
|
3568
|
+
function run(node) {
|
|
3569
|
+
if (!node || !node.compute) return node;
|
|
3570
|
+
if (!node.state) node.state = {};
|
|
3571
|
+
for (const key of Object.keys(node.compute)) {
|
|
3572
|
+
try {
|
|
3573
|
+
const val = evalExpr(node.compute[key], node);
|
|
3574
|
+
deepSet(node.state, key, val);
|
|
3575
|
+
} catch (err) {
|
|
3576
|
+
console.error(`CardCompute.run error on "${node.id || "?"}.${key}":`, err);
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3579
|
+
return node;
|
|
3580
|
+
}
|
|
3581
|
+
function resolve(node, path) {
|
|
3582
|
+
return deepGet(node, path);
|
|
3583
|
+
}
|
|
3584
|
+
function registerFunction(name, fn) {
|
|
3585
|
+
_customFns[name] = fn;
|
|
3586
|
+
}
|
|
3587
|
+
var VALID_ELEMENT_KINDS = /* @__PURE__ */ new Set([
|
|
3588
|
+
"metric",
|
|
3589
|
+
"table",
|
|
3590
|
+
"chart",
|
|
3591
|
+
"form",
|
|
3592
|
+
"filter",
|
|
3593
|
+
"list",
|
|
3594
|
+
"notes",
|
|
3595
|
+
"todo",
|
|
3596
|
+
"alert",
|
|
3597
|
+
"narrative",
|
|
3598
|
+
"badge",
|
|
3599
|
+
"text",
|
|
3600
|
+
"markdown",
|
|
3601
|
+
"custom"
|
|
3602
|
+
]);
|
|
3603
|
+
var VALID_SOURCE_KINDS = /* @__PURE__ */ new Set(["api", "websocket", "static", "llm"]);
|
|
3604
|
+
var VALID_STATUSES = /* @__PURE__ */ new Set(["fresh", "stale", "loading", "error"]);
|
|
3605
|
+
var CARD_ALLOWED_KEYS = /* @__PURE__ */ new Set(["id", "type", "meta", "data", "view", "state", "compute"]);
|
|
3606
|
+
var SOURCE_ALLOWED_KEYS = /* @__PURE__ */ new Set(["id", "type", "meta", "data", "source", "state", "compute"]);
|
|
3607
|
+
function validateNode(node) {
|
|
3608
|
+
const errors = [];
|
|
3609
|
+
if (!node || typeof node !== "object" || Array.isArray(node)) {
|
|
3610
|
+
return { ok: false, errors: ["Node must be a non-null object"] };
|
|
3611
|
+
}
|
|
3612
|
+
const n = node;
|
|
3613
|
+
if (typeof n.id !== "string" || !n.id) {
|
|
3614
|
+
errors.push("id: required, must be a non-empty string");
|
|
3615
|
+
}
|
|
3616
|
+
if (n.type !== "card" && n.type !== "source") {
|
|
3617
|
+
errors.push('type: must be "card" or "source"');
|
|
3618
|
+
return { ok: false, errors };
|
|
3619
|
+
}
|
|
3620
|
+
const allowed = n.type === "card" ? CARD_ALLOWED_KEYS : SOURCE_ALLOWED_KEYS;
|
|
3621
|
+
for (const key of Object.keys(n)) {
|
|
3622
|
+
if (!allowed.has(key)) errors.push(`Unknown top-level key: "${key}"`);
|
|
3623
|
+
}
|
|
3624
|
+
if (n.state == null || typeof n.state !== "object" || Array.isArray(n.state)) {
|
|
3625
|
+
errors.push("state: required, must be an object");
|
|
3626
|
+
} else {
|
|
3627
|
+
const state = n.state;
|
|
3628
|
+
if (state.status != null && !VALID_STATUSES.has(state.status)) {
|
|
3629
|
+
errors.push(`state.status: must be one of: ${[...VALID_STATUSES].join(", ")}`);
|
|
3630
|
+
}
|
|
3631
|
+
}
|
|
3632
|
+
if (n.meta != null) {
|
|
3633
|
+
if (typeof n.meta !== "object" || Array.isArray(n.meta)) {
|
|
3634
|
+
errors.push("meta: must be an object");
|
|
3635
|
+
} else {
|
|
3636
|
+
const meta = n.meta;
|
|
3637
|
+
if (meta.title != null && typeof meta.title !== "string") errors.push("meta.title: must be a string");
|
|
3638
|
+
if (meta.tags != null && !Array.isArray(meta.tags)) errors.push("meta.tags: must be an array");
|
|
3639
|
+
}
|
|
3640
|
+
}
|
|
3641
|
+
if (n.data != null) {
|
|
3642
|
+
if (typeof n.data !== "object" || Array.isArray(n.data)) {
|
|
3643
|
+
errors.push("data: must be an object");
|
|
3644
|
+
} else {
|
|
3645
|
+
const data = n.data;
|
|
3646
|
+
if (data.requires != null && !Array.isArray(data.requires)) errors.push("data.requires: must be an array of strings");
|
|
3647
|
+
if (data.provides != null && (typeof data.provides !== "object" || Array.isArray(data.provides))) errors.push("data.provides: must be an object");
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
if (n.compute != null) {
|
|
3651
|
+
if (typeof n.compute !== "object" || Array.isArray(n.compute)) {
|
|
3652
|
+
errors.push("compute: must be an object");
|
|
3653
|
+
} else {
|
|
3654
|
+
for (const [key, expr] of Object.entries(n.compute)) {
|
|
3655
|
+
if (!expr || typeof expr !== "object" || Array.isArray(expr)) {
|
|
3656
|
+
errors.push(`compute.${key}: must be a compute expression object`);
|
|
3657
|
+
} else if (!expr.fn) {
|
|
3658
|
+
errors.push(`compute.${key}: missing required "fn" property`);
|
|
3659
|
+
} else {
|
|
3660
|
+
const fn = expr.fn;
|
|
3661
|
+
if (!_fns[fn] && !_customFns[fn]) {
|
|
3662
|
+
errors.push(`compute.${key}: unknown function "${fn}"`);
|
|
3663
|
+
}
|
|
3664
|
+
}
|
|
3665
|
+
}
|
|
3666
|
+
}
|
|
3667
|
+
}
|
|
3668
|
+
if (n.type === "card") {
|
|
3669
|
+
if (n.source != null) errors.push('Card nodes must not have "source" \u2014 use type "source" instead');
|
|
3670
|
+
if (n.view == null || typeof n.view !== "object" || Array.isArray(n.view)) {
|
|
3671
|
+
errors.push("view: required for card nodes, must be an object");
|
|
3672
|
+
} else {
|
|
3673
|
+
const view = n.view;
|
|
3674
|
+
if (!Array.isArray(view.elements) || view.elements.length === 0) {
|
|
3675
|
+
errors.push("view.elements: required, must be a non-empty array");
|
|
3676
|
+
} else {
|
|
3677
|
+
view.elements.forEach((elem, i) => {
|
|
3678
|
+
if (!elem || typeof elem !== "object") {
|
|
3679
|
+
errors.push(`view.elements[${i}]: must be an object`);
|
|
3680
|
+
return;
|
|
3681
|
+
}
|
|
3682
|
+
if (!elem.kind || typeof elem.kind !== "string") {
|
|
3683
|
+
errors.push(`view.elements[${i}].kind: required, must be a string`);
|
|
3684
|
+
} else if (!VALID_ELEMENT_KINDS.has(elem.kind)) {
|
|
3685
|
+
errors.push(`view.elements[${i}].kind: unknown kind "${elem.kind}". Valid: ${[...VALID_ELEMENT_KINDS].join(", ")}`);
|
|
3686
|
+
}
|
|
3687
|
+
if (elem.data != null && (typeof elem.data !== "object" || Array.isArray(elem.data))) {
|
|
3688
|
+
errors.push(`view.elements[${i}].data: must be an object`);
|
|
3689
|
+
}
|
|
3690
|
+
});
|
|
3691
|
+
}
|
|
3692
|
+
if (view.layout != null && (typeof view.layout !== "object" || Array.isArray(view.layout))) {
|
|
3693
|
+
errors.push("view.layout: must be an object");
|
|
3694
|
+
}
|
|
3695
|
+
if (view.features != null && (typeof view.features !== "object" || Array.isArray(view.features))) {
|
|
3696
|
+
errors.push("view.features: must be an object");
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3700
|
+
if (n.type === "source") {
|
|
3701
|
+
if (n.view != null) errors.push('Source nodes must not have "view" \u2014 use type "card" instead');
|
|
3702
|
+
if (n.source == null || typeof n.source !== "object" || Array.isArray(n.source)) {
|
|
3703
|
+
errors.push("source: required for source nodes, must be an object");
|
|
3704
|
+
} else {
|
|
3705
|
+
const src = n.source;
|
|
3706
|
+
if (!src.kind || !VALID_SOURCE_KINDS.has(src.kind)) {
|
|
3707
|
+
errors.push(`source.kind: required, must be one of: ${[...VALID_SOURCE_KINDS].join(", ")}`);
|
|
3708
|
+
}
|
|
3709
|
+
if (typeof src.bindTo !== "string" || !src.bindTo) {
|
|
3710
|
+
errors.push("source.bindTo: required, must be a state path string");
|
|
3711
|
+
} else if (!src.bindTo.startsWith("state.")) {
|
|
3712
|
+
errors.push('source.bindTo: must start with "state."');
|
|
3713
|
+
}
|
|
3714
|
+
}
|
|
3715
|
+
}
|
|
3716
|
+
return { ok: errors.length === 0, errors };
|
|
3717
|
+
}
|
|
3718
|
+
var CardCompute = {
|
|
3719
|
+
run,
|
|
3720
|
+
eval: evalExpr,
|
|
3721
|
+
resolve,
|
|
3722
|
+
validate: validateNode,
|
|
3723
|
+
registerFunction,
|
|
3724
|
+
get functions() {
|
|
3725
|
+
const all = {};
|
|
3726
|
+
for (const k of Object.keys(_fns)) all[k] = _fns[k];
|
|
3727
|
+
for (const k of Object.keys(_customFns)) all[k] = _customFns[k];
|
|
3728
|
+
return all;
|
|
3729
|
+
}
|
|
3730
|
+
};
|
|
3731
|
+
|
|
3285
3732
|
exports.COMPLETION_STRATEGIES = COMPLETION_STRATEGIES;
|
|
3286
3733
|
exports.CONFLICT_STRATEGIES = CONFLICT_STRATEGIES;
|
|
3734
|
+
exports.CardCompute = CardCompute;
|
|
3287
3735
|
exports.DEFAULTS = DEFAULTS;
|
|
3288
3736
|
exports.EXECUTION_MODES = EXECUTION_MODES;
|
|
3289
3737
|
exports.EXECUTION_STATUS = EXECUTION_STATUS;
|