@sanity/sdk 3.1.0 → 3.3.0-rc.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/dist/_exports/_internal.d.ts +52 -21
- package/dist/_exports/_internal.d.ts.map +1 -1
- package/dist/_exports/_internal.js +10 -9
- package/dist/_exports/_internal.js.map +1 -1
- package/dist/_exports/agent.d.ts +7 -7
- package/dist/_exports/agent.d.ts.map +1 -1
- package/dist/_exports/agent.js +1 -1
- package/dist/_exports/comlink.d.ts +9 -9
- package/dist/_exports/comlink.d.ts.map +1 -1
- package/dist/_exports/comlink.js +3 -2
- package/dist/_exports/comlink.js.map +1 -1
- package/dist/_exports/dashboard.d.ts +2 -0
- package/dist/_exports/dashboard.js +2 -0
- package/dist/{authStore-C-OEkGaM.d.ts → authStore-BGoEGxOq.d.ts} +531 -63
- package/dist/authStore-BGoEGxOq.d.ts.map +1 -0
- package/dist/{clientStore-gUALQ0qW.js → clientStore-QDrJNTM4.js} +128 -7
- package/dist/clientStore-QDrJNTM4.js.map +1 -0
- package/dist/{createSanityInstance-DCePpBYW.d.ts → createSanityInstance-BQIqyGLX.d.ts} +16 -1
- package/dist/createSanityInstance-BQIqyGLX.d.ts.map +1 -0
- package/dist/{object-BwynGPRZ.js → createStateSourceAction-DdAhEfnZ.js} +2 -82
- package/dist/createStateSourceAction-DdAhEfnZ.js.map +1 -0
- package/dist/defineProperty-Bho9Ngsi.js +31 -0
- package/dist/{getNodeState-E0hjqFOo.js → getNodeState-B4nG0sjm.js} +3 -2
- package/dist/{getNodeState-E0hjqFOo.js.map → getNodeState-B4nG0sjm.js.map} +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +371 -54
- package/dist/index.js.map +1 -1
- package/dist/object-CxXlSVg7.js +83 -0
- package/dist/object-CxXlSVg7.js.map +1 -0
- package/dist/topicStore-Ct7IFNtY.js +565 -0
- package/dist/topicStore-Ct7IFNtY.js.map +1 -0
- package/dist/topicStore-DXvuH9h4.d.ts +435 -0
- package/dist/topicStore-DXvuH9h4.d.ts.map +1 -0
- package/dist/{version-C90rNWaX.js → version-CIhX5ppJ.js} +6 -6
- package/dist/{version-C90rNWaX.js.map → version-CIhX5ppJ.js.map} +1 -1
- package/package.json +20 -19
- package/src/_exports/_internal.ts +12 -0
- package/src/_exports/dashboard.ts +36 -0
- package/src/_exports/index.ts +17 -1
- package/src/applications/applications.test-d.ts +28 -4
- package/src/applications/applications.ts +39 -14
- package/src/auth/authMode.test.ts +24 -0
- package/src/auth/authMode.ts +8 -3
- package/src/auth/authStore.test.ts +18 -0
- package/src/auth/authStore.ts +15 -0
- package/src/auth/authStrategy.ts +2 -0
- package/src/auth/handleAuthCallback.test.ts +33 -0
- package/src/auth/handleAuthCallback.ts +11 -1
- package/src/auth/logout.test.ts +53 -0
- package/src/auth/logout.ts +19 -1
- package/src/auth/oauth/oauthActions.test.ts +593 -0
- package/src/auth/oauth/oauthActions.ts +438 -0
- package/src/auth/oauth/oauthAuth.test.ts +229 -0
- package/src/auth/oauth/oauthAuth.ts +178 -0
- package/src/auth/oauth/pkce.test.ts +44 -0
- package/src/auth/oauth/pkce.ts +60 -0
- package/src/auth/oauth/types.ts +12 -0
- package/src/config/authConfig.ts +18 -0
- package/src/dashboard/messageBus/__fixtures__/test-topics.ts +18 -0
- package/src/dashboard/messageBus/bus.test.ts +1033 -0
- package/src/dashboard/messageBus/bus.ts +1246 -0
- package/src/dashboard/messageBus/store.test.ts +142 -0
- package/src/dashboard/messageBus/store.ts +74 -0
- package/src/dashboard/messageBus/topicStore.test.ts +172 -0
- package/src/dashboard/messageBus/topicStore.ts +184 -0
- package/src/dashboard/messageBus/topics.test.ts +71 -0
- package/src/dashboard/messageBus/topics.ts +260 -0
- package/src/document/actions.ts +3 -3
- package/src/document/applyDocumentActions.ts +3 -3
- package/src/document/documentStore.ts +12 -12
- package/src/document/processMutations.ts +58 -22
- package/src/installations/installations.test-d.ts +3 -3
- package/src/installations/installations.ts +2 -19
- package/src/projection/getProjectionState.ts +2 -2
- package/src/projection/resolveProjection.ts +2 -2
- package/src/query/queryStore.ts +3 -3
- package/src/typegen/defineProjection.test.ts +17 -0
- package/src/typegen/defineProjection.ts +19 -0
- package/src/typegen/groqCompat.ts +122 -0
- package/src/typegen/resolve.ts +37 -0
- package/src/utils/createGroqSearchFilter.test.ts +8 -0
- package/src/utils/createGroqSearchFilter.ts +6 -3
- package/dist/authStore-C-OEkGaM.d.ts.map +0 -1
- package/dist/clientStore-gUALQ0qW.js.map +0 -1
- package/dist/createSanityInstance-DCePpBYW.d.ts.map +0 -1
- package/dist/object-BwynGPRZ.js.map +0 -1
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns true when the input is a non-null object.
|
|
3
|
+
*
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
function isObject(value) {
|
|
7
|
+
return typeof value == "object" && !!value;
|
|
8
|
+
}
|
|
9
|
+
const hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value, key), isPlainObject = (value) => {
|
|
10
|
+
if (!isObject(value)) return !1;
|
|
11
|
+
let prototype = Object.getPrototypeOf(value);
|
|
12
|
+
return prototype === Object.prototype || prototype === null;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Creates a shallow copy without the provided property.
|
|
16
|
+
*
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
function omitProperty(value, key) {
|
|
20
|
+
if (!value) return {};
|
|
21
|
+
let { [key]: _omitted, ...rest } = value;
|
|
22
|
+
return rest;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Creates a shallow copy containing only the provided properties.
|
|
26
|
+
*
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
function pickProperties(value, keys) {
|
|
30
|
+
let result = {};
|
|
31
|
+
for (let key of keys) hasOwn(value, key) && (result[key] = value[key]);
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
const areSetsEqual = (left, right) => {
|
|
35
|
+
if (left.size !== right.size) return !1;
|
|
36
|
+
let unmatched = [...right];
|
|
37
|
+
outer: for (let leftValue of left) {
|
|
38
|
+
for (let index = 0; index < unmatched.length; index++) if (isDeepEqual(leftValue, unmatched[index])) {
|
|
39
|
+
unmatched.splice(index, 1);
|
|
40
|
+
continue outer;
|
|
41
|
+
}
|
|
42
|
+
return !1;
|
|
43
|
+
}
|
|
44
|
+
return unmatched.length === 0;
|
|
45
|
+
}, areMapsEqual = (left, right) => {
|
|
46
|
+
if (left.size !== right.size) return !1;
|
|
47
|
+
let unmatched = [...right.entries()];
|
|
48
|
+
outer: for (let [leftKey, leftValue] of left) {
|
|
49
|
+
for (let index = 0; index < unmatched.length; index++) {
|
|
50
|
+
let [rightKey, rightValue] = unmatched[index];
|
|
51
|
+
if (isDeepEqual(leftKey, rightKey) && isDeepEqual(leftValue, rightValue)) {
|
|
52
|
+
unmatched.splice(index, 1);
|
|
53
|
+
continue outer;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return !1;
|
|
57
|
+
}
|
|
58
|
+
return unmatched.length === 0;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Compares values deeply across the plain object, array, map, and set shapes used by the SDK.
|
|
62
|
+
* This helper is intended for acyclic SDK data structures and does not guard against circular
|
|
63
|
+
* references.
|
|
64
|
+
*
|
|
65
|
+
* @internal
|
|
66
|
+
*/
|
|
67
|
+
function isDeepEqual(left, right) {
|
|
68
|
+
if (Object.is(left, right)) return !0;
|
|
69
|
+
if (!isObject(left) || !isObject(right)) return !1;
|
|
70
|
+
if (left instanceof Date && right instanceof Date) return left.getTime() === right.getTime();
|
|
71
|
+
if (left instanceof RegExp && right instanceof RegExp) return left.source === right.source && left.flags === right.flags;
|
|
72
|
+
if (left instanceof Set && right instanceof Set) return areSetsEqual(left, right);
|
|
73
|
+
if (left instanceof Map && right instanceof Map) return areMapsEqual(left, right);
|
|
74
|
+
if (Array.isArray(left) || Array.isArray(right)) return !Array.isArray(left) || !Array.isArray(right) || left.length !== right.length ? !1 : left.every((value, index) => isDeepEqual(value, right[index]));
|
|
75
|
+
if (!isPlainObject(left) || !isPlainObject(right)) return !1;
|
|
76
|
+
let leftKeys = Object.keys(left), rightKeys = Object.keys(right);
|
|
77
|
+
if (leftKeys.length !== rightKeys.length) return !1;
|
|
78
|
+
for (let key of leftKeys) if (!hasOwn(right, key) || !isDeepEqual(left[key], right[key])) return !1;
|
|
79
|
+
return !0;
|
|
80
|
+
}
|
|
81
|
+
export { pickProperties as i, isObject as n, omitProperty as r, isDeepEqual as t };
|
|
82
|
+
|
|
83
|
+
//# sourceMappingURL=object-CxXlSVg7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"object-CxXlSVg7.js","names":[],"sources":["../src/utils/object.ts"],"sourcesContent":["/**\n * Returns true when the input is a non-null object.\n *\n * @internal\n */\nexport function isObject(value: unknown): value is object {\n return typeof value === 'object' && value !== null\n}\n\nconst hasOwn = (value: object, key: PropertyKey) => Object.prototype.hasOwnProperty.call(value, key)\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> => {\n if (!isObject(value)) return false\n\n const prototype = Object.getPrototypeOf(value)\n return prototype === Object.prototype || prototype === null\n}\n\n/**\n * Creates a shallow copy without the provided property.\n *\n * @internal\n */\nexport function omitProperty<T extends object, K extends keyof T>(\n value: T | null | undefined,\n key: K,\n): Omit<T, K> {\n if (!value) return {} as Omit<T, K>\n\n const {[key]: _omitted, ...rest} = value\n return rest\n}\n\n/**\n * Creates a shallow copy containing only the provided properties.\n *\n * @internal\n */\nexport function pickProperties<T extends object, K extends keyof T>(\n value: T,\n keys: readonly K[],\n): Pick<T, K> {\n const result = {} as Pick<T, K>\n\n for (const key of keys) {\n if (hasOwn(value, key)) {\n result[key] = value[key]\n }\n }\n\n return result\n}\n\nconst areSetsEqual = (left: Set<unknown>, right: Set<unknown>): boolean => {\n if (left.size !== right.size) return false\n\n const unmatched = [...right]\n\n outer: for (const leftValue of left) {\n for (let index = 0; index < unmatched.length; index++) {\n if (isDeepEqual(leftValue, unmatched[index])) {\n unmatched.splice(index, 1)\n continue outer\n }\n }\n\n return false\n }\n\n return unmatched.length === 0\n}\n\nconst areMapsEqual = (left: Map<unknown, unknown>, right: Map<unknown, unknown>): boolean => {\n if (left.size !== right.size) return false\n\n const unmatched = [...right.entries()]\n\n outer: for (const [leftKey, leftValue] of left) {\n for (let index = 0; index < unmatched.length; index++) {\n const [rightKey, rightValue] = unmatched[index]\n\n if (isDeepEqual(leftKey, rightKey) && isDeepEqual(leftValue, rightValue)) {\n unmatched.splice(index, 1)\n continue outer\n }\n }\n\n return false\n }\n\n return unmatched.length === 0\n}\n\n/**\n * Compares values deeply across the plain object, array, map, and set shapes used by the SDK.\n * This helper is intended for acyclic SDK data structures and does not guard against circular\n * references.\n *\n * @internal\n */\nexport function isDeepEqual<T>(left: T, right: T): boolean {\n if (Object.is(left, right)) return true\n\n if (!isObject(left) || !isObject(right)) return false\n\n if (left instanceof Date && right instanceof Date) {\n return left.getTime() === right.getTime()\n }\n\n if (left instanceof RegExp && right instanceof RegExp) {\n return left.source === right.source && left.flags === right.flags\n }\n\n if (left instanceof Set && right instanceof Set) {\n return areSetsEqual(left, right)\n }\n\n if (left instanceof Map && right instanceof Map) {\n return areMapsEqual(left, right)\n }\n\n if (Array.isArray(left) || Array.isArray(right)) {\n if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) return false\n\n return left.every((value, index) => isDeepEqual(value, right[index]))\n }\n\n if (!isPlainObject(left) || !isPlainObject(right)) return false\n\n const leftKeys = Object.keys(left)\n const rightKeys = Object.keys(right)\n\n if (leftKeys.length !== rightKeys.length) return false\n\n for (const key of leftKeys) {\n if (!hasOwn(right, key) || !isDeepEqual(left[key], right[key])) {\n return false\n }\n }\n\n return true\n}\n"],"mappings":";;;;;AAKA,SAAgB,SAAS,OAAiC;CACxD,OAAO,OAAO,SAAU,cAAY;AACtC;AAEA,MAAM,UAAU,OAAe,QAAqB,OAAO,UAAU,eAAe,KAAK,OAAO,GAAG,GAE7F,iBAAiB,UAAqD;CAC1E,IAAI,CAAC,SAAS,KAAK,GAAG,OAAO;CAE7B,IAAM,YAAY,OAAO,eAAe,KAAK;CAC7C,OAAO,cAAc,OAAO,aAAa,cAAc;AACzD;;;;;;AAOA,SAAgB,aACd,OACA,KACY;CACZ,IAAI,CAAC,OAAO,OAAO,CAAC;CAEpB,IAAM,GAAE,MAAM,UAAU,GAAG,SAAQ;CACnC,OAAO;AACT;;;;;;AAOA,SAAgB,eACd,OACA,MACY;CACZ,IAAM,SAAS,CAAC;CAEhB,KAAK,IAAM,OAAO,MAChB,AAAI,OAAO,OAAO,GAAG,MACnB,OAAO,OAAO,MAAM;CAIxB,OAAO;AACT;AAEA,MAAM,gBAAgB,MAAoB,UAAiC;CACzE,IAAI,KAAK,SAAS,MAAM,MAAM,OAAO;CAErC,IAAM,YAAY,CAAC,GAAG,KAAK;CAE3B,OAAO,KAAK,IAAM,aAAa,MAAM;EACnC,KAAK,IAAI,QAAQ,GAAG,QAAQ,UAAU,QAAQ,SAC5C,IAAI,YAAY,WAAW,UAAU,MAAM,GAAG;GAC5C,UAAU,OAAO,OAAO,CAAC;GACzB,SAAS;EACX;EAGF,OAAO;CACT;CAEA,OAAO,UAAU,WAAW;AAC9B,GAEM,gBAAgB,MAA6B,UAA0C;CAC3F,IAAI,KAAK,SAAS,MAAM,MAAM,OAAO;CAErC,IAAM,YAAY,CAAC,GAAG,MAAM,QAAQ,CAAC;CAErC,OAAO,KAAK,IAAM,CAAC,SAAS,cAAc,MAAM;EAC9C,KAAK,IAAI,QAAQ,GAAG,QAAQ,UAAU,QAAQ,SAAS;GACrD,IAAM,CAAC,UAAU,cAAc,UAAU;GAEzC,IAAI,YAAY,SAAS,QAAQ,KAAK,YAAY,WAAW,UAAU,GAAG;IACxE,UAAU,OAAO,OAAO,CAAC;IACzB,SAAS;GACX;EACF;EAEA,OAAO;CACT;CAEA,OAAO,UAAU,WAAW;AAC9B;;;;;;;;AASA,SAAgB,YAAe,MAAS,OAAmB;CACzD,IAAI,OAAO,GAAG,MAAM,KAAK,GAAG,OAAO;CAEnC,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC,SAAS,KAAK,GAAG,OAAO;CAEhD,IAAI,gBAAgB,QAAQ,iBAAiB,MAC3C,OAAO,KAAK,QAAQ,MAAM,MAAM,QAAQ;CAG1C,IAAI,gBAAgB,UAAU,iBAAiB,QAC7C,OAAO,KAAK,WAAW,MAAM,UAAU,KAAK,UAAU,MAAM;CAG9D,IAAI,gBAAgB,OAAO,iBAAiB,KAC1C,OAAO,aAAa,MAAM,KAAK;CAGjC,IAAI,gBAAgB,OAAO,iBAAiB,KAC1C,OAAO,aAAa,MAAM,KAAK;CAGjC,IAAI,MAAM,QAAQ,IAAI,KAAK,MAAM,QAAQ,KAAK,GAG5C,OAFI,CAAC,MAAM,QAAQ,IAAI,KAAK,CAAC,MAAM,QAAQ,KAAK,KAAK,KAAK,WAAW,MAAM,SAAe,KAEnF,KAAK,OAAO,OAAO,UAAU,YAAY,OAAO,MAAM,MAAM,CAAC;CAGtE,IAAI,CAAC,cAAc,IAAI,KAAK,CAAC,cAAc,KAAK,GAAG,OAAO;CAE1D,IAAM,WAAW,OAAO,KAAK,IAAI,GAC3B,YAAY,OAAO,KAAK,KAAK;CAEnC,IAAI,SAAS,WAAW,UAAU,QAAQ,OAAO;CAEjD,KAAK,IAAM,OAAO,UAChB,IAAI,CAAC,OAAO,OAAO,GAAG,KAAK,CAAC,YAAY,KAAK,MAAM,MAAM,IAAI,GAC3D,OAAO;CAIX,OAAO;AACT"}
|
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
import { a as createActionBinder, o as defineStore, t as createStateSourceAction } from "./createStateSourceAction-DdAhEfnZ.js";
|
|
2
|
+
import { n as setCleanupTimeout } from "./setCleanupTimeout-BiM6q1KF.js";
|
|
3
|
+
import { t as _defineProperty } from "./defineProperty-Bho9Ngsi.js";
|
|
4
|
+
import { BehaviorSubject, ReplaySubject, Subject, concat, defer, filter, firstValueFrom, from, map, takeUntil } from "rxjs";
|
|
5
|
+
import "@sanity/types";
|
|
6
|
+
const stateTopic = (seed) => ({
|
|
7
|
+
kind: "state",
|
|
8
|
+
seed
|
|
9
|
+
}), dashboardEvent = {
|
|
10
|
+
kind: "event",
|
|
11
|
+
ownership: { type: "same_app" }
|
|
12
|
+
}, DASHBOARD_TOPIC_MANIFEST = {
|
|
13
|
+
"applications.config": stateTopic(void 0),
|
|
14
|
+
"applications.foreground": stateTopic(void 0),
|
|
15
|
+
"applications.list": stateTopic(void 0),
|
|
16
|
+
"auth.token": stateTopic(void 0),
|
|
17
|
+
"auth.token.refresh": dashboardEvent,
|
|
18
|
+
"navigation.location": stateTopic(void 0),
|
|
19
|
+
"navigation.location.update": dashboardEvent,
|
|
20
|
+
"organizations.current": stateTopic(void 0),
|
|
21
|
+
"panels.mode": stateTopic({
|
|
22
|
+
ok: !0,
|
|
23
|
+
value: null
|
|
24
|
+
}),
|
|
25
|
+
"panels.mode.set": dashboardEvent,
|
|
26
|
+
"preferences.color-scheme": stateTopic(void 0),
|
|
27
|
+
"preferences.dock-locked": stateTopic(void 0),
|
|
28
|
+
"users.current": stateTopic(void 0)
|
|
29
|
+
}, topicMigrations = {};
|
|
30
|
+
/**
|
|
31
|
+
* An error raised by the message bus protocol.
|
|
32
|
+
* @public
|
|
33
|
+
*/
|
|
34
|
+
var MessageBusError = class extends Error {
|
|
35
|
+
/** Creates a message bus protocol error. */
|
|
36
|
+
constructor(code, message, options) {
|
|
37
|
+
super(message ?? code, options), _defineProperty(this, "code", void 0), this.name = "MessageBusError", this.code = code;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const MESSAGE_BUS_KEY = Symbol.for("sanity.os.bus"), MESSAGE_BUS_PROTOCOL_KEY = Symbol.for("sanity.os.protocol"), MESSAGE_BUS_REGISTRY_KEY = Symbol.for("sanity.os.registry"), MESSAGE_BUS_PENDING_REPLY_KEY = Symbol.for("sanity.os.request"), DEFAULT_TIMEOUT_MS = 5e3, NO_VALUE = Symbol.for("sanity.os.no-value");
|
|
41
|
+
function resolveStateSubject(registry, connection, type) {
|
|
42
|
+
if (connection.abort.signal.aborted) throw new MessageBusError("ABORTED");
|
|
43
|
+
let subject = connection.stateSubjects.get(type);
|
|
44
|
+
if (!subject) {
|
|
45
|
+
let topic = registry.topics.get(type);
|
|
46
|
+
topic || console.warn(`[sanity-sdk:message-bus] state topic "${type}" is not declared`);
|
|
47
|
+
let seed = topic?.kind === "state" ? topic.seed : void 0;
|
|
48
|
+
subject = new BehaviorSubject(seed === void 0 ? NO_VALUE : seed), connection.stateSubjects.set(type, subject);
|
|
49
|
+
}
|
|
50
|
+
return subject;
|
|
51
|
+
}
|
|
52
|
+
function toStateSource(values, getCurrent) {
|
|
53
|
+
let source = values;
|
|
54
|
+
source.getCurrent = getCurrent;
|
|
55
|
+
let firstValue;
|
|
56
|
+
return Object.defineProperty(source, "firstValue", { get: () => firstValue ??= firstValueFrom(values) }), source;
|
|
57
|
+
}
|
|
58
|
+
function assertCompatibleTopicManifest(registry, manifest) {
|
|
59
|
+
for (let [type, entry] of Object.entries(manifest)) {
|
|
60
|
+
let existing = registry.topics.get(type);
|
|
61
|
+
if (existing && existing.kind !== entry.kind) throw new MessageBusError("PROTOCOL_MISMATCH", `topic "${type}" is declared "${entry.kind}" but the installed bus knows it as "${existing.kind}"`);
|
|
62
|
+
if (existing?.kind === "event" && entry.kind === "event" && existing.ownership.type !== entry.ownership.type) throw new MessageBusError("OWNERSHIP_MISMATCH", `topic "${type}" has conflicting ownership`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function mergeTopicManifest(registry, manifest) {
|
|
66
|
+
assertCompatibleTopicManifest(registry, manifest);
|
|
67
|
+
for (let [type, entry] of Object.entries(manifest)) registry.topics.set(type, entry);
|
|
68
|
+
}
|
|
69
|
+
function resolveEventSubject(registry, type) {
|
|
70
|
+
let subject = registry.eventSubjects.get(type);
|
|
71
|
+
return subject || (subject = new Subject(), registry.eventSubjects.set(type, subject)), subject;
|
|
72
|
+
}
|
|
73
|
+
function settleReply(reply, outcome) {
|
|
74
|
+
reply.settled || (reply.settled = !0, reply.settlePromise ? reply.settlePromise(outcome) : reply.outcomeBeforeAwait = outcome);
|
|
75
|
+
}
|
|
76
|
+
function createEventMessage(appId, moduleId, type, payload, pendingReply) {
|
|
77
|
+
let message = {
|
|
78
|
+
type,
|
|
79
|
+
payload,
|
|
80
|
+
meta: {
|
|
81
|
+
appId,
|
|
82
|
+
moduleId,
|
|
83
|
+
timestamp: Date.now()
|
|
84
|
+
},
|
|
85
|
+
reply: (value) => {
|
|
86
|
+
if (pendingReply.settled) {
|
|
87
|
+
console.warn(`[sanity-sdk:message-bus] reply ignored for "${type}": no waiting caller or already replied`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
settleReply(pendingReply, {
|
|
91
|
+
ok: !0,
|
|
92
|
+
value
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
get signal() {
|
|
96
|
+
return pendingReply.responderSignal;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
return message[MESSAGE_BUS_PENDING_REPLY_KEY] = pendingReply, message;
|
|
100
|
+
}
|
|
101
|
+
function createReplyPromise(pendingReply, options) {
|
|
102
|
+
return new Promise((resolve, reject) => {
|
|
103
|
+
let settlePromise = (outcome) => outcome.ok ? resolve(outcome.value) : reject(outcome.error);
|
|
104
|
+
if (pendingReply.outcomeBeforeAwait) {
|
|
105
|
+
pendingReply.responderAbort.abort(), settlePromise(pendingReply.outcomeBeforeAwait);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (!options.hadResponderAtEmission) {
|
|
109
|
+
reject(new MessageBusError("NO_RESPONDER"));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
let timeoutMs = options.timeout === void 0 ? DEFAULT_TIMEOUT_MS : options.timeout, timer, onAbort = () => settleReply(pendingReply, {
|
|
113
|
+
ok: !1,
|
|
114
|
+
error: new MessageBusError("ABORTED")
|
|
115
|
+
});
|
|
116
|
+
pendingReply.settlePromise = (outcome) => {
|
|
117
|
+
timer !== void 0 && clearTimeout(timer), options.signal?.removeEventListener("abort", onAbort), pendingReply.responderAbort.abort(), settlePromise(outcome);
|
|
118
|
+
}, timeoutMs !== null && (timer = setTimeout(() => settleReply(pendingReply, {
|
|
119
|
+
ok: !1,
|
|
120
|
+
error: new MessageBusError("TIMEOUT")
|
|
121
|
+
}), timeoutMs)), options.signal && (options.signal.aborted ? onAbort() : options.signal.addEventListener("abort", onAbort, { once: !0 }));
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
function createLazyReply(awaitReply) {
|
|
125
|
+
return {
|
|
126
|
+
then: (onFulfilled, onRejected) => awaitReply().then(onFulfilled, onRejected),
|
|
127
|
+
catch: (onRejected) => awaitReply().then(void 0, onRejected),
|
|
128
|
+
finally: (onFinally) => awaitReply().finally(onFinally)
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function emitEvent(registry, type, payload, options, appId, moduleId) {
|
|
132
|
+
let hadResponderAtEmission = (registry.responderCounts.get(type) ?? 0) > 0, responderAbort = new AbortController(), pendingReply = {
|
|
133
|
+
responderAbort,
|
|
134
|
+
responderSignal: scopeSignal(options?.signal, responderAbort.signal),
|
|
135
|
+
settled: !1
|
|
136
|
+
};
|
|
137
|
+
resolveEventSubject(registry, type).next(createEventMessage(appId, moduleId, type, payload, pendingReply));
|
|
138
|
+
let awaitReply = () => pendingReply.replyPromise ??= createReplyPromise(pendingReply, {
|
|
139
|
+
...options,
|
|
140
|
+
hadResponderAtEmission
|
|
141
|
+
});
|
|
142
|
+
return createLazyReply(awaitReply);
|
|
143
|
+
}
|
|
144
|
+
const isStateTopic = (registry, type) => registry.topics.get(type)?.kind === "state", canRespond = (registry, ownership, appId) => ownership.type === "any_app" || appId === registry.appId;
|
|
145
|
+
function emit(registry, type, payload, options, appId, moduleId, connectionSignal) {
|
|
146
|
+
if (isStateTopic(registry, type)) throw new MessageBusError("OWNERSHIP_MISMATCH", `Cannot emit state topic "${type}" from app "${appId}". The host writes state to each connection through its client handle. Read it with query() or subscribe().`);
|
|
147
|
+
return emitEvent(registry, type, payload, {
|
|
148
|
+
...options,
|
|
149
|
+
signal: scopeSignal(options?.signal, registry.resetAbort.signal, connectionSignal)
|
|
150
|
+
}, appId, moduleId);
|
|
151
|
+
}
|
|
152
|
+
function emitState(registry, connection, type, value) {
|
|
153
|
+
let subject = resolveStateSubject(registry, connection, type);
|
|
154
|
+
Object.is(subject.getValue(), value) || subject.next(value);
|
|
155
|
+
}
|
|
156
|
+
function query(source, type, options, connectionSignal, resetSignal) {
|
|
157
|
+
let current = source.getCurrent();
|
|
158
|
+
if (current !== void 0) return Promise.resolve(current);
|
|
159
|
+
let signal = scopeSignal(options?.signal, resetSignal, connectionSignal);
|
|
160
|
+
if (signal.aborted) return Promise.reject(new MessageBusError("ABORTED"));
|
|
161
|
+
let timeoutMs = options?.timeout === void 0 ? DEFAULT_TIMEOUT_MS : options.timeout;
|
|
162
|
+
return new Promise((resolve, reject) => {
|
|
163
|
+
let timer = timeoutMs === null ? void 0 : setTimeout(() => reject(new MessageBusError("TIMEOUT", `query("${type}") timed out`)), timeoutMs), clear = () => {
|
|
164
|
+
timer !== void 0 && clearTimeout(timer);
|
|
165
|
+
}, onAbort = () => {
|
|
166
|
+
clear(), reject(new MessageBusError("ABORTED"));
|
|
167
|
+
};
|
|
168
|
+
signal?.addEventListener("abort", onAbort, { once: !0 }), source.firstValue.then((value) => {
|
|
169
|
+
clear(), signal?.removeEventListener("abort", onAbort), resolve(value);
|
|
170
|
+
}, (error) => {
|
|
171
|
+
clear(), signal?.removeEventListener("abort", onAbort), reject(error);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
function unsubscribeOnAbort(subscription, signal) {
|
|
176
|
+
if (signal) {
|
|
177
|
+
if (signal.aborted) {
|
|
178
|
+
subscription.unsubscribe();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
signal.addEventListener("abort", () => subscription.unsubscribe(), { once: !0 });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function scopeSignal(signal, ...lifecycles) {
|
|
185
|
+
return signal ? AbortSignal.any([signal, ...lifecycles]) : AbortSignal.any(lifecycles);
|
|
186
|
+
}
|
|
187
|
+
function invokeResponder(handler, message) {
|
|
188
|
+
let pendingReply = message[MESSAGE_BUS_PENDING_REPLY_KEY], fail = (error) => {
|
|
189
|
+
pendingReply && settleReply(pendingReply, {
|
|
190
|
+
ok: !1,
|
|
191
|
+
error: new MessageBusError("HANDLER_THREW", void 0, { cause: error })
|
|
192
|
+
});
|
|
193
|
+
};
|
|
194
|
+
try {
|
|
195
|
+
let result = handler(message);
|
|
196
|
+
result instanceof Promise && result.catch(fail);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
fail(error);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function respond(registry, type, handler, options, appId, connectionSignal) {
|
|
202
|
+
let topic = registry.topics.get(type);
|
|
203
|
+
if (topic?.kind === "event" && !canRespond(registry, topic.ownership, appId)) throw new MessageBusError("OWNERSHIP_MISMATCH", `Cannot register a handler for event topic "${type}" from app "${appId}". Only the app that owns this topic can respond to it. Other apps can send it with emit().`);
|
|
204
|
+
registry.responderCounts.set(type, (registry.responderCounts.get(type) ?? 0) + 1);
|
|
205
|
+
let subscription = resolveEventSubject(registry, type).subscribe((message) => invokeResponder(handler, message));
|
|
206
|
+
subscription.add(() => {
|
|
207
|
+
let count = registry.responderCounts.get(type) ?? 0;
|
|
208
|
+
count > 0 && registry.responderCounts.set(type, count - 1);
|
|
209
|
+
}), unsubscribeOnAbort(subscription, scopeSignal(options?.signal, registry.resetAbort.signal, connectionSignal));
|
|
210
|
+
}
|
|
211
|
+
const bundledMigrations = () => new Map(Object.entries(topicMigrations));
|
|
212
|
+
/**
|
|
213
|
+
* Creates an isolated message bus without installing it globally and returns the host's
|
|
214
|
+
* connection to it.
|
|
215
|
+
* @internal
|
|
216
|
+
*/
|
|
217
|
+
function createIsolatedMessageBus(appId, config = {}) {
|
|
218
|
+
appId || throwMissingAppId();
|
|
219
|
+
let registry = {
|
|
220
|
+
appId,
|
|
221
|
+
topics: /* @__PURE__ */ new Map(),
|
|
222
|
+
connections: /* @__PURE__ */ new Set(),
|
|
223
|
+
connected$: new Subject(),
|
|
224
|
+
eventSubjects: /* @__PURE__ */ new Map(),
|
|
225
|
+
responderCounts: /* @__PURE__ */ new Map(),
|
|
226
|
+
migrations: config.migrations ?? bundledMigrations(),
|
|
227
|
+
resetAbort: new AbortController(),
|
|
228
|
+
generation: 0
|
|
229
|
+
};
|
|
230
|
+
return mergeTopicManifest(registry, DASHBOARD_TOPIC_MANIFEST), createConnection(registry, {
|
|
231
|
+
appId,
|
|
232
|
+
migrations: config.migrations
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
const identityMigration = {
|
|
236
|
+
up: (value) => value,
|
|
237
|
+
down: (value) => value
|
|
238
|
+
};
|
|
239
|
+
function effectiveTopicVersions(migrations) {
|
|
240
|
+
let versions = /* @__PURE__ */ new Map();
|
|
241
|
+
for (let [topic, steps] of migrations) {
|
|
242
|
+
let version = 1;
|
|
243
|
+
for (let step of steps) step.to > version && (version = step.to);
|
|
244
|
+
versions.set(topic, version);
|
|
245
|
+
}
|
|
246
|
+
return versions;
|
|
247
|
+
}
|
|
248
|
+
function migrationTransformAt(steps, version, select) {
|
|
249
|
+
let step = steps?.find((candidate) => candidate.from === version);
|
|
250
|
+
return (step ? select(step) : void 0) ?? identityMigration;
|
|
251
|
+
}
|
|
252
|
+
function migrateVersionedValue(steps, value, from, to, select) {
|
|
253
|
+
if (from === to) return value;
|
|
254
|
+
let current = value;
|
|
255
|
+
if (from < to) for (let version = from; version < to; version++) current = migrationTransformAt(steps, version, select).up(current);
|
|
256
|
+
else for (let version = from; version > to; version--) current = migrationTransformAt(steps, version - 1, select).down(current);
|
|
257
|
+
return current;
|
|
258
|
+
}
|
|
259
|
+
function createTopicCompatibility(installedMigrations, applicationMigrations) {
|
|
260
|
+
let installedVersions = effectiveTopicVersions(installedMigrations), applicationVersions = effectiveTopicVersions(applicationMigrations), applicationVersion = (type) => applicationVersions.get(type) ?? 1, installedVersion = (type) => installedVersions.get(type) ?? 1, migrationChainFor = (type) => (applicationVersion(type) > installedVersion(type) ? applicationMigrations : installedMigrations).get(type), createVersionAdapter = (select) => ({
|
|
261
|
+
toInstalled: (type, value) => migrateVersionedValue(migrationChainFor(type), value, applicationVersion(type), installedVersion(type), select),
|
|
262
|
+
toApplication: (type, value) => migrateVersionedValue(migrationChainFor(type), value, installedVersion(type), applicationVersion(type), select)
|
|
263
|
+
}), stateValueAndEventPayload = createVersionAdapter((step) => step), eventReply = createVersionAdapter((step) => step.reply);
|
|
264
|
+
return {
|
|
265
|
+
toInstalledEmission: stateValueAndEventPayload.toInstalled,
|
|
266
|
+
toApplicationStateValue: stateValueAndEventPayload.toApplication,
|
|
267
|
+
toApplicationEventPayload: stateValueAndEventPayload.toApplication,
|
|
268
|
+
toInstalledEventReply: eventReply.toInstalled,
|
|
269
|
+
toApplicationEventReply: eventReply.toApplication
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function projectCurrent(input, project) {
|
|
273
|
+
let lastInput, lastOutput, cached = !1;
|
|
274
|
+
return () => {
|
|
275
|
+
let current = input();
|
|
276
|
+
if (current !== void 0) return (!cached || current !== lastInput) && (lastInput = current, lastOutput = project(current), cached = !0), lastOutput;
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
function createStateSource(subject, project, completeOn) {
|
|
280
|
+
let getCurrent = () => {
|
|
281
|
+
let current = subject.getValue();
|
|
282
|
+
return current === NO_VALUE ? void 0 : current;
|
|
283
|
+
};
|
|
284
|
+
return toStateSource(subject.pipe(filter((value) => value !== NO_VALUE), map(project), takeUntil(completeOn)), projectCurrent(getCurrent, project));
|
|
285
|
+
}
|
|
286
|
+
function migrateEventMessage(message, payload, reply) {
|
|
287
|
+
return {
|
|
288
|
+
type: message.type,
|
|
289
|
+
payload: payload(message.payload),
|
|
290
|
+
meta: message.meta,
|
|
291
|
+
reply: (value) => message.reply(reply(value)),
|
|
292
|
+
get signal() {
|
|
293
|
+
return message.signal;
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
function migrateEventReply(result, project) {
|
|
298
|
+
let projected;
|
|
299
|
+
return createLazyReply(() => projected ??= Promise.resolve(result).then(project));
|
|
300
|
+
}
|
|
301
|
+
function createRejectedConnection(connectionError) {
|
|
302
|
+
let throwConnectionError = () => {
|
|
303
|
+
throw connectionError();
|
|
304
|
+
};
|
|
305
|
+
return {
|
|
306
|
+
emit: throwConnectionError,
|
|
307
|
+
query: () => Promise.reject(connectionError()),
|
|
308
|
+
subscribe: throwConnectionError,
|
|
309
|
+
disconnect: () => {}
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Connects an application to an isolated message bus.
|
|
314
|
+
* @internal
|
|
315
|
+
*/
|
|
316
|
+
function connectApplicationToMessageBus(installedMessageBus, config) {
|
|
317
|
+
config.appId || throwMissingAppId();
|
|
318
|
+
let { appId } = config, installedProtocol = installedMessageBus[MESSAGE_BUS_PROTOCOL_KEY];
|
|
319
|
+
if (installedProtocol !== 1) return console.error(`[sanity-sdk:message-bus] protocol mismatch for "${appId}": installed ${String(installedProtocol)}, this copy speaks 1`), createRejectedConnection(() => new MessageBusError("PROTOCOL_MISMATCH", `installed message bus speaks protocol ${String(installedProtocol)}, this copy speaks 1`));
|
|
320
|
+
let registry = installedMessageBus[MESSAGE_BUS_REGISTRY_KEY];
|
|
321
|
+
if (!registry) return console.error(`[sanity-sdk:message-bus] incompatible message bus for "${appId}"`), createRejectedConnection(() => new MessageBusError("PROTOCOL_MISMATCH", "installed message bus does not expose a compatible registry"));
|
|
322
|
+
try {
|
|
323
|
+
mergeTopicManifest(registry, DASHBOARD_TOPIC_MANIFEST);
|
|
324
|
+
} catch (error) {
|
|
325
|
+
return console.error(`[sanity-sdk:message-bus] topic manifest conflict for "${appId}"`, { error }), createRejectedConnection(() => error);
|
|
326
|
+
}
|
|
327
|
+
return createConnection(registry, config);
|
|
328
|
+
}
|
|
329
|
+
function createClient(registry, record, compatibility) {
|
|
330
|
+
return {
|
|
331
|
+
appId: record.appId,
|
|
332
|
+
moduleId: record.moduleId,
|
|
333
|
+
closed: record.abort.signal,
|
|
334
|
+
emit: (type, value) => {
|
|
335
|
+
if (record.abort.signal.aborted) {
|
|
336
|
+
console.warn(`[sanity-sdk:message-bus] "${type}" not written: connection "${record.moduleId}" has closed`);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
emitState(registry, record, type, compatibility.toInstalledEmission(type, value));
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
function createConnectionsSource(registry, self, compatibility, completeOn) {
|
|
344
|
+
let clients = /* @__PURE__ */ new WeakMap(), clientFor = (record) => {
|
|
345
|
+
let client = clients.get(record);
|
|
346
|
+
return client || (client = createClient(registry, record, compatibility), clients.set(record, client)), client;
|
|
347
|
+
};
|
|
348
|
+
return defer(() => concat(from([...registry.connections]), registry.connected$)).pipe(filter((record) => record !== self), map(clientFor), takeUntil(completeOn));
|
|
349
|
+
}
|
|
350
|
+
function createConnection(registry, config) {
|
|
351
|
+
let { appId } = config, moduleId = config.moduleId ?? appId, connectionAbort = new AbortController(), connectionSignal = connectionAbort.signal, connectionAborted$ = new ReplaySubject(1);
|
|
352
|
+
connectionSignal.addEventListener("abort", () => connectionAborted$.next(), { once: !0 });
|
|
353
|
+
let record = {
|
|
354
|
+
appId,
|
|
355
|
+
moduleId,
|
|
356
|
+
stateSubjects: /* @__PURE__ */ new Map(),
|
|
357
|
+
abort: connectionAbort
|
|
358
|
+
}, compatibility = createTopicCompatibility(registry.migrations, config.migrations ?? bundledMigrations()), isState = (type) => isStateTopic(registry, type), applicationStreams = /* @__PURE__ */ new Map(), streamGeneration = registry.generation, cachedStream = (type, create) => {
|
|
359
|
+
streamGeneration !== registry.generation && (applicationStreams.clear(), streamGeneration = registry.generation);
|
|
360
|
+
let stream = applicationStreams.get(type);
|
|
361
|
+
return stream || (stream = create(), applicationStreams.set(type, stream)), stream;
|
|
362
|
+
}, stateSource = (type) => cachedStream(type, () => createStateSource(resolveStateSubject(registry, record, type), (value) => compatibility.toApplicationStateValue(type, value), connectionAborted$)), eventStream = (type) => cachedStream(type, () => resolveEventSubject(registry, type).pipe(map((message) => compatibility.toApplicationEventPayload(type, message.payload)), takeUntil(connectionAborted$))), throwIfDisconnected = () => {
|
|
363
|
+
if (connectionSignal.aborted) throw new MessageBusError("ABORTED");
|
|
364
|
+
}, connection = {
|
|
365
|
+
emit: (type, payload, options) => (throwIfDisconnected(), migrateEventReply(emit(registry, type, compatibility.toInstalledEmission(type, payload), options, appId, moduleId, connectionSignal), (value) => compatibility.toApplicationEventReply(type, value))),
|
|
366
|
+
query: (type, options) => connectionSignal.aborted ? Promise.reject(new MessageBusError("ABORTED")) : query(stateSource(type), type, options, connectionSignal, registry.resetAbort.signal),
|
|
367
|
+
subscribe: (type, handler, options) => {
|
|
368
|
+
if (throwIfDisconnected(), isState(type)) {
|
|
369
|
+
let source = stateSource(type);
|
|
370
|
+
if (!handler) return source;
|
|
371
|
+
unsubscribeOnAbort(source.subscribe(handler), scopeSignal(options?.signal, registry.resetAbort.signal, connectionSignal));
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if (!handler) return eventStream(type);
|
|
375
|
+
respond(registry, type, (message) => handler(migrateEventMessage(message, (value) => compatibility.toApplicationEventPayload(type, value), (value) => compatibility.toInstalledEventReply(type, value))), options, appId, connectionSignal);
|
|
376
|
+
},
|
|
377
|
+
disconnect: () => {
|
|
378
|
+
if (!connectionSignal.aborted) {
|
|
379
|
+
connectionAbort.abort();
|
|
380
|
+
for (let subject of record.stateSubjects.values()) subject.complete();
|
|
381
|
+
record.stateSubjects.clear(), registry.connections.delete(record);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
appId === registry.appId && Object.assign(connection, { connections: createConnectionsSource(registry, record, compatibility, connectionAborted$) });
|
|
386
|
+
let instance = connection;
|
|
387
|
+
return instance[MESSAGE_BUS_REGISTRY_KEY] = registry, instance[MESSAGE_BUS_PROTOCOL_KEY] = 1, registry.connections.add(record), registry.connected$.next(record), instance;
|
|
388
|
+
}
|
|
389
|
+
function reset(registry) {
|
|
390
|
+
registry.resetAbort.abort();
|
|
391
|
+
for (let connection of registry.connections) {
|
|
392
|
+
for (let subject of connection.stateSubjects.values()) subject.complete();
|
|
393
|
+
connection.stateSubjects.clear();
|
|
394
|
+
}
|
|
395
|
+
for (let subject of registry.eventSubjects.values()) subject.complete();
|
|
396
|
+
registry.topics.clear(), registry.eventSubjects.clear(), registry.responderCounts.clear(), registry.resetAbort = new AbortController(), registry.generation += 1, mergeTopicManifest(registry, DASHBOARD_TOPIC_MANIFEST);
|
|
397
|
+
}
|
|
398
|
+
function throwMissingAppId() {
|
|
399
|
+
throw new MessageBusError("MISSING_APP_ID", "Cannot initialize the message bus without an app ID. Build the application with the Sanity CLI or pass an app ID when connecting.");
|
|
400
|
+
}
|
|
401
|
+
const resolveAppId = (appId) => appId ?? (typeof __SANITY_APP_ID__ == "string" ? __SANITY_APP_ID__ : void 0);
|
|
402
|
+
function getInstalledMessageBus() {
|
|
403
|
+
let bus = globalThis[MESSAGE_BUS_KEY];
|
|
404
|
+
return typeof bus == "object" && bus && MESSAGE_BUS_REGISTRY_KEY in bus ? bus : void 0;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Returns whether a message bus registry is installed.
|
|
408
|
+
* @internal
|
|
409
|
+
*/
|
|
410
|
+
function isMessageBusInstalled() {
|
|
411
|
+
return getInstalledMessageBus() !== void 0;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Connects to the installed message bus, or returns `undefined` when no compatible connection exists.
|
|
415
|
+
* @public
|
|
416
|
+
*/
|
|
417
|
+
function connectMessageBus(options = {}) {
|
|
418
|
+
let installedMessageBus = getInstalledMessageBus();
|
|
419
|
+
if (!installedMessageBus) return;
|
|
420
|
+
let appId = resolveAppId(options.appId);
|
|
421
|
+
if (!appId) {
|
|
422
|
+
console.warn("[sanity-sdk:message-bus] cannot connect without an app ID; build with the Sanity CLI or pass appId");
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
let connection = connectApplicationToMessageBus(installedMessageBus, {
|
|
426
|
+
appId,
|
|
427
|
+
moduleId: options.moduleId
|
|
428
|
+
});
|
|
429
|
+
return MESSAGE_BUS_REGISTRY_KEY in connection ? connection : void 0;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Resets the installed message bus for test isolation.
|
|
433
|
+
* @internal
|
|
434
|
+
*/
|
|
435
|
+
function resetMessageBus() {
|
|
436
|
+
let installedMessageBus = getInstalledMessageBus();
|
|
437
|
+
installedMessageBus && reset(installedMessageBus[MESSAGE_BUS_REGISTRY_KEY]);
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Installs the shared message bus and returns the host's connection, or connects to its
|
|
441
|
+
* existing installation. Only the host calls this; another app's ID is rejected because the
|
|
442
|
+
* connection it would get has no `connections`.
|
|
443
|
+
* @internal
|
|
444
|
+
*/
|
|
445
|
+
function installMessageBus(options = {}) {
|
|
446
|
+
let appId = resolveAppId(options.appId) ?? throwMissingAppId(), installedMessageBus = getInstalledMessageBus();
|
|
447
|
+
if (installedMessageBus) {
|
|
448
|
+
let installedAppId = installedMessageBus[MESSAGE_BUS_REGISTRY_KEY].appId;
|
|
449
|
+
if (installedAppId !== appId) throw new MessageBusError("OWNERSHIP_MISMATCH", `Cannot install the message bus as "${appId}": it is already installed by "${installedAppId}". Applications connect with connectMessageBus().`);
|
|
450
|
+
return connectApplicationToMessageBus(installedMessageBus, { appId });
|
|
451
|
+
}
|
|
452
|
+
let globals = globalThis;
|
|
453
|
+
globals[MESSAGE_BUS_KEY] && console.error("[sanity-sdk:message-bus] overwriting an incompatible message bus already installed");
|
|
454
|
+
let host = createIsolatedMessageBus(appId);
|
|
455
|
+
return globals[MESSAGE_BUS_KEY] = host, host;
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Returns the message bus connection for this instance, connecting on first call.
|
|
459
|
+
*
|
|
460
|
+
* @remarks
|
|
461
|
+
* One connection is made per {@link SanityInstance}, carrying that window's
|
|
462
|
+
* module identity. The first caller wins; later callers get the cached
|
|
463
|
+
* connection. When no host bus is installed it returns `undefined` without
|
|
464
|
+
* caching, so a later call retries once a host installs the bus.
|
|
465
|
+
* @internal
|
|
466
|
+
*/
|
|
467
|
+
const getDashboardMessageBus = createActionBinder((instance) => ({ name: instance.instanceId }))(defineStore({
|
|
468
|
+
name: "dashboardMessageBus",
|
|
469
|
+
getInitialState: () => ({}),
|
|
470
|
+
initialize: ({ state }) => () => state.get().connection?.disconnect()
|
|
471
|
+
}), ({ state }, moduleId) => {
|
|
472
|
+
let current = state.get().connection;
|
|
473
|
+
if (current) return current;
|
|
474
|
+
if (!isMessageBusInstalled()) return;
|
|
475
|
+
let connection = connectMessageBus({ moduleId });
|
|
476
|
+
if (connection) return state.set("connect", { connection }), connection;
|
|
477
|
+
});
|
|
478
|
+
/**
|
|
479
|
+
* Returns the instance's message bus connection, or throws when no dashboard host has
|
|
480
|
+
* installed a bus. `action` names what the caller was about to do, e.g. `read topic "x"`.
|
|
481
|
+
* @internal
|
|
482
|
+
*/
|
|
483
|
+
function requireDashboardMessageBus(instance, action) {
|
|
484
|
+
let messageBus = getDashboardMessageBus(instance);
|
|
485
|
+
if (!messageBus) throw Error(`Cannot ${action} without an installed dashboard message bus`);
|
|
486
|
+
return messageBus;
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Returns whether a dashboard host has installed a message bus.
|
|
490
|
+
*
|
|
491
|
+
* @remarks
|
|
492
|
+
* Answers "does a host own this session", not "has this instance connected yet". Callers
|
|
493
|
+
* such as the login redirect run before {@link getDashboardMessageBus} has been called for
|
|
494
|
+
* the instance, so this must not depend on the connection state.
|
|
495
|
+
* @internal
|
|
496
|
+
*/
|
|
497
|
+
function isDashboardEnvironment() {
|
|
498
|
+
return isMessageBusInstalled();
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* An error raised when a dashboard state topic reports failure.
|
|
502
|
+
* @public
|
|
503
|
+
*/
|
|
504
|
+
var TopicError = class extends Error {
|
|
505
|
+
/** Creates an error for a failed topic. */
|
|
506
|
+
constructor(topic) {
|
|
507
|
+
super(`Topic "${topic}" failed`), _defineProperty(this, "topic", void 0), this.name = "TopicError", this.topic = topic;
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
function isTopicResult(value) {
|
|
511
|
+
return typeof value == "object" && !!value && "ok" in value && typeof value.ok == "boolean";
|
|
512
|
+
}
|
|
513
|
+
function unwrapTopicResult(topic, value) {
|
|
514
|
+
if (!isTopicResult(value)) return value;
|
|
515
|
+
if (!value.ok) throw new TopicError(topic);
|
|
516
|
+
return value.value;
|
|
517
|
+
}
|
|
518
|
+
const TOPIC_STATE_CLEAR_DELAY = 1e3, bindActionByInstance = createActionBinder((instance) => ({ name: instance.instanceId })), dashboardTopicsStore = defineStore({
|
|
519
|
+
name: "dashboardTopics",
|
|
520
|
+
getInitialState: () => ({ topics: {} })
|
|
521
|
+
}), getMessageBus = (instance, topic) => requireDashboardMessageBus(instance, `read topic "${topic}"`);
|
|
522
|
+
function getSource(instance, topic) {
|
|
523
|
+
return getMessageBus(instance, topic).subscribe(topic);
|
|
524
|
+
}
|
|
525
|
+
const updateEntry = (topic, update) => (state) => {
|
|
526
|
+
let { [topic]: entry = { subscribers: 0 }, ...rest } = state.topics, next = update(entry);
|
|
527
|
+
return { topics: next.subscribers > 0 ? {
|
|
528
|
+
...rest,
|
|
529
|
+
[topic]: next
|
|
530
|
+
} : rest };
|
|
531
|
+
}, addSubscriber = (topic) => updateEntry(topic, (entry) => ({
|
|
532
|
+
...entry,
|
|
533
|
+
subscribers: entry.subscribers + 1
|
|
534
|
+
})), removeSubscriber = (topic) => updateEntry(topic, (entry) => ({
|
|
535
|
+
...entry,
|
|
536
|
+
subscribers: entry.subscribers - 1
|
|
537
|
+
})), getTopicState = bindActionByInstance(dashboardTopicsStore, createStateSourceAction({
|
|
538
|
+
selector: ({ state, instance }, topic) => {
|
|
539
|
+
let current = getSource(instance, topic).getCurrent(), entry = state.topics[topic];
|
|
540
|
+
if (current === void 0 && entry?.error) throw entry.error;
|
|
541
|
+
return unwrapTopicResult(topic, current);
|
|
542
|
+
},
|
|
543
|
+
onSubscribe: ({ state, instance }, topic) => {
|
|
544
|
+
state.set("addSubscriber", addSubscriber(topic));
|
|
545
|
+
let subscription = getSource(instance, topic).subscribe(() => state.set("publish", updateEntry(topic, (entry) => ({ ...entry }))));
|
|
546
|
+
return () => {
|
|
547
|
+
subscription.unsubscribe(), setCleanupTimeout(() => state.set("removeSubscriber", removeSubscriber(topic)), TOPIC_STATE_CLEAR_DELAY);
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
})), resolveTopic = bindActionByInstance(dashboardTopicsStore, ({ state, instance }, topic) => {
|
|
551
|
+
let existing = state.get().topics[topic]?.pending;
|
|
552
|
+
if (existing) return existing;
|
|
553
|
+
let release = () => setCleanupTimeout(() => state.set("removeSubscriber", removeSubscriber(topic)), TOPIC_STATE_CLEAR_DELAY), pending = getMessageBus(instance, topic).query(topic).then(() => state.set("resolved", updateEntry(topic, ({ pending: _pending, ...entry }) => entry)), (error) => state.set("failed", updateEntry(topic, ({ pending: _pending, ...entry }) => ({
|
|
554
|
+
...entry,
|
|
555
|
+
error
|
|
556
|
+
}))));
|
|
557
|
+
return pending.then(release, release), state.set("query", updateEntry(topic, (entry) => ({
|
|
558
|
+
...entry,
|
|
559
|
+
pending,
|
|
560
|
+
subscribers: entry.subscribers + 1
|
|
561
|
+
}))), pending;
|
|
562
|
+
});
|
|
563
|
+
export { isDashboardEnvironment as a, connectMessageBus as c, getDashboardMessageBus as i, installMessageBus as l, getTopicState as n, requireDashboardMessageBus as o, resolveTopic as r, MessageBusError as s, TopicError as t, resetMessageBus as u };
|
|
564
|
+
|
|
565
|
+
//# sourceMappingURL=topicStore-Ct7IFNtY.js.map
|