@monimesl/dot-service 0.1.0-alpha.2 → 0.1.0-alpha.23
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/index.d.ts +9 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -6
- package/dist/index.js.map +1 -1
- package/dist/{page-BfYJ-G-D.d.ts → page-DBnKjef6.d.ts} +34 -4
- package/dist/page-DBnKjef6.d.ts.map +1 -0
- package/dist/service-CBsppV0p.js +557 -0
- package/dist/service-CBsppV0p.js.map +1 -0
- package/dist/testing.d.ts +7 -3
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +46 -19
- package/dist/testing.js.map +1 -1
- package/package.json +3 -2
- package/dist/page-BfYJ-G-D.d.ts.map +0 -1
- package/dist/service-dAFmZ_Mh.js +0 -340
- package/dist/service-dAFmZ_Mh.js.map +0 -1
package/dist/testing.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { f as State, s as PageDefinition } from "./page-
|
|
2
|
-
import { DotEvent, Navigation, PageLaunchOptions, PageState } from "@monimesl/dot-core";
|
|
1
|
+
import { f as State, s as PageDefinition } from "./page-DBnKjef6.js";
|
|
2
|
+
import { DotEvent, Navigation, PageBars, PageLaunchOptions, PageState } from "@monimesl/dot-core";
|
|
3
3
|
//#region src/testing.d.ts
|
|
4
4
|
export type TestHost = Readonly<Record<string, (params: never) => unknown>>;
|
|
5
5
|
export interface TestPageOptions {
|
|
6
6
|
id?: string;
|
|
7
7
|
options?: PageLaunchOptions;
|
|
8
8
|
host?: TestHost;
|
|
9
|
+
results?: Readonly<Record<string, unknown>>;
|
|
9
10
|
}
|
|
10
11
|
export interface HostCallRecord {
|
|
11
12
|
readonly method: string;
|
|
@@ -16,12 +17,15 @@ export interface PageTest<S extends PageState> {
|
|
|
16
17
|
readonly patches: readonly PageState[];
|
|
17
18
|
readonly navigations: readonly Navigation[];
|
|
18
19
|
readonly hostCalls: readonly HostCallRecord[];
|
|
20
|
+
readonly bars: PageBars;
|
|
21
|
+
readonly result: unknown;
|
|
19
22
|
tap(name: string, detail?: unknown): Promise<void>;
|
|
20
23
|
dispatch(name: string, event?: DotEvent): Promise<void>;
|
|
21
24
|
show(): Promise<void>;
|
|
22
25
|
hide(): Promise<void>;
|
|
23
26
|
unload(): Promise<void>;
|
|
27
|
+
refresh(): Promise<void>;
|
|
24
28
|
}
|
|
25
|
-
export declare function testPage<S extends PageState>(definition: PageDefinition<S>, { id, options, host }?: TestPageOptions): Promise<PageTest<S>>;
|
|
29
|
+
export declare function testPage<S extends PageState>(definition: PageDefinition<S>, { id, options, host, results }?: TestPageOptions): Promise<PageTest<S>>;
|
|
26
30
|
//#endregion
|
|
27
31
|
//# sourceMappingURL=testing.d.ts.map
|
package/dist/testing.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.d.ts","names":[],"sources":["../src/testing.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"testing.d.ts","names":[],"sources":["../src/testing.ts"],"mappings":";;;YAuBY,WAAW,SAAS,gBAAgB;iBAE/B;EACf;EACA,UAAU;EAEV,OAAO;EAGP,UAAU,SAAS;;iBAGJ;WACN;WACA;;iBAGM,SAAS,UAAU;WACzB,OAAO,MAAM;WACb,kBAAkB;WAClB,sBAAsB;WACtB,oBAAoB;WAEpB,MAAM;WAEN;EAET,IAAI,cAAc,mBAAmB;EAErC,SAAS,cAAc,QAAQ,WAAW;EAE1C,QAAQ;EAER,QAAQ;EAER,UAAU;EAGV,WAAW;;wBAGS,SAAS,UAAU,WACvC,YAAY,eAAe,MACzB,IAAa,SAAc,MAAW,YAAgB,kBACvD,QAAQ,SAAS"}
|
package/dist/testing.js
CHANGED
|
@@ -1,30 +1,31 @@
|
|
|
1
|
-
import { i as definePage, n as defineDot, r as Page } from "./service-
|
|
1
|
+
import { a as cancelledError, i as definePage, n as defineDot, r as Page } from "./service-CBsppV0p.js";
|
|
2
2
|
import { HostError, connectHost, createEvent, createHostRequest } from "@monimesl/dot-core";
|
|
3
3
|
//#region src/testing.ts
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
})
|
|
9
|
-
async function testPage(definition, { id = "test", options = {}, host = {} } = {}) {
|
|
4
|
+
const globals = globalThis;
|
|
5
|
+
globals.definePage = definePage;
|
|
6
|
+
globals.defineDot = defineDot;
|
|
7
|
+
globals.createEvent = createEvent;
|
|
8
|
+
async function testPage(definition, { id = "test", options = {}, host = {}, results = {} } = {}) {
|
|
10
9
|
const patches = [];
|
|
11
10
|
const navigations = [];
|
|
12
11
|
const hostCalls = [];
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
method,
|
|
16
|
-
params
|
|
17
|
-
});
|
|
18
|
-
const handler = host[method];
|
|
19
|
-
if (!handler) {
|
|
20
|
-
const error = new HostError("unsupported", `The test gives no host method "${method}"`);
|
|
21
|
-
return createHostRequest(Promise.reject(error));
|
|
22
|
-
}
|
|
23
|
-
return createHostRequest(Promise.resolve().then(() => handler(params)));
|
|
24
|
-
});
|
|
12
|
+
let result;
|
|
13
|
+
connectHost(fakeHostCaller(host, hostCalls));
|
|
25
14
|
const page = new Page(1, id, definition, {
|
|
26
15
|
commit: (_page, patch) => patches.push(patch),
|
|
16
|
+
showBars: () => {},
|
|
27
17
|
navigate: (_page, navigation) => navigations.push(navigation),
|
|
18
|
+
pushForResult: (_page, navigation) => {
|
|
19
|
+
navigations.push(navigation);
|
|
20
|
+
return Promise.resolve(results[navigation.page]);
|
|
21
|
+
},
|
|
22
|
+
backWithResult: (_page, given) => {
|
|
23
|
+
result = given;
|
|
24
|
+
navigations.push({
|
|
25
|
+
action: "back",
|
|
26
|
+
delta: 1
|
|
27
|
+
});
|
|
28
|
+
},
|
|
28
29
|
strict: true
|
|
29
30
|
});
|
|
30
31
|
const dispatch = async (name, event = createEvent("tap")) => {
|
|
@@ -36,6 +37,12 @@ async function testPage(definition, { id = "test", options = {}, host = {} } = {
|
|
|
36
37
|
get state() {
|
|
37
38
|
return page.state;
|
|
38
39
|
},
|
|
40
|
+
get bars() {
|
|
41
|
+
return page.bars;
|
|
42
|
+
},
|
|
43
|
+
get result() {
|
|
44
|
+
return result;
|
|
45
|
+
},
|
|
39
46
|
patches,
|
|
40
47
|
navigations,
|
|
41
48
|
hostCalls,
|
|
@@ -46,7 +53,27 @@ async function testPage(definition, { id = "test", options = {}, host = {} } = {
|
|
|
46
53
|
unload: async () => {
|
|
47
54
|
page.close();
|
|
48
55
|
await page.lifecycle("onUnload");
|
|
56
|
+
},
|
|
57
|
+
refresh: () => page.lifecycle("onRefresh")
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function fakeHostCaller(host, hostCalls) {
|
|
61
|
+
return (method, params) => {
|
|
62
|
+
hostCalls.push({
|
|
63
|
+
method,
|
|
64
|
+
params
|
|
65
|
+
});
|
|
66
|
+
const handler = host[method];
|
|
67
|
+
if (!handler) {
|
|
68
|
+
const error = new HostError("unsupported", `The test has no host method "${method}". Add it to testPage's \`host\` option`);
|
|
69
|
+
return createHostRequest(Promise.reject(error));
|
|
49
70
|
}
|
|
71
|
+
let cancel = () => {};
|
|
72
|
+
const answer = new Promise((resolve, reject) => {
|
|
73
|
+
cancel = () => reject(cancelledError(method));
|
|
74
|
+
Promise.resolve().then(() => handler(params)).then(resolve, reject);
|
|
75
|
+
});
|
|
76
|
+
return createHostRequest(answer, () => cancel());
|
|
50
77
|
};
|
|
51
78
|
}
|
|
52
79
|
//#endregion
|
package/dist/testing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.js","names":[],"sources":["../src/testing.ts"],"sourcesContent":["import type {
|
|
1
|
+
{"version":3,"file":"testing.js","names":[],"sources":["../src/testing.ts"],"sourcesContent":["import type {\n DotEvent,\n HostCaller,\n Navigation,\n PageBars,\n PageLaunchOptions,\n PageState,\n} from '@monimesl/dot-core';\nimport { HostError, connectHost, createEvent, createHostRequest } from '@monimesl/dot-core';\n\nimport type { PageDefinition, State } from './page';\nimport { definePage, Page } from './page';\nimport { defineDot } from './service';\nimport { cancelledError } from './host-errors';\n\n// A dot's code uses these without imports, and the compiler adds the imports when it bundles.\n// Tests load that code directly, so this module sets them up. Import it first.\nconst globals = globalThis as unknown as Record<string, unknown>;\nglobals.definePage = definePage;\nglobals.defineDot = defineDot;\nglobals.createEvent = createEvent;\n\n// Fake host methods by name, such as `'dash.ui.alert': () => ({ action: 'ok' })`.\nexport type TestHost = Readonly<Record<string, (params: never) => unknown>>;\n\nexport interface TestPageOptions {\n id?: string;\n options?: PageLaunchOptions;\n // Host methods the page may call. Any other call rejects with `unsupported`.\n host?: TestHost;\n // What each page the test page opens with pushForResult gives back, by its id. A page not listed\n // gives undefined, as if the person went back.\n results?: Readonly<Record<string, unknown>>;\n}\n\nexport interface HostCallRecord {\n readonly method: string;\n readonly params: unknown;\n}\n\nexport interface PageTest<S extends PageState> {\n readonly state: State<S>;\n readonly patches: readonly PageState[];\n readonly navigations: readonly Navigation[];\n readonly hostCalls: readonly HostCallRecord[];\n // What Dash's bars show over the page now.\n readonly bars: PageBars;\n // What the page gave back with backWithResult, or undefined.\n readonly result: unknown;\n\n tap(name: string, detail?: unknown): Promise<void>;\n\n dispatch(name: string, event?: DotEvent): Promise<void>;\n\n show(): Promise<void>;\n\n hide(): Promise<void>;\n\n unload(): Promise<void>;\n\n // Runs the page's onRefresh, as when the person pulls it down.\n refresh(): Promise<void>;\n}\n\nexport async function testPage<S extends PageState>(\n definition: PageDefinition<S>,\n { id = 'test', options = {}, host = {}, results = {} }: TestPageOptions = {},\n): Promise<PageTest<S>> {\n const patches: PageState[] = [];\n const navigations: Navigation[] = [];\n const hostCalls: HostCallRecord[] = [];\n let result: unknown;\n connectHost(fakeHostCaller(host, hostCalls));\n const page = new Page<S>(1, id, definition, {\n commit: (_page, patch) => patches.push(patch),\n showBars: () => {},\n navigate: (_page, navigation) => navigations.push(navigation),\n pushForResult: (_page, navigation) => {\n navigations.push(navigation);\n return Promise.resolve(results[navigation.page]);\n },\n backWithResult: (_page, given) => {\n result = given;\n navigations.push({ action: 'back', delta: 1 });\n },\n strict: true,\n });\n const dispatch = async (name: string, event: DotEvent = createEvent('tap')) => {\n await page.dispatch(name, event);\n };\n\n await page.lifecycle('onLoad', options);\n await page.lifecycle('onShow');\n\n return {\n get state() {\n return page.state;\n },\n get bars() {\n return page.bars;\n },\n get result() {\n return result;\n },\n patches,\n navigations,\n hostCalls,\n tap: (name, detail = {}) => dispatch(name, createEvent('tap', detail)),\n dispatch,\n show: () => page.lifecycle('onShow'),\n hide: () => page.lifecycle('onHide'),\n unload: async () => {\n page.close();\n await page.lifecycle('onUnload');\n },\n refresh: () => page.lifecycle('onRefresh'),\n };\n}\n\n// Answers each call with the test's fake for that method.\nfunction fakeHostCaller(host: TestHost, hostCalls: HostCallRecord[]): HostCaller {\n return (method, params) => {\n hostCalls.push({ method, params });\n const handler = host[method];\n if (!handler) {\n const error = new HostError(\n 'unsupported',\n `The test has no host method \"${method}\". Add it to testPage's \\`host\\` option`,\n );\n return createHostRequest(Promise.reject(error));\n }\n // Cancelling rejects the call, as it does in a running dot.\n let cancel = () => {};\n const answer = new Promise<unknown>((resolve, reject) => {\n cancel = () => reject(cancelledError(method));\n Promise.resolve()\n .then(() => handler(params as never))\n .then(resolve, reject);\n });\n return createHostRequest(answer, () => cancel());\n };\n}\n"],"mappings":";;;AAiBA,MAAM,UAAU;AAChB,QAAQ,aAAa;AACrB,QAAQ,YAAY;AACpB,QAAQ,cAAc;AA4CtB,eAAsB,SACpB,YACA,EAAE,KAAK,QAAQ,UAAU,CAAC,GAAG,OAAO,CAAC,GAAG,UAAU,CAAC,MAAuB,CAAC,GACrD;CACtB,MAAM,UAAuB,CAAC;CAC9B,MAAM,cAA4B,CAAC;CACnC,MAAM,YAA8B,CAAC;CACrC,IAAI;CACJ,YAAY,eAAe,MAAM,SAAS,CAAC;CAC3C,MAAM,OAAO,IAAI,KAAQ,GAAG,IAAI,YAAY;EAC1C,SAAS,OAAO,UAAU,QAAQ,KAAK,KAAK;EAC5C,gBAAgB,CAAC;EACjB,WAAW,OAAO,eAAe,YAAY,KAAK,UAAU;EAC5D,gBAAgB,OAAO,eAAe;GACpC,YAAY,KAAK,UAAU;GAC3B,OAAO,QAAQ,QAAQ,QAAQ,WAAW,KAAK;EACjD;EACA,iBAAiB,OAAO,UAAU;GAChC,SAAS;GACT,YAAY,KAAK;IAAE,QAAQ;IAAQ,OAAO;GAAE,CAAC;EAC/C;EACA,QAAQ;CACV,CAAC;CACD,MAAM,WAAW,OAAO,MAAc,QAAkB,YAAY,KAAK,MAAM;EAC7E,MAAM,KAAK,SAAS,MAAM,KAAK;CACjC;CAEA,MAAM,KAAK,UAAU,UAAU,OAAO;CACtC,MAAM,KAAK,UAAU,QAAQ;CAE7B,OAAO;EACL,IAAI,QAAQ;GACV,OAAO,KAAK;EACd;EACA,IAAI,OAAO;GACT,OAAO,KAAK;EACd;EACA,IAAI,SAAS;GACX,OAAO;EACT;EACA;EACA;EACA;EACA,MAAM,MAAM,SAAS,CAAC,MAAM,SAAS,MAAM,YAAY,OAAO,MAAM,CAAC;EACrE;EACA,YAAY,KAAK,UAAU,QAAQ;EACnC,YAAY,KAAK,UAAU,QAAQ;EACnC,QAAQ,YAAY;GAClB,KAAK,MAAM;GACX,MAAM,KAAK,UAAU,UAAU;EACjC;EACA,eAAe,KAAK,UAAU,WAAW;CAC3C;AACF;AAGA,SAAS,eAAe,MAAgB,WAAyC;CAC/E,QAAQ,QAAQ,WAAW;EACzB,UAAU,KAAK;GAAE;GAAQ;EAAO,CAAC;EACjC,MAAM,UAAU,KAAK;EACrB,IAAI,CAAC,SAAS;GACZ,MAAM,QAAQ,IAAI,UAChB,eACA,gCAAgC,OAAO,wCACzC;GACA,OAAO,kBAAkB,QAAQ,OAAO,KAAK,CAAC;EAChD;EAEA,IAAI,eAAe,CAAC;EACpB,MAAM,SAAS,IAAI,SAAkB,SAAS,WAAW;GACvD,eAAe,OAAO,eAAe,MAAM,CAAC;GAC5C,QAAQ,QAAQ,CAAC,CACd,WAAW,QAAQ,MAAe,CAAC,CAAC,CACpC,KAAK,SAAS,MAAM;EACzB,CAAC;EACD,OAAO,kBAAkB,cAAc,OAAO,CAAC;CACjD;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monimesl/dot-service",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.23",
|
|
4
4
|
"description": "Dot logic layer: pages, state and plugins",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"main": "./dist/index.js",
|
|
31
31
|
"types": "./dist/index.d.ts",
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@monimesl/dot-core": "0.1.0-alpha.
|
|
33
|
+
"@monimesl/dot-core": "0.1.0-alpha.23",
|
|
34
|
+
"@monimesl/dot-dash": "0.1.0-alpha.23"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"publint": "^0.3.24",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"page-BfYJ-G-D.d.ts","names":[],"sources":["../src/page.ts"],"mappings":";;QASQ;YAEI;;KAKA,gBAAgB,uCAExB,cAAc;UAED;EACf,KAAK,MAAM,QAAQ,UAAU;EAE7B,QAAQ,MAAM,QAAQ,UAAU;EAEhC,MAAM,MAAM,QAAQ,UAAU;EAE9B,KAAK;;KAGK,aAAa,KAAK,eAAc,6BACxC,IACA,+BACc,WAAW,IAAI,aAAa,EAAE,SAC1C;KAEM,MAAM,KAAK,aAAa;KAExB,aAAa,KAAK,QAAQ,MAAM;KAEhC,YAAY,KAAK,aAAa,OAAO,OAAO,MAAM,OAAO,aAAa;KAItE,SAAS;EACnB,SAAS,OAAO,MAAM,IAAI,OAAO,WAAW,aAAa;;KAG/C,OAAO;EACjB,OAAO,OAAO,MAAM,IAAI,OAAO,kBAAkB;;KAI9C,UAAU,KAAK,YAAW,iBAAiB,YAAY,iBAAgB;UAE3D,YAAY,UAAU,WAAW,IAAI;WAC3C;WACA;WACA,OAAO,MAAM;EAEtB,SAAS,QAAQ,YAAY;WAEpB,uBAAuB,WAAW,SAAS,OAAO,UAAU,GAAG;WAC/D,qBAAqB,WAAW,SAAS,OAAO,UAAU,GAAG,QAAQ;;UAG/D,cAAc,UAAU,WAAW,IAAI,YAAY,YAAY,GAAG,IAAI;WAC5E,YAAY;;UAGN,YAAY,UAAU,WAAW,IAAI;EACpD;EACA,cAAc;EACd,QAAQ,QAAQ,aAAa,SAAS,YAAY,GAAG,IAAI;EACzD,YAAY,KAAK;EACjB,UAAU,KAAK,SAAS,cAAc,GAAG,IAAI;;UAG9B,eAAe,UAAU,YAAY;WAC3C;WACA,cAAc;WACd,QAAQ,QAAQ;WAChB,YAAY,SAAS,eAAe,SAAS;WAC7C,UAAU,SAAS,eAAe,OAAO;;iBAGpC,WACd,UAAU,WACV,WAAW,eAAe,SAAS,KACnC,WAAW,eAAe,OAAO,KACjC,SAAS,YAAY,GAAG,IAAI,MAAM,eAAe;UAIlC;EACf,OAAO,MAAM,MAAM,OAAO;EAE1B,SAAS,MAAM,MAAM,YAAY;WAExB;;KAGN,UAAU,KAAK,eAAe,SAAS;KACvC,QAAQ,KAAK,eAAe,OAAO;cAI3B,KAAK,UAAU,YAAY;;WAC7B;WACA;WACA,SAAS,YAAY,GAAG,UAAU,IAAI,QAAQ;WAC9C,eAAe,cAAc,GAAG,UAAU,IAAI,QAAQ;EAOnD,YAAA,oBAAoB,YAAY,YAAY,eAAe,IAAI,MAAM;MA2B7E,SAAS,MAAM;EAInB;EAKA,SAAS,QAAQ,YAAY;EAqB7B,OAAO,cAAc,QAAQ;EAQvB,IAAI,cAAc,QAAQ,WAAW;EAS3C,SAAS,cAAc,OAAO,WAAW;EAanC,UAAU,gBAAgB,WAC9B,UAAU,MACP,MAAM,WAAW,UAAU,MAC7B;EASH"}
|
package/dist/service-dAFmZ_Mh.js
DELETED
|
@@ -1,340 +0,0 @@
|
|
|
1
|
-
import { DEFAULT_SYSTEM_CONTEXT, EventEmitter, HostError, MAX_STACK_DEPTH, MessageChannel, applyChanges, connectHost, createHostRequest } from "@monimesl/dot-core";
|
|
2
|
-
//#region src/host-calls.ts
|
|
3
|
-
var HostCalls = class {
|
|
4
|
-
#channel;
|
|
5
|
-
#waiting = /* @__PURE__ */ new Map();
|
|
6
|
-
#nextId = 1;
|
|
7
|
-
constructor(channel) {
|
|
8
|
-
this.#channel = channel;
|
|
9
|
-
channel.handle("hostResult", (result) => this.#settle(result));
|
|
10
|
-
}
|
|
11
|
-
call(method, params, options) {
|
|
12
|
-
const id = this.#nextId++;
|
|
13
|
-
const promise = new Promise((resolve, reject) => {
|
|
14
|
-
this.#waiting.set(id, {
|
|
15
|
-
method,
|
|
16
|
-
resolve,
|
|
17
|
-
reject
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
const message = {
|
|
21
|
-
id,
|
|
22
|
-
method,
|
|
23
|
-
params
|
|
24
|
-
};
|
|
25
|
-
if (options.timeout !== void 0) message.timeout = options.timeout;
|
|
26
|
-
this.#channel.send("hostCall", message);
|
|
27
|
-
return createHostRequest(promise, () => this.#cancel(id));
|
|
28
|
-
}
|
|
29
|
-
#cancel(id) {
|
|
30
|
-
const call = this.#waiting.get(id);
|
|
31
|
-
if (!call) return;
|
|
32
|
-
this.#waiting.delete(id);
|
|
33
|
-
this.#channel.send("hostCancel", { id });
|
|
34
|
-
call.reject(new HostError("cancelled", `"${call.method}" was cancelled`));
|
|
35
|
-
}
|
|
36
|
-
#settle(result) {
|
|
37
|
-
const call = this.#waiting.get(result.id);
|
|
38
|
-
if (!call) return;
|
|
39
|
-
this.#waiting.delete(result.id);
|
|
40
|
-
if ("error" in result) call.reject(new HostError(result.error.code, result.error.message));
|
|
41
|
-
else call.resolve(result.value);
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
//#endregion
|
|
45
|
-
//#region src/page.ts
|
|
46
|
-
function definePage(options) {
|
|
47
|
-
return options;
|
|
48
|
-
}
|
|
49
|
-
var Page = class {
|
|
50
|
-
id;
|
|
51
|
-
instanceId;
|
|
52
|
-
context;
|
|
53
|
-
actionContext;
|
|
54
|
-
#definition;
|
|
55
|
-
#host;
|
|
56
|
-
#state;
|
|
57
|
-
#closed = false;
|
|
58
|
-
#previousHook = Promise.resolve();
|
|
59
|
-
constructor(instanceId, id, definition, host) {
|
|
60
|
-
this.id = id;
|
|
61
|
-
this.instanceId = instanceId;
|
|
62
|
-
this.#definition = definition;
|
|
63
|
-
this.#host = host;
|
|
64
|
-
this.#state = this.#freeze(JSON.parse(JSON.stringify(definition.initialState)));
|
|
65
|
-
const { mutations = {}, actions = {} } = definition;
|
|
66
|
-
const clash = Object.keys(mutations).find((name) => name in actions);
|
|
67
|
-
if (clash) throw new Error(`Page "${id}": "${clash}" is both a mutation and an action`);
|
|
68
|
-
const mutationCalls = mapNames(mutations, (name) => (event) => this.mutate(name, event));
|
|
69
|
-
const actionCalls = mapNames(actions, (name) => (event) => this.act(name, event));
|
|
70
|
-
this.context = new HookContext(this, mutationCalls, actionCalls);
|
|
71
|
-
this.actionContext = new HandlerContext(this, mutationCalls, actionCalls, this.#createNavigation());
|
|
72
|
-
}
|
|
73
|
-
get state() {
|
|
74
|
-
return this.#state;
|
|
75
|
-
}
|
|
76
|
-
handlers() {
|
|
77
|
-
const { mutations = {}, actions = {} } = this.#definition;
|
|
78
|
-
return [...Object.keys(mutations), ...Object.keys(actions)];
|
|
79
|
-
}
|
|
80
|
-
setState(update) {
|
|
81
|
-
if (this.#closed) return;
|
|
82
|
-
const changes = typeof update === "function" ? update(this.#state) : update;
|
|
83
|
-
if (changes === void 0) return;
|
|
84
|
-
const next = applyChanges(this.#state, changes);
|
|
85
|
-
const patch = {};
|
|
86
|
-
for (const key of Object.keys(next)) if (next[key] !== this.#state[key]) patch[key] = next[key];
|
|
87
|
-
this.#state = this.#freeze(next);
|
|
88
|
-
if (Object.keys(patch).length > 0) this.#host.commit(this, patch);
|
|
89
|
-
}
|
|
90
|
-
mutate(name, event) {
|
|
91
|
-
const mutation = this.#definition.mutations?.[name];
|
|
92
|
-
if (!mutation) throw new Error(`Page "${this.id}" has no mutation "${name}"`);
|
|
93
|
-
this.#runMutation(mutation, event);
|
|
94
|
-
}
|
|
95
|
-
async act(name, event) {
|
|
96
|
-
const action = this.#definition.actions?.[name];
|
|
97
|
-
if (!action) throw new Error(`Page "${this.id}" has no action "${name}"`);
|
|
98
|
-
await this.#runAction(action, event);
|
|
99
|
-
}
|
|
100
|
-
dispatch(name, event) {
|
|
101
|
-
const action = this.#definition.actions?.[name];
|
|
102
|
-
if (action) return this.#runAction(action, event);
|
|
103
|
-
const mutation = this.#definition.mutations?.[name];
|
|
104
|
-
if (!mutation) throw new Error(`Page "${this.id}" has no mutation or action "${name}"`);
|
|
105
|
-
this.#runMutation(mutation, event);
|
|
106
|
-
return Promise.resolve();
|
|
107
|
-
}
|
|
108
|
-
async lifecycle(hookName, ...args) {
|
|
109
|
-
const hook = this.#definition.hooks?.[hookName];
|
|
110
|
-
const run = this.#previousHook.then(() => hook?.call(this.context, ...args));
|
|
111
|
-
this.#previousHook = run.catch(() => {});
|
|
112
|
-
await run;
|
|
113
|
-
}
|
|
114
|
-
close() {
|
|
115
|
-
this.#closed = true;
|
|
116
|
-
}
|
|
117
|
-
#runMutation(mutation, event) {
|
|
118
|
-
this.setState((state) => mutation(state, event));
|
|
119
|
-
}
|
|
120
|
-
async #runAction(action, event) {
|
|
121
|
-
await action.call(this.actionContext, this.#state, event);
|
|
122
|
-
}
|
|
123
|
-
#createNavigation() {
|
|
124
|
-
return {
|
|
125
|
-
push: (page, options = {}) => this.#navigate({
|
|
126
|
-
action: "push",
|
|
127
|
-
page,
|
|
128
|
-
options
|
|
129
|
-
}),
|
|
130
|
-
replace: (page, options = {}) => this.#navigate({
|
|
131
|
-
action: "replace",
|
|
132
|
-
page,
|
|
133
|
-
options
|
|
134
|
-
}),
|
|
135
|
-
reset: (page, options = {}) => this.#navigate({
|
|
136
|
-
action: "reset",
|
|
137
|
-
page,
|
|
138
|
-
options
|
|
139
|
-
}),
|
|
140
|
-
back: (delta = 1) => this.#navigate({
|
|
141
|
-
action: "back",
|
|
142
|
-
delta
|
|
143
|
-
})
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
#navigate(navigation) {
|
|
147
|
-
if (!this.#closed) this.#host.navigate(this, navigation);
|
|
148
|
-
}
|
|
149
|
-
#freeze(state) {
|
|
150
|
-
return this.#host.strict ? deepFreeze(state) : state;
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
|
-
var HookContext = class {
|
|
154
|
-
id;
|
|
155
|
-
instanceId;
|
|
156
|
-
mutations;
|
|
157
|
-
actions;
|
|
158
|
-
#page;
|
|
159
|
-
constructor(page, mutations, actions) {
|
|
160
|
-
this.id = page.id;
|
|
161
|
-
this.instanceId = page.instanceId;
|
|
162
|
-
this.mutations = mutations;
|
|
163
|
-
this.actions = actions;
|
|
164
|
-
this.#page = page;
|
|
165
|
-
}
|
|
166
|
-
get state() {
|
|
167
|
-
return this.#page.state;
|
|
168
|
-
}
|
|
169
|
-
setState = (update) => {
|
|
170
|
-
this.#page.setState(update);
|
|
171
|
-
};
|
|
172
|
-
};
|
|
173
|
-
var HandlerContext = class extends HookContext {
|
|
174
|
-
navigation;
|
|
175
|
-
constructor(page, mutations, actions, navigation) {
|
|
176
|
-
super(page, mutations, actions);
|
|
177
|
-
this.navigation = navigation;
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
function mapNames(record, create) {
|
|
181
|
-
return Object.fromEntries(Object.keys(record).map((name) => [name, create(name)]));
|
|
182
|
-
}
|
|
183
|
-
function deepFreeze(value) {
|
|
184
|
-
if (typeof value === "object" && value !== null && !Object.isFrozen(value)) {
|
|
185
|
-
Object.freeze(value);
|
|
186
|
-
Object.values(value).forEach(deepFreeze);
|
|
187
|
-
}
|
|
188
|
-
return value;
|
|
189
|
-
}
|
|
190
|
-
//#endregion
|
|
191
|
-
//#region src/service.ts
|
|
192
|
-
function defineDot(dot) {
|
|
193
|
-
return dot;
|
|
194
|
-
}
|
|
195
|
-
var Service = class {
|
|
196
|
-
channel;
|
|
197
|
-
hooks = new EventEmitter();
|
|
198
|
-
#definitions;
|
|
199
|
-
#pages = /* @__PURE__ */ new Map();
|
|
200
|
-
#dot;
|
|
201
|
-
#strict;
|
|
202
|
-
#theme;
|
|
203
|
-
#systemContext = DEFAULT_SYSTEM_CONTEXT;
|
|
204
|
-
#stack = {
|
|
205
|
-
pages: [],
|
|
206
|
-
limit: MAX_STACK_DEPTH
|
|
207
|
-
};
|
|
208
|
-
#hostCalls;
|
|
209
|
-
constructor({ transport, pages, dot = {}, strict = false }) {
|
|
210
|
-
this.channel = new MessageChannel(transport);
|
|
211
|
-
this.#hostCalls = new HostCalls(this.channel);
|
|
212
|
-
this.#definitions = pages;
|
|
213
|
-
this.#dot = dot;
|
|
214
|
-
this.#strict = strict;
|
|
215
|
-
this.#theme = dot.theme ?? {};
|
|
216
|
-
this.channel.handle("connect", () => this.channel.send("setTheme", { theme: this.#theme }));
|
|
217
|
-
this.channel.handle("syncStack", (stack) => {
|
|
218
|
-
this.#stack = stack;
|
|
219
|
-
});
|
|
220
|
-
this.channel.handle("syncSystemContext", (context) => {
|
|
221
|
-
this.#systemContext = context;
|
|
222
|
-
});
|
|
223
|
-
this.channel.handle("load", ({ instanceId, page, options }) => this.loadPage(instanceId, page, options));
|
|
224
|
-
this.channel.handle("unload", ({ instanceId }) => this.unloadPage(instanceId));
|
|
225
|
-
this.channel.handle("show", ({ instanceId }) => {
|
|
226
|
-
const page = this.getPage(instanceId);
|
|
227
|
-
this.hooks.emit("pageShow", page);
|
|
228
|
-
this.reportError(page.lifecycle("onShow"), instanceId);
|
|
229
|
-
});
|
|
230
|
-
this.channel.handle("hide", ({ instanceId }) => {
|
|
231
|
-
const page = this.getPage(instanceId);
|
|
232
|
-
this.hooks.emit("pageHide", page);
|
|
233
|
-
this.reportError(page.lifecycle("onHide"), instanceId);
|
|
234
|
-
});
|
|
235
|
-
this.channel.handle("event", ({ instanceId, handler, event }) => {
|
|
236
|
-
const page = this.getPage(instanceId);
|
|
237
|
-
this.hooks.emit("event", page, handler, event);
|
|
238
|
-
this.reportError(page.dispatch(handler, event), instanceId);
|
|
239
|
-
});
|
|
240
|
-
dot.plugins?.forEach((plugin) => this.use(plugin));
|
|
241
|
-
}
|
|
242
|
-
use(plugin) {
|
|
243
|
-
plugin.setup(this);
|
|
244
|
-
return this;
|
|
245
|
-
}
|
|
246
|
-
start() {
|
|
247
|
-
const stopListening = this.channel.listen((error, message) => {
|
|
248
|
-
this.#reportError(error, instanceIdOf(message));
|
|
249
|
-
});
|
|
250
|
-
connectHost((method, params, options) => this.#hostCalls.call(method, params, options));
|
|
251
|
-
this.hooks.emit("launch");
|
|
252
|
-
this.reportError(this.#runLaunchHook());
|
|
253
|
-
return () => {
|
|
254
|
-
stopListening();
|
|
255
|
-
connectHost(null);
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
get theme() {
|
|
259
|
-
return this.#theme;
|
|
260
|
-
}
|
|
261
|
-
get systemContext() {
|
|
262
|
-
return this.#systemContext;
|
|
263
|
-
}
|
|
264
|
-
setTheme(theme) {
|
|
265
|
-
this.#theme = theme;
|
|
266
|
-
this.channel.send("setTheme", { theme });
|
|
267
|
-
}
|
|
268
|
-
getPage(instanceId) {
|
|
269
|
-
const page = this.#pages.get(instanceId);
|
|
270
|
-
if (!page) throw new Error(`Page instance ${instanceId} is not loaded`);
|
|
271
|
-
return page;
|
|
272
|
-
}
|
|
273
|
-
loadPage(instanceId, id, options = {}) {
|
|
274
|
-
const page = new Page(instanceId, id, this.#definition(id), {
|
|
275
|
-
commit: (page, patch) => {
|
|
276
|
-
this.hooks.emit("setState", page, patch);
|
|
277
|
-
this.channel.send("setPageState", {
|
|
278
|
-
instanceId: page.instanceId,
|
|
279
|
-
patchState: patch
|
|
280
|
-
});
|
|
281
|
-
},
|
|
282
|
-
navigate: (page, navigation) => {
|
|
283
|
-
if (navigation.action !== "back") this.#definition(navigation.page);
|
|
284
|
-
const { pages, limit } = this.#stack;
|
|
285
|
-
if (navigation.action === "push" && pages.length >= limit) throw new Error(`Cannot open "${navigation.page}": ${limit} pages are already open. Use replace or back.`);
|
|
286
|
-
this.hooks.emit("navigate", page, navigation);
|
|
287
|
-
this.channel.send("navigate", {
|
|
288
|
-
...navigation,
|
|
289
|
-
from: page.instanceId
|
|
290
|
-
});
|
|
291
|
-
},
|
|
292
|
-
strict: this.#strict
|
|
293
|
-
});
|
|
294
|
-
this.#pages.set(instanceId, page);
|
|
295
|
-
this.channel.send("initPageState", {
|
|
296
|
-
instanceId,
|
|
297
|
-
state: page.state
|
|
298
|
-
});
|
|
299
|
-
this.hooks.emit("pageLoad", page);
|
|
300
|
-
this.reportError(page.lifecycle("onLoad", options), instanceId);
|
|
301
|
-
return page;
|
|
302
|
-
}
|
|
303
|
-
unloadPage(instanceId) {
|
|
304
|
-
const page = this.#pages.get(instanceId);
|
|
305
|
-
if (!page) return;
|
|
306
|
-
this.#pages.delete(instanceId);
|
|
307
|
-
page.close();
|
|
308
|
-
this.reportError(page.lifecycle("onUnload"), instanceId);
|
|
309
|
-
this.hooks.emit("pageUnload", page);
|
|
310
|
-
}
|
|
311
|
-
#definition(id) {
|
|
312
|
-
const definition = this.#definitions[id];
|
|
313
|
-
if (!definition) throw new Error(`Unknown page "${id}"`);
|
|
314
|
-
return definition;
|
|
315
|
-
}
|
|
316
|
-
reportError(work, instanceId) {
|
|
317
|
-
work.catch((error) => {
|
|
318
|
-
this.#reportError(error, instanceId);
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
async #runLaunchHook() {
|
|
322
|
-
await this.#dot.hooks?.onLaunch?.call(this);
|
|
323
|
-
}
|
|
324
|
-
#reportError(error, instanceId) {
|
|
325
|
-
this.hooks.emit("error", error, instanceId);
|
|
326
|
-
const reason = error instanceof Error ? error : new Error(String(error));
|
|
327
|
-
this.#dot.hooks?.onError?.call(this, reason);
|
|
328
|
-
const report = { message: reason.message };
|
|
329
|
-
if (instanceId !== void 0) report.instanceId = instanceId;
|
|
330
|
-
this.channel.send("error", report);
|
|
331
|
-
}
|
|
332
|
-
};
|
|
333
|
-
function instanceIdOf(message) {
|
|
334
|
-
const payload = message.payload;
|
|
335
|
-
if (typeof payload?.instanceId === "number") return payload.instanceId;
|
|
336
|
-
}
|
|
337
|
-
//#endregion
|
|
338
|
-
export { definePage as i, defineDot as n, Page as r, Service as t };
|
|
339
|
-
|
|
340
|
-
//# sourceMappingURL=service-dAFmZ_Mh.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"service-dAFmZ_Mh.js","names":[],"sources":["../src/host-calls.ts","../src/page.ts","../src/service.ts"],"sourcesContent":["import type {\n HostCallOptions,\n HostRequest,\n HostResult,\n LogicLayerMessages,\n} from '@monimesl/dot-core';\nimport { HostError, createHostRequest } from '@monimesl/dot-core';\n\nimport type { ServiceChannel } from './service';\n\ninterface WaitingCall {\n readonly method: string;\n resolve(value: unknown): void;\n reject(error: HostError): void;\n}\n\n// Calls to the host that are waiting for their `hostResult`.\nexport class HostCalls {\n readonly #channel: ServiceChannel;\n readonly #waiting = new Map<number, WaitingCall>();\n #nextId = 1;\n\n constructor(channel: ServiceChannel) {\n this.#channel = channel;\n channel.handle('hostResult', (result) => this.#settle(result));\n }\n\n call(method: string, params: unknown, options: HostCallOptions): HostRequest<unknown> {\n const id = this.#nextId++;\n const promise = new Promise<unknown>((resolve, reject) => {\n this.#waiting.set(id, { method, resolve, reject });\n });\n const message: LogicLayerMessages['hostCall'] = { id, method, params };\n if (options.timeout !== undefined) {\n message.timeout = options.timeout;\n }\n this.#channel.send('hostCall', message);\n return createHostRequest(promise, () => this.#cancel(id));\n }\n\n #cancel(id: number): void {\n const call = this.#waiting.get(id);\n if (!call) {\n return;\n }\n this.#waiting.delete(id);\n this.#channel.send('hostCancel', { id });\n call.reject(new HostError('cancelled', `\"${call.method}\" was cancelled`));\n }\n\n #settle(result: HostResult): void {\n const call = this.#waiting.get(result.id);\n // A call cancelled here may still get the host's late answer, which no one wants any more.\n if (!call) {\n return;\n }\n this.#waiting.delete(result.id);\n if ('error' in result) {\n call.reject(new HostError(result.error.code, result.error.message));\n } else {\n call.resolve(result.value);\n }\n }\n}\n","import type {\n DotEvent,\n Navigation,\n PageHooks,\n PageLaunchOptions,\n PageState,\n} from '@monimesl/dot-core';\nimport { applyChanges } from '@monimesl/dot-core';\n\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-empty-object-type\n interface DotPageIds {}\n}\n\n// Any string until `.dot/pages.d.ts` lists the dot's pages; the brackets stop TypeScript from\n// applying the condition to each id separately.\nexport type PageId = [keyof DotPageIds] extends [never]\n ? string\n : Extract<keyof DotPageIds, string>;\n\nexport interface PageNavigation {\n push(page: PageId, options?: PageLaunchOptions): void;\n\n replace(page: PageId, options?: PageLaunchOptions): void;\n\n reset(page: PageId, options?: PageLaunchOptions): void;\n\n back(delta?: number): void;\n}\n\nexport type DeepReadonly<T> = T extends (...args: never[]) => unknown\n ? T\n : T extends object\n ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n : T;\n\nexport type State<S> = DeepReadonly<S>;\n\nexport type StateChanges<S> = Partial<State<S>>;\n\nexport type StateUpdate<S> = StateChanges<S> | ((state: State<S>) => StateChanges<S> | undefined);\n\n// Declared as methods, whose parameters TypeScript checks loosely, so a handler may narrow its\n// event, e.g. `(state, event: DotEvent<{ size: number }>) => ...`.\nexport type Mutation<S> = {\n mutation(state: State<S>, event: DotEvent): StateChanges<S> | undefined;\n}['mutation'];\n\nexport type Action<S> = {\n action(state: State<S>, event: DotEvent): void | Promise<void>;\n}['action'];\n\n// A handler's parameters after `state`: what calling it from another handler takes.\ntype EventArgs<F> = F extends (state: never, ...rest: infer R) => unknown ? R : never;\n\nexport interface PageContext<S extends PageState, Mu, Ac> {\n readonly id: string;\n readonly instanceId: number;\n readonly state: State<S>;\n\n setState(update: StateUpdate<S>): void;\n\n readonly mutations: { readonly [K in keyof Mu]: (...event: EventArgs<Mu[K]>) => void };\n readonly actions: { readonly [K in keyof Ac]: (...event: EventArgs<Ac[K]>) => Promise<void> };\n}\n\nexport interface ActionContext<S extends PageState, Mu, Ac> extends PageContext<S, Mu, Ac> {\n readonly navigation: PageNavigation;\n}\n\nexport interface PageOptions<S extends PageState, Mu, Ac> {\n id?: string;\n initialState: S;\n hooks?: Partial<PageHooks> & ThisType<PageContext<S, Mu, Ac>>;\n mutations?: Mu & ThisType<void>;\n actions?: Ac & ThisType<ActionContext<S, Mu, Ac>>;\n}\n\nexport interface PageDefinition<S extends PageState = PageState> {\n readonly id?: string;\n readonly initialState: S;\n readonly hooks?: Partial<PageHooks>;\n readonly mutations?: Readonly<Record<string, Mutation<S>>>;\n readonly actions?: Readonly<Record<string, Action<S>>>;\n}\n\nexport function definePage<\n S extends PageState,\n Mu extends Record<string, Mutation<S>>,\n Ac extends Record<string, Action<S>>,\n>(options: PageOptions<S, Mu, Ac>): PageDefinition<S> {\n return options;\n}\n\nexport interface PageHost {\n commit(page: Page, patch: PageState): void;\n\n navigate(page: Page, navigation: Navigation): void;\n\n readonly strict?: boolean;\n}\n\ntype Mutations<S> = Record<string, Mutation<S>>;\ntype Actions<S> = Record<string, Action<S>>;\ntype MutationCalls<S extends PageState> = PageContext<S, Mutations<S>, Actions<S>>['mutations'];\ntype ActionCalls<S extends PageState> = PageContext<S, Mutations<S>, Actions<S>>['actions'];\n\nexport class Page<S extends PageState = PageState> {\n readonly id: string;\n readonly instanceId: number;\n readonly context: PageContext<S, Mutations<S>, Actions<S>>;\n readonly actionContext: ActionContext<S, Mutations<S>, Actions<S>>;\n readonly #definition: PageDefinition<S>;\n readonly #host: PageHost;\n #state: State<S>;\n #closed = false;\n #previousHook: Promise<unknown> = Promise.resolve();\n\n constructor(instanceId: number, id: string, definition: PageDefinition<S>, host: PageHost) {\n this.id = id;\n this.instanceId = instanceId;\n this.#definition = definition;\n this.#host = host;\n this.#state = this.#freeze(JSON.parse(JSON.stringify(definition.initialState)) as State<S>);\n\n const { mutations = {}, actions = {} } = definition;\n const clash = Object.keys(mutations).find((name) => name in actions);\n if (clash) {\n throw new Error(`Page \"${id}\": \"${clash}\" is both a mutation and an action`);\n }\n\n const mutationCalls = mapNames(\n mutations,\n (name) => (event?: DotEvent) => this.mutate(name, event),\n );\n const actionCalls = mapNames(actions, (name) => (event?: DotEvent) => this.act(name, event));\n this.context = new HookContext(this, mutationCalls, actionCalls);\n this.actionContext = new HandlerContext(\n this,\n mutationCalls,\n actionCalls,\n this.#createNavigation(),\n );\n }\n\n get state(): State<S> {\n return this.#state;\n }\n\n handlers(): string[] {\n const { mutations = {}, actions = {} } = this.#definition;\n return [...Object.keys(mutations), ...Object.keys(actions)];\n }\n\n setState(update: StateUpdate<S>): void {\n if (this.#closed) {\n return;\n }\n const changes = typeof update === 'function' ? update(this.#state) : update;\n if (changes === undefined) {\n return;\n }\n const next = applyChanges(this.#state, changes);\n const patch: PageState = {};\n for (const key of Object.keys(next) as (keyof State<S> & string)[]) {\n if (next[key] !== this.#state[key]) {\n patch[key] = next[key];\n }\n }\n this.#state = this.#freeze(next);\n if (Object.keys(patch).length > 0) {\n this.#host.commit(this as Page, patch);\n }\n }\n\n mutate(name: string, event?: DotEvent): void {\n const mutation = this.#definition.mutations?.[name];\n if (!mutation) {\n throw new Error(`Page \"${this.id}\" has no mutation \"${name}\"`);\n }\n this.#runMutation(mutation, event);\n }\n\n async act(name: string, event?: DotEvent): Promise<void> {\n const action = this.#definition.actions?.[name];\n if (!action) {\n throw new Error(`Page \"${this.id}\" has no action \"${name}\"`);\n }\n await this.#runAction(action, event);\n }\n\n // Runs the mutation or action named by a view event. A mutation is done when this returns.\n dispatch(name: string, event: DotEvent): Promise<void> {\n const action = this.#definition.actions?.[name];\n if (action) {\n return this.#runAction(action, event);\n }\n const mutation = this.#definition.mutations?.[name];\n if (!mutation) {\n throw new Error(`Page \"${this.id}\" has no mutation or action \"${name}\"`);\n }\n this.#runMutation(mutation, event);\n return Promise.resolve();\n }\n\n async lifecycle<K extends keyof PageHooks>(\n hookName: K,\n ...args: Parameters<PageHooks[K]>\n ): Promise<void> {\n const hook = this.#definition.hooks?.[hookName] as\n ((...hookArgs: Parameters<PageHooks[K]>) => unknown) | undefined;\n // Hooks run one at a time, in call order; a failed hook does not block the next one.\n const run = this.#previousHook.then(() => hook?.call(this.context, ...args));\n this.#previousHook = run.catch(() => {});\n await run;\n }\n\n close(): void {\n this.#closed = true;\n }\n\n // Handlers called from other handlers may leave out the event, so it can be undefined here.\n #runMutation(mutation: Mutation<S>, event: DotEvent | undefined): void {\n this.setState((state) => mutation(state, event as DotEvent));\n }\n\n async #runAction(action: Action<S>, event: DotEvent | undefined): Promise<void> {\n await action.call(this.actionContext, this.#state, event as DotEvent);\n }\n\n #createNavigation(): PageNavigation {\n return {\n push: (page, options = {}) => this.#navigate({ action: 'push', page, options }),\n replace: (page, options = {}) => this.#navigate({ action: 'replace', page, options }),\n reset: (page, options = {}) => this.#navigate({ action: 'reset', page, options }),\n back: (delta = 1) => this.#navigate({ action: 'back', delta }),\n };\n }\n\n #navigate(navigation: Navigation): void {\n if (!this.#closed) {\n this.#host.navigate(this as Page, navigation);\n }\n }\n\n #freeze(state: State<S>): State<S> {\n return this.#host.strict ? deepFreeze(state) : state;\n }\n}\n\n// What hooks get as `this`. `setState` is bound, so a hook may destructure it.\nclass HookContext<S extends PageState> implements PageContext<S, Mutations<S>, Actions<S>> {\n readonly id: string;\n readonly instanceId: number;\n readonly mutations: MutationCalls<S>;\n readonly actions: ActionCalls<S>;\n readonly #page: Page<S>;\n\n constructor(page: Page<S>, mutations: MutationCalls<S>, actions: ActionCalls<S>) {\n this.id = page.id;\n this.instanceId = page.instanceId;\n this.mutations = mutations;\n this.actions = actions;\n this.#page = page;\n }\n\n get state(): State<S> {\n return this.#page.state;\n }\n\n readonly setState = (update: StateUpdate<S>): void => {\n this.#page.setState(update);\n };\n}\n\n// What actions get as `this`: the hook context plus navigation.\nclass HandlerContext<S extends PageState>\n extends HookContext<S>\n implements ActionContext<S, Mutations<S>, Actions<S>>\n{\n readonly navigation: PageNavigation;\n\n constructor(\n page: Page<S>,\n mutations: MutationCalls<S>,\n actions: ActionCalls<S>,\n navigation: PageNavigation,\n ) {\n super(page, mutations, actions);\n this.navigation = navigation;\n }\n}\n\nfunction mapNames<T>(\n record: Readonly<Record<string, unknown>>,\n create: (name: string) => T,\n): Record<string, T> {\n return Object.fromEntries(Object.keys(record).map((name) => [name, create(name)]));\n}\n\nfunction deepFreeze<T>(value: T): T {\n if (typeof value === 'object' && value !== null && !Object.isFrozen(value)) {\n Object.freeze(value);\n Object.values(value).forEach(deepFreeze);\n }\n return value;\n}\n","import type {\n DotHooks,\n DotEvent,\n Envelope,\n HostLayerMessages,\n LogicLayerMessages,\n Navigation,\n PageLaunchOptions,\n PageState,\n SystemContext,\n Theme,\n Transport,\n UILayerMessages,\n} from '@monimesl/dot-core';\nimport {\n DEFAULT_SYSTEM_CONTEXT,\n connectHost,\n EventEmitter,\n MAX_STACK_DEPTH,\n MessageChannel,\n} from '@monimesl/dot-core';\n\nimport { HostCalls } from './host-calls';\nimport type { PageDefinition } from './page';\nimport { Page } from './page';\n\nexport type ServiceChannel = MessageChannel<\n LogicLayerMessages,\n UILayerMessages & HostLayerMessages\n>;\n\nexport interface ServiceHooks {\n launch(): void;\n\n pageLoad(page: Page): void;\n\n pageUnload(page: Page): void;\n\n pageShow(page: Page): void;\n\n pageHide(page: Page): void;\n\n // A page asked to navigate; the request is on its way to the view layer.\n navigate(page: Page, navigation: Navigation): void;\n\n // Before a handler runs\n event(page: Page, handler: string, event: DotEvent): void;\n\n // After the state changes, with the changed keys, before they are sent to the view layer\n setState(page: Page, patch: PageState): void;\n\n // The instance is known when the error came from one page.\n error(error: unknown, instanceId: number | undefined): void;\n}\n\nexport interface ServicePlugin {\n readonly name: string;\n\n setup(service: Service): void;\n}\n\nexport interface DotContext {\n readonly theme: Theme;\n readonly systemContext: SystemContext;\n\n setTheme(theme: Theme): void;\n}\n\nexport interface DotDefinition {\n readonly theme?: Theme;\n readonly plugins?: readonly ServicePlugin[];\n readonly pages: readonly [string, ...string[]];\n readonly hooks?: Partial<DotHooks> & ThisType<DotContext>;\n}\n\nexport function defineDot(dot: DotDefinition): DotDefinition {\n return dot;\n}\n\nexport interface ServiceOptions {\n transport: Transport;\n dot?: Partial<DotDefinition>;\n pages: Record<string, PageDefinition>;\n strict?: boolean;\n}\n\n// The logic layer: owns every page's state, runs handlers, and sends state changes to the view layer.\nexport class Service implements DotContext {\n readonly channel: ServiceChannel;\n readonly hooks = new EventEmitter<ServiceHooks>();\n readonly #definitions: Record<string, PageDefinition>;\n readonly #pages = new Map<number, Page>();\n readonly #dot: Partial<DotDefinition>;\n readonly #strict: boolean;\n #theme: Theme;\n #systemContext: SystemContext = DEFAULT_SYSTEM_CONTEXT;\n #stack: HostLayerMessages['syncStack'] = { pages: [], limit: MAX_STACK_DEPTH };\n readonly #hostCalls: HostCalls;\n\n constructor({ transport, pages, dot = {}, strict = false }: ServiceOptions) {\n this.channel = new MessageChannel(transport);\n this.#hostCalls = new HostCalls(this.channel);\n this.#definitions = pages;\n this.#dot = dot;\n this.#strict = strict;\n this.#theme = dot.theme ?? {};\n\n this.channel.handle('connect', () => this.channel.send('setTheme', { theme: this.#theme }));\n this.channel.handle('syncStack', (stack) => {\n this.#stack = stack;\n });\n this.channel.handle('syncSystemContext', (context) => {\n this.#systemContext = context;\n });\n this.channel.handle('load', ({ instanceId, page, options }) =>\n this.loadPage(instanceId, page, options),\n );\n this.channel.handle('unload', ({ instanceId }) => this.unloadPage(instanceId));\n this.channel.handle('show', ({ instanceId }) => {\n const page = this.getPage(instanceId);\n this.hooks.emit('pageShow', page);\n this.reportError(page.lifecycle('onShow'), instanceId);\n });\n this.channel.handle('hide', ({ instanceId }) => {\n const page = this.getPage(instanceId);\n this.hooks.emit('pageHide', page);\n this.reportError(page.lifecycle('onHide'), instanceId);\n });\n this.channel.handle('event', ({ instanceId, handler, event }) => {\n const page = this.getPage(instanceId);\n this.hooks.emit('event', page, handler, event);\n this.reportError(page.dispatch(handler, event), instanceId);\n });\n\n dot.plugins?.forEach((plugin) => this.use(plugin));\n }\n\n use(plugin: ServicePlugin): this {\n plugin.setup(this);\n return this;\n }\n\n start(): () => void {\n const stopListening = this.channel.listen((error, message) => {\n this.#reportError(error, instanceIdOf(message));\n });\n // Host calls, such as `dash.ui.alert`, now go through this service's channel.\n connectHost((method, params, options) => this.#hostCalls.call(method, params, options));\n this.hooks.emit('launch');\n this.reportError(this.#runLaunchHook());\n return () => {\n stopListening();\n connectHost(null);\n };\n }\n\n get theme(): Theme {\n return this.#theme;\n }\n\n get systemContext(): SystemContext {\n return this.#systemContext;\n }\n\n setTheme(theme: Theme): void {\n this.#theme = theme;\n this.channel.send('setTheme', { theme });\n }\n\n getPage(instanceId: number): Page {\n const page = this.#pages.get(instanceId);\n if (!page) {\n throw new Error(`Page instance ${instanceId} is not loaded`);\n }\n return page;\n }\n\n loadPage(instanceId: number, id: string, options: PageLaunchOptions = {}): Page {\n const page = new Page(instanceId, id, this.#definition(id), {\n commit: (page, patch) => {\n this.hooks.emit('setState', page, patch);\n this.channel.send('setPageState', { instanceId: page.instanceId, patchState: patch });\n },\n navigate: (page, navigation) => {\n if (navigation.action !== 'back') {\n this.#definition(navigation.page);\n }\n const { pages, limit } = this.#stack;\n if (navigation.action === 'push' && pages.length >= limit) {\n throw new Error(\n `Cannot open \"${navigation.page}\": ${limit} pages are already open. Use replace or back.`,\n );\n }\n this.hooks.emit('navigate', page, navigation);\n this.channel.send('navigate', { ...navigation, from: page.instanceId });\n },\n strict: this.#strict,\n });\n this.#pages.set(instanceId, page);\n this.channel.send('initPageState', { instanceId, state: page.state });\n this.hooks.emit('pageLoad', page);\n this.reportError(page.lifecycle('onLoad', options), instanceId);\n return page;\n }\n\n unloadPage(instanceId: number): void {\n const page = this.#pages.get(instanceId);\n if (!page) {\n return;\n }\n this.#pages.delete(instanceId);\n page.close();\n this.reportError(page.lifecycle('onUnload'), instanceId);\n this.hooks.emit('pageUnload', page);\n }\n\n #definition(id: string): PageDefinition {\n const definition = this.#definitions[id];\n if (!definition) {\n throw new Error(`Unknown page \"${id}\"`);\n }\n return definition;\n }\n\n reportError(work: Promise<void>, instanceId?: number): void {\n work.catch((error: unknown) => {\n this.#reportError(error, instanceId);\n });\n }\n\n async #runLaunchHook(): Promise<void> {\n await this.#dot.hooks?.onLaunch?.call(this);\n }\n\n #reportError(error: unknown, instanceId: number | undefined): void {\n this.hooks.emit('error', error, instanceId);\n const reason = error instanceof Error ? error : new Error(String(error));\n this.#dot.hooks?.onError?.call(this, reason);\n const report: LogicLayerMessages['error'] = { message: reason.message };\n if (instanceId !== undefined) {\n report.instanceId = instanceId;\n }\n this.channel.send('error', report);\n }\n}\n\n// The page instance a channel message was for, if it names one.\nfunction instanceIdOf(message: Envelope): number | undefined {\n const payload = message.payload as { instanceId?: unknown } | undefined;\n if (typeof payload?.instanceId === 'number') {\n return payload.instanceId;\n }\n return undefined;\n}\n"],"mappings":"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"}
|