@kehto/paja 0.9.0 → 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 +8 -9
- package/dist/browser-host.js +249 -281
- package/dist/browser-host.js.map +1 -1
- package/dist/{chunk-OWVINAW7.js → chunk-EOCGC7A2.js} +30 -36
- package/dist/{chunk-OWVINAW7.js.map → chunk-EOCGC7A2.js.map} +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +17 -19
- package/dist/index.js +1 -1
- package/package.json +11 -11
package/dist/browser-host.js
CHANGED
|
@@ -262,7 +262,7 @@ function uploadMap(action) {
|
|
|
262
262
|
return { senderCap: "upload:write", recipientCap: null };
|
|
263
263
|
}
|
|
264
264
|
function intentMap(action) {
|
|
265
|
-
if (action === "changed" || action === "
|
|
265
|
+
if (action === "changed" || action === "invoke.result" || action === "available.result" || action === "handlers.result") {
|
|
266
266
|
return { senderCap: null, recipientCap: "intent:read" };
|
|
267
267
|
}
|
|
268
268
|
if (action === "invoke") return { senderCap: "intent:write", recipientCap: null };
|
|
@@ -623,7 +623,7 @@ function deserialize2(json) {
|
|
|
623
623
|
return defaultConfig();
|
|
624
624
|
}
|
|
625
625
|
|
|
626
|
-
// ../../node_modules/.pnpm/@napplet+core@0.
|
|
626
|
+
// ../../node_modules/.pnpm/@napplet+core@0.31.0/node_modules/@napplet/core/dist/index.js
|
|
627
627
|
function createDispatch() {
|
|
628
628
|
const handlers = /* @__PURE__ */ new Map();
|
|
629
629
|
function registerNap2(domain, handler) {
|
|
@@ -4862,77 +4862,57 @@ function nappletNamespacePrelude(domains) {
|
|
|
4862
4862
|
};
|
|
4863
4863
|
}
|
|
4864
4864
|
function makeIntent() {
|
|
4865
|
-
const deliveryHandlers = /* @__PURE__ */ new Set();
|
|
4866
|
-
const pendingDeliveries = [];
|
|
4867
4865
|
const hasOwn2 = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
|
|
4868
|
-
const
|
|
4869
|
-
if (!value || typeof value !== "object" ||
|
|
4870
|
-
|
|
4866
|
+
const normalizeRequest = (value) => {
|
|
4867
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
4868
|
+
throw new TypeError("Intent request must be an object");
|
|
4871
4869
|
}
|
|
4872
|
-
const
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
archetype: delivery.archetype,
|
|
4876
|
-
action: delivery.action,
|
|
4877
|
-
convention: delivery.convention,
|
|
4878
|
-
...hasOwn2(delivery, "payload") ? { payload: delivery.payload } : {}
|
|
4879
|
-
};
|
|
4880
|
-
};
|
|
4881
|
-
listen((event) => {
|
|
4882
|
-
if (!isParentMessage(event)) return;
|
|
4883
|
-
const msg = event.data;
|
|
4884
|
-
if (typeof msg !== "object" || msg === null || msg.type !== "intent.deliver") return;
|
|
4885
|
-
const delivery = normalizeDelivery(msg.delivery);
|
|
4886
|
-
if (!delivery) return;
|
|
4887
|
-
if (deliveryHandlers.size === 0) pendingDeliveries.push(delivery);
|
|
4888
|
-
else for (const handler of deliveryHandlers) handler(delivery);
|
|
4889
|
-
});
|
|
4890
|
-
function normalizeIntentUri(uri, explicitPayload, actionMustBe) {
|
|
4891
|
-
if (typeof uri !== "string" || !uri.startsWith("napplet:")) {
|
|
4892
|
-
throw new TypeError("Intent convention URI must start with napplet:");
|
|
4870
|
+
const supplied = value;
|
|
4871
|
+
if (hasOwn2(supplied, "sender")) {
|
|
4872
|
+
throw new TypeError("Intent callers cannot supply sender");
|
|
4893
4873
|
}
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
if (!match) throw new TypeError("Intent convention URI must be napplet:<archetype>/<intent>");
|
|
4898
|
-
if (actionMustBe !== void 0 && match[2] !== actionMustBe) {
|
|
4899
|
-
throw new TypeError(`Intent convention URI must use the ${actionMustBe} intent`);
|
|
4874
|
+
const allowed22 = ["archetype", "action", "convention", "payload", "handler", "behavior"];
|
|
4875
|
+
if (Object.keys(supplied).some((field) => !allowed22.includes(field))) {
|
|
4876
|
+
throw new TypeError("Intent request contains unsupported fields");
|
|
4900
4877
|
}
|
|
4901
|
-
|
|
4902
|
-
archetype
|
|
4903
|
-
action: match[2],
|
|
4904
|
-
convention: normalized.topic,
|
|
4905
|
-
...normalized.payload === void 0 ? {} : { payload: normalized.payload }
|
|
4906
|
-
};
|
|
4907
|
-
}
|
|
4908
|
-
const invoke = (uri, opts, actionMustBe) => {
|
|
4909
|
-
const supplied = opts && typeof opts === "object" ? opts : void 0;
|
|
4910
|
-
const explicitPayload = supplied?.payload !== void 0;
|
|
4911
|
-
const normalized = normalizeIntentUri(uri, explicitPayload, actionMustBe);
|
|
4912
|
-
if (supplied && hasOwn2(supplied, "sender")) {
|
|
4913
|
-
throw new TypeError("Intent callers cannot supply sender");
|
|
4878
|
+
if (typeof supplied.archetype !== "string" || supplied.archetype.length === 0) {
|
|
4879
|
+
throw new TypeError("Intent request requires an archetype");
|
|
4914
4880
|
}
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4881
|
+
const action = supplied.action === void 0 ? "open" : supplied.action;
|
|
4882
|
+
if (typeof action !== "string" || action.length === 0) {
|
|
4883
|
+
throw new TypeError("Intent action must be a non-empty string");
|
|
4884
|
+
}
|
|
4885
|
+
if (supplied.convention !== void 0 && (typeof supplied.convention !== "string" || !/^napplet:[^/?#\s]+\/[^/?#\s]+$/.test(supplied.convention))) {
|
|
4886
|
+
throw new TypeError("Intent convention must be queryless");
|
|
4887
|
+
}
|
|
4888
|
+
if (supplied.handler !== void 0 && (typeof supplied.handler !== "string" || supplied.handler.length === 0)) {
|
|
4889
|
+
throw new TypeError("Intent handler must be a non-empty string");
|
|
4919
4890
|
}
|
|
4920
4891
|
const behaviorValue = supplied?.behavior;
|
|
4921
4892
|
const behavior = behaviorValue && typeof behaviorValue === "object" ? behaviorValue : void 0;
|
|
4893
|
+
if (behaviorValue !== void 0 && (!behavior || Array.isArray(behaviorValue) || Object.keys(behavior).some((field) => field !== "focus" && field !== "newWindow" && field !== "reuse") || hasOwn2(behavior, "focus") && typeof behavior.focus !== "boolean" || hasOwn2(behavior, "newWindow") && typeof behavior.newWindow !== "boolean" || hasOwn2(behavior, "reuse") && typeof behavior.reuse !== "boolean")) {
|
|
4894
|
+
throw new TypeError("Intent behavior contains unsupported fields");
|
|
4895
|
+
}
|
|
4922
4896
|
const sanitizedBehavior = {
|
|
4923
4897
|
...typeof behavior?.focus === "boolean" ? { focus: behavior.focus } : {},
|
|
4898
|
+
...typeof behavior?.newWindow === "boolean" ? { newWindow: behavior.newWindow } : {},
|
|
4924
4899
|
...typeof behavior?.reuse === "boolean" ? { reuse: behavior.reuse } : {}
|
|
4925
4900
|
};
|
|
4926
|
-
|
|
4901
|
+
return {
|
|
4902
|
+
archetype: supplied.archetype,
|
|
4903
|
+
action,
|
|
4904
|
+
...typeof supplied.convention === "string" ? { convention: supplied.convention } : {},
|
|
4905
|
+
...hasOwn2(supplied, "payload") ? { payload: supplied.payload } : {},
|
|
4906
|
+
...typeof supplied.handler === "string" ? { handler: supplied.handler } : {},
|
|
4907
|
+
...Object.keys(sanitizedBehavior).length === 0 ? {} : { behavior: sanitizedBehavior }
|
|
4908
|
+
};
|
|
4909
|
+
};
|
|
4910
|
+
const invoke = (value) => {
|
|
4911
|
+
const normalized = normalizeRequest(value);
|
|
4927
4912
|
return request(
|
|
4928
4913
|
{
|
|
4929
4914
|
type: "intent.invoke",
|
|
4930
|
-
request:
|
|
4931
|
-
...normalized,
|
|
4932
|
-
...explicitPayload ? { payload: supplied?.payload } : {},
|
|
4933
|
-
...handler === void 0 ? {} : { handler },
|
|
4934
|
-
...Object.keys(sanitizedBehavior).length === 0 ? {} : { behavior: sanitizedBehavior }
|
|
4935
|
-
}
|
|
4915
|
+
request: normalized
|
|
4936
4916
|
},
|
|
4937
4917
|
"intent.invoke.result",
|
|
4938
4918
|
(msg) => fieldOrThrow(msg, "result", "intent.invoke.result missing result")
|
|
@@ -4940,7 +4920,12 @@ function nappletNamespacePrelude(domains) {
|
|
|
4940
4920
|
};
|
|
4941
4921
|
return {
|
|
4942
4922
|
invoke,
|
|
4943
|
-
open: (
|
|
4923
|
+
open: (archetype, payload, opts) => invoke({
|
|
4924
|
+
archetype,
|
|
4925
|
+
action: "open",
|
|
4926
|
+
...payload === void 0 ? {} : { payload },
|
|
4927
|
+
...opts
|
|
4928
|
+
}),
|
|
4944
4929
|
available: (archetype) => request(
|
|
4945
4930
|
{ type: "intent.available", archetype },
|
|
4946
4931
|
"intent.available.result",
|
|
@@ -4955,14 +4940,7 @@ function nappletNamespacePrelude(domains) {
|
|
|
4955
4940
|
"intent.changed",
|
|
4956
4941
|
callback,
|
|
4957
4942
|
(msg) => msg.availability
|
|
4958
|
-
)
|
|
4959
|
-
onDelivery: (callback) => {
|
|
4960
|
-
if (typeof callback !== "function") throw new TypeError("Intent delivery handler must be a function");
|
|
4961
|
-
deliveryHandlers.add(callback);
|
|
4962
|
-
const pending = pendingDeliveries.splice(0);
|
|
4963
|
-
for (const delivery of pending) callback(delivery);
|
|
4964
|
-
return subscriptionHandle(() => deliveryHandlers.delete(callback));
|
|
4965
|
-
}
|
|
4943
|
+
)
|
|
4966
4944
|
};
|
|
4967
4945
|
}
|
|
4968
4946
|
function makeWebrtc() {
|
|
@@ -11721,14 +11699,17 @@ function hasOwn(value, key) {
|
|
|
11721
11699
|
}
|
|
11722
11700
|
function validateIntentRequest(value) {
|
|
11723
11701
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
11724
|
-
return { error: "invalid
|
|
11702
|
+
return { error: "invalid request" };
|
|
11725
11703
|
}
|
|
11726
11704
|
const request = value;
|
|
11727
|
-
if (typeof request.archetype !== "string" || request.archetype.length === 0
|
|
11728
|
-
return { error: "invalid
|
|
11705
|
+
if (typeof request.archetype !== "string" || request.archetype.length === 0) {
|
|
11706
|
+
return { error: "invalid request" };
|
|
11707
|
+
}
|
|
11708
|
+
const action = request.action === void 0 ? "open" : request.action;
|
|
11709
|
+
if (typeof action !== "string" || action.length === 0) {
|
|
11710
|
+
return { error: "invalid request" };
|
|
11729
11711
|
}
|
|
11730
|
-
|
|
11731
|
-
if (!match || match[1] !== request.archetype || match[2] !== request.action) {
|
|
11712
|
+
if (request.convention !== void 0 && (typeof request.convention !== "string" || !/^napplet:[^/?#\s]+\/[^/?#\s]+$/.test(request.convention))) {
|
|
11732
11713
|
return { error: "invalid convention" };
|
|
11733
11714
|
}
|
|
11734
11715
|
const allowedKeys = ["archetype", "action", "convention", "payload", "handler", "behavior"];
|
|
@@ -11744,19 +11725,20 @@ function validateIntentRequest(value) {
|
|
|
11744
11725
|
return { error: "invoke rejected" };
|
|
11745
11726
|
}
|
|
11746
11727
|
const record = request.behavior;
|
|
11747
|
-
if (Object.keys(record).some((key) => key !== "focus" && key !== "reuse") || hasOwn(record, "focus") && typeof record.focus !== "boolean" || hasOwn(record, "reuse") && typeof record.reuse !== "boolean") {
|
|
11728
|
+
if (Object.keys(record).some((key) => key !== "focus" && key !== "newWindow" && key !== "reuse") || hasOwn(record, "focus") && typeof record.focus !== "boolean" || hasOwn(record, "newWindow") && typeof record.newWindow !== "boolean" || hasOwn(record, "reuse") && typeof record.reuse !== "boolean") {
|
|
11748
11729
|
return { error: "invoke rejected" };
|
|
11749
11730
|
}
|
|
11750
11731
|
behavior = {
|
|
11751
11732
|
...record.focus === void 0 ? {} : { focus: record.focus },
|
|
11733
|
+
...record.newWindow === void 0 ? {} : { newWindow: record.newWindow },
|
|
11752
11734
|
...record.reuse === void 0 ? {} : { reuse: record.reuse }
|
|
11753
11735
|
};
|
|
11754
11736
|
}
|
|
11755
11737
|
return {
|
|
11756
11738
|
request: {
|
|
11757
11739
|
archetype: request.archetype,
|
|
11758
|
-
action
|
|
11759
|
-
convention: request.convention,
|
|
11740
|
+
action,
|
|
11741
|
+
...typeof request.convention === "string" ? { convention: request.convention } : {},
|
|
11760
11742
|
...hasOwn(request, "payload") ? { payload: request.payload } : {},
|
|
11761
11743
|
...typeof request.handler === "string" ? { handler: request.handler } : {},
|
|
11762
11744
|
...behavior === void 0 ? {} : { behavior }
|
|
@@ -11773,11 +11755,20 @@ function settleResolverCall(call, send, resultType, id, onValue) {
|
|
|
11773
11755
|
}
|
|
11774
11756
|
pending.then((value) => send(onValue(value))).catch((error) => send({ type: resultType, id, error: toErrorMessage4(error) }));
|
|
11775
11757
|
}
|
|
11776
|
-
function
|
|
11758
|
+
function rejectedResult(request, error) {
|
|
11759
|
+
return {
|
|
11760
|
+
ok: false,
|
|
11761
|
+
archetype: request.archetype,
|
|
11762
|
+
action: request.action ?? "open",
|
|
11763
|
+
handled: false,
|
|
11764
|
+
error
|
|
11765
|
+
};
|
|
11766
|
+
}
|
|
11767
|
+
function rejectInvoke(send, id, request, error = "invoke rejected") {
|
|
11777
11768
|
send({
|
|
11778
11769
|
type: "intent.invoke.result",
|
|
11779
11770
|
id,
|
|
11780
|
-
result:
|
|
11771
|
+
result: rejectedResult(request, error)
|
|
11781
11772
|
});
|
|
11782
11773
|
}
|
|
11783
11774
|
function createIntentService(options) {
|
|
@@ -11792,46 +11783,40 @@ function createIntentService(options) {
|
|
|
11792
11783
|
const id = m.id ?? "";
|
|
11793
11784
|
const validation = validateIntentRequest(m.request);
|
|
11794
11785
|
if ("error" in validation) {
|
|
11795
|
-
|
|
11796
|
-
|
|
11786
|
+
const raw = typeof m.request === "object" && m.request !== null ? m.request : {};
|
|
11787
|
+
rejectInvoke(
|
|
11788
|
+
send,
|
|
11797
11789
|
id,
|
|
11798
|
-
|
|
11799
|
-
|
|
11790
|
+
{
|
|
11791
|
+
archetype: typeof raw.archetype === "string" ? raw.archetype : "",
|
|
11792
|
+
action: typeof raw.action === "string" ? raw.action : "open"
|
|
11793
|
+
},
|
|
11794
|
+
validation.error
|
|
11795
|
+
);
|
|
11800
11796
|
return;
|
|
11801
11797
|
}
|
|
11802
11798
|
const sender = runtimeContext?.resolveDTag(windowId);
|
|
11803
11799
|
if (!sender) {
|
|
11804
|
-
rejectInvoke(send, id);
|
|
11800
|
+
rejectInvoke(send, id, validation.request);
|
|
11805
11801
|
return;
|
|
11806
11802
|
}
|
|
11807
11803
|
let pending;
|
|
11808
11804
|
try {
|
|
11809
11805
|
pending = Promise.resolve(resolver.invoke(validation.request, { sender }));
|
|
11810
11806
|
} catch {
|
|
11811
|
-
rejectInvoke(send, id);
|
|
11807
|
+
rejectInvoke(send, id, validation.request);
|
|
11812
11808
|
return;
|
|
11813
11809
|
}
|
|
11814
11810
|
void pending.then(
|
|
11815
|
-
(
|
|
11816
|
-
if (outcome.result.ok && (!("retained" in outcome) || typeof outcome.retained.start !== "function")) {
|
|
11817
|
-
rejectInvoke(send, id);
|
|
11818
|
-
return;
|
|
11819
|
-
}
|
|
11811
|
+
(result2) => {
|
|
11820
11812
|
send({
|
|
11821
11813
|
type: "intent.invoke.result",
|
|
11822
11814
|
id,
|
|
11823
|
-
result:
|
|
11815
|
+
result: result2
|
|
11824
11816
|
});
|
|
11825
|
-
if (!outcome.result.ok || !("retained" in outcome)) return;
|
|
11826
|
-
try {
|
|
11827
|
-
const started = outcome.retained.start();
|
|
11828
|
-
void Promise.resolve(started).catch(() => {
|
|
11829
|
-
});
|
|
11830
|
-
} catch {
|
|
11831
|
-
}
|
|
11832
11817
|
},
|
|
11833
11818
|
() => {
|
|
11834
|
-
rejectInvoke(send, id);
|
|
11819
|
+
rejectInvoke(send, id, validation.request);
|
|
11835
11820
|
}
|
|
11836
11821
|
).catch(() => {
|
|
11837
11822
|
});
|
|
@@ -11916,128 +11901,134 @@ function candidatesFor(catalog, archetype, defaultHandler) {
|
|
|
11916
11901
|
...entry.title === void 0 ? {} : { title: entry.title },
|
|
11917
11902
|
actions: [...support.actions],
|
|
11918
11903
|
conventions: [...support.conventions],
|
|
11919
|
-
contracts: support.contracts.map((contract) => ({
|
|
11920
|
-
convention: contract.convention,
|
|
11921
|
-
...contract.eventKinds === void 0 ? {} : { eventKinds: [...contract.eventKinds] }
|
|
11922
|
-
})),
|
|
11923
11904
|
...entry.dTag === defaultHandler ? { isDefault: true } : {}
|
|
11924
11905
|
});
|
|
11925
11906
|
}
|
|
11926
11907
|
return candidates;
|
|
11927
11908
|
}
|
|
11928
|
-
function reject(error) {
|
|
11929
|
-
return {
|
|
11909
|
+
function reject(request, error) {
|
|
11910
|
+
return {
|
|
11911
|
+
ok: false,
|
|
11912
|
+
archetype: request.archetype,
|
|
11913
|
+
action: request.action ?? "open",
|
|
11914
|
+
handled: false,
|
|
11915
|
+
error
|
|
11916
|
+
};
|
|
11917
|
+
}
|
|
11918
|
+
async function availabilityForCatalog(options, archetype) {
|
|
11919
|
+
const catalog = await options.loadCatalog();
|
|
11920
|
+
const defaultHandler = options.getDefaultHandler?.(archetype);
|
|
11921
|
+
const candidates = candidatesFor(catalog, archetype, defaultHandler);
|
|
11922
|
+
return {
|
|
11923
|
+
archetype,
|
|
11924
|
+
available: candidates.length > 0,
|
|
11925
|
+
candidates,
|
|
11926
|
+
hasDefault: defaultHandler !== void 0 && candidates.some((candidate) => candidate.dTag === defaultHandler)
|
|
11927
|
+
};
|
|
11928
|
+
}
|
|
11929
|
+
async function chooseCompatible(options, request, candidates, sender) {
|
|
11930
|
+
if (!options.chooseHandler) {
|
|
11931
|
+
return request.handler === "choose" ? { error: "user cancelled" } : { error: "invoke rejected" };
|
|
11932
|
+
}
|
|
11933
|
+
const picked = await options.chooseHandler(request.archetype, candidates, sender);
|
|
11934
|
+
if (picked === void 0) return { error: "user cancelled" };
|
|
11935
|
+
const candidate = candidates.find((item) => item.dTag === picked);
|
|
11936
|
+
return candidate ? { candidate } : { error: "invoke rejected" };
|
|
11937
|
+
}
|
|
11938
|
+
async function pickHandler(options, request, compatible, sender) {
|
|
11939
|
+
const preference = request.handler;
|
|
11940
|
+
if (typeof preference === "string" && preference !== "default" && preference !== "choose") {
|
|
11941
|
+
const candidate = compatible.find((item) => item.dTag === preference);
|
|
11942
|
+
if (!candidate || !options.authorizeExplicitHandler) return { error: "invoke rejected" };
|
|
11943
|
+
let authorized = false;
|
|
11944
|
+
try {
|
|
11945
|
+
authorized = await options.authorizeExplicitHandler(
|
|
11946
|
+
sender,
|
|
11947
|
+
preference,
|
|
11948
|
+
request,
|
|
11949
|
+
candidate
|
|
11950
|
+
);
|
|
11951
|
+
} catch {
|
|
11952
|
+
return { error: "invoke rejected" };
|
|
11953
|
+
}
|
|
11954
|
+
return authorized ? { candidate } : { error: "invoke rejected" };
|
|
11955
|
+
}
|
|
11956
|
+
if (preference === "choose") {
|
|
11957
|
+
return chooseCompatible(options, request, compatible, sender);
|
|
11958
|
+
}
|
|
11959
|
+
const defaultCandidate = compatible.find((candidate) => candidate.isDefault === true);
|
|
11960
|
+
if (preference === "default") {
|
|
11961
|
+
return defaultCandidate ? { candidate: defaultCandidate } : { error: "invoke rejected" };
|
|
11962
|
+
}
|
|
11963
|
+
if (defaultCandidate) return { candidate: defaultCandidate };
|
|
11964
|
+
if (compatible.length === 1) return { candidate: compatible[0] };
|
|
11965
|
+
return chooseCompatible(options, request, compatible, sender);
|
|
11966
|
+
}
|
|
11967
|
+
async function invokeFromCatalog(options, request, context) {
|
|
11968
|
+
const catalog = await options.loadCatalog();
|
|
11969
|
+
const defaultHandler = options.getDefaultHandler?.(request.archetype);
|
|
11970
|
+
const candidates = candidatesFor(catalog, request.archetype, defaultHandler);
|
|
11971
|
+
if (candidates.length === 0) return reject(request, "no handler");
|
|
11972
|
+
const action = request.action ?? "open";
|
|
11973
|
+
const actionCompatible = candidates.filter((candidate) => candidate.actions.includes(action));
|
|
11974
|
+
if (actionCompatible.length === 0) return reject(request, "unsupported action");
|
|
11975
|
+
const compatible = request.convention === void 0 ? actionCompatible : actionCompatible.filter((candidate) => candidate.conventions.includes(request.convention));
|
|
11976
|
+
if (compatible.length === 0) return reject(request, "unsupported convention");
|
|
11977
|
+
const sender = context.sender;
|
|
11978
|
+
if (typeof sender !== "string" || sender.length === 0) {
|
|
11979
|
+
return reject(request, "invoke rejected");
|
|
11980
|
+
}
|
|
11981
|
+
const selected = await pickHandler(options, request, compatible, sender);
|
|
11982
|
+
if ("error" in selected) return reject(request, selected.error);
|
|
11983
|
+
const convention = request.convention ?? selected.candidate.conventions.find((value) => {
|
|
11984
|
+
const match = /^napplet:[^/?#\s]+\/([^/?#\s]+)$/.exec(value);
|
|
11985
|
+
return match?.[1] === action;
|
|
11986
|
+
});
|
|
11987
|
+
if (!convention) return reject(request, "unsupported convention");
|
|
11988
|
+
const behavior = request.behavior === void 0 ? void 0 : Object.freeze({
|
|
11989
|
+
...request.behavior.focus === void 0 ? {} : { focus: request.behavior.focus },
|
|
11990
|
+
...request.behavior.newWindow === void 0 ? {} : { newWindow: request.behavior.newWindow },
|
|
11991
|
+
...request.behavior.reuse === void 0 ? {} : { reuse: request.behavior.reuse }
|
|
11992
|
+
});
|
|
11993
|
+
const params = Object.freeze({
|
|
11994
|
+
handler: selected.candidate.dTag,
|
|
11995
|
+
sender,
|
|
11996
|
+
archetype: request.archetype,
|
|
11997
|
+
action,
|
|
11998
|
+
convention,
|
|
11999
|
+
...request.payload === void 0 ? {} : { payload: request.payload },
|
|
12000
|
+
...behavior === void 0 ? {} : { behavior }
|
|
12001
|
+
});
|
|
12002
|
+
let target;
|
|
12003
|
+
try {
|
|
12004
|
+
target = await options.targets.dispatch(params);
|
|
12005
|
+
} catch {
|
|
12006
|
+
return reject(request, "invoke failed");
|
|
12007
|
+
}
|
|
12008
|
+
if (!target || typeof target.windowId !== "string" || target.windowId.length === 0) {
|
|
12009
|
+
return reject(request, "invoke failed");
|
|
12010
|
+
}
|
|
12011
|
+
return {
|
|
12012
|
+
ok: true,
|
|
12013
|
+
archetype: request.archetype,
|
|
12014
|
+
action,
|
|
12015
|
+
handled: true,
|
|
12016
|
+
handler: selected.candidate.dTag,
|
|
12017
|
+
windowId: target.windowId,
|
|
12018
|
+
convention
|
|
12019
|
+
};
|
|
11930
12020
|
}
|
|
11931
12021
|
function createCatalogIntentResolver(options) {
|
|
11932
12022
|
if (!options || typeof options.loadCatalog !== "function") {
|
|
11933
12023
|
throw new Error("createCatalogIntentResolver: options.loadCatalog is required");
|
|
11934
12024
|
}
|
|
11935
|
-
if (!options.targets || typeof options.targets.
|
|
12025
|
+
if (!options.targets || typeof options.targets.dispatch !== "function") {
|
|
11936
12026
|
throw new Error("createCatalogIntentResolver: options.targets is required");
|
|
11937
12027
|
}
|
|
11938
|
-
const {
|
|
11939
|
-
loadCatalog,
|
|
11940
|
-
targets,
|
|
11941
|
-
getDefaultHandler,
|
|
11942
|
-
chooseHandler,
|
|
11943
|
-
authorizeExplicitHandler
|
|
11944
|
-
} = options;
|
|
11945
12028
|
const listeners = /* @__PURE__ */ new Set();
|
|
11946
|
-
|
|
11947
|
-
const catalog = await loadCatalog();
|
|
11948
|
-
const defaultHandler = getDefaultHandler?.(archetype);
|
|
11949
|
-
const candidates = candidatesFor(catalog, archetype, defaultHandler);
|
|
11950
|
-
return {
|
|
11951
|
-
archetype,
|
|
11952
|
-
available: candidates.length > 0,
|
|
11953
|
-
candidates,
|
|
11954
|
-
hasDefault: defaultHandler !== void 0 && candidates.some((candidate) => candidate.dTag === defaultHandler)
|
|
11955
|
-
};
|
|
11956
|
-
}
|
|
11957
|
-
async function chooseCompatible(request, candidates, sender) {
|
|
11958
|
-
if (!chooseHandler) {
|
|
11959
|
-
return request.handler === "choose" ? { error: "user cancelled" } : { error: "invoke rejected" };
|
|
11960
|
-
}
|
|
11961
|
-
const picked = await chooseHandler(request.archetype, candidates, sender);
|
|
11962
|
-
if (picked === void 0) return { error: "user cancelled" };
|
|
11963
|
-
const candidate = candidates.find((item) => item.dTag === picked);
|
|
11964
|
-
return candidate ? { candidate } : { error: "invoke rejected" };
|
|
11965
|
-
}
|
|
11966
|
-
async function pickHandler(request, compatible, sender) {
|
|
11967
|
-
const preference = request.handler;
|
|
11968
|
-
if (typeof preference === "string" && preference !== "default" && preference !== "choose") {
|
|
11969
|
-
const candidate = compatible.find((item) => item.dTag === preference);
|
|
11970
|
-
if (!candidate || !authorizeExplicitHandler) return { error: "invoke rejected" };
|
|
11971
|
-
let authorized = false;
|
|
11972
|
-
try {
|
|
11973
|
-
authorized = await authorizeExplicitHandler(sender, preference, request, candidate);
|
|
11974
|
-
} catch {
|
|
11975
|
-
return { error: "invoke rejected" };
|
|
11976
|
-
}
|
|
11977
|
-
return authorized ? { candidate } : { error: "invoke rejected" };
|
|
11978
|
-
}
|
|
11979
|
-
if (preference === "choose") {
|
|
11980
|
-
return chooseCompatible(request, compatible, sender);
|
|
11981
|
-
}
|
|
11982
|
-
const defaultCandidate = compatible.find((candidate) => candidate.isDefault === true);
|
|
11983
|
-
if (preference === "default") {
|
|
11984
|
-
return defaultCandidate ? { candidate: defaultCandidate } : { error: "invoke rejected" };
|
|
11985
|
-
}
|
|
11986
|
-
if (defaultCandidate) return { candidate: defaultCandidate };
|
|
11987
|
-
if (compatible.length === 1) return { candidate: compatible[0] };
|
|
11988
|
-
return chooseCompatible(request, compatible, sender);
|
|
11989
|
-
}
|
|
11990
|
-
async function invoke(request, context) {
|
|
11991
|
-
const catalog = await loadCatalog();
|
|
11992
|
-
const defaultHandler = getDefaultHandler?.(request.archetype);
|
|
11993
|
-
const candidates = candidatesFor(catalog, request.archetype, defaultHandler);
|
|
11994
|
-
if (candidates.length === 0) return { result: reject("no handler") };
|
|
11995
|
-
const compatible = candidates.filter((candidate) => candidate.contracts.some((contract) => contract.convention === request.convention));
|
|
11996
|
-
if (compatible.length === 0) return { result: reject("unsupported convention") };
|
|
11997
|
-
const sender = context.sender;
|
|
11998
|
-
if (typeof sender !== "string" || sender.length === 0) {
|
|
11999
|
-
return { result: reject("invoke rejected") };
|
|
12000
|
-
}
|
|
12001
|
-
const selected = await pickHandler(request, compatible, sender);
|
|
12002
|
-
if ("error" in selected) return { result: reject(selected.error) };
|
|
12003
|
-
const delivery = Object.freeze({
|
|
12004
|
-
sender,
|
|
12005
|
-
archetype: request.archetype,
|
|
12006
|
-
action: request.action,
|
|
12007
|
-
convention: request.convention,
|
|
12008
|
-
...request.payload === void 0 ? {} : { payload: request.payload }
|
|
12009
|
-
});
|
|
12010
|
-
const behavior = request.behavior === void 0 ? void 0 : Object.freeze({
|
|
12011
|
-
...request.behavior.focus === void 0 ? {} : { focus: request.behavior.focus },
|
|
12012
|
-
...request.behavior.reuse === void 0 ? {} : { reuse: request.behavior.reuse }
|
|
12013
|
-
});
|
|
12014
|
-
const params = Object.freeze({
|
|
12015
|
-
handler: selected.candidate.dTag,
|
|
12016
|
-
delivery,
|
|
12017
|
-
...behavior === void 0 ? {} : { behavior }
|
|
12018
|
-
});
|
|
12019
|
-
let retained;
|
|
12020
|
-
try {
|
|
12021
|
-
retained = await targets.retain(params);
|
|
12022
|
-
} catch {
|
|
12023
|
-
return { result: reject("invoke rejected") };
|
|
12024
|
-
}
|
|
12025
|
-
if (!retained || typeof retained.start !== "function") {
|
|
12026
|
-
return { result: reject("invoke rejected") };
|
|
12027
|
-
}
|
|
12028
|
-
return {
|
|
12029
|
-
result: {
|
|
12030
|
-
ok: true,
|
|
12031
|
-
archetype: request.archetype,
|
|
12032
|
-
action: request.action,
|
|
12033
|
-
convention: request.convention,
|
|
12034
|
-
handler: selected.candidate.dTag
|
|
12035
|
-
},
|
|
12036
|
-
retained
|
|
12037
|
-
};
|
|
12038
|
-
}
|
|
12029
|
+
const availabilityFor = (archetype) => availabilityForCatalog(options, archetype);
|
|
12039
12030
|
async function handlers() {
|
|
12040
|
-
const catalog = await loadCatalog();
|
|
12031
|
+
const catalog = await options.loadCatalog();
|
|
12041
12032
|
const archetypes = /* @__PURE__ */ new Set();
|
|
12042
12033
|
for (const entry of catalog) {
|
|
12043
12034
|
for (const slug of Object.keys(entry.archetypes)) archetypes.add(slug);
|
|
@@ -12045,7 +12036,7 @@ function createCatalogIntentResolver(options) {
|
|
|
12045
12036
|
return Promise.all([...archetypes].map((archetype) => availabilityFor(archetype)));
|
|
12046
12037
|
}
|
|
12047
12038
|
return {
|
|
12048
|
-
invoke,
|
|
12039
|
+
invoke: (request, context) => invokeFromCatalog(options, request, context),
|
|
12049
12040
|
available: availabilityFor,
|
|
12050
12041
|
handlers,
|
|
12051
12042
|
onChanged(listener) {
|
|
@@ -12072,21 +12063,12 @@ function actionFromConvention(slug, convention) {
|
|
|
12072
12063
|
}
|
|
12073
12064
|
function manifestToIntentCatalogEntry(manifest) {
|
|
12074
12065
|
const archetypes = {};
|
|
12075
|
-
for (const { slug, convention
|
|
12066
|
+
for (const { slug, convention } of manifest.archetypes) {
|
|
12076
12067
|
const action = actionFromConvention(slug, convention);
|
|
12077
|
-
if (eventKinds?.some((kind) => !Number.isSafeInteger(kind) || kind < 0)) {
|
|
12078
|
-
throw new TypeError("manifest archetype event kinds must be unsigned safe integers");
|
|
12079
|
-
}
|
|
12080
12068
|
const support = archetypes[slug] ??= {
|
|
12081
12069
|
actions: [],
|
|
12082
|
-
conventions: []
|
|
12083
|
-
contracts: []
|
|
12070
|
+
conventions: []
|
|
12084
12071
|
};
|
|
12085
|
-
const contract = {
|
|
12086
|
-
convention,
|
|
12087
|
-
...eventKinds === void 0 || eventKinds.length === 0 ? {} : { eventKinds: [...eventKinds] }
|
|
12088
|
-
};
|
|
12089
|
-
support.contracts.push(contract);
|
|
12090
12072
|
if (!support.actions.includes(action)) support.actions.push(action);
|
|
12091
12073
|
if (!support.conventions.includes(convention)) support.conventions.push(convention);
|
|
12092
12074
|
}
|
|
@@ -13095,25 +13077,13 @@ var BrowserIntentController = class {
|
|
|
13095
13077
|
}
|
|
13096
13078
|
options;
|
|
13097
13079
|
maxAttempts;
|
|
13098
|
-
|
|
13099
|
-
const
|
|
13100
|
-
let started;
|
|
13101
|
-
let delivered = false;
|
|
13102
|
-
return {
|
|
13103
|
-
start: () => {
|
|
13104
|
-
started ??= this.start(retained, () => delivered, () => {
|
|
13105
|
-
delivered = true;
|
|
13106
|
-
});
|
|
13107
|
-
return started;
|
|
13108
|
-
}
|
|
13109
|
-
};
|
|
13110
|
-
}
|
|
13111
|
-
async start(params, isDelivered, markDelivered) {
|
|
13080
|
+
async dispatch(params) {
|
|
13081
|
+
const dispatch2 = freezeDispatch(params);
|
|
13112
13082
|
let reason = "no-current-target";
|
|
13113
13083
|
for (let attempt = 1; attempt <= this.maxAttempts; attempt += 1) {
|
|
13114
13084
|
let generation;
|
|
13115
13085
|
try {
|
|
13116
|
-
generation = await this.options.openOrReuse(
|
|
13086
|
+
generation = await this.options.openOrReuse(dispatch2, attempt);
|
|
13117
13087
|
} catch {
|
|
13118
13088
|
reason = "open-failed";
|
|
13119
13089
|
continue;
|
|
@@ -13132,17 +13102,18 @@ var BrowserIntentController = class {
|
|
|
13132
13102
|
reason = "no-current-target";
|
|
13133
13103
|
continue;
|
|
13134
13104
|
}
|
|
13135
|
-
if (isDelivered()) return;
|
|
13136
|
-
markDelivered();
|
|
13137
13105
|
try {
|
|
13138
|
-
await this.options.send(generation,
|
|
13139
|
-
|
|
13106
|
+
await this.options.send(generation, dispatch2);
|
|
13107
|
+
const windowId = this.options.getWindowId(generation);
|
|
13108
|
+
if (!windowId) throw new Error("intent target window is unavailable");
|
|
13109
|
+
return { windowId };
|
|
13140
13110
|
} catch {
|
|
13141
|
-
this.options.onTerminal?.(
|
|
13142
|
-
|
|
13111
|
+
this.options.onTerminal?.(dispatch2, "send-failed");
|
|
13112
|
+
throw new Error("intent target send failed");
|
|
13143
13113
|
}
|
|
13144
13114
|
}
|
|
13145
|
-
this.options.onTerminal?.(
|
|
13115
|
+
this.options.onTerminal?.(dispatch2, reason);
|
|
13116
|
+
throw new Error(`intent target ${reason}`);
|
|
13146
13117
|
}
|
|
13147
13118
|
};
|
|
13148
13119
|
function normalizeAttempts(value) {
|
|
@@ -13150,15 +13121,22 @@ function normalizeAttempts(value) {
|
|
|
13150
13121
|
if (!Number.isFinite(value)) throw new TypeError("maxAttempts must be finite");
|
|
13151
13122
|
return Math.min(MAX_INTENT_DELIVERY_ATTEMPTS, Math.max(1, Math.floor(value)));
|
|
13152
13123
|
}
|
|
13153
|
-
function
|
|
13124
|
+
function freezeDispatch(params) {
|
|
13154
13125
|
if (!params || typeof params.handler !== "string" || params.handler.length === 0) {
|
|
13155
|
-
throw new TypeError("Intent
|
|
13126
|
+
throw new TypeError("Intent dispatch requires a handler");
|
|
13127
|
+
}
|
|
13128
|
+
if (typeof params.sender !== "string" || params.sender.length === 0 || typeof params.archetype !== "string" || params.archetype.length === 0 || typeof params.action !== "string" || params.action.length === 0 || typeof params.convention !== "string" || params.convention.length === 0) {
|
|
13129
|
+
throw new TypeError("Intent dispatch requires canonical routing fields");
|
|
13156
13130
|
}
|
|
13157
|
-
const
|
|
13131
|
+
const payload = freezeValue(params.payload);
|
|
13158
13132
|
const behavior = params.behavior === void 0 ? void 0 : freezeValue({ ...params.behavior });
|
|
13159
13133
|
return Object.freeze({
|
|
13160
13134
|
handler: params.handler,
|
|
13161
|
-
|
|
13135
|
+
sender: params.sender,
|
|
13136
|
+
archetype: params.archetype,
|
|
13137
|
+
action: params.action,
|
|
13138
|
+
convention: params.convention,
|
|
13139
|
+
...params.payload === void 0 ? {} : { payload },
|
|
13162
13140
|
...behavior === void 0 ? {} : { behavior }
|
|
13163
13141
|
});
|
|
13164
13142
|
}
|
|
@@ -13192,10 +13170,10 @@ var InstalledNappletCatalog = class {
|
|
|
13192
13170
|
aggregateHash: resolved.aggregateHash,
|
|
13193
13171
|
pointer: copyPointer(resolved.pointer),
|
|
13194
13172
|
...resolved.manifest.title === void 0 ? {} : { title: resolved.manifest.title },
|
|
13173
|
+
requires: [...resolved.manifest.requires],
|
|
13195
13174
|
archetypes: resolved.manifest.archetypes.map((archetype) => ({
|
|
13196
13175
|
slug: archetype.slug,
|
|
13197
|
-
convention: archetype.convention
|
|
13198
|
-
...archetype.eventKinds === void 0 ? {} : { eventKinds: [...archetype.eventKinds] }
|
|
13176
|
+
convention: archetype.convention
|
|
13199
13177
|
}))
|
|
13200
13178
|
});
|
|
13201
13179
|
this.records.set(record.dTag, record);
|
|
@@ -13231,13 +13209,12 @@ var InstalledNappletCatalog = class {
|
|
|
13231
13209
|
}
|
|
13232
13210
|
/** Return exact handler candidates derived only from installed manifests. */
|
|
13233
13211
|
intentCatalog() {
|
|
13234
|
-
return this.installed().map((record) => manifestToIntentCatalogEntry({
|
|
13212
|
+
return this.installed().filter((record) => record.requires.includes("inc")).map((record) => manifestToIntentCatalogEntry({
|
|
13235
13213
|
dTag: record.dTag,
|
|
13236
13214
|
...record.title === void 0 ? {} : { title: record.title },
|
|
13237
13215
|
archetypes: record.archetypes.map((archetype) => ({
|
|
13238
13216
|
slug: archetype.slug,
|
|
13239
|
-
convention: archetype.convention
|
|
13240
|
-
...archetype.eventKinds === void 0 ? {} : { eventKinds: [...archetype.eventKinds] }
|
|
13217
|
+
convention: archetype.convention
|
|
13241
13218
|
}))
|
|
13242
13219
|
}));
|
|
13243
13220
|
}
|
|
@@ -13273,10 +13250,8 @@ function copyPointer(pointer) {
|
|
|
13273
13250
|
function freezeRecord(record) {
|
|
13274
13251
|
return Object.freeze({
|
|
13275
13252
|
...record,
|
|
13276
|
-
|
|
13277
|
-
|
|
13278
|
-
...archetype.eventKinds === void 0 ? {} : { eventKinds: Object.freeze([...archetype.eventKinds]) }
|
|
13279
|
-
})))
|
|
13253
|
+
requires: Object.freeze([...record.requires]),
|
|
13254
|
+
archetypes: Object.freeze(record.archetypes.map((archetype) => Object.freeze({ ...archetype })))
|
|
13280
13255
|
});
|
|
13281
13256
|
}
|
|
13282
13257
|
|
|
@@ -14898,6 +14873,7 @@ function createDefaultIntentHost() {
|
|
|
14898
14873
|
openOrReuse: () => null,
|
|
14899
14874
|
waitForReady: () => void 0,
|
|
14900
14875
|
isCurrent: () => false,
|
|
14876
|
+
getWindowId: () => null,
|
|
14901
14877
|
send: () => void 0
|
|
14902
14878
|
})
|
|
14903
14879
|
};
|
|
@@ -18925,7 +18901,7 @@ function aggregateTagValue(tags) {
|
|
|
18925
18901
|
return void 0;
|
|
18926
18902
|
}
|
|
18927
18903
|
|
|
18928
|
-
// ../nip/dist/chunk-
|
|
18904
|
+
// ../nip/dist/chunk-ZQSTY6HR.js
|
|
18929
18905
|
var MIB = 1024 * 1024;
|
|
18930
18906
|
var NAPPLET_ARTIFACT_CACHE_NAME = "kehto:napplet-artifacts:v1";
|
|
18931
18907
|
var SCHEMA_VERSION = 1;
|
|
@@ -19390,28 +19366,13 @@ function archetypesFromTags(tags) {
|
|
|
19390
19366
|
"archetype slug must match the convention archetype"
|
|
19391
19367
|
);
|
|
19392
19368
|
}
|
|
19393
|
-
|
|
19394
|
-
|
|
19395
|
-
|
|
19396
|
-
|
|
19397
|
-
|
|
19398
|
-
|
|
19399
|
-
|
|
19400
|
-
}
|
|
19401
|
-
const kind = Number(match[1]);
|
|
19402
|
-
if (!Number.isSafeInteger(kind) || kind < 0) {
|
|
19403
|
-
throw new NappletResolutionError(
|
|
19404
|
-
"invalid-manifest",
|
|
19405
|
-
"archetype event kinds must be unsigned safe integers"
|
|
19406
|
-
);
|
|
19407
|
-
}
|
|
19408
|
-
return kind;
|
|
19409
|
-
});
|
|
19410
|
-
out.push({
|
|
19411
|
-
slug,
|
|
19412
|
-
convention,
|
|
19413
|
-
...eventKinds.length === 0 ? {} : { eventKinds }
|
|
19414
|
-
});
|
|
19369
|
+
if (tag.length !== 3) {
|
|
19370
|
+
throw new NappletResolutionError(
|
|
19371
|
+
"invalid-manifest",
|
|
19372
|
+
"archetype tags must contain exactly slug and convention"
|
|
19373
|
+
);
|
|
19374
|
+
}
|
|
19375
|
+
out.push({ slug, convention });
|
|
19415
19376
|
}
|
|
19416
19377
|
return out;
|
|
19417
19378
|
}
|
|
@@ -19773,7 +19734,7 @@ function createPajaIntentTargetOptions(getState, getContext, effects = {}) {
|
|
|
19773
19734
|
const current = state.tabs.find(
|
|
19774
19735
|
(tab2) => matchesInstalledNappletRecord(record, tab2.resolvedTarget) && isCurrentRuntimeTabGeneration(state, context, tab2)
|
|
19775
19736
|
);
|
|
19776
|
-
if (current && params.behavior?.reuse !== false) {
|
|
19737
|
+
if (current && params.behavior?.newWindow !== true && params.behavior?.reuse !== false) {
|
|
19777
19738
|
return bindPajaIntentGeneration(current, record, context.runtime);
|
|
19778
19739
|
}
|
|
19779
19740
|
const resolved = await resolvePajaPointer(record.pointer.value, pajaPointerResolverOptions(context));
|
|
@@ -19810,7 +19771,12 @@ function createPajaIntentTargetOptions(getState, getContext, effects = {}) {
|
|
|
19810
19771
|
}
|
|
19811
19772
|
return true;
|
|
19812
19773
|
},
|
|
19813
|
-
|
|
19774
|
+
getWindowId(generation) {
|
|
19775
|
+
const state = getState();
|
|
19776
|
+
const tab = state ? findRuntimeTabGeneration(state, generation) : null;
|
|
19777
|
+
return tab?.windowId ?? null;
|
|
19778
|
+
},
|
|
19779
|
+
send(generation, params) {
|
|
19814
19780
|
const state = getState();
|
|
19815
19781
|
const context = getContext();
|
|
19816
19782
|
const tab = state && context ? findRuntimeTabGeneration(state, generation) : null;
|
|
@@ -19823,8 +19789,10 @@ function createPajaIntentTargetOptions(getState, getContext, effects = {}) {
|
|
|
19823
19789
|
throw new Error("intent target source is no longer registered");
|
|
19824
19790
|
}
|
|
19825
19791
|
createPajaPostMessageProxy(source, state, tab.windowId).postMessage({
|
|
19826
|
-
type: "
|
|
19827
|
-
|
|
19792
|
+
type: "inc.event",
|
|
19793
|
+
topic: params.convention,
|
|
19794
|
+
sender: params.sender,
|
|
19795
|
+
...params.payload === void 0 ? {} : { payload: params.payload }
|
|
19828
19796
|
}, "*");
|
|
19829
19797
|
}
|
|
19830
19798
|
};
|
|
@@ -19841,12 +19809,12 @@ function pajaPointerResolverOptions(context) {
|
|
|
19841
19809
|
}
|
|
19842
19810
|
function recordSupportsDelivery(record, params) {
|
|
19843
19811
|
return record.archetypes.some(
|
|
19844
|
-
(archetype) => archetype.slug === params.
|
|
19812
|
+
(archetype) => archetype.slug === params.archetype && archetype.convention === params.convention
|
|
19845
19813
|
);
|
|
19846
19814
|
}
|
|
19847
19815
|
function resolvedSupportsDelivery(resolved, params) {
|
|
19848
19816
|
return resolved.manifest.archetypes.some(
|
|
19849
|
-
(archetype) => archetype.slug === params.
|
|
19817
|
+
(archetype) => archetype.slug === params.archetype && archetype.convention === params.convention
|
|
19850
19818
|
);
|
|
19851
19819
|
}
|
|
19852
19820
|
function findRuntimeTabGeneration(state, generation) {
|