tutuca 0.9.96 → 0.9.98
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/tutuca-cli.js +193 -61
- package/dist/tutuca-dev.ext.js +80 -31
- package/dist/tutuca-dev.js +80 -31
- package/dist/tutuca-dev.min.js +2 -2
- package/dist/tutuca-extra.ext.js +60 -30
- package/dist/tutuca-extra.js +60 -30
- package/dist/tutuca-extra.min.js +2 -2
- package/dist/tutuca-storybook.js +11 -4
- package/dist/tutuca.ext.js +60 -30
- package/dist/tutuca.js +60 -30
- package/dist/tutuca.min.js +2 -2
- package/package.json +3 -3
- package/skill/tutuca/advanced.md +14 -5
- package/skill/tutuca/core.md +78 -15
- package/skill/tutuca/margaui.md +25 -13
- package/skill/tutuca/patterns/README.md +1 -0
- package/skill/tutuca/patterns/filter-a-list.md +3 -1
- package/skill/tutuca/patterns/filter-and-paginate.md +116 -0
- package/skill/tutuca/patterns/paginate-a-list.md +3 -1
- package/skill/tutuca/storybook.md +7 -2
- package/skill/tutuca/testing.md +11 -0
- package/skill/tutuca-source/tutuca.ext.js +60 -30
package/dist/tutuca-cli.js
CHANGED
|
@@ -9334,7 +9334,7 @@ class WeakMapDomCache {
|
|
|
9334
9334
|
const key = keys[i];
|
|
9335
9335
|
let next = cur.get(key);
|
|
9336
9336
|
if (!next) {
|
|
9337
|
-
if (
|
|
9337
|
+
if (!isWeakKey(key)) {
|
|
9338
9338
|
this.badKey += 1;
|
|
9339
9339
|
return;
|
|
9340
9340
|
}
|
|
@@ -9347,7 +9347,7 @@ class WeakMapDomCache {
|
|
|
9347
9347
|
const leaf = cur.get(lastKey);
|
|
9348
9348
|
if (leaf)
|
|
9349
9349
|
leaf[cacheKey] = v;
|
|
9350
|
-
else if (
|
|
9350
|
+
else if (isWeakKey(lastKey))
|
|
9351
9351
|
cur.set(lastKey, { [cacheKey]: v });
|
|
9352
9352
|
else
|
|
9353
9353
|
this.badKey += 1;
|
|
@@ -9359,6 +9359,7 @@ class WeakMapDomCache {
|
|
|
9359
9359
|
return { hit, miss, badKey };
|
|
9360
9360
|
}
|
|
9361
9361
|
}
|
|
9362
|
+
var isWeakKey = (k) => k !== null && (typeof k === "object" || typeof k === "function");
|
|
9362
9363
|
|
|
9363
9364
|
// src/vdom.js
|
|
9364
9365
|
function childOpts(vnode, ns, opts) {
|
|
@@ -9798,37 +9799,47 @@ class Renderer {
|
|
|
9798
9799
|
renderEach(stack, iterInfo, node, viewName) {
|
|
9799
9800
|
const { seq, filter, loopWith } = iterInfo.eval(stack);
|
|
9800
9801
|
const r = [];
|
|
9801
|
-
const { iterData, start, end } = unpackLoopResult(loopWith.call(stack.it, seq), seq);
|
|
9802
|
-
|
|
9803
|
-
|
|
9804
|
-
|
|
9805
|
-
|
|
9806
|
-
|
|
9807
|
-
|
|
9802
|
+
const { iterData, start, end, keys } = unpackLoopResult(loopWith.call(stack.it, seq, makeLoopCtx(stack, filter)), seq);
|
|
9803
|
+
const renderOne = (key, value, attrName) => {
|
|
9804
|
+
const dom = this.renderIt(stack.enter(value, { key }, true), node, key, viewName);
|
|
9805
|
+
this.pushEachEntry(r, node.nodeId, attrName, key, dom);
|
|
9806
|
+
};
|
|
9807
|
+
if (keys)
|
|
9808
|
+
imKeysIter(seq, renderOne, keys);
|
|
9809
|
+
else
|
|
9810
|
+
getSeqInfo(seq)(seq, (key, value, attrName) => {
|
|
9811
|
+
if (filter.call(stack.it, key, value, iterData))
|
|
9812
|
+
renderOne(key, value, attrName);
|
|
9813
|
+
}, start, end);
|
|
9808
9814
|
return r;
|
|
9809
9815
|
}
|
|
9810
9816
|
renderEachWhen(stack, iterInfo, view, nid) {
|
|
9811
9817
|
const { seq, filter, loopWith, enricher } = iterInfo.eval(stack);
|
|
9812
9818
|
const r = [];
|
|
9813
9819
|
const it = stack.it;
|
|
9814
|
-
const { iterData, start, end } = unpackLoopResult(loopWith.call(it, seq), seq);
|
|
9815
|
-
|
|
9816
|
-
|
|
9817
|
-
|
|
9818
|
-
|
|
9819
|
-
|
|
9820
|
-
|
|
9821
|
-
|
|
9822
|
-
|
|
9823
|
-
|
|
9824
|
-
|
|
9825
|
-
|
|
9826
|
-
|
|
9827
|
-
|
|
9828
|
-
this.cache.set(cachePath, cacheKey, dom);
|
|
9829
|
-
}
|
|
9820
|
+
const { iterData, start, end, keys } = unpackLoopResult(loopWith.call(it, seq, makeLoopCtx(stack, filter)), seq);
|
|
9821
|
+
const renderOne = (key, value, attrName) => {
|
|
9822
|
+
const cachePath = enricher ? [view, it, value] : [view, value];
|
|
9823
|
+
const binds = { key, value };
|
|
9824
|
+
const cacheKey = `${nid}-${key}`;
|
|
9825
|
+
if (enricher)
|
|
9826
|
+
enricher.call(it, binds, key, value, iterData);
|
|
9827
|
+
const cachedNode = this.cache.get(cachePath, cacheKey);
|
|
9828
|
+
if (cachedNode)
|
|
9829
|
+
this.pushEachEntry(r, nid, attrName, key, cachedNode);
|
|
9830
|
+
else {
|
|
9831
|
+
const dom = this.renderView(view, stack.enter(value, binds, false));
|
|
9832
|
+
this.pushEachEntry(r, nid, attrName, key, dom);
|
|
9833
|
+
this.cache.set(cachePath, cacheKey, dom);
|
|
9830
9834
|
}
|
|
9831
|
-
}
|
|
9835
|
+
};
|
|
9836
|
+
if (keys)
|
|
9837
|
+
imKeysIter(seq, renderOne, keys);
|
|
9838
|
+
else
|
|
9839
|
+
getSeqInfo(seq)(seq, (key, value, attrName) => {
|
|
9840
|
+
if (filter.call(it, key, value, iterData))
|
|
9841
|
+
renderOne(key, value, attrName);
|
|
9842
|
+
}, start, end);
|
|
9832
9843
|
return r;
|
|
9833
9844
|
}
|
|
9834
9845
|
renderView(view, stack) {
|
|
@@ -9860,8 +9871,15 @@ var DATASET_ATTRS, getSeqInfo = (seq) => isIndexed(seq) ? imIndexedIter : isKeye
|
|
|
9860
9871
|
return [s, e < s ? s : e];
|
|
9861
9872
|
}, filterAlwaysTrue = (_v, _k, _seq) => true, nullLoopWith = (seq) => ({ iterData: { seq } }), unpackLoopResult = (result, seq) => {
|
|
9862
9873
|
const r = result ?? {};
|
|
9863
|
-
return { iterData: r.iterData ?? { seq }, start: r.start, end: r.end };
|
|
9864
|
-
},
|
|
9874
|
+
return { iterData: r.iterData ?? { seq }, start: r.start, end: r.end, keys: r.keys };
|
|
9875
|
+
}, imKeysIter = (seq, visit, keys) => {
|
|
9876
|
+
const attrName = isIndexed(seq) ? "si" : "sk";
|
|
9877
|
+
for (const key of keys)
|
|
9878
|
+
visit(key, seq.get(key), attrName);
|
|
9879
|
+
}, makeLoopCtx = (stack, filter) => ({
|
|
9880
|
+
lookup: (name) => stack.lookupBind(name),
|
|
9881
|
+
filter: (key, value, iterData) => filter.call(stack.it, key, value, iterData)
|
|
9882
|
+
}), imIndexedIter = (seq, visit, start, end) => {
|
|
9865
9883
|
const [s, e] = normalizeRange(start, end, seq.size);
|
|
9866
9884
|
for (let i = s;i < e; i++)
|
|
9867
9885
|
visit(i, seq.get(i), "si");
|
|
@@ -10048,11 +10066,11 @@ class IterInfo {
|
|
|
10048
10066
|
return { seq, filter, loopWith, enricher };
|
|
10049
10067
|
}
|
|
10050
10068
|
enrichBinds(stack, key) {
|
|
10051
|
-
const { seq, loopWith, enricher } = this.eval(stack);
|
|
10069
|
+
const { seq, filter, loopWith, enricher } = this.eval(stack);
|
|
10052
10070
|
const value = seq?.get ? seq.get(key, null) : null;
|
|
10053
10071
|
const binds = { key, value };
|
|
10054
10072
|
if (enricher) {
|
|
10055
|
-
const { iterData } = unpackLoopResult(loopWith.call(stack.it, seq), seq);
|
|
10073
|
+
const { iterData } = unpackLoopResult(loopWith.call(stack.it, seq, makeLoopCtx(stack, filter)), seq);
|
|
10056
10074
|
enricher.call(stack.it, binds, key, value, iterData);
|
|
10057
10075
|
}
|
|
10058
10076
|
return binds;
|
|
@@ -14759,6 +14777,13 @@ function phaseOps(phase) {
|
|
|
14759
14777
|
function resolveArgs(args, self) {
|
|
14760
14778
|
return typeof args === "function" ? args(self) ?? [] : args ?? [];
|
|
14761
14779
|
}
|
|
14780
|
+
function phaseHasBubble(phase) {
|
|
14781
|
+
if (!phase)
|
|
14782
|
+
return false;
|
|
14783
|
+
if (phase.bubble?.length)
|
|
14784
|
+
return true;
|
|
14785
|
+
return (phase.do ?? []).some((op) => op.type === "bubble");
|
|
14786
|
+
}
|
|
14762
14787
|
function dispatchPhase(dispatcher, targetPath, phase, self) {
|
|
14763
14788
|
if (!phase)
|
|
14764
14789
|
return;
|
|
@@ -14937,6 +14962,8 @@ async function driveStack(stack, value, phase, opts = {}) {
|
|
|
14937
14962
|
const t = info?.transaction;
|
|
14938
14963
|
opts.onMessage({ kind: t?.handlerProp ?? "input", name: t?.name, args: t?.args, path: t?.path }, old, val);
|
|
14939
14964
|
});
|
|
14965
|
+
if (phaseHasBubble(phase))
|
|
14966
|
+
console.warn("drive(): a `bubble` action is a no-op here — drive originates at the root and bubbles travel child→parent, so there is no ancestor to receive it (and the root's own bubble handler is skipped). To exercise a bubble handler, call it directly.");
|
|
14940
14967
|
dispatchPhase(rootDispatcher(transactor), new Path([]), phase, value);
|
|
14941
14968
|
await transactor.settle();
|
|
14942
14969
|
return transactor.state.val;
|
|
@@ -15112,13 +15139,17 @@ var init__registry = __esm(() => {
|
|
|
15112
15139
|
exitOn: (result) => result.hasErrors ? 3 : 0
|
|
15113
15140
|
},
|
|
15114
15141
|
test: {
|
|
15115
|
-
describe: "Run tests defined by getTests() (optional <name> to filter by component).",
|
|
15142
|
+
describe: "Run tests defined by getTests() (optional <name> to filter by component). Pass a directory to run every *.test.js / *.dev.js under it.",
|
|
15116
15143
|
defaultFormat: "cli",
|
|
15117
15144
|
needsEnv: true,
|
|
15118
15145
|
parseOptions: {
|
|
15119
15146
|
grep: { type: "string" },
|
|
15120
15147
|
bail: { type: "boolean" }
|
|
15121
15148
|
},
|
|
15149
|
+
acceptsDir: true,
|
|
15150
|
+
dirMatch: (name) => name.endsWith(".test.js") || name.endsWith(".dev.js"),
|
|
15151
|
+
dirFilter: (normalized) => typeof normalized.mod.getTests === "function",
|
|
15152
|
+
mergeResults: (reports) => new TestReport({ modules: reports.flatMap((r) => r.modules) }),
|
|
15122
15153
|
run: (normalized, { values, positionals }) => runTests({
|
|
15123
15154
|
getTests: normalized.mod.getTests,
|
|
15124
15155
|
components: normalized.components,
|
|
@@ -16010,6 +16041,23 @@ var init_env2 = __esm(() => {
|
|
|
16010
16041
|
init_lint_check();
|
|
16011
16042
|
});
|
|
16012
16043
|
|
|
16044
|
+
// tools/cli/walk.js
|
|
16045
|
+
import { readdirSync as readdirSync2 } from "node:fs";
|
|
16046
|
+
import { resolve as resolve4 } from "node:path";
|
|
16047
|
+
function walkFiles(dir, { match }, acc = []) {
|
|
16048
|
+
for (const e of readdirSync2(dir, { withFileTypes: true })) {
|
|
16049
|
+
if (e.name.startsWith(".") || e.name === "node_modules")
|
|
16050
|
+
continue;
|
|
16051
|
+
const full = resolve4(dir, e.name);
|
|
16052
|
+
if (e.isDirectory())
|
|
16053
|
+
walkFiles(full, { match }, acc);
|
|
16054
|
+
else if (e.isFile() && match(e.name))
|
|
16055
|
+
acc.push(full);
|
|
16056
|
+
}
|
|
16057
|
+
return acc;
|
|
16058
|
+
}
|
|
16059
|
+
var init_walk = () => {};
|
|
16060
|
+
|
|
16013
16061
|
// tools/cli/commands/storybook.js
|
|
16014
16062
|
var exports_storybook = {};
|
|
16015
16063
|
__export(exports_storybook, {
|
|
@@ -16020,45 +16068,34 @@ import {
|
|
|
16020
16068
|
createReadStream,
|
|
16021
16069
|
existsSync as existsSync4,
|
|
16022
16070
|
mkdirSync as mkdirSync3,
|
|
16023
|
-
readdirSync as readdirSync2,
|
|
16024
16071
|
readFileSync as readFileSync3,
|
|
16025
16072
|
statSync,
|
|
16026
16073
|
writeFileSync
|
|
16027
16074
|
} from "node:fs";
|
|
16028
16075
|
import { createServer } from "node:http";
|
|
16029
|
-
import { dirname as dirname4, join, normalize, relative as relative2, resolve as
|
|
16076
|
+
import { dirname as dirname4, join, normalize, relative as relative2, resolve as resolve5, sep } from "node:path";
|
|
16030
16077
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
16031
16078
|
import { parseArgs as parseArgs3 } from "node:util";
|
|
16032
|
-
function findDevModules(root
|
|
16033
|
-
|
|
16034
|
-
if (e.name.startsWith(".") || e.name === "node_modules")
|
|
16035
|
-
continue;
|
|
16036
|
-
const full = resolve4(dir, e.name);
|
|
16037
|
-
if (e.isDirectory()) {
|
|
16038
|
-
findDevModules(root, full, acc);
|
|
16039
|
-
} else if (e.isFile() && e.name.endsWith(".dev.js")) {
|
|
16040
|
-
acc.push(`/${relative2(root, full).split(sep).join("/")}`);
|
|
16041
|
-
}
|
|
16042
|
-
}
|
|
16043
|
-
return acc;
|
|
16079
|
+
function findDevModules(root) {
|
|
16080
|
+
return walkFiles(root, { match: (name) => name.endsWith(".dev.js") }).map((full) => `/${relative2(root, full).split(sep).join("/")}`);
|
|
16044
16081
|
}
|
|
16045
16082
|
function findSelf() {
|
|
16046
16083
|
const here = dirname4(fileURLToPath4(import.meta.url));
|
|
16047
16084
|
const pkgCandidates = [
|
|
16048
|
-
|
|
16049
|
-
|
|
16085
|
+
resolve5(here, "..", "..", "..", "package.json"),
|
|
16086
|
+
resolve5(here, "..", "package.json")
|
|
16050
16087
|
];
|
|
16051
16088
|
const pkgPath = pkgCandidates.find(existsSync4);
|
|
16052
16089
|
const version = pkgPath ? JSON.parse(readFileSync3(pkgPath, "utf8")).version : "latest";
|
|
16053
|
-
const distCandidates = [
|
|
16054
|
-
const distRoot = distCandidates.find((d) => existsSync4(
|
|
16090
|
+
const distCandidates = [resolve5(here, "..", "..", "..", "dist"), resolve5(here, ".")];
|
|
16091
|
+
const distRoot = distCandidates.find((d) => existsSync4(resolve5(d, "tutuca-storybook.js"))) ?? null;
|
|
16055
16092
|
return { version, distRoot };
|
|
16056
16093
|
}
|
|
16057
16094
|
function resolveTutucaBase(projectDir, self, forCdn) {
|
|
16058
16095
|
if (forCdn)
|
|
16059
16096
|
return { base: `https://cdn.jsdelivr.net/npm/tutuca@${self.version}/dist`, serveDist: null };
|
|
16060
|
-
const nm =
|
|
16061
|
-
if (existsSync4(
|
|
16097
|
+
const nm = resolve5(projectDir, "node_modules", "tutuca", "dist");
|
|
16098
|
+
if (existsSync4(resolve5(nm, "tutuca-dev.js"))) {
|
|
16062
16099
|
return { base: "/node_modules/tutuca/dist", serveDist: null };
|
|
16063
16100
|
}
|
|
16064
16101
|
if (self.distRoot)
|
|
@@ -16133,7 +16170,7 @@ async function runDevTests(projectDir, devModuleUrls) {
|
|
|
16133
16170
|
const failures = [];
|
|
16134
16171
|
const importErrors = [];
|
|
16135
16172
|
for (const url of devModuleUrls) {
|
|
16136
|
-
const abs =
|
|
16173
|
+
const abs = resolve5(projectDir, url.slice(1));
|
|
16137
16174
|
let mod;
|
|
16138
16175
|
try {
|
|
16139
16176
|
mod = await import(abs);
|
|
@@ -16165,7 +16202,7 @@ async function discoverModules(projectDir, devModuleUrls) {
|
|
|
16165
16202
|
await createNodeEnv();
|
|
16166
16203
|
const modules = [];
|
|
16167
16204
|
for (const url of devModuleUrls) {
|
|
16168
|
-
const abs =
|
|
16205
|
+
const abs = resolve5(projectDir, url.slice(1));
|
|
16169
16206
|
try {
|
|
16170
16207
|
const mod = await import(abs);
|
|
16171
16208
|
const { normalized, present } = normalizeModule(mod, { path: abs });
|
|
@@ -16257,7 +16294,7 @@ async function run4(argv, opts = {}) {
|
|
|
16257
16294
|
`);
|
|
16258
16295
|
return;
|
|
16259
16296
|
}
|
|
16260
|
-
const projectDir =
|
|
16297
|
+
const projectDir = resolve5(parsed.positionals[0] ?? process.cwd());
|
|
16261
16298
|
if (!existsSync4(projectDir) || !statSync(projectDir).isDirectory()) {
|
|
16262
16299
|
emitError(opts, {
|
|
16263
16300
|
code: CODES.USAGE_MISSING_ARGUMENT,
|
|
@@ -16265,7 +16302,7 @@ async function run4(argv, opts = {}) {
|
|
|
16265
16302
|
hint: "Pass a project directory to scan, or omit it to use the current directory."
|
|
16266
16303
|
});
|
|
16267
16304
|
}
|
|
16268
|
-
const devModuleUrls = findDevModules(projectDir
|
|
16305
|
+
const devModuleUrls = findDevModules(projectDir);
|
|
16269
16306
|
if (devModuleUrls.length === 0) {
|
|
16270
16307
|
emitError(opts, {
|
|
16271
16308
|
code: CODES.USAGE_MISSING_ARGUMENT,
|
|
@@ -16277,13 +16314,13 @@ async function run4(argv, opts = {}) {
|
|
|
16277
16314
|
const check = !parsed.values["no-check"];
|
|
16278
16315
|
const self = findSelf();
|
|
16279
16316
|
if (parsed.values.out) {
|
|
16280
|
-
const outDir =
|
|
16317
|
+
const outDir = resolve5(parsed.values.out);
|
|
16281
16318
|
mkdirSync3(outDir, { recursive: true });
|
|
16282
16319
|
const { base: base2 } = resolveTutucaBase(projectDir, self, true);
|
|
16283
16320
|
const imports2 = buildImports(base2, { margaui });
|
|
16284
16321
|
const bootstrapName = "tutuca-storybook.bootstrap.js";
|
|
16285
|
-
writeFileSync(
|
|
16286
|
-
writeFileSync(
|
|
16322
|
+
writeFileSync(resolve5(outDir, "index.html"), renderIndexHtml(imports2, { margaui, bootstrapUrl: `./${bootstrapName}` }));
|
|
16323
|
+
writeFileSync(resolve5(outDir, bootstrapName), renderBootstrap(devModuleUrls, { margaui, check }));
|
|
16287
16324
|
process.stdout.write(`wrote static storybook → ${relative2(process.cwd(), outDir) || "."}/
|
|
16288
16325
|
index.html + ${bootstrapName} (${devModuleUrls.length} dev modules, CDN import map)
|
|
16289
16326
|
Host it from the project root so /*.dev.js paths resolve.
|
|
@@ -16409,6 +16446,7 @@ var init_storybook = __esm(() => {
|
|
|
16409
16446
|
init_test();
|
|
16410
16447
|
init_env2();
|
|
16411
16448
|
init_errors();
|
|
16449
|
+
init_walk();
|
|
16412
16450
|
MIME = {
|
|
16413
16451
|
".js": "text/javascript",
|
|
16414
16452
|
".mjs": "text/javascript",
|
|
@@ -16942,17 +16980,60 @@ ${group}
|
|
|
16942
16980
|
// tools/tutuca.js
|
|
16943
16981
|
init_install_skill();
|
|
16944
16982
|
init_storybook();
|
|
16983
|
+
|
|
16984
|
+
// tools/cli/dev-build-hook.js
|
|
16985
|
+
import { register } from "node:module";
|
|
16986
|
+
function installDevBuildResolveHook() {
|
|
16987
|
+
let devUrl;
|
|
16988
|
+
try {
|
|
16989
|
+
devUrl = import.meta.resolve("tutuca/dev");
|
|
16990
|
+
} catch {
|
|
16991
|
+
return false;
|
|
16992
|
+
}
|
|
16993
|
+
const hookSource = `
|
|
16994
|
+
let devUrl;
|
|
16995
|
+
export async function initialize(data) { devUrl = data.devUrl; }
|
|
16996
|
+
export async function resolve(specifier, context, nextResolve) {
|
|
16997
|
+
return specifier === "tutuca"
|
|
16998
|
+
? { url: devUrl, shortCircuit: true }
|
|
16999
|
+
: nextResolve(specifier, context);
|
|
17000
|
+
}`;
|
|
17001
|
+
try {
|
|
17002
|
+
register(`data:text/javascript,${encodeURIComponent(hookSource)}`, import.meta.url, {
|
|
17003
|
+
data: { devUrl }
|
|
17004
|
+
});
|
|
17005
|
+
return true;
|
|
17006
|
+
} catch {
|
|
17007
|
+
return false;
|
|
17008
|
+
}
|
|
17009
|
+
}
|
|
17010
|
+
|
|
17011
|
+
// tools/tutuca.js
|
|
16945
17012
|
init_errors();
|
|
16946
17013
|
|
|
16947
17014
|
// tools/cli/with-module.js
|
|
16948
17015
|
init_env2();
|
|
17016
|
+
init_errors();
|
|
17017
|
+
import { statSync as statSync2 } from "node:fs";
|
|
16949
17018
|
import { parseArgs as parseArgs4 } from "node:util";
|
|
16950
17019
|
|
|
16951
17020
|
// tools/cli/load.js
|
|
16952
|
-
import {
|
|
17021
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
17022
|
+
import { resolve as resolve6 } from "node:path";
|
|
16953
17023
|
async function loadAndNormalize(modulePath) {
|
|
16954
|
-
const abs =
|
|
16955
|
-
|
|
17024
|
+
const abs = resolve6(modulePath);
|
|
17025
|
+
let mod;
|
|
17026
|
+
try {
|
|
17027
|
+
mod = await import(abs);
|
|
17028
|
+
} catch (e) {
|
|
17029
|
+
const entryProblem = e?.code === "ERR_UNSUPPORTED_DIR_IMPORT" || e?.code === "ERR_MODULE_NOT_FOUND" && !existsSync5(abs);
|
|
17030
|
+
if (entryProblem) {
|
|
17031
|
+
const err = new Error(e.code === "ERR_UNSUPPORTED_DIR_IMPORT" ? `expected a module file, got a directory: ${modulePath}` : `module not found: ${modulePath}`);
|
|
17032
|
+
err.code = "ERR_MODULE_LOAD_FAILED";
|
|
17033
|
+
throw err;
|
|
17034
|
+
}
|
|
17035
|
+
throw e;
|
|
17036
|
+
}
|
|
16956
17037
|
const { normalized } = normalizeModule(mod, { path: abs });
|
|
16957
17038
|
return normalized;
|
|
16958
17039
|
}
|
|
@@ -17455,15 +17536,57 @@ async function emit(result, { format: format5, pretty, output }) {
|
|
|
17455
17536
|
}
|
|
17456
17537
|
|
|
17457
17538
|
// tools/cli/with-module.js
|
|
17539
|
+
init_walk();
|
|
17458
17540
|
async function runCommand(cmd, argv, globalOpts) {
|
|
17459
17541
|
const parsed = parseArgs4({
|
|
17460
17542
|
args: argv,
|
|
17461
17543
|
options: cmd.parseOptions ?? {},
|
|
17462
17544
|
allowPositionals: true
|
|
17463
17545
|
});
|
|
17546
|
+
let stat = null;
|
|
17547
|
+
try {
|
|
17548
|
+
stat = statSync2(globalOpts.module);
|
|
17549
|
+
} catch {}
|
|
17550
|
+
if (stat?.isDirectory()) {
|
|
17551
|
+
await runOnDir(cmd, parsed, globalOpts);
|
|
17552
|
+
return;
|
|
17553
|
+
}
|
|
17464
17554
|
const normalized = await loadAndNormalize(globalOpts.module);
|
|
17465
17555
|
const env = cmd.needsEnv ? await createNodeEnv() : null;
|
|
17466
17556
|
const result = await cmd.run(normalized, parsed, env);
|
|
17557
|
+
await emitResult(cmd, result, globalOpts);
|
|
17558
|
+
}
|
|
17559
|
+
async function runOnDir(cmd, parsed, globalOpts) {
|
|
17560
|
+
if (!cmd.acceptsDir) {
|
|
17561
|
+
emitError(globalOpts, {
|
|
17562
|
+
code: CODES.MODULE_LOAD_FAILED,
|
|
17563
|
+
message: `expected a module file, got a directory: ${globalOpts.module}`,
|
|
17564
|
+
hint: "This command takes a single module file (.js). Pass a file path."
|
|
17565
|
+
});
|
|
17566
|
+
return;
|
|
17567
|
+
}
|
|
17568
|
+
const files = walkFiles(globalOpts.module, { match: cmd.dirMatch });
|
|
17569
|
+
const normalizedAll = [];
|
|
17570
|
+
for (const file of files) {
|
|
17571
|
+
const normalized = await loadAndNormalize(file);
|
|
17572
|
+
if (!cmd.dirFilter || cmd.dirFilter(normalized))
|
|
17573
|
+
normalizedAll.push(normalized);
|
|
17574
|
+
}
|
|
17575
|
+
if (normalizedAll.length === 0) {
|
|
17576
|
+
emitError(globalOpts, {
|
|
17577
|
+
code: CODES.MODULE_LOAD_FAILED,
|
|
17578
|
+
message: `no test modules found under ${globalOpts.module}`,
|
|
17579
|
+
hint: "Test modules are *.test.js / *.dev.js files that export getTests()."
|
|
17580
|
+
});
|
|
17581
|
+
return;
|
|
17582
|
+
}
|
|
17583
|
+
const env = cmd.needsEnv ? await createNodeEnv() : null;
|
|
17584
|
+
const results = [];
|
|
17585
|
+
for (const normalized of normalizedAll)
|
|
17586
|
+
results.push(await cmd.run(normalized, parsed, env));
|
|
17587
|
+
await emitResult(cmd, cmd.mergeResults(results), globalOpts);
|
|
17588
|
+
}
|
|
17589
|
+
async function emitResult(cmd, result, globalOpts) {
|
|
17467
17590
|
await emit(result, {
|
|
17468
17591
|
format: globalOpts.format ?? cmd.defaultFormat,
|
|
17469
17592
|
pretty: globalOpts.pretty,
|
|
@@ -17572,6 +17695,8 @@ async function main() {
|
|
|
17572
17695
|
opts.module = rest[1];
|
|
17573
17696
|
commandArgs = rest.slice(2);
|
|
17574
17697
|
}
|
|
17698
|
+
if (command === "test")
|
|
17699
|
+
installDevBuildResolveHook();
|
|
17575
17700
|
try {
|
|
17576
17701
|
await runCommand(cmd, commandArgs, opts);
|
|
17577
17702
|
} catch (e) {
|
|
@@ -17609,6 +17734,13 @@ async function main() {
|
|
|
17609
17734
|
hint: shape.hint ?? `Run \`tutuca help ${command}\` for valid flags.`
|
|
17610
17735
|
});
|
|
17611
17736
|
}
|
|
17737
|
+
if (e?.code === "ERR_MODULE_LOAD_FAILED") {
|
|
17738
|
+
emitError(opts, {
|
|
17739
|
+
code: CODES.MODULE_LOAD_FAILED,
|
|
17740
|
+
message: e.message,
|
|
17741
|
+
hint: "Pass a module file path. `tutuca test` also accepts a directory to run every test module under it."
|
|
17742
|
+
});
|
|
17743
|
+
}
|
|
17612
17744
|
throw e;
|
|
17613
17745
|
}
|
|
17614
17746
|
}
|
package/dist/tutuca-dev.ext.js
CHANGED
|
@@ -1322,6 +1322,8 @@ class RequestHandler {
|
|
|
1322
1322
|
import { isIndexed, isKeyed } from "immutable";
|
|
1323
1323
|
|
|
1324
1324
|
// src/cache.js
|
|
1325
|
+
var isWeakKey = (k) => k !== null && (typeof k === "object" || typeof k === "function");
|
|
1326
|
+
|
|
1325
1327
|
class NullDomCache {
|
|
1326
1328
|
get(_keys, _cacheKey) {}
|
|
1327
1329
|
set(_keys, _cacheKey, _v) {}
|
|
@@ -1365,7 +1367,7 @@ class WeakMapDomCache {
|
|
|
1365
1367
|
const key = keys[i];
|
|
1366
1368
|
let next = cur.get(key);
|
|
1367
1369
|
if (!next) {
|
|
1368
|
-
if (
|
|
1370
|
+
if (!isWeakKey(key)) {
|
|
1369
1371
|
this.badKey += 1;
|
|
1370
1372
|
return;
|
|
1371
1373
|
}
|
|
@@ -1378,7 +1380,7 @@ class WeakMapDomCache {
|
|
|
1378
1380
|
const leaf = cur.get(lastKey);
|
|
1379
1381
|
if (leaf)
|
|
1380
1382
|
leaf[cacheKey] = v;
|
|
1381
|
-
else if (
|
|
1383
|
+
else if (isWeakKey(lastKey))
|
|
1382
1384
|
cur.set(lastKey, { [cacheKey]: v });
|
|
1383
1385
|
else
|
|
1384
1386
|
this.badKey += 1;
|
|
@@ -1840,37 +1842,47 @@ class Renderer {
|
|
|
1840
1842
|
renderEach(stack, iterInfo, node, viewName) {
|
|
1841
1843
|
const { seq, filter, loopWith } = iterInfo.eval(stack);
|
|
1842
1844
|
const r = [];
|
|
1843
|
-
const { iterData, start, end } = unpackLoopResult(loopWith.call(stack.it, seq), seq);
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1845
|
+
const { iterData, start, end, keys } = unpackLoopResult(loopWith.call(stack.it, seq, makeLoopCtx(stack, filter)), seq);
|
|
1846
|
+
const renderOne = (key, value, attrName) => {
|
|
1847
|
+
const dom = this.renderIt(stack.enter(value, { key }, true), node, key, viewName);
|
|
1848
|
+
this.pushEachEntry(r, node.nodeId, attrName, key, dom);
|
|
1849
|
+
};
|
|
1850
|
+
if (keys)
|
|
1851
|
+
imKeysIter(seq, renderOne, keys);
|
|
1852
|
+
else
|
|
1853
|
+
getSeqInfo(seq)(seq, (key, value, attrName) => {
|
|
1854
|
+
if (filter.call(stack.it, key, value, iterData))
|
|
1855
|
+
renderOne(key, value, attrName);
|
|
1856
|
+
}, start, end);
|
|
1850
1857
|
return r;
|
|
1851
1858
|
}
|
|
1852
1859
|
renderEachWhen(stack, iterInfo, view, nid) {
|
|
1853
1860
|
const { seq, filter, loopWith, enricher } = iterInfo.eval(stack);
|
|
1854
1861
|
const r = [];
|
|
1855
1862
|
const it = stack.it;
|
|
1856
|
-
const { iterData, start, end } = unpackLoopResult(loopWith.call(it, seq), seq);
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
this.cache.set(cachePath, cacheKey, dom);
|
|
1871
|
-
}
|
|
1863
|
+
const { iterData, start, end, keys } = unpackLoopResult(loopWith.call(it, seq, makeLoopCtx(stack, filter)), seq);
|
|
1864
|
+
const renderOne = (key, value, attrName) => {
|
|
1865
|
+
const cachePath = enricher ? [view, it, value] : [view, value];
|
|
1866
|
+
const binds = { key, value };
|
|
1867
|
+
const cacheKey = `${nid}-${key}`;
|
|
1868
|
+
if (enricher)
|
|
1869
|
+
enricher.call(it, binds, key, value, iterData);
|
|
1870
|
+
const cachedNode = this.cache.get(cachePath, cacheKey);
|
|
1871
|
+
if (cachedNode)
|
|
1872
|
+
this.pushEachEntry(r, nid, attrName, key, cachedNode);
|
|
1873
|
+
else {
|
|
1874
|
+
const dom = this.renderView(view, stack.enter(value, binds, false));
|
|
1875
|
+
this.pushEachEntry(r, nid, attrName, key, dom);
|
|
1876
|
+
this.cache.set(cachePath, cacheKey, dom);
|
|
1872
1877
|
}
|
|
1873
|
-
}
|
|
1878
|
+
};
|
|
1879
|
+
if (keys)
|
|
1880
|
+
imKeysIter(seq, renderOne, keys);
|
|
1881
|
+
else
|
|
1882
|
+
getSeqInfo(seq)(seq, (key, value, attrName) => {
|
|
1883
|
+
if (filter.call(it, key, value, iterData))
|
|
1884
|
+
renderOne(key, value, attrName);
|
|
1885
|
+
}, start, end);
|
|
1874
1886
|
return r;
|
|
1875
1887
|
}
|
|
1876
1888
|
renderView(view, stack) {
|
|
@@ -1906,8 +1918,17 @@ var filterAlwaysTrue = (_v, _k, _seq) => true;
|
|
|
1906
1918
|
var nullLoopWith = (seq) => ({ iterData: { seq } });
|
|
1907
1919
|
var unpackLoopResult = (result, seq) => {
|
|
1908
1920
|
const r = result ?? {};
|
|
1909
|
-
return { iterData: r.iterData ?? { seq }, start: r.start, end: r.end };
|
|
1921
|
+
return { iterData: r.iterData ?? { seq }, start: r.start, end: r.end, keys: r.keys };
|
|
1922
|
+
};
|
|
1923
|
+
var imKeysIter = (seq, visit, keys) => {
|
|
1924
|
+
const attrName = isIndexed(seq) ? "si" : "sk";
|
|
1925
|
+
for (const key of keys)
|
|
1926
|
+
visit(key, seq.get(key), attrName);
|
|
1910
1927
|
};
|
|
1928
|
+
var makeLoopCtx = (stack, filter) => ({
|
|
1929
|
+
lookup: (name) => stack.lookupBind(name),
|
|
1930
|
+
filter: (key, value, iterData) => filter.call(stack.it, key, value, iterData)
|
|
1931
|
+
});
|
|
1911
1932
|
var imIndexedIter = (seq, visit, start, end) => {
|
|
1912
1933
|
const [s, e] = normalizeRange(start, end, seq.size);
|
|
1913
1934
|
for (let i = s;i < e; i++)
|
|
@@ -2455,11 +2476,11 @@ class IterInfo {
|
|
|
2455
2476
|
return { seq, filter, loopWith, enricher };
|
|
2456
2477
|
}
|
|
2457
2478
|
enrichBinds(stack, key) {
|
|
2458
|
-
const { seq, loopWith, enricher } = this.eval(stack);
|
|
2479
|
+
const { seq, filter, loopWith, enricher } = this.eval(stack);
|
|
2459
2480
|
const value = seq?.get ? seq.get(key, null) : null;
|
|
2460
2481
|
const binds = { key, value };
|
|
2461
2482
|
if (enricher) {
|
|
2462
|
-
const { iterData } = unpackLoopResult(loopWith.call(stack.it, seq), seq);
|
|
2483
|
+
const { iterData } = unpackLoopResult(loopWith.call(stack.it, seq, makeLoopCtx(stack, filter)), seq);
|
|
2463
2484
|
enricher.call(stack.it, binds, key, value, iterData);
|
|
2464
2485
|
}
|
|
2465
2486
|
return binds;
|
|
@@ -6038,6 +6059,13 @@ function phaseOps(phase) {
|
|
|
6038
6059
|
function resolveArgs(args, self) {
|
|
6039
6060
|
return typeof args === "function" ? args(self) ?? [] : args ?? [];
|
|
6040
6061
|
}
|
|
6062
|
+
function phaseHasBubble(phase) {
|
|
6063
|
+
if (!phase)
|
|
6064
|
+
return false;
|
|
6065
|
+
if (phase.bubble?.length)
|
|
6066
|
+
return true;
|
|
6067
|
+
return (phase.do ?? []).some((op) => op.type === "bubble");
|
|
6068
|
+
}
|
|
6041
6069
|
function dispatchPhase(dispatcher, targetPath, phase, self) {
|
|
6042
6070
|
if (!phase)
|
|
6043
6071
|
return;
|
|
@@ -6974,6 +7002,8 @@ async function driveStack(stack, value, phase, opts = {}) {
|
|
|
6974
7002
|
const t = info?.transaction;
|
|
6975
7003
|
opts.onMessage({ kind: t?.handlerProp ?? "input", name: t?.name, args: t?.args, path: t?.path }, old, val);
|
|
6976
7004
|
});
|
|
7005
|
+
if (phaseHasBubble(phase))
|
|
7006
|
+
console.warn("drive(): a `bubble` action is a no-op here — drive originates at the root and bubbles travel child→parent, so there is no ancestor to receive it (and the root's own bubble handler is skipped). To exercise a bubble handler, call it directly.");
|
|
6977
7007
|
dispatchPhase(rootDispatcher(transactor), new Path([]), phase, value);
|
|
6978
7008
|
await transactor.settle();
|
|
6979
7009
|
return transactor.state.val;
|
|
@@ -7962,7 +7992,8 @@ class FieldSet extends Field {
|
|
|
7962
7992
|
}
|
|
7963
7993
|
function mkCompField(field, scope, args) {
|
|
7964
7994
|
const Comp = scope?.lookupComponent(field.type) ?? null;
|
|
7965
|
-
|
|
7995
|
+
if (Comp === null)
|
|
7996
|
+
console.warn(scope ? `component field "${field.name}": component "${field.type}" not found in scope` : `component field "${field.name}": cannot resolve component "${field.type}" — built without a registered scope (use ${field.type}.make({}) as the default, or build via a registered component)`);
|
|
7966
7997
|
return Comp?.make({ ...field.args, ...args }, { scope }) ?? null;
|
|
7967
7998
|
}
|
|
7968
7999
|
|
|
@@ -8136,13 +8167,30 @@ function resolveAlter(Comp, name) {
|
|
|
8136
8167
|
}
|
|
8137
8168
|
return fn;
|
|
8138
8169
|
}
|
|
8170
|
+
var seqGet = (seq, key) => Array.isArray(seq) ? seq[key] : seq.get ? seq.get(key) : seq[key];
|
|
8139
8171
|
function collectIterBindings(Comp, compInstance, seq, opts = {}) {
|
|
8140
8172
|
const whenFn = resolveAlter(Comp, opts.when) ?? filterAlwaysTrue;
|
|
8141
8173
|
const loopWithFn = resolveAlter(Comp, opts.loopWith) ?? nullLoopWith;
|
|
8142
8174
|
const enrichFn = resolveAlter(Comp, opts.enrichWith);
|
|
8175
|
+
const scopeEnrichFn = resolveAlter(Comp, opts.scopeEnrich);
|
|
8143
8176
|
const it = compInstance;
|
|
8144
|
-
const
|
|
8177
|
+
const scope = scopeEnrichFn ? scopeEnrichFn.call(it) ?? {} : opts.scope ?? {};
|
|
8178
|
+
const ctx = {
|
|
8179
|
+
lookup: (name) => scope[name],
|
|
8180
|
+
filter: (key, value, iterData2) => whenFn.call(it, key, value, iterData2)
|
|
8181
|
+
};
|
|
8182
|
+
const { iterData, start, end, keys } = unpackLoopResult(loopWithFn.call(it, seq, ctx), seq);
|
|
8145
8183
|
const out = [];
|
|
8184
|
+
if (keys) {
|
|
8185
|
+
for (const key of keys) {
|
|
8186
|
+
const value = seqGet(seq, key);
|
|
8187
|
+
const binds = { key, value };
|
|
8188
|
+
if (enrichFn)
|
|
8189
|
+
enrichFn.call(it, binds, key, value, iterData);
|
|
8190
|
+
out.push(binds);
|
|
8191
|
+
}
|
|
8192
|
+
return out;
|
|
8193
|
+
}
|
|
8146
8194
|
const iter = pickIter(seq);
|
|
8147
8195
|
iter(seq, (key, value) => {
|
|
8148
8196
|
if (!whenFn.call(it, key, value, iterData))
|
|
@@ -8406,6 +8454,7 @@ export {
|
|
|
8406
8454
|
removeIn,
|
|
8407
8455
|
remove,
|
|
8408
8456
|
phaseOps,
|
|
8457
|
+
phaseHasBubble,
|
|
8409
8458
|
mergeWith,
|
|
8410
8459
|
mergeDeepWith,
|
|
8411
8460
|
mergeDeep,
|