tutuca 0.9.115 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/tutuca-cli.js +475 -247
- package/dist/tutuca-components.js +13 -19
- package/dist/tutuca-dev.ext.js +410 -202
- package/dist/tutuca-dev.js +410 -202
- package/dist/tutuca-dev.min.js +4 -4
- package/dist/tutuca-extra.ext.js +281 -149
- package/dist/tutuca-extra.js +281 -149
- package/dist/tutuca-extra.min.js +3 -3
- package/dist/tutuca-storybook.js +83 -76
- package/dist/tutuca.ext.js +281 -149
- package/dist/tutuca.js +281 -149
- package/dist/tutuca.min.js +3 -3
- package/package.json +1 -1
- package/skill/tutuca/SKILL.md +4 -4
- package/skill/tutuca/advanced.md +1 -1
- package/skill/tutuca/cli.md +2 -2
- package/skill/tutuca/component-design.md +33 -29
- package/skill/tutuca/core.md +57 -42
- package/skill/tutuca/messages-and-intents.md +398 -0
- package/skill/tutuca/patterns/README.md +1 -1
- package/skill/tutuca/patterns/add-a-story.md +2 -2
- package/skill/tutuca/patterns/coordinate-components.md +39 -18
- package/skill/tutuca/patterns/file-input.md +2 -2
- package/skill/tutuca/patterns/handle-events.md +2 -2
- package/skill/tutuca/semantics.md +41 -35
- package/skill/tutuca/storybook.md +34 -27
- package/skill/tutuca/testing.md +49 -47
- package/skill/tutuca-source/tutuca.ext.js +281 -149
- package/skill/tutuca/request-response.md +0 -341
package/dist/tutuca-dev.js
CHANGED
|
@@ -9144,8 +9144,8 @@ function parseProvide(s, px) {
|
|
|
9144
9144
|
function parseMacroAttr(s, px) {
|
|
9145
9145
|
return _parseSingle(s, px, G_ALL);
|
|
9146
9146
|
}
|
|
9147
|
-
function
|
|
9148
|
-
return _parseHandler(s, px, "
|
|
9147
|
+
function parseReceiveHandler(s, px) {
|
|
9148
|
+
return _parseHandler(s, px, "receive", true, true);
|
|
9149
9149
|
}
|
|
9150
9150
|
function parseAlterHandler(s, px) {
|
|
9151
9151
|
const r = _parseHandler(s, px, "alter", false, false);
|
|
@@ -9688,11 +9688,11 @@ var EventHandler = class _EventHandler {
|
|
|
9688
9688
|
return [this.handlerVal.evalAsHandler(stack), argValues];
|
|
9689
9689
|
}
|
|
9690
9690
|
static parse(s, px) {
|
|
9691
|
-
const r =
|
|
9691
|
+
const r = parseReceiveHandler(s, px);
|
|
9692
9692
|
return r === null ? null : new _EventHandler(r.handlerVal, r.args);
|
|
9693
9693
|
}
|
|
9694
9694
|
};
|
|
9695
|
-
var
|
|
9695
|
+
var IntentHandler = class {
|
|
9696
9696
|
constructor(name, fn) {
|
|
9697
9697
|
this.name = name;
|
|
9698
9698
|
this.fn = fn;
|
|
@@ -13385,7 +13385,7 @@ var KNOWN_COMPONENT_SPEC_KEYS = new Set(
|
|
|
13385
13385
|
)
|
|
13386
13386
|
);
|
|
13387
13387
|
var EMPTY_SET2 = /* @__PURE__ */ new Set();
|
|
13388
|
-
var FRAMEWORK_WELL_KNOWN_EXTRAS = /* @__PURE__ */ new Set(["
|
|
13388
|
+
var FRAMEWORK_WELL_KNOWN_EXTRAS = /* @__PURE__ */ new Set(["intentOverridesField"]);
|
|
13389
13389
|
var KNOWN_DIRECTIVE_NAMES = /* @__PURE__ */ new Set([
|
|
13390
13390
|
"dangerouslysetinnerhtml",
|
|
13391
13391
|
"slot",
|
|
@@ -13410,11 +13410,13 @@ var LOOKUP_TARGET_MALFORMED = "LOOKUP_TARGET_MALFORMED";
|
|
|
13410
13410
|
var RENDER_IT_OUTSIDE_OF_LOOP = "RENDER_IT_OUTSIDE_OF_LOOP";
|
|
13411
13411
|
var UNKNOWN_EVENT_MODIFIER = "UNKNOWN_EVENT_MODIFIER";
|
|
13412
13412
|
var UNKNOWN_HANDLER_ARG_NAME = "UNKNOWN_HANDLER_ARG_NAME";
|
|
13413
|
-
var
|
|
13414
|
-
var
|
|
13415
|
-
var
|
|
13416
|
-
var
|
|
13417
|
-
var
|
|
13413
|
+
var RECEIVE_HANDLER_NOT_IMPLEMENTED = "RECEIVE_HANDLER_NOT_IMPLEMENTED";
|
|
13414
|
+
var RETIRED_HANDLER_BUCKET = "RETIRED_HANDLER_BUCKET";
|
|
13415
|
+
var RETIRED_CTX_VERB = "RETIRED_CTX_VERB";
|
|
13416
|
+
var HANDLER_NAME_COLLISION = "HANDLER_NAME_COLLISION";
|
|
13417
|
+
var RECEIVE_HANDLER_METHOD_NOT_IMPLEMENTED = "RECEIVE_HANDLER_METHOD_NOT_IMPLEMENTED";
|
|
13418
|
+
var RECEIVE_HANDLER_FOR_METHOD = "RECEIVE_HANDLER_FOR_METHOD";
|
|
13419
|
+
var METHOD_FOR_RECEIVE_HANDLER = "METHOD_FOR_RECEIVE_HANDLER";
|
|
13418
13420
|
var FIELD_VAL_NOT_DEFINED = "FIELD_VAL_NOT_DEFINED";
|
|
13419
13421
|
var FIELD_VAL_IS_METHOD = "FIELD_VAL_IS_METHOD";
|
|
13420
13422
|
var METHOD_VAL_NOT_DEFINED = "METHOD_VAL_NOT_DEFINED";
|
|
@@ -13532,6 +13534,9 @@ function checkComponent(Comp, lx = new LintContext(), { wellKnownExtras = EMPTY_
|
|
|
13532
13534
|
checkProvidesAreAddressable(lx, Comp);
|
|
13533
13535
|
checkLookupShapes(lx, Comp);
|
|
13534
13536
|
checkHandlersNotAsync(lx, Comp);
|
|
13537
|
+
checkRetiredBuckets(lx, Comp);
|
|
13538
|
+
checkRetiredCtxVerbs(lx, Comp);
|
|
13539
|
+
checkHandlerNameCollisions(lx, Comp);
|
|
13535
13540
|
checkScopedStyleTopLevel(lx, Comp);
|
|
13536
13541
|
const referencedAlters = /* @__PURE__ */ new Set();
|
|
13537
13542
|
const referencedInputs = /* @__PURE__ */ new Set();
|
|
@@ -13545,7 +13550,6 @@ function checkComponent(Comp, lx = new LintContext(), { wellKnownExtras = EMPTY_
|
|
|
13545
13550
|
);
|
|
13546
13551
|
}
|
|
13547
13552
|
checkUnreferencedAlterHandlers(lx, Comp, referencedAlters);
|
|
13548
|
-
checkUnreferencedInputHandlers(lx, Comp, referencedInputs);
|
|
13549
13553
|
checkUnreferencedDynamics(lx, Comp, referencedDynamics);
|
|
13550
13554
|
return lx;
|
|
13551
13555
|
});
|
|
@@ -13815,7 +13819,7 @@ function mkAttrValEnv(Comp, referencedAlters, referencedDynamics) {
|
|
|
13815
13819
|
return { fields, proto, scope, alter, referencedAlters, dynamicMap, referencedDynamics };
|
|
13816
13820
|
}
|
|
13817
13821
|
function checkEventHandlersHaveImpls(lx, Comp, referencedInputs) {
|
|
13818
|
-
const { input, views, Class } = Comp;
|
|
13822
|
+
const { receive: input, views, Class } = Comp;
|
|
13819
13823
|
const { prototype: proto } = Class;
|
|
13820
13824
|
for (const viewName in views) {
|
|
13821
13825
|
lx.push({ viewName }, () => {
|
|
@@ -13832,13 +13836,13 @@ function checkEventHandlersHaveImpls(lx, Comp, referencedInputs) {
|
|
|
13832
13836
|
if (input[name] === void 0) {
|
|
13833
13837
|
const isMethodFix = protoHasMethod(proto, name);
|
|
13834
13838
|
lx.error(
|
|
13835
|
-
|
|
13839
|
+
RECEIVE_HANDLER_NOT_IMPLEMENTED,
|
|
13836
13840
|
{ name, handler, event, eventName, originAttr },
|
|
13837
13841
|
isMethodFix ? { kind: "add-prefix", from: name, to: `$${name}` } : replaceNameSuggestion(name, Object.keys(input))
|
|
13838
13842
|
);
|
|
13839
13843
|
if (isMethodFix) {
|
|
13840
13844
|
lx.hint(
|
|
13841
|
-
|
|
13845
|
+
METHOD_FOR_RECEIVE_HANDLER,
|
|
13842
13846
|
{ name, handler, event, eventName, originAttr },
|
|
13843
13847
|
{ kind: "add-prefix", from: name, to: `$${name}` }
|
|
13844
13848
|
);
|
|
@@ -13850,13 +13854,13 @@ function checkEventHandlersHaveImpls(lx, Comp, referencedInputs) {
|
|
|
13850
13854
|
if (!protoHasMethod(proto, name)) {
|
|
13851
13855
|
const isInputFix = input[name] !== void 0;
|
|
13852
13856
|
lx.error(
|
|
13853
|
-
|
|
13857
|
+
RECEIVE_HANDLER_METHOD_NOT_IMPLEMENTED,
|
|
13854
13858
|
{ name, handler, event, eventName, originAttr },
|
|
13855
13859
|
isInputFix ? { kind: "drop-prefix", from: `$${name}`, to: name } : replaceNameSuggestion(name, collectProtoMethodNames(proto))
|
|
13856
13860
|
);
|
|
13857
13861
|
if (isInputFix) {
|
|
13858
13862
|
lx.hint(
|
|
13859
|
-
|
|
13863
|
+
RECEIVE_HANDLER_FOR_METHOD,
|
|
13860
13864
|
{ name, handler, event, eventName, originAttr },
|
|
13861
13865
|
{ kind: "drop-prefix", from: `$${name}`, to: name }
|
|
13862
13866
|
);
|
|
@@ -14105,12 +14109,83 @@ function checkConsistentAttrs(lx, Comp, referencedAlters, referencedDynamics) {
|
|
|
14105
14109
|
});
|
|
14106
14110
|
}
|
|
14107
14111
|
}
|
|
14112
|
+
var isPlainObj = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
14113
|
+
var RETIRED_BUCKETS = {
|
|
14114
|
+
input: {
|
|
14115
|
+
to: "receive",
|
|
14116
|
+
how: "rename the bucket to `receive` - a view's own @on.* name and a parent's ctx.send are the same message now, and nothing can tell them apart"
|
|
14117
|
+
},
|
|
14118
|
+
bubble: {
|
|
14119
|
+
to: "intent",
|
|
14120
|
+
how: "rename the bucket to `intent` - and note the walk changed: a handler that calls ctx.reply() ENDS it, where ctx.stopPropagation() used to be the only way to stop"
|
|
14121
|
+
},
|
|
14122
|
+
response: {
|
|
14123
|
+
to: "receive",
|
|
14124
|
+
how: "split each arm into `receive` arms named after the intent: <name>Ok takes the result alone, <name>Error takes the error alone, and the optional <name>Unhandled means nothing claimed it"
|
|
14125
|
+
}
|
|
14126
|
+
};
|
|
14127
|
+
function checkRetiredBuckets(lx, Comp) {
|
|
14128
|
+
const extra = Comp.extra;
|
|
14129
|
+
if (!extra) return;
|
|
14130
|
+
for (const key of Object.keys(extra)) {
|
|
14131
|
+
if (!Object.hasOwn(RETIRED_BUCKETS, key)) continue;
|
|
14132
|
+
const retired = RETIRED_BUCKETS[key];
|
|
14133
|
+
const names = isPlainObj(extra[key]) ? Object.keys(extra[key]) : [];
|
|
14134
|
+
lx.error(RETIRED_HANDLER_BUCKET, { key, to: retired.to, how: retired.how, names });
|
|
14135
|
+
}
|
|
14136
|
+
}
|
|
14137
|
+
var RETIRED_CTX_VERBS = {
|
|
14138
|
+
bubble: 'ctx.intent(name, args, { route: ["dyn"] }) - the route says it walks the ancestors, where the verb used to',
|
|
14139
|
+
request: 'ctx.intent(name, args, { route: ["lex"] }) - and the outcome comes back as a `receive` arm named <name>Ok / <name>Error / <name>Unhandled, so onOkName and onErrorName are gone',
|
|
14140
|
+
stopPropagation: "ctx.stop()",
|
|
14141
|
+
inputAtPath: "ctx.sendAtPath(path, name, args)",
|
|
14142
|
+
requestAtPath: 'ctx.intentAtPath(path, name, args, { route: ["lex"] })'
|
|
14143
|
+
};
|
|
14144
|
+
var RETIRED_CTX_RE = new RegExp(
|
|
14145
|
+
`\\bctx\\.(${Object.keys(RETIRED_CTX_VERBS).join("|")})\\s*\\(`,
|
|
14146
|
+
"g"
|
|
14147
|
+
);
|
|
14148
|
+
function checkRetiredCtxVerbs(lx, Comp) {
|
|
14149
|
+
for (const bucket of ["receive", "intent", "alter", "methods", "statics"]) {
|
|
14150
|
+
const block = bucket === "methods" || bucket === "statics" ? Comp.spec?.[bucket] : Comp[bucket];
|
|
14151
|
+
if (!isPlainObj(block)) continue;
|
|
14152
|
+
for (const name in block) {
|
|
14153
|
+
const fn = block[name];
|
|
14154
|
+
if (typeof fn !== "function") continue;
|
|
14155
|
+
const src = String(fn);
|
|
14156
|
+
for (const m of src.matchAll(RETIRED_CTX_RE)) {
|
|
14157
|
+
const verb = m[1];
|
|
14158
|
+
lx.error(RETIRED_CTX_VERB, { verb, to: RETIRED_CTX_VERBS[verb], bucket, name });
|
|
14159
|
+
}
|
|
14160
|
+
}
|
|
14161
|
+
}
|
|
14162
|
+
}
|
|
14163
|
+
function checkHandlerNameCollisions(lx, Comp) {
|
|
14164
|
+
const receive = Comp.receive ?? {};
|
|
14165
|
+
const intent = Comp.intent ?? {};
|
|
14166
|
+
for (const name in intent)
|
|
14167
|
+
if (receive[name] !== void 0)
|
|
14168
|
+
lx.error(HANDLER_NAME_COLLISION, {
|
|
14169
|
+
name,
|
|
14170
|
+
problem: `"${name}" is declared in both \`receive\` and \`intent\``,
|
|
14171
|
+
fix: "one is addressed and one is routed; give them different names so a reader can tell which a call site meant"
|
|
14172
|
+
});
|
|
14173
|
+
for (const name in receive)
|
|
14174
|
+
for (const suffix of ["Ok", "Error", "Unhandled"])
|
|
14175
|
+
if (receive[name + suffix] !== void 0 && intent[name] === void 0)
|
|
14176
|
+
lx.warn(HANDLER_NAME_COLLISION, {
|
|
14177
|
+
name,
|
|
14178
|
+
problem: `\`receive\` declares both "${name}" and its answer name "${name + suffix}"`,
|
|
14179
|
+
fix: `an intent named "${name}" would dispatch its answer to "${name + suffix}" - rename one unless that is what you meant`
|
|
14180
|
+
});
|
|
14181
|
+
}
|
|
14108
14182
|
function checkUnknownSpecKeys(lx, Comp, wellKnownExtras) {
|
|
14109
14183
|
const extra = Comp.extra;
|
|
14110
14184
|
if (!extra) return;
|
|
14111
14185
|
let candidates = null;
|
|
14112
14186
|
for (const key of Object.keys(extra)) {
|
|
14113
14187
|
if (FRAMEWORK_WELL_KNOWN_EXTRAS.has(key) || wellKnownExtras.has(key)) continue;
|
|
14188
|
+
if (Object.hasOwn(RETIRED_BUCKETS, key)) continue;
|
|
14114
14189
|
candidates ??= [
|
|
14115
14190
|
...KNOWN_COMPONENT_SPEC_KEYS,
|
|
14116
14191
|
...FRAMEWORK_WELL_KNOWN_EXTRAS,
|
|
@@ -14158,15 +14233,8 @@ function checkUnreferencedAlterHandlers(lx, Comp, referencedAlters) {
|
|
|
14158
14233
|
}
|
|
14159
14234
|
}
|
|
14160
14235
|
}
|
|
14161
|
-
|
|
14162
|
-
|
|
14163
|
-
if (!referencedInputs.has(name)) {
|
|
14164
|
-
lx.hint(INPUT_HANDLER_NOT_REFERENCED, { name });
|
|
14165
|
-
}
|
|
14166
|
-
}
|
|
14167
|
-
}
|
|
14168
|
-
var HANDLER_CHANNELS = ["input", "receive", "bubble", "response", "alter"];
|
|
14169
|
-
var ASYNC_HANDLER_HELP = "Move the async work into a request handler and trigger it with ctx.request('name', args), then handle the result in a synchronous response handler. To coordinate other components, keep the handler synchronous and use ctx.send to deliver a message or ctx.bubble to raise an event.";
|
|
14236
|
+
var HANDLER_CHANNELS = ["receive", "intent", "alter"];
|
|
14237
|
+
var ASYNC_HANDLER_HELP = `Move the async work into a scope-registered intent handler and trigger it with ctx.intent("name", args, { route: ["lex"] }), then read the outcome in the synchronous \`receive\` arms named after it: <name>Ok, <name>Error and <name>Unhandled. To coordinate other components, keep the handler synchronous and use ctx.send to address one, or ctx.intent to let a route find one.`;
|
|
14170
14238
|
function checkHandlersNotAsync(lx, Comp) {
|
|
14171
14239
|
for (const channel of HANDLER_CHANNELS) {
|
|
14172
14240
|
const block = Comp[channel];
|
|
@@ -14362,8 +14430,11 @@ var Components = class {
|
|
|
14362
14430
|
getHandlerFor(v, name, key) {
|
|
14363
14431
|
return this.getCompFor(v)?.[key][name] ?? null;
|
|
14364
14432
|
}
|
|
14365
|
-
|
|
14366
|
-
|
|
14433
|
+
// The `lex` leg of an intent's route: the handlers registered on the scope chain of
|
|
14434
|
+
// the component that raised it, innermost first. A list, not a first match — see
|
|
14435
|
+
// ComponentStack.lookupIntentChain.
|
|
14436
|
+
getIntentChainFor(v, name) {
|
|
14437
|
+
return this.getCompFor(v)?.scope.lookupIntentChain(name) ?? [];
|
|
14367
14438
|
}
|
|
14368
14439
|
compileStyles() {
|
|
14369
14440
|
const styles2 = [];
|
|
@@ -14376,7 +14447,7 @@ var ComponentStack = class _ComponentStack {
|
|
|
14376
14447
|
this.comps = comps;
|
|
14377
14448
|
this.parent = parent;
|
|
14378
14449
|
this.byName = {};
|
|
14379
|
-
this.
|
|
14450
|
+
this.intentsByName = {};
|
|
14380
14451
|
this.macros = {};
|
|
14381
14452
|
}
|
|
14382
14453
|
enter() {
|
|
@@ -14408,11 +14479,21 @@ var ComponentStack = class _ComponentStack {
|
|
|
14408
14479
|
getCompFor(v) {
|
|
14409
14480
|
return this.comps.getCompFor(v);
|
|
14410
14481
|
}
|
|
14411
|
-
|
|
14412
|
-
|
|
14482
|
+
// Handlers that answer an intent's `lex` leg. A name maps to a LIST, because the leg
|
|
14483
|
+
// walks: a handler that returns PASS declines and hands the intent to the next one.
|
|
14484
|
+
// A bare function is accepted as a one-element list.
|
|
14485
|
+
registerIntentHandlers(handlers) {
|
|
14486
|
+
for (const name in handlers) {
|
|
14487
|
+
const fns = Array.isArray(handlers[name]) ? handlers[name] : [handlers[name]];
|
|
14488
|
+
this.intentsByName[name] = fns.map((fn) => new IntentHandler(name, fn));
|
|
14489
|
+
}
|
|
14413
14490
|
}
|
|
14414
|
-
|
|
14415
|
-
|
|
14491
|
+
// Innermost scope first, then outward. Concatenated rather than first-match, so a
|
|
14492
|
+
// declining handler can hand the intent to one further up.
|
|
14493
|
+
lookupIntentChain(name) {
|
|
14494
|
+
const here = this.intentsByName[name] ?? [];
|
|
14495
|
+
const up = this.parent?.lookupIntentChain(name) ?? [];
|
|
14496
|
+
return up.length === 0 ? here : here.concat(up);
|
|
14416
14497
|
}
|
|
14417
14498
|
lookupComponent(name) {
|
|
14418
14499
|
return this.byName[name] ?? this.parent?.lookupComponent(name) ?? null;
|
|
@@ -14443,7 +14524,7 @@ var LookupInfo = class {
|
|
|
14443
14524
|
}
|
|
14444
14525
|
};
|
|
14445
14526
|
var isString = (v) => typeof v === "string";
|
|
14446
|
-
var _rawSpecKeys = "name view style commonStyle globalStyle
|
|
14527
|
+
var _rawSpecKeys = "name view style commonStyle globalStyle receive intent alter views provide lookup fields methods statics";
|
|
14447
14528
|
var KNOWN_SPEC_KEYS = new Set(_rawSpecKeys.split(" "));
|
|
14448
14529
|
var _compId = 0;
|
|
14449
14530
|
var Component = class {
|
|
@@ -14454,10 +14535,8 @@ var Component = class {
|
|
|
14454
14535
|
this.views = { main: new View("main", o.view, o.style) };
|
|
14455
14536
|
this.commonStyle = o.commonStyle ?? "";
|
|
14456
14537
|
this.globalStyle = o.globalStyle ?? "";
|
|
14457
|
-
this.input = o.input ?? {};
|
|
14458
14538
|
this.receive = o.receive ?? {};
|
|
14459
|
-
this.
|
|
14460
|
-
this.response = o.response ?? {};
|
|
14539
|
+
this.intent = o.intent ?? {};
|
|
14461
14540
|
this.alter = o.alter ?? {};
|
|
14462
14541
|
for (const name in o.views ?? {}) {
|
|
14463
14542
|
const v = o.views[name];
|
|
@@ -14519,7 +14598,7 @@ var Component = class {
|
|
|
14519
14598
|
};
|
|
14520
14599
|
|
|
14521
14600
|
// src/on.js
|
|
14522
|
-
var OP_KINDS = ["send", "
|
|
14601
|
+
var OP_KINDS = ["send", "intent"];
|
|
14523
14602
|
function phaseOps(phase) {
|
|
14524
14603
|
const ops = [];
|
|
14525
14604
|
for (const type3 of OP_KINDS) for (const a of phase[type3] ?? []) ops.push({ type: type3, ...a });
|
|
@@ -14529,11 +14608,6 @@ function phaseOps(phase) {
|
|
|
14529
14608
|
function resolveArgs(args, self) {
|
|
14530
14609
|
return typeof args === "function" ? args(self) ?? [] : args ?? [];
|
|
14531
14610
|
}
|
|
14532
|
-
function phaseHasBubble(phase) {
|
|
14533
|
-
if (!phase) return false;
|
|
14534
|
-
if (phase.bubble?.length) return true;
|
|
14535
|
-
return (phase.do ?? []).some((op) => op.type === "bubble");
|
|
14536
|
-
}
|
|
14537
14611
|
function dispatchPhase(dispatcher, targetPath, phase, self) {
|
|
14538
14612
|
if (!phase) return;
|
|
14539
14613
|
for (const op of phaseOps(phase)) {
|
|
@@ -14542,18 +14616,8 @@ function dispatchPhase(dispatcher, targetPath, phase, self) {
|
|
|
14542
14616
|
case "send":
|
|
14543
14617
|
dispatcher.sendAtPath(targetPath, op.name, args, op.opts);
|
|
14544
14618
|
break;
|
|
14545
|
-
case "
|
|
14546
|
-
dispatcher.
|
|
14547
|
-
skipSelf: true,
|
|
14548
|
-
bubbles: true,
|
|
14549
|
-
...op.opts
|
|
14550
|
-
});
|
|
14551
|
-
break;
|
|
14552
|
-
case "request":
|
|
14553
|
-
dispatcher.requestAtPath(targetPath, op.name, args, op.opts);
|
|
14554
|
-
break;
|
|
14555
|
-
case "input":
|
|
14556
|
-
dispatcher.inputAtPath(targetPath, op.name, args, op.opts);
|
|
14619
|
+
case "intent":
|
|
14620
|
+
dispatcher.intentAtPath(targetPath, op.name, args, op.opts);
|
|
14557
14621
|
break;
|
|
14558
14622
|
}
|
|
14559
14623
|
}
|
|
@@ -14725,8 +14789,11 @@ var Transactor = class {
|
|
|
14725
14789
|
this.transactions.push(t);
|
|
14726
14790
|
this.onTransactionPushed(t);
|
|
14727
14791
|
}
|
|
14728
|
-
// Subscribe to a normalized record for every handler invocation
|
|
14729
|
-
//
|
|
14792
|
+
// Subscribe to a normalized record for every handler invocation. `kind` is the
|
|
14793
|
+
// handler bucket (`receive` / `intent`), plus `answer` — an observation-only kind for
|
|
14794
|
+
// a message the runtime wrote to answer an intent. The BUCKET does not know about
|
|
14795
|
+
// that distinction, and must not: a handler cannot tell an answer from a message its
|
|
14796
|
+
// parent sent. Returns an unsubscribe function. Records
|
|
14730
14797
|
// carry: kind, name, args, path, pathKeys, targetPath, handler, handlerName,
|
|
14731
14798
|
// matched, before, after, parent, timestamp. Purely observational.
|
|
14732
14799
|
observe(cb) {
|
|
@@ -14779,23 +14846,14 @@ var Transactor = class {
|
|
|
14779
14846
|
this.pushTransaction(t);
|
|
14780
14847
|
return this._link(t, parent);
|
|
14781
14848
|
}
|
|
14782
|
-
|
|
14783
|
-
|
|
14784
|
-
|
|
14785
|
-
|
|
14786
|
-
}
|
|
14787
|
-
pushBubble(path, name, args = [], opts = {}, parent = null, targetPath = null) {
|
|
14788
|
-
const newOpts = opts.skipSelf ? { ...opts, skipSelf: false } : opts;
|
|
14789
|
-
const t = new BubbleEvent(path, this, name, args, parent, newOpts, targetPath);
|
|
14790
|
-
this.pushTransaction(t);
|
|
14791
|
-
return this._link(t, parent);
|
|
14792
|
-
}
|
|
14793
|
-
pushRequest(path, name, args = [], opts = {}, parent = null) {
|
|
14849
|
+
// Raise an intent: a job the sender did not address, walked along a route until
|
|
14850
|
+
// something answers. `opts.route` is a list of legs (see DEFAULT_ROUTE) and
|
|
14851
|
+
// `opts.livePath` opts out of pinning the answer's destination keys.
|
|
14852
|
+
pushIntent(path, name, args = [], opts = {}, parent = null) {
|
|
14794
14853
|
const release = parent ? parent.completion.track() : null;
|
|
14795
|
-
const
|
|
14796
|
-
|
|
14797
|
-
|
|
14798
|
-
return p;
|
|
14854
|
+
const walk = new IntentWalk(this, path, name, args, opts, parent, release);
|
|
14855
|
+
walk.advance();
|
|
14856
|
+
return walk;
|
|
14799
14857
|
}
|
|
14800
14858
|
// Drain queued transactions and await in-flight requests until quiescent. Each
|
|
14801
14859
|
// awaited request enqueues a ResponseEvent, which may dispatch more work, so we
|
|
@@ -14806,57 +14864,6 @@ var Transactor = class {
|
|
|
14806
14864
|
if (this._inflight.size) await Promise.allSettled([...this._inflight]);
|
|
14807
14865
|
}
|
|
14808
14866
|
}
|
|
14809
|
-
async _runRequest(path, name, args = [], opts = {}, parent = null, release = null) {
|
|
14810
|
-
let released = false;
|
|
14811
|
-
const transfer = (t) => {
|
|
14812
|
-
if (release) {
|
|
14813
|
-
released = true;
|
|
14814
|
-
t.completion.whenSubtreeSettled().then(release);
|
|
14815
|
-
}
|
|
14816
|
-
};
|
|
14817
|
-
try {
|
|
14818
|
-
const curRoot = this.state.val;
|
|
14819
|
-
const txnPath = path.toTransactionPath();
|
|
14820
|
-
const curLeaf = txnPath.lookup(curRoot);
|
|
14821
|
-
const found = this.comps.getRequestFor(curLeaf, name);
|
|
14822
|
-
const handler = found ?? mkReq404(name);
|
|
14823
|
-
if (this._observers.length > 0) {
|
|
14824
|
-
const reqPath = txnPath.pinKeys(curRoot);
|
|
14825
|
-
this._emit({
|
|
14826
|
-
kind: "request",
|
|
14827
|
-
name,
|
|
14828
|
-
args,
|
|
14829
|
-
path: reqPath,
|
|
14830
|
-
pathKeys: reqPath.toKeys(),
|
|
14831
|
-
targetPath: path,
|
|
14832
|
-
handler: found?.fn ?? null,
|
|
14833
|
-
handlerName: found?.fn?.name || name,
|
|
14834
|
-
matched: found ? "exact" : "none",
|
|
14835
|
-
before: curLeaf,
|
|
14836
|
-
after: void 0,
|
|
14837
|
-
parent,
|
|
14838
|
-
timestamp: Date.now()
|
|
14839
|
-
});
|
|
14840
|
-
}
|
|
14841
|
-
const reqCtx = new RequestContext(path, this, parent, curRoot);
|
|
14842
|
-
const resHandlerName = opts?.onResName ?? name;
|
|
14843
|
-
const resPath = opts?.livePath ? null : txnPath.pinKeys(curRoot);
|
|
14844
|
-
const push = (specificName, baseName, singleArg, result, error) => {
|
|
14845
|
-
const resArgs = specificName ? [singleArg] : [result, error];
|
|
14846
|
-
const t = new ResponseEvent(path, this, specificName ?? baseName, resArgs, parent, resPath);
|
|
14847
|
-
transfer(t);
|
|
14848
|
-
this.pushTransaction(t);
|
|
14849
|
-
};
|
|
14850
|
-
try {
|
|
14851
|
-
const result = await handler.fn.apply(null, [...args, reqCtx]);
|
|
14852
|
-
push(opts?.onOkName, resHandlerName, result, result, null);
|
|
14853
|
-
} catch (error) {
|
|
14854
|
-
push(opts?.onErrorName, resHandlerName, error, null, error);
|
|
14855
|
-
}
|
|
14856
|
-
} finally {
|
|
14857
|
-
if (release && !released) release();
|
|
14858
|
-
}
|
|
14859
|
-
}
|
|
14860
14867
|
get hasPendingTransactions() {
|
|
14861
14868
|
return this.transactions.length > 0;
|
|
14862
14869
|
}
|
|
@@ -14873,6 +14880,7 @@ var Transactor = class {
|
|
|
14873
14880
|
this._emitTransaction(transaction, curState);
|
|
14874
14881
|
} else console.warn("undefined new state", { curState, transaction });
|
|
14875
14882
|
} finally {
|
|
14883
|
+
transaction.ensureWalkAdvanced?.();
|
|
14876
14884
|
transaction._completion?.ensureSelfSettled();
|
|
14877
14885
|
transaction._completion?.releaseSelf();
|
|
14878
14886
|
}
|
|
@@ -14881,12 +14889,6 @@ var Transactor = class {
|
|
|
14881
14889
|
this.transact(new InputEvent(path, event, eventHandler, this, dragInfo));
|
|
14882
14890
|
}
|
|
14883
14891
|
};
|
|
14884
|
-
function mkReq404(name) {
|
|
14885
|
-
const fn = () => {
|
|
14886
|
-
throw new Error(`Request not found: ${name}`);
|
|
14887
|
-
};
|
|
14888
|
-
return { fn };
|
|
14889
|
-
}
|
|
14890
14892
|
function nullHandler() {
|
|
14891
14893
|
return this;
|
|
14892
14894
|
}
|
|
@@ -14917,6 +14919,15 @@ var Transaction = class {
|
|
|
14917
14919
|
}
|
|
14918
14920
|
afterTransaction() {
|
|
14919
14921
|
}
|
|
14922
|
+
// Ending a walk and forwarding belong to the two dispatch buckets; the base warns
|
|
14923
|
+
// rather than throwing, so a stray call from the wrong place is a message and not a
|
|
14924
|
+
// crash. `walk` is undefined here, which is how ctx.reply/ctx.fail tell the two apart.
|
|
14925
|
+
stop() {
|
|
14926
|
+
console.warn('ctx.stop() is only meaningful in an "intent" handler - ignored');
|
|
14927
|
+
}
|
|
14928
|
+
forward(_opts) {
|
|
14929
|
+
console.warn('ctx.forward() needs a "receive" or "intent" handler - ignored');
|
|
14930
|
+
}
|
|
14920
14931
|
buildRootStack(root, comps) {
|
|
14921
14932
|
return Stack2.root(comps, root);
|
|
14922
14933
|
}
|
|
@@ -14978,18 +14989,40 @@ var InputEvent = class extends Transaction {
|
|
|
14978
14989
|
this._dispatchPath ??= this.path.compact();
|
|
14979
14990
|
return this._dispatchPath;
|
|
14980
14991
|
}
|
|
14992
|
+
// A DOM event is an ADDRESSED message like any other — it reaches the component that
|
|
14993
|
+
// owns the view and stops. It keeps its own class only because it resolves its handler
|
|
14994
|
+
// from the compiled view and runs synchronously, not because it is a different channel.
|
|
14981
14995
|
get observeKind() {
|
|
14982
|
-
return "
|
|
14996
|
+
return "receive";
|
|
14983
14997
|
}
|
|
14984
14998
|
get observeName() {
|
|
14985
14999
|
return this.e?.type ?? null;
|
|
14986
15000
|
}
|
|
15001
|
+
// A view's name is a message. `forward` is where that name can LEAVE the component:
|
|
15002
|
+
// it becomes an intent with the same name and the same arguments, so a view that says
|
|
15003
|
+
// `@on.click="saveDraft .text"` never has to change when an ancestor takes the job over.
|
|
15004
|
+
// (A `$method` handler has a name too, so forwarding one raises an intent under it.)
|
|
15005
|
+
forward(opts) {
|
|
15006
|
+
this._forward = opts ?? {};
|
|
15007
|
+
}
|
|
15008
|
+
afterTransaction() {
|
|
15009
|
+
const f = this._forward;
|
|
15010
|
+
if (f === void 0) return;
|
|
15011
|
+
this._forward = void 0;
|
|
15012
|
+
const { args = this._handlerArgs ?? [], ...rest } = f;
|
|
15013
|
+
const hv = this.handler?.handlerCall?.handlerVal ?? this.handler?.handlerVal;
|
|
15014
|
+
const name = hv?.name;
|
|
15015
|
+
if (name === void 0)
|
|
15016
|
+
return console.warn("ctx.forward() from a handler with no name - ignored");
|
|
15017
|
+
this.transactor.pushIntent(this.dispatchPath, name, args, rest, this);
|
|
15018
|
+
}
|
|
14987
15019
|
buildRootStack(root, comps) {
|
|
14988
15020
|
return Stack2.root(comps, root, this);
|
|
14989
15021
|
}
|
|
14990
15022
|
getHandlerAndArgs(root, _instance, comps) {
|
|
14991
15023
|
const stack = this.buildStack(root, comps);
|
|
14992
15024
|
const [handler, args] = this.handler.getHandlerAndArgs(stack, this);
|
|
15025
|
+
this._handlerArgs = [...args];
|
|
14993
15026
|
const path = this.dispatchPath;
|
|
14994
15027
|
args.push(new EventContext(path, this.transactor, this));
|
|
14995
15028
|
return [handler, args];
|
|
@@ -15074,39 +15107,190 @@ var NameArgsTransaction = class extends Transaction {
|
|
|
15074
15107
|
return [handler, [...this.args, new EventContext(this.path, this.transactor, this)]];
|
|
15075
15108
|
}
|
|
15076
15109
|
};
|
|
15077
|
-
var ResponseEvent = class extends NameArgsTransaction {
|
|
15078
|
-
handlerProp = "response";
|
|
15079
|
-
constructor(path, transactor, name, args, parent, txnPath = null) {
|
|
15080
|
-
super(path, transactor, name, args, parent);
|
|
15081
|
-
this._txnPath = txnPath;
|
|
15082
|
-
}
|
|
15083
|
-
getTransactionPath() {
|
|
15084
|
-
return this._txnPath ?? super.getTransactionPath();
|
|
15085
|
-
}
|
|
15086
|
-
};
|
|
15087
15110
|
var SendEvent = class extends NameArgsTransaction {
|
|
15088
15111
|
handlerProp = "receive";
|
|
15089
|
-
|
|
15090
|
-
|
|
15112
|
+
// `receive` is the ADDRESSED bucket, and it is the only one: a view's `@on.click`,
|
|
15113
|
+
// a parent's ctx.send, the host's sendAtRoot and an answer to an intent all arrive
|
|
15114
|
+
// here, and a handler cannot tell them apart. Splitting on where a message came from
|
|
15115
|
+
// would let the view's name through and turn the parent's away, though the two say
|
|
15116
|
+
// the same thing — and neither a test nor a parent could drive the component.
|
|
15117
|
+
get observeKind() {
|
|
15118
|
+
return this._isAnswer ? "answer" : "receive";
|
|
15119
|
+
}
|
|
15120
|
+
// `forward` in a RECEIVE body starts a walk: the message that arrived becomes an
|
|
15121
|
+
// intent, keeping its name and payload. This is what lets a view's name leave the
|
|
15122
|
+
// component without the view changing.
|
|
15123
|
+
forward(opts) {
|
|
15124
|
+
this._forward = opts ?? {};
|
|
15091
15125
|
}
|
|
15092
15126
|
afterTransaction() {
|
|
15093
|
-
const
|
|
15094
|
-
if (
|
|
15095
|
-
|
|
15127
|
+
const f = this._forward;
|
|
15128
|
+
if (f === void 0) return;
|
|
15129
|
+
this._forward = void 0;
|
|
15130
|
+
const { args = this.args, ...rest } = f;
|
|
15131
|
+
this.transactor.pushIntent(this.path, this.name, args, rest, this);
|
|
15096
15132
|
}
|
|
15097
15133
|
};
|
|
15098
|
-
var
|
|
15099
|
-
handlerProp = "
|
|
15100
|
-
constructor(path, transactor,
|
|
15101
|
-
super(path, transactor, name, args, parent,
|
|
15102
|
-
this.
|
|
15134
|
+
var IntentEvent = class extends NameArgsTransaction {
|
|
15135
|
+
handlerProp = "intent";
|
|
15136
|
+
constructor(path, transactor, walk) {
|
|
15137
|
+
super(path, transactor, walk.name, walk.args, walk.parent, {});
|
|
15138
|
+
this.walk = walk;
|
|
15139
|
+
this.targetPath = walk.origin;
|
|
15140
|
+
}
|
|
15141
|
+
// `forward` in an INTENT body amends the hop about to be pushed — it does not push
|
|
15142
|
+
// one itself, because a walk advances on its own.
|
|
15143
|
+
forward(opts) {
|
|
15144
|
+
this.walk.amend(opts, this.path);
|
|
15103
15145
|
}
|
|
15104
|
-
|
|
15105
|
-
this.
|
|
15146
|
+
stop() {
|
|
15147
|
+
this.walk.finish(null, null);
|
|
15148
|
+
}
|
|
15149
|
+
afterTransaction() {
|
|
15150
|
+
this.ensureWalkAdvanced();
|
|
15151
|
+
}
|
|
15152
|
+
// Idempotent, because both afterTransaction and the transactor's `finally` call it —
|
|
15153
|
+
// whichever gets there first advances the walk exactly once.
|
|
15154
|
+
ensureWalkAdvanced() {
|
|
15155
|
+
if (this._advanced) return;
|
|
15156
|
+
this._advanced = true;
|
|
15157
|
+
this.walk.advance();
|
|
15106
15158
|
}
|
|
15107
15159
|
};
|
|
15108
|
-
var
|
|
15109
|
-
|
|
15160
|
+
var PASS = /* @__PURE__ */ Symbol.for("tutuca.intent.pass");
|
|
15161
|
+
var DEFAULT_ROUTE = ["dyn", "lex"];
|
|
15162
|
+
var INTENT_DEPTH = 64;
|
|
15163
|
+
var IntentWalk = class {
|
|
15164
|
+
constructor(transactor, path, name, args, opts, parent, release) {
|
|
15165
|
+
this.transactor = transactor;
|
|
15166
|
+
this.name = name;
|
|
15167
|
+
this.args = args;
|
|
15168
|
+
this.route = opts?.route ?? DEFAULT_ROUTE;
|
|
15169
|
+
this.parent = parent;
|
|
15170
|
+
this.release = release;
|
|
15171
|
+
this.origin = path;
|
|
15172
|
+
this.answerPath = opts?.livePath ? null : path.toTransactionPath().pinKeys(transactor.state.val);
|
|
15173
|
+
this.legIndex = 0;
|
|
15174
|
+
this.dynAt = path;
|
|
15175
|
+
this.hops = 0;
|
|
15176
|
+
this.ended = false;
|
|
15177
|
+
}
|
|
15178
|
+
// Offer the intent to the next hop. Called once at dispatch, and again by every hop
|
|
15179
|
+
// that ran without answering.
|
|
15180
|
+
advance() {
|
|
15181
|
+
if (this.ended) return;
|
|
15182
|
+
if (this.hops++ >= INTENT_DEPTH) return this.exhaust("intentDepth");
|
|
15183
|
+
while (this.legIndex < this.route.length) {
|
|
15184
|
+
const leg = this.route[this.legIndex];
|
|
15185
|
+
if (leg === "dyn") {
|
|
15186
|
+
if (this.dynAt.steps.length === 0) {
|
|
15187
|
+
this.legIndex++;
|
|
15188
|
+
continue;
|
|
15189
|
+
}
|
|
15190
|
+
this.dynAt = this.dynAt.popStep();
|
|
15191
|
+
this.transactor.pushTransaction(new IntentEvent(this.dynAt, this.transactor, this));
|
|
15192
|
+
return;
|
|
15193
|
+
}
|
|
15194
|
+
if (leg === "lex") {
|
|
15195
|
+
this.legIndex++;
|
|
15196
|
+
return this._tryLex();
|
|
15197
|
+
}
|
|
15198
|
+
console.warn("unknown intent route leg", leg, '- expected "dyn" or "lex"');
|
|
15199
|
+
this.legIndex++;
|
|
15200
|
+
}
|
|
15201
|
+
this.exhaust("noHandler");
|
|
15202
|
+
}
|
|
15203
|
+
// The `lex` leg: the handlers registered on the scope chain of the component that
|
|
15204
|
+
// raised the intent, in order.
|
|
15205
|
+
_tryLex() {
|
|
15206
|
+
const root = this.transactor.state.val;
|
|
15207
|
+
const txnPath = this.origin.toTransactionPath();
|
|
15208
|
+
const leaf = txnPath.lookup(root);
|
|
15209
|
+
const chain = this.transactor.comps.getIntentChainFor(leaf, this.name);
|
|
15210
|
+
if (this.transactor._observers.length > 0) {
|
|
15211
|
+
const path = txnPath.pinKeys(root);
|
|
15212
|
+
this.transactor._emit({
|
|
15213
|
+
kind: "intent",
|
|
15214
|
+
name: this.name,
|
|
15215
|
+
args: this.args,
|
|
15216
|
+
path,
|
|
15217
|
+
pathKeys: path.toKeys(),
|
|
15218
|
+
targetPath: this.origin,
|
|
15219
|
+
handler: chain[0]?.fn ?? null,
|
|
15220
|
+
handlerName: chain[0]?.fn?.name || this.name,
|
|
15221
|
+
matched: chain.length > 0 ? "exact" : "none",
|
|
15222
|
+
before: leaf,
|
|
15223
|
+
after: void 0,
|
|
15224
|
+
parent: this.parent,
|
|
15225
|
+
timestamp: Date.now()
|
|
15226
|
+
});
|
|
15227
|
+
}
|
|
15228
|
+
if (chain.length === 0) return this.advance();
|
|
15229
|
+
const p = this._runLex(chain, 0);
|
|
15230
|
+
this.transactor._inflight.add(p);
|
|
15231
|
+
p.finally(() => this.transactor._inflight.delete(p));
|
|
15232
|
+
}
|
|
15233
|
+
async _runLex(chain, i) {
|
|
15234
|
+
if (this.ended) return;
|
|
15235
|
+
if (i >= chain.length) return this.advance();
|
|
15236
|
+
const ctx = new IntentContext(this.origin, this.transactor, this.parent);
|
|
15237
|
+
try {
|
|
15238
|
+
const res = await chain[i].fn.apply(null, [...this.args, ctx]);
|
|
15239
|
+
if (res === PASS) return this._runLex(chain, i + 1);
|
|
15240
|
+
this.answer("Ok", res);
|
|
15241
|
+
} catch (error) {
|
|
15242
|
+
this.answer("Error", error);
|
|
15243
|
+
}
|
|
15244
|
+
}
|
|
15245
|
+
// A hop answered. Ends the walk.
|
|
15246
|
+
answer(suffix, value) {
|
|
15247
|
+
this.finish(`${this.name}${suffix}`, [value]);
|
|
15248
|
+
}
|
|
15249
|
+
// `forward` from an intent body: keep walking, optionally with new arguments or a
|
|
15250
|
+
// narrowed route.
|
|
15251
|
+
amend(opts, from) {
|
|
15252
|
+
if (opts?.args !== void 0) this.args = opts.args;
|
|
15253
|
+
if (opts?.route !== void 0) {
|
|
15254
|
+
this.route = opts.route;
|
|
15255
|
+
this.legIndex = 0;
|
|
15256
|
+
this.dynAt = from ?? this.dynAt;
|
|
15257
|
+
}
|
|
15258
|
+
}
|
|
15259
|
+
// The route ran out. What the sender hears depends only on what it DECLARES, which is
|
|
15260
|
+
// how "a sender expects an answer if and only if it declares an answer arm" becomes
|
|
15261
|
+
// code — nobody writes it down twice, and there is no schema to keep in step.
|
|
15262
|
+
exhaust(reason) {
|
|
15263
|
+
if (this.ended) return;
|
|
15264
|
+
const { name, args } = this;
|
|
15265
|
+
const comp = this.transactor.comps.getCompFor(
|
|
15266
|
+
this.origin.toTransactionPath().lookup(this.transactor.state.val)
|
|
15267
|
+
);
|
|
15268
|
+
const declares = (n) => comp?.receive?.[n] !== void 0;
|
|
15269
|
+
if (declares(`${name}Unhandled`)) {
|
|
15270
|
+
this.finish(`${name}Unhandled`, args);
|
|
15271
|
+
} else if (declares(`${name}Error`)) {
|
|
15272
|
+
this.finish(`${name}Error`, [reason]);
|
|
15273
|
+
} else {
|
|
15274
|
+
if (declares(`${name}Ok`))
|
|
15275
|
+
console.warn(
|
|
15276
|
+
`intent "${name}" was not answered (${reason}) and this component declares only "${name}Ok" - add "${name}Unhandled" or "${name}Error" to handle it`
|
|
15277
|
+
);
|
|
15278
|
+
this.finish(null, null);
|
|
15279
|
+
}
|
|
15280
|
+
}
|
|
15281
|
+
// End the walk. `name === null` means no answer goes out, which is what `stop` and a
|
|
15282
|
+
// fire-and-forget notification both are — the tracked unit is released here instead
|
|
15283
|
+
// of following an answer's subtree.
|
|
15284
|
+
finish(name, args) {
|
|
15285
|
+
if (this.ended) return;
|
|
15286
|
+
this.ended = true;
|
|
15287
|
+
if (name === null) return this.release?.();
|
|
15288
|
+
const path = this.answerPath ?? this.origin;
|
|
15289
|
+
const t = new SendEvent(path, this.transactor, name, args, this.parent, {});
|
|
15290
|
+
t._isAnswer = true;
|
|
15291
|
+
this.transactor.pushTransaction(t);
|
|
15292
|
+
if (this.release) t.completion.whenSubtreeSettled().then(this.release);
|
|
15293
|
+
}
|
|
15110
15294
|
};
|
|
15111
15295
|
var Completion = class {
|
|
15112
15296
|
constructor() {
|
|
@@ -15187,23 +15371,21 @@ var Dispatcher = class {
|
|
|
15187
15371
|
get at() {
|
|
15188
15372
|
return new PathChanges(this);
|
|
15189
15373
|
}
|
|
15374
|
+
// A message is ADDRESSED: it names one component and stops there.
|
|
15190
15375
|
send(name, args, opts) {
|
|
15191
15376
|
return this.sendAtPath(this.path, name, args, opts);
|
|
15192
15377
|
}
|
|
15193
|
-
bubble(name, args, opts) {
|
|
15194
|
-
return this.send(name, args, { skipSelf: true, bubbles: true, ...opts });
|
|
15195
|
-
}
|
|
15196
15378
|
sendAtPath(path, name, args, opts) {
|
|
15197
15379
|
return this.transactor.pushSend(path, name, args, opts, this.parent);
|
|
15198
15380
|
}
|
|
15199
|
-
|
|
15200
|
-
|
|
15381
|
+
// An intent is ROUTED: it names a job and walks until something answers. The verb
|
|
15382
|
+
// does not decide which scope answers — `opts.route` does, written here at the call
|
|
15383
|
+
// site, which is where the decision actually is.
|
|
15384
|
+
intent(name, args, opts) {
|
|
15385
|
+
return this.intentAtPath(this.path, name, args, opts);
|
|
15201
15386
|
}
|
|
15202
|
-
|
|
15203
|
-
return this.transactor.
|
|
15204
|
-
}
|
|
15205
|
-
inputAtPath(path, name, args, opts) {
|
|
15206
|
-
return this.transactor.pushInput(path, name, args, opts, this.parent);
|
|
15387
|
+
intentAtPath(path, name, args, opts) {
|
|
15388
|
+
return this.transactor.pushIntent(path, name, args, opts, this.parent);
|
|
15207
15389
|
}
|
|
15208
15390
|
lookupTypeFor(name, inst) {
|
|
15209
15391
|
return this.transactor.comps.getCompFor(inst).scope.lookupComponent(name);
|
|
@@ -15216,12 +15398,30 @@ var EventContext = class extends Dispatcher {
|
|
|
15216
15398
|
get targetPath() {
|
|
15217
15399
|
return this.parent.targetPath;
|
|
15218
15400
|
}
|
|
15219
|
-
|
|
15220
|
-
|
|
15401
|
+
// Answer the intent being handled, with a result or with an error. Either ENDS the
|
|
15402
|
+
// walk. Legal only in an `intent` handler: a message has no sender waiting on it.
|
|
15403
|
+
reply(value) {
|
|
15404
|
+
return this.parent.walk === void 0 ? warnNotIntent("reply") : this.parent.walk.answer("Ok", value);
|
|
15405
|
+
}
|
|
15406
|
+
fail(error) {
|
|
15407
|
+
return this.parent.walk === void 0 ? warnNotIntent("fail") : this.parent.walk.answer("Error", error);
|
|
15408
|
+
}
|
|
15409
|
+
// End the walk answering nothing — "served, and no answer".
|
|
15410
|
+
stop() {
|
|
15411
|
+
return this.parent.stop();
|
|
15412
|
+
}
|
|
15413
|
+
// From an `intent` body: hand the intent to the next hop, optionally amending it.
|
|
15414
|
+
// From a `receive` body: turn the message that arrived into an intent. One word from
|
|
15415
|
+
// both ends of a walk; which one you get depends on which bucket you are in.
|
|
15416
|
+
forward(opts) {
|
|
15417
|
+
return this.parent.forward(opts);
|
|
15221
15418
|
}
|
|
15222
15419
|
};
|
|
15223
|
-
var
|
|
15420
|
+
var IntentContext = class extends Dispatcher {
|
|
15224
15421
|
};
|
|
15422
|
+
function warnNotIntent(verb) {
|
|
15423
|
+
console.warn(`ctx.${verb}() is only meaningful in an "intent" handler - ignored`);
|
|
15424
|
+
}
|
|
15225
15425
|
var PathChanges = class extends PathBuilder {
|
|
15226
15426
|
constructor(dispatcher) {
|
|
15227
15427
|
super();
|
|
@@ -15230,8 +15430,8 @@ var PathChanges = class extends PathBuilder {
|
|
|
15230
15430
|
send(name, args, opts) {
|
|
15231
15431
|
return this.dispatcher.sendAtPath(this.buildPath(), name, args, opts);
|
|
15232
15432
|
}
|
|
15233
|
-
|
|
15234
|
-
return this.
|
|
15433
|
+
intent(name, args, opts) {
|
|
15434
|
+
return this.dispatcher.intentAtPath(this.buildPath(), name, args, opts);
|
|
15235
15435
|
}
|
|
15236
15436
|
buildPath() {
|
|
15237
15437
|
return this.dispatcher.path.concat(this.pathChanges);
|
|
@@ -15503,29 +15703,24 @@ function captureError(e) {
|
|
|
15503
15703
|
if ("actual" in e) out.actual = e.actual;
|
|
15504
15704
|
return out;
|
|
15505
15705
|
}
|
|
15506
|
-
function buildStack({ components = [], macros = null,
|
|
15706
|
+
function buildStack({ components = [], macros = null, intentHandlers = null } = {}) {
|
|
15507
15707
|
const stack = new ComponentStack();
|
|
15508
15708
|
stack.registerComponents(components);
|
|
15509
15709
|
if (macros) stack.registerMacros(macros);
|
|
15510
|
-
if (
|
|
15710
|
+
if (intentHandlers) stack.registerIntentHandlers(intentHandlers);
|
|
15511
15711
|
return stack;
|
|
15512
15712
|
}
|
|
15513
15713
|
async function driveStack(stack, value, phase, opts = {}) {
|
|
15514
|
-
const warn = opts.warn ?? console.warn;
|
|
15515
15714
|
const transactor = new Transactor(stack.comps, value);
|
|
15516
15715
|
if (opts.onMessage)
|
|
15517
15716
|
transactor.state.onChange(({ val, old, info }) => {
|
|
15518
15717
|
const t = info?.transaction;
|
|
15519
15718
|
opts.onMessage(
|
|
15520
|
-
{ kind: t?.handlerProp ?? "
|
|
15719
|
+
{ kind: t?.handlerProp ?? "receive", name: t?.name, args: t?.args, path: t?.path },
|
|
15521
15720
|
old,
|
|
15522
15721
|
val
|
|
15523
15722
|
);
|
|
15524
15723
|
});
|
|
15525
|
-
if (phaseHasBubble(phase))
|
|
15526
|
-
warn(
|
|
15527
|
-
"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."
|
|
15528
|
-
);
|
|
15529
15724
|
dispatchPhase(rootDispatcher(transactor), new Path([]), phase, value);
|
|
15530
15725
|
await transactor.settle();
|
|
15531
15726
|
return transactor.state.val;
|
|
@@ -15541,7 +15736,7 @@ async function runTests({
|
|
|
15541
15736
|
name = null,
|
|
15542
15737
|
grep = null,
|
|
15543
15738
|
bail = false,
|
|
15544
|
-
|
|
15739
|
+
intentHandlers = null,
|
|
15545
15740
|
macros = null
|
|
15546
15741
|
} = {}) {
|
|
15547
15742
|
const counts = { pass: 0, fail: 0, skip: 0, total: 0 };
|
|
@@ -15555,7 +15750,7 @@ async function runTests({
|
|
|
15555
15750
|
}
|
|
15556
15751
|
const { describe, test: test3, moduleTests } = makeCollector({ path, components });
|
|
15557
15752
|
let _stack = null;
|
|
15558
|
-
const getStack = () => _stack ??= buildStack({ components, macros,
|
|
15753
|
+
const getStack = () => _stack ??= buildStack({ components, macros, intentHandlers });
|
|
15559
15754
|
const driveLocal = (value, phase, opts = {}) => driveStack(getStack(), value, phase, opts);
|
|
15560
15755
|
await getTests({ describe, test: test3, expect: expect2, drive: driveLocal });
|
|
15561
15756
|
let bailed = false;
|
|
@@ -15621,7 +15816,7 @@ async function runTests({
|
|
|
15621
15816
|
}
|
|
15622
15817
|
|
|
15623
15818
|
// tools/format/console.js
|
|
15624
|
-
var
|
|
15819
|
+
var PASS2 = "color: #0a0; font-weight: bold";
|
|
15625
15820
|
var FAIL = "color: #c00; font-weight: bold";
|
|
15626
15821
|
var SKIP = "color: #888";
|
|
15627
15822
|
var DIM = "color: #888";
|
|
@@ -15636,7 +15831,7 @@ function reportTestNode(node) {
|
|
|
15636
15831
|
}
|
|
15637
15832
|
const dur = node.status === "skip" ? "" : ` (${Math.round(node.durationMs)}ms)`;
|
|
15638
15833
|
if (node.status === "pass") {
|
|
15639
|
-
console.log(`%c✓%c ${node.title}%c${dur}`,
|
|
15834
|
+
console.log(`%c✓%c ${node.title}%c${dur}`, PASS2, RESET, DIM);
|
|
15640
15835
|
} else if (node.status === "skip") {
|
|
15641
15836
|
console.log(`%c○ ${node.title}%c (skipped)`, SKIP, RESET);
|
|
15642
15837
|
} else {
|
|
@@ -15663,7 +15858,7 @@ function reportTestReportToConsole(report) {
|
|
|
15663
15858
|
const summary = `${c.pass} passed, ${c.fail} failed, ${c.skip} skipped (${c.total} total)`;
|
|
15664
15859
|
if (c.fail > 0) console.error(`%c${summary}`, FAIL);
|
|
15665
15860
|
else if (c.total === 0) console.log(`%c${summary}`, DIM);
|
|
15666
|
-
else console.log(`%c${summary}`,
|
|
15861
|
+
else console.log(`%c${summary}`, PASS2);
|
|
15667
15862
|
console.groupEnd();
|
|
15668
15863
|
}
|
|
15669
15864
|
return report;
|
|
@@ -15753,16 +15948,22 @@ function lintIdToMessage(id, info) {
|
|
|
15753
15948
|
}
|
|
15754
15949
|
case "UNKNOWN_HANDLER_ARG_NAME":
|
|
15755
15950
|
return `Unknown handler argument '${info.name}'${fmtOriginSuffix(info)}`;
|
|
15756
|
-
case "
|
|
15757
|
-
|
|
15758
|
-
|
|
15759
|
-
|
|
15760
|
-
case "
|
|
15951
|
+
case "RETIRED_HANDLER_BUCKET": {
|
|
15952
|
+
const which = info.names?.length ? ` (${info.names.join(", ")})` : "";
|
|
15953
|
+
return `\`${info.key}\` is no longer a handler bucket${which} — ${info.how}`;
|
|
15954
|
+
}
|
|
15955
|
+
case "RETIRED_CTX_VERB":
|
|
15956
|
+
return `ctx.${info.verb}() no longer exists — use ${info.to} (in \`${info.bucket}.${info.name}\`)`;
|
|
15957
|
+
case "HANDLER_NAME_COLLISION":
|
|
15958
|
+
return `${info.problem} — ${info.fix}`;
|
|
15959
|
+
case "RECEIVE_HANDLER_NOT_IMPLEMENTED":
|
|
15960
|
+
return `Receive handler '${info.name}' is not implemented${fmtEventSuffix(info)}`;
|
|
15961
|
+
case "RECEIVE_HANDLER_METHOD_NOT_IMPLEMENTED":
|
|
15761
15962
|
return `Method '$${info.name}' is not implemented${fmtEventSuffix(info)}`;
|
|
15762
|
-
case "
|
|
15763
|
-
return `'$${info.name}' is a method reference, but '${info.name}' is defined as
|
|
15764
|
-
case "
|
|
15765
|
-
return `'${info.name}' is
|
|
15963
|
+
case "RECEIVE_HANDLER_FOR_METHOD":
|
|
15964
|
+
return `'$${info.name}' is a method reference, but '${info.name}' is defined as a receive handler${fmtEventSuffix(info)}`;
|
|
15965
|
+
case "METHOD_FOR_RECEIVE_HANDLER":
|
|
15966
|
+
return `'${info.name}' is a receive handler reference, but '${info.name}' is defined as a method${fmtEventSuffix(info)}`;
|
|
15766
15967
|
case "FIELD_NAME_RESERVED_BY_RECORD":
|
|
15767
15968
|
return `Field '${info.name}' collides with the Immutable Record API: '.${info.name}' reads the Record member, not your value — rename the field (its value is only reachable via .get('${info.name}'))`;
|
|
15768
15969
|
case "FIELD_VAL_NOT_DEFINED":
|
|
@@ -16810,10 +17011,9 @@ function getComponentDoc(comp) {
|
|
|
16810
17011
|
name: k,
|
|
16811
17012
|
sig: getSignature(k, methods[k])
|
|
16812
17013
|
}));
|
|
16813
|
-
const
|
|
16814
|
-
|
|
16815
|
-
|
|
16816
|
-
}));
|
|
17014
|
+
const sigs = (bucket) => Object.keys(bucket ?? {}).map((k) => ({ name: k, sig: getSignature(k, bucket[k]) }));
|
|
17015
|
+
const receiveHandlers = sigs(comp.receive);
|
|
17016
|
+
const intentHandlers = sigs(comp.intent);
|
|
16817
17017
|
const fieldDocs = [];
|
|
16818
17018
|
for (const fieldName in fields) {
|
|
16819
17019
|
const field = fields[fieldName];
|
|
@@ -16824,7 +17024,13 @@ function getComponentDoc(comp) {
|
|
|
16824
17024
|
methods: getFieldMethods(field)
|
|
16825
17025
|
});
|
|
16826
17026
|
}
|
|
16827
|
-
return {
|
|
17027
|
+
return {
|
|
17028
|
+
name,
|
|
17029
|
+
methods: userMethods,
|
|
17030
|
+
receive: receiveHandlers,
|
|
17031
|
+
intent: intentHandlers,
|
|
17032
|
+
fields: fieldDocs
|
|
17033
|
+
};
|
|
16828
17034
|
}
|
|
16829
17035
|
function docComponents(normalized, { name = null } = {}) {
|
|
16830
17036
|
const comps = normalized.components;
|
|
@@ -16931,13 +17137,9 @@ export {
|
|
|
16931
17137
|
FIELD_VAL_IS_METHOD,
|
|
16932
17138
|
FIELD_VAL_NOT_DEFINED,
|
|
16933
17139
|
GLOBAL_SELECTOR_IN_SCOPED_STYLE,
|
|
17140
|
+
HANDLER_NAME_COLLISION,
|
|
16934
17141
|
IF_NO_BRANCH_SET,
|
|
16935
17142
|
Map2 as IMap,
|
|
16936
|
-
INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD,
|
|
16937
|
-
INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER,
|
|
16938
|
-
INPUT_HANDLER_METHOD_NOT_IMPLEMENTED,
|
|
16939
|
-
INPUT_HANDLER_NOT_IMPLEMENTED,
|
|
16940
|
-
INPUT_HANDLER_NOT_REFERENCED,
|
|
16941
17143
|
Set2 as ISet,
|
|
16942
17144
|
LOOKUP_BAD_SHAPE,
|
|
16943
17145
|
LOOKUP_TARGET_MALFORMED,
|
|
@@ -16950,6 +17152,7 @@ export {
|
|
|
16950
17152
|
List,
|
|
16951
17153
|
MAYBE_ADD_AT_PREFIX,
|
|
16952
17154
|
MAYBE_DROP_AT_PREFIX,
|
|
17155
|
+
METHOD_FOR_RECEIVE_HANDLER,
|
|
16953
17156
|
METHOD_VAL_IS_FIELD,
|
|
16954
17157
|
METHOD_VAL_NOT_DEFINED,
|
|
16955
17158
|
Map2 as Map,
|
|
@@ -16959,12 +17162,18 @@ export {
|
|
|
16959
17162
|
OrderedMap as OMap,
|
|
16960
17163
|
OrderedMap,
|
|
16961
17164
|
OrderedSet,
|
|
17165
|
+
PASS,
|
|
16962
17166
|
PLACEHOLDERLESS_TEMPLATE_STRING,
|
|
16963
17167
|
PROVIDE_NOT_ADDRESSABLE,
|
|
16964
17168
|
PairSorting,
|
|
16965
17169
|
ParseContext,
|
|
17170
|
+
RECEIVE_HANDLER_FOR_METHOD,
|
|
17171
|
+
RECEIVE_HANDLER_METHOD_NOT_IMPLEMENTED,
|
|
17172
|
+
RECEIVE_HANDLER_NOT_IMPLEMENTED,
|
|
16966
17173
|
REDUNDANT_TEMPLATE_STRING,
|
|
16967
17174
|
RENDER_IT_OUTSIDE_OF_LOOP,
|
|
17175
|
+
RETIRED_CTX_VERB,
|
|
17176
|
+
RETIRED_HANDLER_BUCKET,
|
|
16968
17177
|
Range,
|
|
16969
17178
|
Record,
|
|
16970
17179
|
RenderBatch,
|
|
@@ -17035,7 +17244,6 @@ export {
|
|
|
17035
17244
|
mergeDeep$1 as mergeDeep,
|
|
17036
17245
|
mergeDeepWith$1 as mergeDeepWith,
|
|
17037
17246
|
mergeWith$1 as mergeWith,
|
|
17038
|
-
phaseHasBubble,
|
|
17039
17247
|
phaseOps,
|
|
17040
17248
|
remove,
|
|
17041
17249
|
removeIn,
|