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-cli.js
CHANGED
|
@@ -4891,8 +4891,8 @@ function parseSequence(s, px) {
|
|
|
4891
4891
|
function parseMacroAttr(s, px) {
|
|
4892
4892
|
return _parseSingle(s, px, G_ALL);
|
|
4893
4893
|
}
|
|
4894
|
-
function
|
|
4895
|
-
return _parseHandler(s, px, "
|
|
4894
|
+
function parseReceiveHandler(s, px) {
|
|
4895
|
+
return _parseHandler(s, px, "receive", true, true);
|
|
4896
4896
|
}
|
|
4897
4897
|
function parseAlterHandler(s, px) {
|
|
4898
4898
|
const r = _parseHandler(s, px, "alter", false, false);
|
|
@@ -5222,7 +5222,7 @@ function getAttrParser(px) {
|
|
|
5222
5222
|
_attrParser.clear(px);
|
|
5223
5223
|
return _attrParser;
|
|
5224
5224
|
}
|
|
5225
|
-
var Attributes, booleanAttrsRaw, booleanAttrs, AttrParser, ConstAttrs, DynAttrs, BaseAttr, Attr, ConstAttr, RawHtmlAttr, NOT_SET_VAL, IfAttr, _attrParser, EventHandler,
|
|
5225
|
+
var Attributes, booleanAttrsRaw, booleanAttrs, AttrParser, ConstAttrs, DynAttrs, BaseAttr, Attr, ConstAttr, RawHtmlAttr, NOT_SET_VAL, IfAttr, _attrParser, EventHandler, IntentHandler;
|
|
5226
5226
|
var init_attribute = __esm({
|
|
5227
5227
|
"src/attribute.js"() {
|
|
5228
5228
|
init_value();
|
|
@@ -5482,11 +5482,11 @@ var init_attribute = __esm({
|
|
|
5482
5482
|
return [this.handlerVal.evalAsHandler(stack), argValues];
|
|
5483
5483
|
}
|
|
5484
5484
|
static parse(s, px) {
|
|
5485
|
-
const r =
|
|
5485
|
+
const r = parseReceiveHandler(s, px);
|
|
5486
5486
|
return r === null ? null : new _EventHandler(r.handlerVal, r.args);
|
|
5487
5487
|
}
|
|
5488
5488
|
};
|
|
5489
|
-
|
|
5489
|
+
IntentHandler = class {
|
|
5490
5490
|
constructor(name, fn) {
|
|
5491
5491
|
this.name = name;
|
|
5492
5492
|
this.fn = fn;
|
|
@@ -9174,6 +9174,9 @@ function checkComponent(Comp, lx = new LintContext(), { wellKnownExtras = EMPTY_
|
|
|
9174
9174
|
checkProvidesAreAddressable(lx, Comp);
|
|
9175
9175
|
checkLookupShapes(lx, Comp);
|
|
9176
9176
|
checkHandlersNotAsync(lx, Comp);
|
|
9177
|
+
checkRetiredBuckets(lx, Comp);
|
|
9178
|
+
checkRetiredCtxVerbs(lx, Comp);
|
|
9179
|
+
checkHandlerNameCollisions(lx, Comp);
|
|
9177
9180
|
checkScopedStyleTopLevel(lx, Comp);
|
|
9178
9181
|
const referencedAlters = /* @__PURE__ */ new Set();
|
|
9179
9182
|
const referencedInputs = /* @__PURE__ */ new Set();
|
|
@@ -9187,7 +9190,6 @@ function checkComponent(Comp, lx = new LintContext(), { wellKnownExtras = EMPTY_
|
|
|
9187
9190
|
);
|
|
9188
9191
|
}
|
|
9189
9192
|
checkUnreferencedAlterHandlers(lx, Comp, referencedAlters);
|
|
9190
|
-
checkUnreferencedInputHandlers(lx, Comp, referencedInputs);
|
|
9191
9193
|
checkUnreferencedDynamics(lx, Comp, referencedDynamics);
|
|
9192
9194
|
return lx;
|
|
9193
9195
|
});
|
|
@@ -9426,7 +9428,7 @@ function mkAttrValEnv(Comp, referencedAlters, referencedDynamics) {
|
|
|
9426
9428
|
return { fields, proto, scope, alter, referencedAlters, dynamicMap, referencedDynamics };
|
|
9427
9429
|
}
|
|
9428
9430
|
function checkEventHandlersHaveImpls(lx, Comp, referencedInputs) {
|
|
9429
|
-
const { input, views, Class } = Comp;
|
|
9431
|
+
const { receive: input, views, Class } = Comp;
|
|
9430
9432
|
const { prototype: proto } = Class;
|
|
9431
9433
|
for (const viewName in views) {
|
|
9432
9434
|
lx.push({ viewName }, () => {
|
|
@@ -9443,13 +9445,13 @@ function checkEventHandlersHaveImpls(lx, Comp, referencedInputs) {
|
|
|
9443
9445
|
if (input[name] === void 0) {
|
|
9444
9446
|
const isMethodFix = protoHasMethod(proto, name);
|
|
9445
9447
|
lx.error(
|
|
9446
|
-
|
|
9448
|
+
RECEIVE_HANDLER_NOT_IMPLEMENTED,
|
|
9447
9449
|
{ name, handler, event, eventName, originAttr },
|
|
9448
9450
|
isMethodFix ? { kind: "add-prefix", from: name, to: `$${name}` } : replaceNameSuggestion(name, Object.keys(input))
|
|
9449
9451
|
);
|
|
9450
9452
|
if (isMethodFix) {
|
|
9451
9453
|
lx.hint(
|
|
9452
|
-
|
|
9454
|
+
METHOD_FOR_RECEIVE_HANDLER,
|
|
9453
9455
|
{ name, handler, event, eventName, originAttr },
|
|
9454
9456
|
{ kind: "add-prefix", from: name, to: `$${name}` }
|
|
9455
9457
|
);
|
|
@@ -9461,13 +9463,13 @@ function checkEventHandlersHaveImpls(lx, Comp, referencedInputs) {
|
|
|
9461
9463
|
if (!protoHasMethod(proto, name)) {
|
|
9462
9464
|
const isInputFix = input[name] !== void 0;
|
|
9463
9465
|
lx.error(
|
|
9464
|
-
|
|
9466
|
+
RECEIVE_HANDLER_METHOD_NOT_IMPLEMENTED,
|
|
9465
9467
|
{ name, handler, event, eventName, originAttr },
|
|
9466
9468
|
isInputFix ? { kind: "drop-prefix", from: `$${name}`, to: name } : replaceNameSuggestion(name, collectProtoMethodNames(proto))
|
|
9467
9469
|
);
|
|
9468
9470
|
if (isInputFix) {
|
|
9469
9471
|
lx.hint(
|
|
9470
|
-
|
|
9472
|
+
RECEIVE_HANDLER_FOR_METHOD,
|
|
9471
9473
|
{ name, handler, event, eventName, originAttr },
|
|
9472
9474
|
{ kind: "drop-prefix", from: `$${name}`, to: name }
|
|
9473
9475
|
);
|
|
@@ -9616,12 +9618,57 @@ function checkConsistentAttrs(lx, Comp, referencedAlters, referencedDynamics) {
|
|
|
9616
9618
|
});
|
|
9617
9619
|
}
|
|
9618
9620
|
}
|
|
9621
|
+
function checkRetiredBuckets(lx, Comp) {
|
|
9622
|
+
const extra = Comp.extra;
|
|
9623
|
+
if (!extra) return;
|
|
9624
|
+
for (const key of Object.keys(extra)) {
|
|
9625
|
+
if (!Object.hasOwn(RETIRED_BUCKETS, key)) continue;
|
|
9626
|
+
const retired = RETIRED_BUCKETS[key];
|
|
9627
|
+
const names = isPlainObj(extra[key]) ? Object.keys(extra[key]) : [];
|
|
9628
|
+
lx.error(RETIRED_HANDLER_BUCKET, { key, to: retired.to, how: retired.how, names });
|
|
9629
|
+
}
|
|
9630
|
+
}
|
|
9631
|
+
function checkRetiredCtxVerbs(lx, Comp) {
|
|
9632
|
+
for (const bucket of ["receive", "intent", "alter", "methods", "statics"]) {
|
|
9633
|
+
const block = bucket === "methods" || bucket === "statics" ? Comp.spec?.[bucket] : Comp[bucket];
|
|
9634
|
+
if (!isPlainObj(block)) continue;
|
|
9635
|
+
for (const name in block) {
|
|
9636
|
+
const fn = block[name];
|
|
9637
|
+
if (typeof fn !== "function") continue;
|
|
9638
|
+
const src = String(fn);
|
|
9639
|
+
for (const m of src.matchAll(RETIRED_CTX_RE)) {
|
|
9640
|
+
const verb = m[1];
|
|
9641
|
+
lx.error(RETIRED_CTX_VERB, { verb, to: RETIRED_CTX_VERBS[verb], bucket, name });
|
|
9642
|
+
}
|
|
9643
|
+
}
|
|
9644
|
+
}
|
|
9645
|
+
}
|
|
9646
|
+
function checkHandlerNameCollisions(lx, Comp) {
|
|
9647
|
+
const receive = Comp.receive ?? {};
|
|
9648
|
+
const intent = Comp.intent ?? {};
|
|
9649
|
+
for (const name in intent)
|
|
9650
|
+
if (receive[name] !== void 0)
|
|
9651
|
+
lx.error(HANDLER_NAME_COLLISION, {
|
|
9652
|
+
name,
|
|
9653
|
+
problem: `"${name}" is declared in both \`receive\` and \`intent\``,
|
|
9654
|
+
fix: "one is addressed and one is routed; give them different names so a reader can tell which a call site meant"
|
|
9655
|
+
});
|
|
9656
|
+
for (const name in receive)
|
|
9657
|
+
for (const suffix of ["Ok", "Error", "Unhandled"])
|
|
9658
|
+
if (receive[name + suffix] !== void 0 && intent[name] === void 0)
|
|
9659
|
+
lx.warn(HANDLER_NAME_COLLISION, {
|
|
9660
|
+
name,
|
|
9661
|
+
problem: `\`receive\` declares both "${name}" and its answer name "${name + suffix}"`,
|
|
9662
|
+
fix: `an intent named "${name}" would dispatch its answer to "${name + suffix}" - rename one unless that is what you meant`
|
|
9663
|
+
});
|
|
9664
|
+
}
|
|
9619
9665
|
function checkUnknownSpecKeys(lx, Comp, wellKnownExtras) {
|
|
9620
9666
|
const extra = Comp.extra;
|
|
9621
9667
|
if (!extra) return;
|
|
9622
9668
|
let candidates = null;
|
|
9623
9669
|
for (const key of Object.keys(extra)) {
|
|
9624
9670
|
if (FRAMEWORK_WELL_KNOWN_EXTRAS.has(key) || wellKnownExtras.has(key)) continue;
|
|
9671
|
+
if (Object.hasOwn(RETIRED_BUCKETS, key)) continue;
|
|
9625
9672
|
candidates ??= [
|
|
9626
9673
|
...KNOWN_COMPONENT_SPEC_KEYS,
|
|
9627
9674
|
...FRAMEWORK_WELL_KNOWN_EXTRAS,
|
|
@@ -9669,13 +9716,6 @@ function checkUnreferencedAlterHandlers(lx, Comp, referencedAlters) {
|
|
|
9669
9716
|
}
|
|
9670
9717
|
}
|
|
9671
9718
|
}
|
|
9672
|
-
function checkUnreferencedInputHandlers(lx, Comp, referencedInputs) {
|
|
9673
|
-
for (const name in Comp.input) {
|
|
9674
|
-
if (!referencedInputs.has(name)) {
|
|
9675
|
-
lx.hint(INPUT_HANDLER_NOT_REFERENCED, { name });
|
|
9676
|
-
}
|
|
9677
|
-
}
|
|
9678
|
-
}
|
|
9679
9719
|
function checkHandlersNotAsync(lx, Comp) {
|
|
9680
9720
|
for (const channel of HANDLER_CHANNELS) {
|
|
9681
9721
|
const block = Comp[channel];
|
|
@@ -9794,7 +9834,7 @@ function checkUnreferencedDynamics(lx, Comp, referencedDynamics) {
|
|
|
9794
9834
|
}
|
|
9795
9835
|
}
|
|
9796
9836
|
}
|
|
9797
|
-
var KNOWN_COMPONENT_SPEC_KEYS, EMPTY_SET2, FRAMEWORK_WELL_KNOWN_EXTRAS, KNOWN_DIRECTIVE_NAMES, ALT_HANDLER_NOT_DEFINED, ALT_HANDLER_NOT_REFERENCED, DYN_VAL_NOT_DEFINED, DYN_ALIAS_NOT_REFERENCED, PROVIDE_NOT_ADDRESSABLE, LOOKUP_BAD_SHAPE, LOOKUP_TARGET_MALFORMED, RENDER_IT_OUTSIDE_OF_LOOP, UNKNOWN_EVENT_MODIFIER, UNKNOWN_HANDLER_ARG_NAME,
|
|
9837
|
+
var KNOWN_COMPONENT_SPEC_KEYS, EMPTY_SET2, FRAMEWORK_WELL_KNOWN_EXTRAS, KNOWN_DIRECTIVE_NAMES, ALT_HANDLER_NOT_DEFINED, ALT_HANDLER_NOT_REFERENCED, DYN_VAL_NOT_DEFINED, DYN_ALIAS_NOT_REFERENCED, PROVIDE_NOT_ADDRESSABLE, LOOKUP_BAD_SHAPE, LOOKUP_TARGET_MALFORMED, RENDER_IT_OUTSIDE_OF_LOOP, UNKNOWN_EVENT_MODIFIER, UNKNOWN_HANDLER_ARG_NAME, RECEIVE_HANDLER_NOT_IMPLEMENTED, RETIRED_HANDLER_BUCKET, RETIRED_CTX_VERB, HANDLER_NAME_COLLISION, RECEIVE_HANDLER_METHOD_NOT_IMPLEMENTED, RECEIVE_HANDLER_FOR_METHOD, METHOD_FOR_RECEIVE_HANDLER, FIELD_VAL_NOT_DEFINED, FIELD_VAL_IS_METHOD, METHOD_VAL_NOT_DEFINED, METHOD_VAL_IS_FIELD, DUPLICATE_ATTR_DEFINITION, IF_NO_BRANCH_SET, UNKNOWN_COMPONENT_NAME, UNKNOWN_MACRO_ARG, UNKNOWN_DIRECTIVE, UNKNOWN_X_OP, UNKNOWN_X_ATTR, X_OP_IGNORES_CHILDREN, MAYBE_DROP_AT_PREFIX, MAYBE_ADD_AT_PREFIX, DEPRECATED_BARE_X_DIRECTIVE, BAD_VALUE, UNSUPPORTED_EXPR_SYNTAX, BINDING_MEMBER_TOO_DEEP, SUGGEST_BINDING_MEMBER, REDUNDANT_TEMPLATE_STRING, PLACEHOLDERLESS_TEMPLATE_STRING, CONSTANT_CONDITION, UNKNOWN_COMPONENT_SPEC_KEY, COMP_FIELD_BAD_SHAPE, ASYNC_HANDLER, TOP_LEVEL_AT_RULE_IN_SCOPED_STYLE, GLOBAL_SELECTOR_IN_SCOPED_STYLE, FIELD_NAME_RESERVED_BY_RECORD, RECORD_FIELD_NAME_COLLISIONS, X_KNOWN_OP_NAMES, X_KNOWN_ATTR_NAMES, HOST_DIRECTIVE_ONLY_NAMES, LEVEL_WARN2, LEVEL_ERROR2, LEVEL_HINT, PARSE_ISSUES, BINDING_MEMBER_TOO_DEEP_RE, UNSUPPORTED_EXPR_GUIDANCE, HTML_LINT_OPTS, NO_WRAPPERS2, ANY_EVENT_MODIFIERS, KNOWN_HANDLER_NAMES, fixTo, BOOL_CONDITION_ORIGINS, isBoolConditionCtx, ATTR_VAL_CHECKERS, NODE_KIND_TO_CTX, isPlainObj, RETIRED_BUCKETS, RETIRED_CTX_VERBS, RETIRED_CTX_RE, HANDLER_CHANNELS, ASYNC_HANDLER_HELP, NON_NESTABLE_AT_RULE, GLOBAL_LEADING_SELECTOR, IGNORE_DIRECTIVE, blankRun, STYLE_TO_GLOBAL_HELP, KNOWN_LOOKUP_KEYS, LintContext, LintParseContext;
|
|
9798
9838
|
var init_lint_check = __esm({
|
|
9799
9839
|
"tools/core/lint-check.js"() {
|
|
9800
9840
|
init_anode();
|
|
@@ -9806,7 +9846,7 @@ var init_lint_check = __esm({
|
|
|
9806
9846
|
)
|
|
9807
9847
|
);
|
|
9808
9848
|
EMPTY_SET2 = /* @__PURE__ */ new Set();
|
|
9809
|
-
FRAMEWORK_WELL_KNOWN_EXTRAS = /* @__PURE__ */ new Set(["
|
|
9849
|
+
FRAMEWORK_WELL_KNOWN_EXTRAS = /* @__PURE__ */ new Set(["intentOverridesField"]);
|
|
9810
9850
|
KNOWN_DIRECTIVE_NAMES = /* @__PURE__ */ new Set([
|
|
9811
9851
|
"dangerouslysetinnerhtml",
|
|
9812
9852
|
"slot",
|
|
@@ -9831,11 +9871,13 @@ var init_lint_check = __esm({
|
|
|
9831
9871
|
RENDER_IT_OUTSIDE_OF_LOOP = "RENDER_IT_OUTSIDE_OF_LOOP";
|
|
9832
9872
|
UNKNOWN_EVENT_MODIFIER = "UNKNOWN_EVENT_MODIFIER";
|
|
9833
9873
|
UNKNOWN_HANDLER_ARG_NAME = "UNKNOWN_HANDLER_ARG_NAME";
|
|
9834
|
-
|
|
9835
|
-
|
|
9836
|
-
|
|
9837
|
-
|
|
9838
|
-
|
|
9874
|
+
RECEIVE_HANDLER_NOT_IMPLEMENTED = "RECEIVE_HANDLER_NOT_IMPLEMENTED";
|
|
9875
|
+
RETIRED_HANDLER_BUCKET = "RETIRED_HANDLER_BUCKET";
|
|
9876
|
+
RETIRED_CTX_VERB = "RETIRED_CTX_VERB";
|
|
9877
|
+
HANDLER_NAME_COLLISION = "HANDLER_NAME_COLLISION";
|
|
9878
|
+
RECEIVE_HANDLER_METHOD_NOT_IMPLEMENTED = "RECEIVE_HANDLER_METHOD_NOT_IMPLEMENTED";
|
|
9879
|
+
RECEIVE_HANDLER_FOR_METHOD = "RECEIVE_HANDLER_FOR_METHOD";
|
|
9880
|
+
METHOD_FOR_RECEIVE_HANDLER = "METHOD_FOR_RECEIVE_HANDLER";
|
|
9839
9881
|
FIELD_VAL_NOT_DEFINED = "FIELD_VAL_NOT_DEFINED";
|
|
9840
9882
|
FIELD_VAL_IS_METHOD = "FIELD_VAL_IS_METHOD";
|
|
9841
9883
|
METHOD_VAL_NOT_DEFINED = "METHOD_VAL_NOT_DEFINED";
|
|
@@ -10028,8 +10070,34 @@ var init_lint_check = __esm({
|
|
|
10028
10070
|
HideNode: { originAttr: "<x hide>" },
|
|
10029
10071
|
PushViewNameNode: { originAttr: "<x push-view>" }
|
|
10030
10072
|
};
|
|
10031
|
-
|
|
10032
|
-
|
|
10073
|
+
isPlainObj = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
10074
|
+
RETIRED_BUCKETS = {
|
|
10075
|
+
input: {
|
|
10076
|
+
to: "receive",
|
|
10077
|
+
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"
|
|
10078
|
+
},
|
|
10079
|
+
bubble: {
|
|
10080
|
+
to: "intent",
|
|
10081
|
+
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"
|
|
10082
|
+
},
|
|
10083
|
+
response: {
|
|
10084
|
+
to: "receive",
|
|
10085
|
+
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"
|
|
10086
|
+
}
|
|
10087
|
+
};
|
|
10088
|
+
RETIRED_CTX_VERBS = {
|
|
10089
|
+
bubble: 'ctx.intent(name, args, { route: ["dyn"] }) - the route says it walks the ancestors, where the verb used to',
|
|
10090
|
+
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',
|
|
10091
|
+
stopPropagation: "ctx.stop()",
|
|
10092
|
+
inputAtPath: "ctx.sendAtPath(path, name, args)",
|
|
10093
|
+
requestAtPath: 'ctx.intentAtPath(path, name, args, { route: ["lex"] })'
|
|
10094
|
+
};
|
|
10095
|
+
RETIRED_CTX_RE = new RegExp(
|
|
10096
|
+
`\\bctx\\.(${Object.keys(RETIRED_CTX_VERBS).join("|")})\\s*\\(`,
|
|
10097
|
+
"g"
|
|
10098
|
+
);
|
|
10099
|
+
HANDLER_CHANNELS = ["receive", "intent", "alter"];
|
|
10100
|
+
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.`;
|
|
10033
10101
|
NON_NESTABLE_AT_RULE = /(?:^|[\s;{}])@(?:-[a-z]+-)?(charset|import|namespace|font-face|keyframes|page|property|counter-style|font-feature-values|font-palette-values|view-transition)\b/gi;
|
|
10034
10102
|
GLOBAL_LEADING_SELECTOR = /(?:^|[{}])\s*(html|body|:root)\b[^{};]*\{/gi;
|
|
10035
10103
|
IGNORE_DIRECTIVE = /\/\*\s*tutuca-lint-ignore\s*\*\//g;
|
|
@@ -10135,36 +10203,30 @@ var init_lint_rules = __esm({
|
|
|
10135
10203
|
group: "Field / method references",
|
|
10136
10204
|
summary: "`$name` calls a method, but `name` is a field — use `.name`."
|
|
10137
10205
|
},
|
|
10138
|
-
//
|
|
10206
|
+
// Receive-handler / method confusion
|
|
10139
10207
|
{
|
|
10140
|
-
code:
|
|
10208
|
+
code: RECEIVE_HANDLER_NOT_IMPLEMENTED,
|
|
10141
10209
|
level: "error",
|
|
10142
|
-
group: "
|
|
10143
|
-
summary: "Bare handler name has no entry in `
|
|
10210
|
+
group: "Receive-handler / method confusion",
|
|
10211
|
+
summary: "Bare handler name has no entry in `receive`."
|
|
10144
10212
|
},
|
|
10145
10213
|
{
|
|
10146
|
-
code:
|
|
10214
|
+
code: RECEIVE_HANDLER_METHOD_NOT_IMPLEMENTED,
|
|
10147
10215
|
level: "error",
|
|
10148
|
-
group: "
|
|
10216
|
+
group: "Receive-handler / method confusion",
|
|
10149
10217
|
summary: "`$handler` has no entry in `methods`."
|
|
10150
10218
|
},
|
|
10151
10219
|
{
|
|
10152
|
-
code:
|
|
10220
|
+
code: RECEIVE_HANDLER_FOR_METHOD,
|
|
10153
10221
|
level: "hint",
|
|
10154
|
-
group: "
|
|
10155
|
-
summary: "`$name` is a method reference, but `name` is
|
|
10222
|
+
group: "Receive-handler / method confusion",
|
|
10223
|
+
summary: "`$name` is a method reference, but `name` is a receive handler — drop the `$`."
|
|
10156
10224
|
},
|
|
10157
10225
|
{
|
|
10158
|
-
code:
|
|
10226
|
+
code: METHOD_FOR_RECEIVE_HANDLER,
|
|
10159
10227
|
level: "hint",
|
|
10160
|
-
group: "
|
|
10161
|
-
summary: "Bare `name` is
|
|
10162
|
-
},
|
|
10163
|
-
{
|
|
10164
|
-
code: INPUT_HANDLER_NOT_REFERENCED,
|
|
10165
|
-
level: "hint",
|
|
10166
|
-
group: "Input-handler / method confusion",
|
|
10167
|
-
summary: "`input` entry is defined but never wired to an `@on.*` event."
|
|
10228
|
+
group: "Receive-handler / method confusion",
|
|
10229
|
+
summary: "Bare `name` is a receive-handler reference, but `name` is a method — add `$`."
|
|
10168
10230
|
},
|
|
10169
10231
|
// Iteration helpers (`alter`)
|
|
10170
10232
|
{
|
|
@@ -10348,12 +10410,33 @@ var init_lint_rules = __esm({
|
|
|
10348
10410
|
group: "Component spec",
|
|
10349
10411
|
summary: "`component({...})` has an unrecognized key; its value is ignored at runtime."
|
|
10350
10412
|
},
|
|
10413
|
+
// Retired four-channel vocabulary (TEMPORARY — remove a couple of releases after
|
|
10414
|
+
// the two-channel change). These exist to drive the migration: the linter reports
|
|
10415
|
+
// every site and says what replaces it, and a clean run IS the finished migration.
|
|
10416
|
+
{
|
|
10417
|
+
code: "RETIRED_HANDLER_BUCKET",
|
|
10418
|
+
level: "error",
|
|
10419
|
+
group: "Retired dispatch vocabulary",
|
|
10420
|
+
summary: "`input`, `bubble` and `response` are no longer handler buckets — four dispatch channels became two, `receive` (addressed) and `intent` (routed)."
|
|
10421
|
+
},
|
|
10422
|
+
{
|
|
10423
|
+
code: "RETIRED_CTX_VERB",
|
|
10424
|
+
level: "error",
|
|
10425
|
+
group: "Retired dispatch vocabulary",
|
|
10426
|
+
summary: "`ctx.bubble`, `ctx.request` and `ctx.stopPropagation` are gone — one `ctx.intent` carries a route, and `ctx.stop()` ends a walk."
|
|
10427
|
+
},
|
|
10428
|
+
{
|
|
10429
|
+
code: "HANDLER_NAME_COLLISION",
|
|
10430
|
+
level: "error",
|
|
10431
|
+
group: "Retired dispatch vocabulary",
|
|
10432
|
+
summary: "Two handlers collapse onto one name, or a `receive` arm collides with an intent's derived answer name."
|
|
10433
|
+
},
|
|
10351
10434
|
// Handler effects
|
|
10352
10435
|
{
|
|
10353
10436
|
code: ASYNC_HANDLER,
|
|
10354
10437
|
level: "error",
|
|
10355
10438
|
group: "Handler effects",
|
|
10356
|
-
summary: "A handler in `
|
|
10439
|
+
summary: "A handler in `receive`/`intent`/`alter` is an async function — handlers must be synchronous. Put the async work in a scope-registered intent handler and reach it with `ctx.intent`, or coordinate with `ctx.send`."
|
|
10357
10440
|
},
|
|
10358
10441
|
// Component styles
|
|
10359
10442
|
{
|
|
@@ -10480,16 +10563,22 @@ function lintIdToMessage(id, info) {
|
|
|
10480
10563
|
}
|
|
10481
10564
|
case "UNKNOWN_HANDLER_ARG_NAME":
|
|
10482
10565
|
return `Unknown handler argument '${info.name}'${fmtOriginSuffix(info)}`;
|
|
10483
|
-
case "
|
|
10484
|
-
|
|
10485
|
-
|
|
10486
|
-
|
|
10487
|
-
case "
|
|
10566
|
+
case "RETIRED_HANDLER_BUCKET": {
|
|
10567
|
+
const which = info.names?.length ? ` (${info.names.join(", ")})` : "";
|
|
10568
|
+
return `\`${info.key}\` is no longer a handler bucket${which} — ${info.how}`;
|
|
10569
|
+
}
|
|
10570
|
+
case "RETIRED_CTX_VERB":
|
|
10571
|
+
return `ctx.${info.verb}() no longer exists — use ${info.to} (in \`${info.bucket}.${info.name}\`)`;
|
|
10572
|
+
case "HANDLER_NAME_COLLISION":
|
|
10573
|
+
return `${info.problem} — ${info.fix}`;
|
|
10574
|
+
case "RECEIVE_HANDLER_NOT_IMPLEMENTED":
|
|
10575
|
+
return `Receive handler '${info.name}' is not implemented${fmtEventSuffix(info)}`;
|
|
10576
|
+
case "RECEIVE_HANDLER_METHOD_NOT_IMPLEMENTED":
|
|
10488
10577
|
return `Method '$${info.name}' is not implemented${fmtEventSuffix(info)}`;
|
|
10489
|
-
case "
|
|
10490
|
-
return `'$${info.name}' is a method reference, but '${info.name}' is defined as
|
|
10491
|
-
case "
|
|
10492
|
-
return `'${info.name}' is
|
|
10578
|
+
case "RECEIVE_HANDLER_FOR_METHOD":
|
|
10579
|
+
return `'$${info.name}' is a method reference, but '${info.name}' is defined as a receive handler${fmtEventSuffix(info)}`;
|
|
10580
|
+
case "METHOD_FOR_RECEIVE_HANDLER":
|
|
10581
|
+
return `'${info.name}' is a receive handler reference, but '${info.name}' is defined as a method${fmtEventSuffix(info)}`;
|
|
10493
10582
|
case "FIELD_NAME_RESERVED_BY_RECORD":
|
|
10494
10583
|
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}'))`;
|
|
10495
10584
|
case "FIELD_VAL_NOT_DEFINED":
|
|
@@ -10677,7 +10766,7 @@ function fmtModuleInfo(info) {
|
|
|
10677
10766
|
lines.push(`Exports: ${[...info.present].join(", ") || "(none)"}`);
|
|
10678
10767
|
lines.push(`Components: ${info.counts.components}`);
|
|
10679
10768
|
lines.push(`Macros: ${info.counts.macros}`);
|
|
10680
|
-
lines.push(`Request handlers: ${info.counts.
|
|
10769
|
+
lines.push(`Request handlers: ${info.counts.intentHandlers}`);
|
|
10681
10770
|
lines.push(`Examples: ${info.counts.examples} (sections: ${info.counts.sections})`);
|
|
10682
10771
|
if (info.warnings.length) {
|
|
10683
10772
|
lines.push("");
|
|
@@ -10730,9 +10819,13 @@ function fmtComponentDocs(docs) {
|
|
|
10730
10819
|
lines.push(" methods:");
|
|
10731
10820
|
for (const m of c.methods) lines.push(` ${m.sig}`);
|
|
10732
10821
|
}
|
|
10733
|
-
|
|
10734
|
-
|
|
10735
|
-
|
|
10822
|
+
for (const [label, bucket] of [
|
|
10823
|
+
["receive", c.receive],
|
|
10824
|
+
["intent", c.intent]
|
|
10825
|
+
]) {
|
|
10826
|
+
if (!bucket?.length) continue;
|
|
10827
|
+
lines.push(` ${label}:`);
|
|
10828
|
+
for (const m of bucket) lines.push(` ${m.sig}`);
|
|
10736
10829
|
}
|
|
10737
10830
|
if (c.fields.length) {
|
|
10738
10831
|
lines.push(" fields:");
|
|
@@ -10928,9 +11021,14 @@ function fmtComponentDocs2(docs) {
|
|
|
10928
11021
|
}
|
|
10929
11022
|
lines.push("");
|
|
10930
11023
|
}
|
|
10931
|
-
|
|
10932
|
-
|
|
10933
|
-
|
|
11024
|
+
for (const [title, bucket] of [
|
|
11025
|
+
["Receive Handlers", comp.receive],
|
|
11026
|
+
["Intent Handlers", comp.intent]
|
|
11027
|
+
]) {
|
|
11028
|
+
if (!bucket?.length) continue;
|
|
11029
|
+
lines.push(`## ${title}
|
|
11030
|
+
`);
|
|
11031
|
+
for (const m of bucket) {
|
|
10934
11032
|
lines.push(`- \`${m.sig}\``);
|
|
10935
11033
|
}
|
|
10936
11034
|
lines.push("");
|
|
@@ -11016,7 +11114,7 @@ function fmtModuleInfo2(info) {
|
|
|
11016
11114
|
lines.push(`- Exports: ${[...info.present].map((k) => `\`${k}\``).join(", ") || "(none)"}`);
|
|
11017
11115
|
lines.push(`- Components: ${info.counts.components}`);
|
|
11018
11116
|
lines.push(`- Macros: ${info.counts.macros}`);
|
|
11019
|
-
lines.push(`- Request handlers: ${info.counts.
|
|
11117
|
+
lines.push(`- Request handlers: ${info.counts.intentHandlers}`);
|
|
11020
11118
|
lines.push(`- Examples: ${info.counts.examples} (sections: ${info.counts.sections})`);
|
|
11021
11119
|
if (info.warnings.length) {
|
|
11022
11120
|
lines.push("", "## Warnings", "");
|
|
@@ -15513,9 +15611,9 @@ function parseExample(raw, index, components, parentPath) {
|
|
|
15513
15611
|
if (raw.value === void 0) {
|
|
15514
15612
|
throw shapeError(`example at ${where} missing "value"`, where);
|
|
15515
15613
|
}
|
|
15516
|
-
const rh = raw.
|
|
15614
|
+
const rh = raw.intentHandlers;
|
|
15517
15615
|
if (rh != null && (typeof rh !== "object" || Array.isArray(rh))) {
|
|
15518
|
-
throw shapeError(`example at ${where} "
|
|
15616
|
+
throw shapeError(`example at ${where} "intentHandlers" must be an object of functions`, where);
|
|
15519
15617
|
}
|
|
15520
15618
|
const on = raw.on;
|
|
15521
15619
|
if (on != null) {
|
|
@@ -15534,7 +15632,7 @@ function parseExample(raw, index, components, parentPath) {
|
|
|
15534
15632
|
}
|
|
15535
15633
|
if (!isPlainObject2(on[key])) {
|
|
15536
15634
|
throw shapeError(
|
|
15537
|
-
`example at ${where} "on.${key}" must be an object of actions (send,
|
|
15635
|
+
`example at ${where} "on.${key}" must be an object of actions (send, intent, do)`,
|
|
15538
15636
|
where
|
|
15539
15637
|
);
|
|
15540
15638
|
}
|
|
@@ -15546,7 +15644,7 @@ function parseExample(raw, index, components, parentPath) {
|
|
|
15546
15644
|
value: raw.value,
|
|
15547
15645
|
view: raw.view ?? "main",
|
|
15548
15646
|
componentName: resolveComponentName(raw.value, components),
|
|
15549
|
-
|
|
15647
|
+
intentHandlers: rh ?? null,
|
|
15550
15648
|
requestHandlerNames: rh ? Object.keys(rh) : [],
|
|
15551
15649
|
on: on ?? null
|
|
15552
15650
|
});
|
|
@@ -15590,7 +15688,7 @@ function normalizeModule(mod, { path = null } = {}) {
|
|
|
15590
15688
|
for (const key of [
|
|
15591
15689
|
"getComponents",
|
|
15592
15690
|
"getMacros",
|
|
15593
|
-
"
|
|
15691
|
+
"getIntentHandlers",
|
|
15594
15692
|
"getExamples",
|
|
15595
15693
|
"getTests",
|
|
15596
15694
|
"getRoot"
|
|
@@ -15599,7 +15697,7 @@ function normalizeModule(mod, { path = null } = {}) {
|
|
|
15599
15697
|
}
|
|
15600
15698
|
const components = present.has("getComponents") ? mod.getComponents() : [];
|
|
15601
15699
|
const macros = present.has("getMacros") ? mod.getMacros() : null;
|
|
15602
|
-
const
|
|
15700
|
+
const intentHandlers = present.has("getIntentHandlers") ? mod.getIntentHandlers() : null;
|
|
15603
15701
|
const root = present.has("getRoot") ? mod.getRoot() : null;
|
|
15604
15702
|
const sections = present.has("getExamples") ? parseSections(mod.getExamples(), components) : [];
|
|
15605
15703
|
return {
|
|
@@ -15608,7 +15706,7 @@ function normalizeModule(mod, { path = null } = {}) {
|
|
|
15608
15706
|
path,
|
|
15609
15707
|
components,
|
|
15610
15708
|
macros,
|
|
15611
|
-
|
|
15709
|
+
intentHandlers,
|
|
15612
15710
|
sections,
|
|
15613
15711
|
root
|
|
15614
15712
|
}),
|
|
@@ -15653,7 +15751,7 @@ var init_module = __esm({
|
|
|
15653
15751
|
value,
|
|
15654
15752
|
view = "main",
|
|
15655
15753
|
componentName = null,
|
|
15656
|
-
|
|
15754
|
+
intentHandlers = null,
|
|
15657
15755
|
requestHandlerNames = [],
|
|
15658
15756
|
on = null
|
|
15659
15757
|
}) {
|
|
@@ -15662,7 +15760,7 @@ var init_module = __esm({
|
|
|
15662
15760
|
this.value = value;
|
|
15663
15761
|
this.view = view;
|
|
15664
15762
|
this.componentName = componentName;
|
|
15665
|
-
this.
|
|
15763
|
+
this.intentHandlers = intentHandlers;
|
|
15666
15764
|
this.requestHandlerNames = requestHandlerNames;
|
|
15667
15765
|
this.on = on;
|
|
15668
15766
|
}
|
|
@@ -15677,12 +15775,12 @@ var init_module = __esm({
|
|
|
15677
15775
|
}
|
|
15678
15776
|
};
|
|
15679
15777
|
NormalizedModule = class {
|
|
15680
|
-
constructor({ mod, path = null, components, macros,
|
|
15778
|
+
constructor({ mod, path = null, components, macros, intentHandlers, sections, root }) {
|
|
15681
15779
|
this.mod = mod;
|
|
15682
15780
|
this.path = path;
|
|
15683
15781
|
this.components = components;
|
|
15684
15782
|
this.macros = macros;
|
|
15685
|
-
this.
|
|
15783
|
+
this.intentHandlers = intentHandlers;
|
|
15686
15784
|
this.sections = sections;
|
|
15687
15785
|
this.root = root;
|
|
15688
15786
|
}
|
|
@@ -15840,7 +15938,7 @@ function describeModule(mod, { path = null } = {}) {
|
|
|
15840
15938
|
const counts = {
|
|
15841
15939
|
components: normalized.components.length,
|
|
15842
15940
|
macros: normalized.macros ? Object.keys(normalized.macros).length : 0,
|
|
15843
|
-
|
|
15941
|
+
intentHandlers: normalized.intentHandlers ? Object.keys(normalized.intentHandlers).length : 0,
|
|
15844
15942
|
examples: normalized.sections.reduce((n, s) => n + s.items.length, 0),
|
|
15845
15943
|
sections: normalized.sections.length
|
|
15846
15944
|
};
|
|
@@ -16027,10 +16125,9 @@ function getComponentDoc(comp) {
|
|
|
16027
16125
|
name: k,
|
|
16028
16126
|
sig: getSignature(k, methods[k])
|
|
16029
16127
|
}));
|
|
16030
|
-
const
|
|
16031
|
-
|
|
16032
|
-
|
|
16033
|
-
}));
|
|
16128
|
+
const sigs = (bucket) => Object.keys(bucket ?? {}).map((k) => ({ name: k, sig: getSignature(k, bucket[k]) }));
|
|
16129
|
+
const receiveHandlers = sigs(comp.receive);
|
|
16130
|
+
const intentHandlers = sigs(comp.intent);
|
|
16034
16131
|
const fieldDocs = [];
|
|
16035
16132
|
for (const fieldName in fields) {
|
|
16036
16133
|
const field = fields[fieldName];
|
|
@@ -16041,7 +16138,13 @@ function getComponentDoc(comp) {
|
|
|
16041
16138
|
methods: getFieldMethods(field)
|
|
16042
16139
|
});
|
|
16043
16140
|
}
|
|
16044
|
-
return {
|
|
16141
|
+
return {
|
|
16142
|
+
name,
|
|
16143
|
+
methods: userMethods,
|
|
16144
|
+
receive: receiveHandlers,
|
|
16145
|
+
intent: intentHandlers,
|
|
16146
|
+
fields: fieldDocs
|
|
16147
|
+
};
|
|
16045
16148
|
}
|
|
16046
16149
|
function docComponents(normalized, { name = null } = {}) {
|
|
16047
16150
|
const comps = normalized.components;
|
|
@@ -16077,8 +16180,11 @@ var init_components = __esm({
|
|
|
16077
16180
|
getHandlerFor(v, name, key) {
|
|
16078
16181
|
return this.getCompFor(v)?.[key][name] ?? null;
|
|
16079
16182
|
}
|
|
16080
|
-
|
|
16081
|
-
|
|
16183
|
+
// The `lex` leg of an intent's route: the handlers registered on the scope chain of
|
|
16184
|
+
// the component that raised it, innermost first. A list, not a first match — see
|
|
16185
|
+
// ComponentStack.lookupIntentChain.
|
|
16186
|
+
getIntentChainFor(v, name) {
|
|
16187
|
+
return this.getCompFor(v)?.scope.lookupIntentChain(name) ?? [];
|
|
16082
16188
|
}
|
|
16083
16189
|
compileStyles() {
|
|
16084
16190
|
const styles2 = [];
|
|
@@ -16091,7 +16197,7 @@ var init_components = __esm({
|
|
|
16091
16197
|
this.comps = comps;
|
|
16092
16198
|
this.parent = parent;
|
|
16093
16199
|
this.byName = {};
|
|
16094
|
-
this.
|
|
16200
|
+
this.intentsByName = {};
|
|
16095
16201
|
this.macros = {};
|
|
16096
16202
|
}
|
|
16097
16203
|
enter() {
|
|
@@ -16123,11 +16229,21 @@ var init_components = __esm({
|
|
|
16123
16229
|
getCompFor(v) {
|
|
16124
16230
|
return this.comps.getCompFor(v);
|
|
16125
16231
|
}
|
|
16126
|
-
|
|
16127
|
-
|
|
16232
|
+
// Handlers that answer an intent's `lex` leg. A name maps to a LIST, because the leg
|
|
16233
|
+
// walks: a handler that returns PASS declines and hands the intent to the next one.
|
|
16234
|
+
// A bare function is accepted as a one-element list.
|
|
16235
|
+
registerIntentHandlers(handlers) {
|
|
16236
|
+
for (const name in handlers) {
|
|
16237
|
+
const fns = Array.isArray(handlers[name]) ? handlers[name] : [handlers[name]];
|
|
16238
|
+
this.intentsByName[name] = fns.map((fn) => new IntentHandler(name, fn));
|
|
16239
|
+
}
|
|
16128
16240
|
}
|
|
16129
|
-
|
|
16130
|
-
|
|
16241
|
+
// Innermost scope first, then outward. Concatenated rather than first-match, so a
|
|
16242
|
+
// declining handler can hand the intent to one further up.
|
|
16243
|
+
lookupIntentChain(name) {
|
|
16244
|
+
const here = this.intentsByName[name] ?? [];
|
|
16245
|
+
const up = this.parent?.lookupIntentChain(name) ?? [];
|
|
16246
|
+
return up.length === 0 ? here : here.concat(up);
|
|
16131
16247
|
}
|
|
16132
16248
|
lookupComponent(name) {
|
|
16133
16249
|
return this.byName[name] ?? this.parent?.lookupComponent(name) ?? null;
|
|
@@ -16136,7 +16252,7 @@ var init_components = __esm({
|
|
|
16136
16252
|
return this.macros[name] ?? this.parent?.lookupMacro(name) ?? null;
|
|
16137
16253
|
}
|
|
16138
16254
|
};
|
|
16139
|
-
_rawSpecKeys = "name view style commonStyle globalStyle
|
|
16255
|
+
_rawSpecKeys = "name view style commonStyle globalStyle receive intent alter views provide lookup fields methods statics";
|
|
16140
16256
|
KNOWN_SPEC_KEYS = new Set(_rawSpecKeys.split(" "));
|
|
16141
16257
|
}
|
|
16142
16258
|
});
|
|
@@ -16148,7 +16264,7 @@ function lintComponents(normalized, { name = null, LintParseContextClass, wellKn
|
|
|
16148
16264
|
const stack = new ComponentStack();
|
|
16149
16265
|
stack.registerComponents(comps);
|
|
16150
16266
|
if (normalized.macros) stack.registerMacros(normalized.macros);
|
|
16151
|
-
if (normalized.
|
|
16267
|
+
if (normalized.intentHandlers) stack.registerIntentHandlers(normalized.intentHandlers);
|
|
16152
16268
|
const results = [];
|
|
16153
16269
|
for (const comp of picked) {
|
|
16154
16270
|
comp.compile(LintParseContextClass);
|
|
@@ -16361,22 +16477,19 @@ var init_stack = __esm({
|
|
|
16361
16477
|
});
|
|
16362
16478
|
|
|
16363
16479
|
// src/transactor.js
|
|
16364
|
-
function mkReq404(name) {
|
|
16365
|
-
const fn = () => {
|
|
16366
|
-
throw new Error(`Request not found: ${name}`);
|
|
16367
|
-
};
|
|
16368
|
-
return { fn };
|
|
16369
|
-
}
|
|
16370
16480
|
function nullHandler() {
|
|
16371
16481
|
return this;
|
|
16372
16482
|
}
|
|
16373
16483
|
function getValue(e) {
|
|
16374
16484
|
return e.target.type === "checkbox" ? e.target.checked : (e instanceof CustomEvent ? e.detail : e.target.value) ?? null;
|
|
16375
16485
|
}
|
|
16486
|
+
function warnNotIntent(verb) {
|
|
16487
|
+
console.warn(`ctx.${verb}() is only meaningful in an "intent" handler - ignored`);
|
|
16488
|
+
}
|
|
16376
16489
|
function rootDispatcher(transactor) {
|
|
16377
16490
|
return new Dispatcher(new Path([]), transactor, null);
|
|
16378
16491
|
}
|
|
16379
|
-
var State2, Transactor, Transaction, toNullIfNaN, InputEvent, NameArgsTransaction,
|
|
16492
|
+
var State2, Transactor, Transaction, toNullIfNaN, InputEvent, NameArgsTransaction, SendEvent, IntentEvent, PASS, DEFAULT_ROUTE, INTENT_DEPTH, IntentWalk, Completion, Dispatcher, EventContext, IntentContext, PathChanges;
|
|
16380
16493
|
var init_transactor = __esm({
|
|
16381
16494
|
"src/transactor.js"() {
|
|
16382
16495
|
init_path();
|
|
@@ -16413,8 +16526,11 @@ var init_transactor = __esm({
|
|
|
16413
16526
|
this.transactions.push(t);
|
|
16414
16527
|
this.onTransactionPushed(t);
|
|
16415
16528
|
}
|
|
16416
|
-
// Subscribe to a normalized record for every handler invocation
|
|
16417
|
-
//
|
|
16529
|
+
// Subscribe to a normalized record for every handler invocation. `kind` is the
|
|
16530
|
+
// handler bucket (`receive` / `intent`), plus `answer` — an observation-only kind for
|
|
16531
|
+
// a message the runtime wrote to answer an intent. The BUCKET does not know about
|
|
16532
|
+
// that distinction, and must not: a handler cannot tell an answer from a message its
|
|
16533
|
+
// parent sent. Returns an unsubscribe function. Records
|
|
16418
16534
|
// carry: kind, name, args, path, pathKeys, targetPath, handler, handlerName,
|
|
16419
16535
|
// matched, before, after, parent, timestamp. Purely observational.
|
|
16420
16536
|
observe(cb) {
|
|
@@ -16467,23 +16583,14 @@ var init_transactor = __esm({
|
|
|
16467
16583
|
this.pushTransaction(t);
|
|
16468
16584
|
return this._link(t, parent);
|
|
16469
16585
|
}
|
|
16470
|
-
|
|
16471
|
-
|
|
16472
|
-
|
|
16473
|
-
|
|
16474
|
-
}
|
|
16475
|
-
pushBubble(path, name, args = [], opts = {}, parent = null, targetPath = null) {
|
|
16476
|
-
const newOpts = opts.skipSelf ? { ...opts, skipSelf: false } : opts;
|
|
16477
|
-
const t = new BubbleEvent(path, this, name, args, parent, newOpts, targetPath);
|
|
16478
|
-
this.pushTransaction(t);
|
|
16479
|
-
return this._link(t, parent);
|
|
16480
|
-
}
|
|
16481
|
-
pushRequest(path, name, args = [], opts = {}, parent = null) {
|
|
16586
|
+
// Raise an intent: a job the sender did not address, walked along a route until
|
|
16587
|
+
// something answers. `opts.route` is a list of legs (see DEFAULT_ROUTE) and
|
|
16588
|
+
// `opts.livePath` opts out of pinning the answer's destination keys.
|
|
16589
|
+
pushIntent(path, name, args = [], opts = {}, parent = null) {
|
|
16482
16590
|
const release = parent ? parent.completion.track() : null;
|
|
16483
|
-
const
|
|
16484
|
-
|
|
16485
|
-
|
|
16486
|
-
return p;
|
|
16591
|
+
const walk = new IntentWalk(this, path, name, args, opts, parent, release);
|
|
16592
|
+
walk.advance();
|
|
16593
|
+
return walk;
|
|
16487
16594
|
}
|
|
16488
16595
|
// Drain queued transactions and await in-flight requests until quiescent. Each
|
|
16489
16596
|
// awaited request enqueues a ResponseEvent, which may dispatch more work, so we
|
|
@@ -16494,57 +16601,6 @@ var init_transactor = __esm({
|
|
|
16494
16601
|
if (this._inflight.size) await Promise.allSettled([...this._inflight]);
|
|
16495
16602
|
}
|
|
16496
16603
|
}
|
|
16497
|
-
async _runRequest(path, name, args = [], opts = {}, parent = null, release = null) {
|
|
16498
|
-
let released = false;
|
|
16499
|
-
const transfer = (t) => {
|
|
16500
|
-
if (release) {
|
|
16501
|
-
released = true;
|
|
16502
|
-
t.completion.whenSubtreeSettled().then(release);
|
|
16503
|
-
}
|
|
16504
|
-
};
|
|
16505
|
-
try {
|
|
16506
|
-
const curRoot = this.state.val;
|
|
16507
|
-
const txnPath = path.toTransactionPath();
|
|
16508
|
-
const curLeaf = txnPath.lookup(curRoot);
|
|
16509
|
-
const found = this.comps.getRequestFor(curLeaf, name);
|
|
16510
|
-
const handler = found ?? mkReq404(name);
|
|
16511
|
-
if (this._observers.length > 0) {
|
|
16512
|
-
const reqPath = txnPath.pinKeys(curRoot);
|
|
16513
|
-
this._emit({
|
|
16514
|
-
kind: "request",
|
|
16515
|
-
name,
|
|
16516
|
-
args,
|
|
16517
|
-
path: reqPath,
|
|
16518
|
-
pathKeys: reqPath.toKeys(),
|
|
16519
|
-
targetPath: path,
|
|
16520
|
-
handler: found?.fn ?? null,
|
|
16521
|
-
handlerName: found?.fn?.name || name,
|
|
16522
|
-
matched: found ? "exact" : "none",
|
|
16523
|
-
before: curLeaf,
|
|
16524
|
-
after: void 0,
|
|
16525
|
-
parent,
|
|
16526
|
-
timestamp: Date.now()
|
|
16527
|
-
});
|
|
16528
|
-
}
|
|
16529
|
-
const reqCtx = new RequestContext(path, this, parent, curRoot);
|
|
16530
|
-
const resHandlerName = opts?.onResName ?? name;
|
|
16531
|
-
const resPath = opts?.livePath ? null : txnPath.pinKeys(curRoot);
|
|
16532
|
-
const push = (specificName, baseName, singleArg, result, error) => {
|
|
16533
|
-
const resArgs = specificName ? [singleArg] : [result, error];
|
|
16534
|
-
const t = new ResponseEvent(path, this, specificName ?? baseName, resArgs, parent, resPath);
|
|
16535
|
-
transfer(t);
|
|
16536
|
-
this.pushTransaction(t);
|
|
16537
|
-
};
|
|
16538
|
-
try {
|
|
16539
|
-
const result = await handler.fn.apply(null, [...args, reqCtx]);
|
|
16540
|
-
push(opts?.onOkName, resHandlerName, result, result, null);
|
|
16541
|
-
} catch (error) {
|
|
16542
|
-
push(opts?.onErrorName, resHandlerName, error, null, error);
|
|
16543
|
-
}
|
|
16544
|
-
} finally {
|
|
16545
|
-
if (release && !released) release();
|
|
16546
|
-
}
|
|
16547
|
-
}
|
|
16548
16604
|
get hasPendingTransactions() {
|
|
16549
16605
|
return this.transactions.length > 0;
|
|
16550
16606
|
}
|
|
@@ -16561,6 +16617,7 @@ var init_transactor = __esm({
|
|
|
16561
16617
|
this._emitTransaction(transaction, curState);
|
|
16562
16618
|
} else console.warn("undefined new state", { curState, transaction });
|
|
16563
16619
|
} finally {
|
|
16620
|
+
transaction.ensureWalkAdvanced?.();
|
|
16564
16621
|
transaction._completion?.ensureSelfSettled();
|
|
16565
16622
|
transaction._completion?.releaseSelf();
|
|
16566
16623
|
}
|
|
@@ -16596,6 +16653,15 @@ var init_transactor = __esm({
|
|
|
16596
16653
|
}
|
|
16597
16654
|
afterTransaction() {
|
|
16598
16655
|
}
|
|
16656
|
+
// Ending a walk and forwarding belong to the two dispatch buckets; the base warns
|
|
16657
|
+
// rather than throwing, so a stray call from the wrong place is a message and not a
|
|
16658
|
+
// crash. `walk` is undefined here, which is how ctx.reply/ctx.fail tell the two apart.
|
|
16659
|
+
stop() {
|
|
16660
|
+
console.warn('ctx.stop() is only meaningful in an "intent" handler - ignored');
|
|
16661
|
+
}
|
|
16662
|
+
forward(_opts) {
|
|
16663
|
+
console.warn('ctx.forward() needs a "receive" or "intent" handler - ignored');
|
|
16664
|
+
}
|
|
16599
16665
|
buildRootStack(root, comps) {
|
|
16600
16666
|
return Stack2.root(comps, root);
|
|
16601
16667
|
}
|
|
@@ -16654,18 +16720,40 @@ var init_transactor = __esm({
|
|
|
16654
16720
|
this._dispatchPath ??= this.path.compact();
|
|
16655
16721
|
return this._dispatchPath;
|
|
16656
16722
|
}
|
|
16723
|
+
// A DOM event is an ADDRESSED message like any other — it reaches the component that
|
|
16724
|
+
// owns the view and stops. It keeps its own class only because it resolves its handler
|
|
16725
|
+
// from the compiled view and runs synchronously, not because it is a different channel.
|
|
16657
16726
|
get observeKind() {
|
|
16658
|
-
return "
|
|
16727
|
+
return "receive";
|
|
16659
16728
|
}
|
|
16660
16729
|
get observeName() {
|
|
16661
16730
|
return this.e?.type ?? null;
|
|
16662
16731
|
}
|
|
16732
|
+
// A view's name is a message. `forward` is where that name can LEAVE the component:
|
|
16733
|
+
// it becomes an intent with the same name and the same arguments, so a view that says
|
|
16734
|
+
// `@on.click="saveDraft .text"` never has to change when an ancestor takes the job over.
|
|
16735
|
+
// (A `$method` handler has a name too, so forwarding one raises an intent under it.)
|
|
16736
|
+
forward(opts) {
|
|
16737
|
+
this._forward = opts ?? {};
|
|
16738
|
+
}
|
|
16739
|
+
afterTransaction() {
|
|
16740
|
+
const f = this._forward;
|
|
16741
|
+
if (f === void 0) return;
|
|
16742
|
+
this._forward = void 0;
|
|
16743
|
+
const { args = this._handlerArgs ?? [], ...rest } = f;
|
|
16744
|
+
const hv = this.handler?.handlerCall?.handlerVal ?? this.handler?.handlerVal;
|
|
16745
|
+
const name = hv?.name;
|
|
16746
|
+
if (name === void 0)
|
|
16747
|
+
return console.warn("ctx.forward() from a handler with no name - ignored");
|
|
16748
|
+
this.transactor.pushIntent(this.dispatchPath, name, args, rest, this);
|
|
16749
|
+
}
|
|
16663
16750
|
buildRootStack(root, comps) {
|
|
16664
16751
|
return Stack2.root(comps, root, this);
|
|
16665
16752
|
}
|
|
16666
16753
|
getHandlerAndArgs(root, _instance, comps) {
|
|
16667
16754
|
const stack = this.buildStack(root, comps);
|
|
16668
16755
|
const [handler, args] = this.handler.getHandlerAndArgs(stack, this);
|
|
16756
|
+
this._handlerArgs = [...args];
|
|
16669
16757
|
const path = this.dispatchPath;
|
|
16670
16758
|
args.push(new EventContext(path, this.transactor, this));
|
|
16671
16759
|
return [handler, args];
|
|
@@ -16750,39 +16838,190 @@ var init_transactor = __esm({
|
|
|
16750
16838
|
return [handler, [...this.args, new EventContext(this.path, this.transactor, this)]];
|
|
16751
16839
|
}
|
|
16752
16840
|
};
|
|
16753
|
-
ResponseEvent = class extends NameArgsTransaction {
|
|
16754
|
-
handlerProp = "response";
|
|
16755
|
-
constructor(path, transactor, name, args, parent, txnPath = null) {
|
|
16756
|
-
super(path, transactor, name, args, parent);
|
|
16757
|
-
this._txnPath = txnPath;
|
|
16758
|
-
}
|
|
16759
|
-
getTransactionPath() {
|
|
16760
|
-
return this._txnPath ?? super.getTransactionPath();
|
|
16761
|
-
}
|
|
16762
|
-
};
|
|
16763
16841
|
SendEvent = class extends NameArgsTransaction {
|
|
16764
16842
|
handlerProp = "receive";
|
|
16765
|
-
|
|
16766
|
-
|
|
16843
|
+
// `receive` is the ADDRESSED bucket, and it is the only one: a view's `@on.click`,
|
|
16844
|
+
// a parent's ctx.send, the host's sendAtRoot and an answer to an intent all arrive
|
|
16845
|
+
// here, and a handler cannot tell them apart. Splitting on where a message came from
|
|
16846
|
+
// would let the view's name through and turn the parent's away, though the two say
|
|
16847
|
+
// the same thing — and neither a test nor a parent could drive the component.
|
|
16848
|
+
get observeKind() {
|
|
16849
|
+
return this._isAnswer ? "answer" : "receive";
|
|
16850
|
+
}
|
|
16851
|
+
// `forward` in a RECEIVE body starts a walk: the message that arrived becomes an
|
|
16852
|
+
// intent, keeping its name and payload. This is what lets a view's name leave the
|
|
16853
|
+
// component without the view changing.
|
|
16854
|
+
forward(opts) {
|
|
16855
|
+
this._forward = opts ?? {};
|
|
16767
16856
|
}
|
|
16768
16857
|
afterTransaction() {
|
|
16769
|
-
const
|
|
16770
|
-
if (
|
|
16771
|
-
|
|
16858
|
+
const f = this._forward;
|
|
16859
|
+
if (f === void 0) return;
|
|
16860
|
+
this._forward = void 0;
|
|
16861
|
+
const { args = this.args, ...rest } = f;
|
|
16862
|
+
this.transactor.pushIntent(this.path, this.name, args, rest, this);
|
|
16772
16863
|
}
|
|
16773
16864
|
};
|
|
16774
|
-
|
|
16775
|
-
handlerProp = "
|
|
16776
|
-
constructor(path, transactor,
|
|
16777
|
-
super(path, transactor, name, args, parent,
|
|
16778
|
-
this.
|
|
16865
|
+
IntentEvent = class extends NameArgsTransaction {
|
|
16866
|
+
handlerProp = "intent";
|
|
16867
|
+
constructor(path, transactor, walk) {
|
|
16868
|
+
super(path, transactor, walk.name, walk.args, walk.parent, {});
|
|
16869
|
+
this.walk = walk;
|
|
16870
|
+
this.targetPath = walk.origin;
|
|
16871
|
+
}
|
|
16872
|
+
// `forward` in an INTENT body amends the hop about to be pushed — it does not push
|
|
16873
|
+
// one itself, because a walk advances on its own.
|
|
16874
|
+
forward(opts) {
|
|
16875
|
+
this.walk.amend(opts, this.path);
|
|
16779
16876
|
}
|
|
16780
|
-
|
|
16781
|
-
this.
|
|
16877
|
+
stop() {
|
|
16878
|
+
this.walk.finish(null, null);
|
|
16879
|
+
}
|
|
16880
|
+
afterTransaction() {
|
|
16881
|
+
this.ensureWalkAdvanced();
|
|
16882
|
+
}
|
|
16883
|
+
// Idempotent, because both afterTransaction and the transactor's `finally` call it —
|
|
16884
|
+
// whichever gets there first advances the walk exactly once.
|
|
16885
|
+
ensureWalkAdvanced() {
|
|
16886
|
+
if (this._advanced) return;
|
|
16887
|
+
this._advanced = true;
|
|
16888
|
+
this.walk.advance();
|
|
16782
16889
|
}
|
|
16783
16890
|
};
|
|
16784
|
-
|
|
16785
|
-
|
|
16891
|
+
PASS = /* @__PURE__ */ Symbol.for("tutuca.intent.pass");
|
|
16892
|
+
DEFAULT_ROUTE = ["dyn", "lex"];
|
|
16893
|
+
INTENT_DEPTH = 64;
|
|
16894
|
+
IntentWalk = class {
|
|
16895
|
+
constructor(transactor, path, name, args, opts, parent, release) {
|
|
16896
|
+
this.transactor = transactor;
|
|
16897
|
+
this.name = name;
|
|
16898
|
+
this.args = args;
|
|
16899
|
+
this.route = opts?.route ?? DEFAULT_ROUTE;
|
|
16900
|
+
this.parent = parent;
|
|
16901
|
+
this.release = release;
|
|
16902
|
+
this.origin = path;
|
|
16903
|
+
this.answerPath = opts?.livePath ? null : path.toTransactionPath().pinKeys(transactor.state.val);
|
|
16904
|
+
this.legIndex = 0;
|
|
16905
|
+
this.dynAt = path;
|
|
16906
|
+
this.hops = 0;
|
|
16907
|
+
this.ended = false;
|
|
16908
|
+
}
|
|
16909
|
+
// Offer the intent to the next hop. Called once at dispatch, and again by every hop
|
|
16910
|
+
// that ran without answering.
|
|
16911
|
+
advance() {
|
|
16912
|
+
if (this.ended) return;
|
|
16913
|
+
if (this.hops++ >= INTENT_DEPTH) return this.exhaust("intentDepth");
|
|
16914
|
+
while (this.legIndex < this.route.length) {
|
|
16915
|
+
const leg = this.route[this.legIndex];
|
|
16916
|
+
if (leg === "dyn") {
|
|
16917
|
+
if (this.dynAt.steps.length === 0) {
|
|
16918
|
+
this.legIndex++;
|
|
16919
|
+
continue;
|
|
16920
|
+
}
|
|
16921
|
+
this.dynAt = this.dynAt.popStep();
|
|
16922
|
+
this.transactor.pushTransaction(new IntentEvent(this.dynAt, this.transactor, this));
|
|
16923
|
+
return;
|
|
16924
|
+
}
|
|
16925
|
+
if (leg === "lex") {
|
|
16926
|
+
this.legIndex++;
|
|
16927
|
+
return this._tryLex();
|
|
16928
|
+
}
|
|
16929
|
+
console.warn("unknown intent route leg", leg, '- expected "dyn" or "lex"');
|
|
16930
|
+
this.legIndex++;
|
|
16931
|
+
}
|
|
16932
|
+
this.exhaust("noHandler");
|
|
16933
|
+
}
|
|
16934
|
+
// The `lex` leg: the handlers registered on the scope chain of the component that
|
|
16935
|
+
// raised the intent, in order.
|
|
16936
|
+
_tryLex() {
|
|
16937
|
+
const root = this.transactor.state.val;
|
|
16938
|
+
const txnPath = this.origin.toTransactionPath();
|
|
16939
|
+
const leaf = txnPath.lookup(root);
|
|
16940
|
+
const chain = this.transactor.comps.getIntentChainFor(leaf, this.name);
|
|
16941
|
+
if (this.transactor._observers.length > 0) {
|
|
16942
|
+
const path = txnPath.pinKeys(root);
|
|
16943
|
+
this.transactor._emit({
|
|
16944
|
+
kind: "intent",
|
|
16945
|
+
name: this.name,
|
|
16946
|
+
args: this.args,
|
|
16947
|
+
path,
|
|
16948
|
+
pathKeys: path.toKeys(),
|
|
16949
|
+
targetPath: this.origin,
|
|
16950
|
+
handler: chain[0]?.fn ?? null,
|
|
16951
|
+
handlerName: chain[0]?.fn?.name || this.name,
|
|
16952
|
+
matched: chain.length > 0 ? "exact" : "none",
|
|
16953
|
+
before: leaf,
|
|
16954
|
+
after: void 0,
|
|
16955
|
+
parent: this.parent,
|
|
16956
|
+
timestamp: Date.now()
|
|
16957
|
+
});
|
|
16958
|
+
}
|
|
16959
|
+
if (chain.length === 0) return this.advance();
|
|
16960
|
+
const p = this._runLex(chain, 0);
|
|
16961
|
+
this.transactor._inflight.add(p);
|
|
16962
|
+
p.finally(() => this.transactor._inflight.delete(p));
|
|
16963
|
+
}
|
|
16964
|
+
async _runLex(chain, i) {
|
|
16965
|
+
if (this.ended) return;
|
|
16966
|
+
if (i >= chain.length) return this.advance();
|
|
16967
|
+
const ctx = new IntentContext(this.origin, this.transactor, this.parent);
|
|
16968
|
+
try {
|
|
16969
|
+
const res = await chain[i].fn.apply(null, [...this.args, ctx]);
|
|
16970
|
+
if (res === PASS) return this._runLex(chain, i + 1);
|
|
16971
|
+
this.answer("Ok", res);
|
|
16972
|
+
} catch (error) {
|
|
16973
|
+
this.answer("Error", error);
|
|
16974
|
+
}
|
|
16975
|
+
}
|
|
16976
|
+
// A hop answered. Ends the walk.
|
|
16977
|
+
answer(suffix, value) {
|
|
16978
|
+
this.finish(`${this.name}${suffix}`, [value]);
|
|
16979
|
+
}
|
|
16980
|
+
// `forward` from an intent body: keep walking, optionally with new arguments or a
|
|
16981
|
+
// narrowed route.
|
|
16982
|
+
amend(opts, from) {
|
|
16983
|
+
if (opts?.args !== void 0) this.args = opts.args;
|
|
16984
|
+
if (opts?.route !== void 0) {
|
|
16985
|
+
this.route = opts.route;
|
|
16986
|
+
this.legIndex = 0;
|
|
16987
|
+
this.dynAt = from ?? this.dynAt;
|
|
16988
|
+
}
|
|
16989
|
+
}
|
|
16990
|
+
// The route ran out. What the sender hears depends only on what it DECLARES, which is
|
|
16991
|
+
// how "a sender expects an answer if and only if it declares an answer arm" becomes
|
|
16992
|
+
// code — nobody writes it down twice, and there is no schema to keep in step.
|
|
16993
|
+
exhaust(reason) {
|
|
16994
|
+
if (this.ended) return;
|
|
16995
|
+
const { name, args } = this;
|
|
16996
|
+
const comp = this.transactor.comps.getCompFor(
|
|
16997
|
+
this.origin.toTransactionPath().lookup(this.transactor.state.val)
|
|
16998
|
+
);
|
|
16999
|
+
const declares = (n) => comp?.receive?.[n] !== void 0;
|
|
17000
|
+
if (declares(`${name}Unhandled`)) {
|
|
17001
|
+
this.finish(`${name}Unhandled`, args);
|
|
17002
|
+
} else if (declares(`${name}Error`)) {
|
|
17003
|
+
this.finish(`${name}Error`, [reason]);
|
|
17004
|
+
} else {
|
|
17005
|
+
if (declares(`${name}Ok`))
|
|
17006
|
+
console.warn(
|
|
17007
|
+
`intent "${name}" was not answered (${reason}) and this component declares only "${name}Ok" - add "${name}Unhandled" or "${name}Error" to handle it`
|
|
17008
|
+
);
|
|
17009
|
+
this.finish(null, null);
|
|
17010
|
+
}
|
|
17011
|
+
}
|
|
17012
|
+
// End the walk. `name === null` means no answer goes out, which is what `stop` and a
|
|
17013
|
+
// fire-and-forget notification both are — the tracked unit is released here instead
|
|
17014
|
+
// of following an answer's subtree.
|
|
17015
|
+
finish(name, args) {
|
|
17016
|
+
if (this.ended) return;
|
|
17017
|
+
this.ended = true;
|
|
17018
|
+
if (name === null) return this.release?.();
|
|
17019
|
+
const path = this.answerPath ?? this.origin;
|
|
17020
|
+
const t = new SendEvent(path, this.transactor, name, args, this.parent, {});
|
|
17021
|
+
t._isAnswer = true;
|
|
17022
|
+
this.transactor.pushTransaction(t);
|
|
17023
|
+
if (this.release) t.completion.whenSubtreeSettled().then(this.release);
|
|
17024
|
+
}
|
|
16786
17025
|
};
|
|
16787
17026
|
Completion = class {
|
|
16788
17027
|
constructor() {
|
|
@@ -16863,23 +17102,21 @@ var init_transactor = __esm({
|
|
|
16863
17102
|
get at() {
|
|
16864
17103
|
return new PathChanges(this);
|
|
16865
17104
|
}
|
|
17105
|
+
// A message is ADDRESSED: it names one component and stops there.
|
|
16866
17106
|
send(name, args, opts) {
|
|
16867
17107
|
return this.sendAtPath(this.path, name, args, opts);
|
|
16868
17108
|
}
|
|
16869
|
-
bubble(name, args, opts) {
|
|
16870
|
-
return this.send(name, args, { skipSelf: true, bubbles: true, ...opts });
|
|
16871
|
-
}
|
|
16872
17109
|
sendAtPath(path, name, args, opts) {
|
|
16873
17110
|
return this.transactor.pushSend(path, name, args, opts, this.parent);
|
|
16874
17111
|
}
|
|
16875
|
-
|
|
16876
|
-
|
|
17112
|
+
// An intent is ROUTED: it names a job and walks until something answers. The verb
|
|
17113
|
+
// does not decide which scope answers — `opts.route` does, written here at the call
|
|
17114
|
+
// site, which is where the decision actually is.
|
|
17115
|
+
intent(name, args, opts) {
|
|
17116
|
+
return this.intentAtPath(this.path, name, args, opts);
|
|
16877
17117
|
}
|
|
16878
|
-
|
|
16879
|
-
return this.transactor.
|
|
16880
|
-
}
|
|
16881
|
-
inputAtPath(path, name, args, opts) {
|
|
16882
|
-
return this.transactor.pushInput(path, name, args, opts, this.parent);
|
|
17118
|
+
intentAtPath(path, name, args, opts) {
|
|
17119
|
+
return this.transactor.pushIntent(path, name, args, opts, this.parent);
|
|
16883
17120
|
}
|
|
16884
17121
|
lookupTypeFor(name, inst) {
|
|
16885
17122
|
return this.transactor.comps.getCompFor(inst).scope.lookupComponent(name);
|
|
@@ -16892,11 +17129,26 @@ var init_transactor = __esm({
|
|
|
16892
17129
|
get targetPath() {
|
|
16893
17130
|
return this.parent.targetPath;
|
|
16894
17131
|
}
|
|
16895
|
-
|
|
16896
|
-
|
|
17132
|
+
// Answer the intent being handled, with a result or with an error. Either ENDS the
|
|
17133
|
+
// walk. Legal only in an `intent` handler: a message has no sender waiting on it.
|
|
17134
|
+
reply(value) {
|
|
17135
|
+
return this.parent.walk === void 0 ? warnNotIntent("reply") : this.parent.walk.answer("Ok", value);
|
|
17136
|
+
}
|
|
17137
|
+
fail(error) {
|
|
17138
|
+
return this.parent.walk === void 0 ? warnNotIntent("fail") : this.parent.walk.answer("Error", error);
|
|
17139
|
+
}
|
|
17140
|
+
// End the walk answering nothing — "served, and no answer".
|
|
17141
|
+
stop() {
|
|
17142
|
+
return this.parent.stop();
|
|
17143
|
+
}
|
|
17144
|
+
// From an `intent` body: hand the intent to the next hop, optionally amending it.
|
|
17145
|
+
// From a `receive` body: turn the message that arrived into an intent. One word from
|
|
17146
|
+
// both ends of a walk; which one you get depends on which bucket you are in.
|
|
17147
|
+
forward(opts) {
|
|
17148
|
+
return this.parent.forward(opts);
|
|
16897
17149
|
}
|
|
16898
17150
|
};
|
|
16899
|
-
|
|
17151
|
+
IntentContext = class extends Dispatcher {
|
|
16900
17152
|
};
|
|
16901
17153
|
PathChanges = class extends PathBuilder {
|
|
16902
17154
|
constructor(dispatcher) {
|
|
@@ -16906,8 +17158,8 @@ var init_transactor = __esm({
|
|
|
16906
17158
|
send(name, args, opts) {
|
|
16907
17159
|
return this.dispatcher.sendAtPath(this.buildPath(), name, args, opts);
|
|
16908
17160
|
}
|
|
16909
|
-
|
|
16910
|
-
return this.
|
|
17161
|
+
intent(name, args, opts) {
|
|
17162
|
+
return this.dispatcher.intentAtPath(this.buildPath(), name, args, opts);
|
|
16911
17163
|
}
|
|
16912
17164
|
buildPath() {
|
|
16913
17165
|
return this.dispatcher.path.concat(this.pathChanges);
|
|
@@ -17185,11 +17437,6 @@ function phaseOps(phase) {
|
|
|
17185
17437
|
function resolveArgs(args, self) {
|
|
17186
17438
|
return typeof args === "function" ? args(self) ?? [] : args ?? [];
|
|
17187
17439
|
}
|
|
17188
|
-
function phaseHasBubble(phase) {
|
|
17189
|
-
if (!phase) return false;
|
|
17190
|
-
if (phase.bubble?.length) return true;
|
|
17191
|
-
return (phase.do ?? []).some((op) => op.type === "bubble");
|
|
17192
|
-
}
|
|
17193
17440
|
function dispatchPhase(dispatcher, targetPath, phase, self) {
|
|
17194
17441
|
if (!phase) return;
|
|
17195
17442
|
for (const op of phaseOps(phase)) {
|
|
@@ -17198,18 +17445,8 @@ function dispatchPhase(dispatcher, targetPath, phase, self) {
|
|
|
17198
17445
|
case "send":
|
|
17199
17446
|
dispatcher.sendAtPath(targetPath, op.name, args, op.opts);
|
|
17200
17447
|
break;
|
|
17201
|
-
case "
|
|
17202
|
-
dispatcher.
|
|
17203
|
-
skipSelf: true,
|
|
17204
|
-
bubbles: true,
|
|
17205
|
-
...op.opts
|
|
17206
|
-
});
|
|
17207
|
-
break;
|
|
17208
|
-
case "request":
|
|
17209
|
-
dispatcher.requestAtPath(targetPath, op.name, args, op.opts);
|
|
17210
|
-
break;
|
|
17211
|
-
case "input":
|
|
17212
|
-
dispatcher.inputAtPath(targetPath, op.name, args, op.opts);
|
|
17448
|
+
case "intent":
|
|
17449
|
+
dispatcher.intentAtPath(targetPath, op.name, args, op.opts);
|
|
17213
17450
|
break;
|
|
17214
17451
|
}
|
|
17215
17452
|
}
|
|
@@ -17217,7 +17454,7 @@ function dispatchPhase(dispatcher, targetPath, phase, self) {
|
|
|
17217
17454
|
var OP_KINDS;
|
|
17218
17455
|
var init_on = __esm({
|
|
17219
17456
|
"src/on.js"() {
|
|
17220
|
-
OP_KINDS = ["send", "
|
|
17457
|
+
OP_KINDS = ["send", "intent"];
|
|
17221
17458
|
}
|
|
17222
17459
|
});
|
|
17223
17460
|
|
|
@@ -17243,7 +17480,7 @@ function renderToHTMLNode(document2, components, macros, rootState, ParseContext
|
|
|
17243
17480
|
const app = new App(container, comps, renderer, ParseContext2);
|
|
17244
17481
|
const scope = app.registerComponents(components);
|
|
17245
17482
|
if (macros) scope.registerMacros(macros);
|
|
17246
|
-
if (opts.
|
|
17483
|
+
if (opts.intentHandlers) scope.registerIntentHandlers(opts.intentHandlers);
|
|
17247
17484
|
app.rootViewName = opts.view ?? null;
|
|
17248
17485
|
app.transactor.state.set(rootState);
|
|
17249
17486
|
app.start(opts);
|
|
@@ -17256,21 +17493,17 @@ function renderToHTMLNode(document2, components, macros, rootState, ParseContext
|
|
|
17256
17493
|
}
|
|
17257
17494
|
};
|
|
17258
17495
|
}
|
|
17259
|
-
async function renderToHTMLDriven(document2, components, macros, rootState, ParseContext2, { phase = null,
|
|
17496
|
+
async function renderToHTMLDriven(document2, components, macros, rootState, ParseContext2, { phase = null, intentHandlers = null, view = null } = {}) {
|
|
17260
17497
|
const { container, app, cleanup } = renderToHTMLNode(
|
|
17261
17498
|
document2,
|
|
17262
17499
|
components,
|
|
17263
17500
|
macros,
|
|
17264
17501
|
rootState,
|
|
17265
17502
|
ParseContext2,
|
|
17266
|
-
{ noCache: true,
|
|
17503
|
+
{ noCache: true, intentHandlers, view }
|
|
17267
17504
|
);
|
|
17268
17505
|
try {
|
|
17269
17506
|
if (phase) {
|
|
17270
|
-
if (phaseHasBubble(phase))
|
|
17271
|
-
warn(
|
|
17272
|
-
"render: a `bubble` action is a no-op here — the example's value is the render root, so there is no ancestor to receive it. Use send/request/input to drive a preset state."
|
|
17273
|
-
);
|
|
17274
17507
|
dispatchPhase(rootDispatcher(app.transactor), new Path([]), phase, app.state.val);
|
|
17275
17508
|
await app.transactor.settle();
|
|
17276
17509
|
}
|
|
@@ -17299,7 +17532,7 @@ function checkView2(value, components, viewName) {
|
|
|
17299
17532
|
return new Error(`view "${viewName}" is not defined on ${comp.name} (has: ${known})`);
|
|
17300
17533
|
}
|
|
17301
17534
|
function handlersFor(normalized, example) {
|
|
17302
|
-
const merged = { ...normalized.
|
|
17535
|
+
const merged = { ...normalized.intentHandlers ?? {}, ...example.intentHandlers ?? {} };
|
|
17303
17536
|
return Object.keys(merged).length > 0 ? merged : null;
|
|
17304
17537
|
}
|
|
17305
17538
|
async function renderExamples(normalized, env, { name = null, title = null, view = null } = {}) {
|
|
@@ -17324,7 +17557,7 @@ async function renderExamples(normalized, env, { name = null, title = null, view
|
|
|
17324
17557
|
env.ParseContext,
|
|
17325
17558
|
{
|
|
17326
17559
|
phase: example.on?.init ?? null,
|
|
17327
|
-
|
|
17560
|
+
intentHandlers: handlersFor(normalized, example),
|
|
17328
17561
|
view: viewName
|
|
17329
17562
|
}
|
|
17330
17563
|
);
|
|
@@ -17488,29 +17721,24 @@ function captureError(e) {
|
|
|
17488
17721
|
if ("actual" in e) out.actual = e.actual;
|
|
17489
17722
|
return out;
|
|
17490
17723
|
}
|
|
17491
|
-
function buildStack({ components = [], macros = null,
|
|
17724
|
+
function buildStack({ components = [], macros = null, intentHandlers = null } = {}) {
|
|
17492
17725
|
const stack = new ComponentStack();
|
|
17493
17726
|
stack.registerComponents(components);
|
|
17494
17727
|
if (macros) stack.registerMacros(macros);
|
|
17495
|
-
if (
|
|
17728
|
+
if (intentHandlers) stack.registerIntentHandlers(intentHandlers);
|
|
17496
17729
|
return stack;
|
|
17497
17730
|
}
|
|
17498
17731
|
async function driveStack(stack, value, phase, opts = {}) {
|
|
17499
|
-
const warn = opts.warn ?? console.warn;
|
|
17500
17732
|
const transactor = new Transactor(stack.comps, value);
|
|
17501
17733
|
if (opts.onMessage)
|
|
17502
17734
|
transactor.state.onChange(({ val, old, info }) => {
|
|
17503
17735
|
const t = info?.transaction;
|
|
17504
17736
|
opts.onMessage(
|
|
17505
|
-
{ kind: t?.handlerProp ?? "
|
|
17737
|
+
{ kind: t?.handlerProp ?? "receive", name: t?.name, args: t?.args, path: t?.path },
|
|
17506
17738
|
old,
|
|
17507
17739
|
val
|
|
17508
17740
|
);
|
|
17509
17741
|
});
|
|
17510
|
-
if (phaseHasBubble(phase))
|
|
17511
|
-
warn(
|
|
17512
|
-
"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."
|
|
17513
|
-
);
|
|
17514
17742
|
dispatchPhase(rootDispatcher(transactor), new Path([]), phase, value);
|
|
17515
17743
|
await transactor.settle();
|
|
17516
17744
|
return transactor.state.val;
|
|
@@ -17523,7 +17751,7 @@ async function runTests({
|
|
|
17523
17751
|
name = null,
|
|
17524
17752
|
grep = null,
|
|
17525
17753
|
bail = false,
|
|
17526
|
-
|
|
17754
|
+
intentHandlers = null,
|
|
17527
17755
|
macros = null
|
|
17528
17756
|
} = {}) {
|
|
17529
17757
|
const counts = { pass: 0, fail: 0, skip: 0, total: 0 };
|
|
@@ -17537,7 +17765,7 @@ async function runTests({
|
|
|
17537
17765
|
}
|
|
17538
17766
|
const { describe: describe6, test: test2, moduleTests } = makeCollector({ path, components });
|
|
17539
17767
|
let _stack = null;
|
|
17540
|
-
const getStack = () => _stack ??= buildStack({ components, macros,
|
|
17768
|
+
const getStack = () => _stack ??= buildStack({ components, macros, intentHandlers });
|
|
17541
17769
|
const driveLocal = (value, phase, opts = {}) => driveStack(getStack(), value, phase, opts);
|
|
17542
17770
|
await getTests({ describe: describe6, test: test2, expect: expect2, drive: driveLocal });
|
|
17543
17771
|
let bailed = false;
|
|
@@ -17706,7 +17934,7 @@ var init_registry = __esm({
|
|
|
17706
17934
|
name: positionals[0] ?? null,
|
|
17707
17935
|
grep: values.grep ?? null,
|
|
17708
17936
|
bail: values.bail ?? false,
|
|
17709
|
-
|
|
17937
|
+
intentHandlers: normalized.intentHandlers,
|
|
17710
17938
|
macros: normalized.macros
|
|
17711
17939
|
}),
|
|
17712
17940
|
exitOn: (result) => result.hasFailures ? 4 : 0
|
|
@@ -18008,7 +18236,7 @@ MODULE CONVENTION
|
|
|
18008
18236
|
export function getMacros() // optional
|
|
18009
18237
|
-> Record<string, Macro> // from tutuca's macro()
|
|
18010
18238
|
|
|
18011
|
-
export function
|
|
18239
|
+
export function getIntentHandlers() // optional
|
|
18012
18240
|
-> Record<string, Function>
|
|
18013
18241
|
|
|
18014
18242
|
export function getTests({ describe, test, expect }) // required for test
|
|
@@ -18530,7 +18758,7 @@ async function runDevTests(projectDir, devModuleUrls) {
|
|
|
18530
18758
|
components: normalized.components,
|
|
18531
18759
|
path: abs,
|
|
18532
18760
|
expect,
|
|
18533
|
-
|
|
18761
|
+
intentHandlers: normalized.intentHandlers,
|
|
18534
18762
|
macros: normalized.macros
|
|
18535
18763
|
});
|
|
18536
18764
|
const m = report.modules[0];
|
|
@@ -18577,11 +18805,11 @@ async function discoverModules(projectDir, devModuleUrls) {
|
|
|
18577
18805
|
title: it.title,
|
|
18578
18806
|
view: it.view,
|
|
18579
18807
|
componentName: it.componentName,
|
|
18580
|
-
|
|
18808
|
+
intentHandlers: it.requestHandlerNames
|
|
18581
18809
|
}))
|
|
18582
18810
|
})),
|
|
18583
18811
|
macros: normalized.macros ? Object.keys(normalized.macros) : [],
|
|
18584
|
-
|
|
18812
|
+
intentHandlers: normalized.intentHandlers ? Object.keys(normalized.intentHandlers) : [],
|
|
18585
18813
|
error: null
|
|
18586
18814
|
});
|
|
18587
18815
|
} catch (e) {
|