@kasperrt/smolter 0.2.0 → 0.3.1
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/.generated/client/deployment.ts +161 -0
- package/{src/internal → .generated}/client/index.ts +3 -0
- package/.generated/core/environment.ts +43 -0
- package/{src/internal → .generated}/core/errors.ts +1 -1
- package/{src/internal → .generated}/core/index.ts +1 -0
- package/.generated/core/navigation-router.ts +107 -0
- package/.generated/core/navigation.ts +147 -0
- package/.generated/core/presentation.ts +108 -0
- package/.generated/core/resource-types.ts +44 -0
- package/.generated/core/resources.ts +444 -0
- package/.generated/core/responses.ts +86 -0
- package/{src/internal → .generated}/core/router.ts +136 -63
- package/{src/internal → .generated}/core/serialize.ts +7 -2
- package/.generated/core/store.ts +25 -0
- package/.generated/core/submission.ts +116 -0
- package/{src/internal → .generated}/core/types.ts +18 -1
- package/.generated/core/url.ts +29 -0
- package/.generated/core/utils.ts +5 -0
- package/.generated/preact/await.tsx +41 -0
- package/.generated/preact/client.tsx +93 -0
- package/.generated/preact/context.ts +59 -0
- package/.generated/preact/document.tsx +198 -0
- package/{src/internal → .generated}/preact/fetcher.ts +43 -23
- package/.generated/preact/form.ts +58 -0
- package/{src/internal → .generated}/preact/hooks.ts +25 -28
- package/.generated/preact/index.ts +34 -0
- package/.generated/preact/lazy-route.ts +100 -0
- package/.generated/preact/navigation-state.ts +9 -0
- package/.generated/preact/navigation.tsx +147 -0
- package/.generated/preact/root-context.ts +21 -0
- package/.generated/preact/root-data.ts +45 -0
- package/.generated/preact/route-hooks.tsx +170 -0
- package/.generated/preact/route-types.ts +76 -0
- package/.generated/preact/router.tsx +107 -0
- package/{src/internal → .generated}/preact/routes.ts +17 -9
- package/.generated/react/context.ts +59 -0
- package/.generated/react/data.ts +25 -0
- package/{src/internal → .generated}/react/fetcher.ts +13 -11
- package/.generated/react/form.ts +58 -0
- package/{src/internal → .generated}/react/hooks.ts +9 -14
- package/{src/internal/preact → .generated/react}/index.ts +5 -1
- package/{src/internal → .generated}/react/link.ts +13 -21
- package/.generated/react/resources/components.tsx +404 -0
- package/{src/internal → .generated}/react/routes.ts +17 -9
- package/.generated/server/application.tsx +185 -0
- package/.generated/server/assets.ts +19 -0
- package/.generated/server/handler.ts +69 -0
- package/.generated/server/http.ts +55 -0
- package/{src/internal → .generated}/server/index.ts +3 -3
- package/.generated/server/node.ts +4 -0
- package/.generated/server/preact.ts +7 -0
- package/.generated/vite/application/browser-boundary.ts +42 -0
- package/.generated/vite/application/lazy-files.ts +138 -0
- package/.generated/vite/application/vite.ts +144 -0
- package/.generated/vite/deployment.ts +46 -0
- package/{src/internal → .generated}/vite/index.ts +11 -4
- package/{src/preact-vite.ts → .generated/vite/preact.ts} +14 -3
- package/{src/react-vite.ts → .generated/vite/react.ts} +8 -3
- package/CHANGELOG.md +63 -0
- package/README.md +277 -35
- package/dist/client/deployment.d.ts +12 -0
- package/dist/client/deployment.js +102 -0
- package/dist/client/deserialize.js +10 -0
- package/dist/{internal/client → client}/index.d.ts +2 -0
- package/dist/client/index.js +47 -0
- package/dist/core/environment.d.ts +12 -0
- package/dist/core/environment.js +23 -0
- package/dist/core/errors.js +7 -0
- package/dist/{internal/core → core}/index.d.ts +1 -0
- package/dist/core/match.js +33 -0
- package/dist/core/navigation-router.d.ts +15 -0
- package/dist/core/navigation-router.js +64 -0
- package/dist/core/navigation.d.ts +15 -0
- package/dist/core/navigation.js +92 -0
- package/dist/core/presentation.d.ts +5 -0
- package/dist/core/presentation.js +69 -0
- package/dist/core/resource-types.d.ts +37 -0
- package/dist/core/resources.d.ts +19 -0
- package/dist/core/resources.js +345 -0
- package/dist/core/responses.d.ts +15 -0
- package/dist/core/responses.js +48 -0
- package/dist/{internal/core → core}/router.d.ts +1 -1
- package/dist/core/router.js +204 -0
- package/dist/{internal/core → core}/serialize.d.ts +2 -0
- package/dist/core/serialize.js +17 -0
- package/dist/core/store.d.ts +6 -0
- package/dist/core/store.js +21 -0
- package/dist/core/submission.d.ts +30 -0
- package/dist/core/submission.js +41 -0
- package/dist/{internal/core → core}/types.d.ts +16 -1
- package/dist/core/url.d.ts +7 -0
- package/dist/core/url.js +12 -0
- package/dist/core/utils.d.ts +5 -0
- package/dist/core/utils.js +5 -0
- package/dist/files/index.js +64 -0
- package/dist/preact/await.d.ts +7 -0
- package/dist/preact/await.js +19 -0
- package/dist/preact/client.d.ts +7 -0
- package/dist/preact/client.js +68 -0
- package/dist/preact/context.d.ts +18 -0
- package/dist/preact/context.js +23 -0
- package/dist/preact/document.d.ts +15 -0
- package/dist/preact/document.js +155 -0
- package/dist/{internal/preact → preact}/fetcher.d.ts +9 -5
- package/dist/preact/fetcher.js +45 -0
- package/dist/{internal/preact → preact}/form.d.ts +5 -6
- package/dist/preact/form.js +21 -0
- package/dist/{internal/preact → preact}/hooks.d.ts +7 -6
- package/dist/preact/hooks.js +25 -0
- package/dist/preact/index.d.ts +18 -0
- package/dist/preact/index.js +15 -0
- package/dist/preact/lazy-route.d.ts +19 -0
- package/dist/preact/lazy-route.js +51 -0
- package/dist/preact/navigation-state.d.ts +2 -0
- package/dist/preact/navigation-state.js +9 -0
- package/dist/preact/navigation.d.ts +28 -0
- package/dist/preact/navigation.js +60 -0
- package/dist/preact/root-context.d.ts +13 -0
- package/dist/preact/root-context.js +9 -0
- package/dist/preact/root-data.d.ts +8 -0
- package/dist/preact/root-data.js +28 -0
- package/dist/preact/route-hooks.d.ts +32 -0
- package/dist/preact/route-hooks.js +94 -0
- package/dist/preact/route-types.d.ts +54 -0
- package/dist/preact/router.d.ts +16 -0
- package/dist/preact/router.js +63 -0
- package/dist/{internal/preact → preact}/routes.d.ts +1 -1
- package/dist/preact/routes.js +12 -0
- package/dist/{internal/preact → preact}/runtime.js +1 -1
- package/dist/react/context.d.ts +18 -0
- package/dist/react/context.js +23 -0
- package/dist/react/data.d.ts +5 -0
- package/dist/react/data.js +3 -0
- package/dist/{internal/react → react}/fetcher.d.ts +2 -2
- package/dist/react/fetcher.js +34 -0
- package/dist/{internal/react → react}/form.d.ts +5 -6
- package/dist/react/form.js +21 -0
- package/dist/react/hooks.js +25 -0
- package/dist/{internal/react → react}/index.d.ts +1 -1
- package/dist/react/index.js +10 -0
- package/dist/{internal/react → react}/link.d.ts +2 -2
- package/dist/react/link.js +27 -0
- package/dist/react/resources/components.d.ts +64 -0
- package/dist/react/resources/components.js +239 -0
- package/dist/{internal/react → react}/routes.d.ts +1 -1
- package/dist/react/routes.js +12 -0
- package/dist/react/runtime.js +1 -0
- package/dist/server/application.d.ts +4 -0
- package/dist/server/application.js +119 -0
- package/dist/server/assets.d.ts +5 -0
- package/dist/server/assets.js +7 -0
- package/dist/server/handler.d.ts +19 -0
- package/dist/server/handler.js +22 -0
- package/dist/server/http.d.ts +4 -0
- package/dist/server/http.js +27 -0
- package/dist/server/index.js +76 -0
- package/dist/server/node.d.ts +3 -0
- package/dist/server/node.js +3 -0
- package/dist/server/preact.d.ts +3 -0
- package/dist/server/preact.js +4 -0
- package/dist/vite/application/browser-boundary.d.ts +3 -0
- package/dist/vite/application/browser-boundary.js +35 -0
- package/dist/vite/application/lazy-files.d.ts +2 -0
- package/dist/vite/application/lazy-files.js +88 -0
- package/dist/vite/application/vite.d.ts +9 -0
- package/dist/vite/application/vite.js +101 -0
- package/dist/vite/deployment.d.ts +8 -0
- package/dist/vite/deployment.js +36 -0
- package/dist/vite/index.js +109 -0
- package/dist/{preact-vite.d.ts → vite/preact.d.ts} +6 -3
- package/dist/vite/preact.js +16 -0
- package/dist/{react-vite.d.ts → vite/react.d.ts} +4 -3
- package/dist/vite/react.js +15 -0
- package/dist/vite/transport.js +35 -0
- package/jsr.json +28 -16
- package/package.json +115 -35
- package/dist/client.d.ts +0 -2
- package/dist/client.js +0 -2
- package/dist/core.d.ts +0 -2
- package/dist/core.js +0 -2
- package/dist/files.d.ts +0 -2
- package/dist/files.js +0 -2
- package/dist/internal/client/deserialize.js +0 -11
- package/dist/internal/client/index.js +0 -44
- package/dist/internal/core/errors.js +0 -6
- package/dist/internal/core/index.js +0 -7
- package/dist/internal/core/match.js +0 -27
- package/dist/internal/core/router.js +0 -189
- package/dist/internal/core/serialize.js +0 -13
- package/dist/internal/core/types.js +0 -5
- package/dist/internal/core/url.d.ts +0 -2
- package/dist/internal/core/url.js +0 -6
- package/dist/internal/files/index.js +0 -77
- package/dist/internal/preact/context.d.ts +0 -18
- package/dist/internal/preact/context.js +0 -20
- package/dist/internal/preact/fetcher.js +0 -28
- package/dist/internal/preact/form.js +0 -41
- package/dist/internal/preact/hooks.js +0 -35
- package/dist/internal/preact/index.d.ts +0 -9
- package/dist/internal/preact/index.js +0 -7
- package/dist/internal/preact/link.d.ts +0 -12
- package/dist/internal/preact/link.js +0 -28
- package/dist/internal/preact/routes.js +0 -22
- package/dist/internal/react/context.d.ts +0 -18
- package/dist/internal/react/context.js +0 -20
- package/dist/internal/react/fetcher.js +0 -28
- package/dist/internal/react/form.js +0 -41
- package/dist/internal/react/hooks.js +0 -35
- package/dist/internal/react/index.js +0 -7
- package/dist/internal/react/link.js +0 -28
- package/dist/internal/react/routes.js +0 -22
- package/dist/internal/react/runtime.js +0 -2
- package/dist/internal/server/index.js +0 -69
- package/dist/internal/vite/index.js +0 -132
- package/dist/internal/vite/transport.js +0 -42
- package/dist/preact-client.d.ts +0 -2
- package/dist/preact-client.js +0 -2
- package/dist/preact-server.d.ts +0 -2
- package/dist/preact-server.js +0 -2
- package/dist/preact-vite.js +0 -9
- package/dist/preact.d.ts +0 -2
- package/dist/preact.js +0 -2
- package/dist/react-client.d.ts +0 -2
- package/dist/react-client.js +0 -2
- package/dist/react-server.d.ts +0 -2
- package/dist/react-server.js +0 -2
- package/dist/react-vite.js +0 -9
- package/dist/react.d.ts +0 -2
- package/dist/react.js +0 -2
- package/dist/server.d.ts +0 -2
- package/dist/server.js +0 -2
- package/dist/utils.d.ts +0 -5
- package/dist/utils.js +0 -5
- package/dist/vite.d.ts +0 -2
- package/dist/vite.js +0 -2
- package/src/client.ts +0 -2
- package/src/core.ts +0 -2
- package/src/files.ts +0 -2
- package/src/internal/core/url.ts +0 -6
- package/src/internal/preact/context.ts +0 -46
- package/src/internal/preact/form.ts +0 -79
- package/src/internal/preact/link.ts +0 -46
- package/src/internal/react/context.ts +0 -46
- package/src/internal/react/form.ts +0 -79
- package/src/internal/react/index.ts +0 -13
- package/src/preact-client.ts +0 -2
- package/src/preact-server.ts +0 -2
- package/src/preact.ts +0 -2
- package/src/react-client.ts +0 -2
- package/src/react-server.ts +0 -2
- package/src/react.ts +0 -2
- package/src/server.ts +0 -2
- package/src/utils.ts +0 -5
- package/src/vite.ts +0 -2
- /package/{src/internal → .generated}/client/deserialize.ts +0 -0
- /package/{src/internal → .generated}/core/match.ts +0 -0
- /package/{src/internal → .generated}/files/index.ts +0 -0
- /package/{src/internal → .generated}/preact/runtime.ts +0 -0
- /package/{src/internal → .generated}/react/runtime.ts +0 -0
- /package/{src/internal → .generated}/vite/transport.ts +0 -0
- /package/dist/{internal/client → client}/deserialize.d.ts +0 -0
- /package/dist/{internal/core → core}/errors.d.ts +0 -0
- /package/dist/{internal/core → core}/match.d.ts +0 -0
- /package/dist/{internal/files → files}/index.d.ts +0 -0
- /package/dist/{internal/preact → preact}/runtime.d.ts +0 -0
- /package/dist/{internal/react → react}/hooks.d.ts +0 -0
- /package/dist/{internal/react → react}/runtime.d.ts +0 -0
- /package/dist/{internal/server → server}/index.d.ts +0 -0
- /package/dist/{internal/vite → vite}/index.d.ts +0 -0
- /package/dist/{internal/vite → vite}/transport.d.ts +0 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { safeWrapAsync } from "./errors.js";
|
|
2
|
+
//#region .generated/core/resources.ts
|
|
3
|
+
var IDLE_ROUTE_STATE = {
|
|
4
|
+
actionData: null,
|
|
5
|
+
actionError: "",
|
|
6
|
+
actionState: "idle",
|
|
7
|
+
loaderData: null,
|
|
8
|
+
loaderError: "",
|
|
9
|
+
loaderInitialized: false,
|
|
10
|
+
loaderState: "idle",
|
|
11
|
+
navigationState: "idle",
|
|
12
|
+
unexpectedError: false
|
|
13
|
+
};
|
|
14
|
+
var ResourceRouter = class {
|
|
15
|
+
#cleanupTimers = /* @__PURE__ */ new Map();
|
|
16
|
+
#controllers = /* @__PURE__ */ new Map();
|
|
17
|
+
#inFlightLoads = /* @__PURE__ */ new Map();
|
|
18
|
+
#listeners = /* @__PURE__ */ new Map();
|
|
19
|
+
#persistentRoutes = /* @__PURE__ */ new Set();
|
|
20
|
+
#routes;
|
|
21
|
+
#states = /* @__PURE__ */ new Map();
|
|
22
|
+
constructor(routes) {
|
|
23
|
+
this.#routes = routes;
|
|
24
|
+
}
|
|
25
|
+
dispose() {
|
|
26
|
+
for (const controller of this.#controllers.values()) controller.abort();
|
|
27
|
+
this.#controllers.clear();
|
|
28
|
+
for (const timer of this.#cleanupTimers.values()) clearTimeout(timer);
|
|
29
|
+
this.#cleanupTimers.clear();
|
|
30
|
+
this.#inFlightLoads.clear();
|
|
31
|
+
this.#listeners.clear();
|
|
32
|
+
this.#persistentRoutes.clear();
|
|
33
|
+
}
|
|
34
|
+
getRoute(routeId) {
|
|
35
|
+
return this.#routes.get(routeId) ?? null;
|
|
36
|
+
}
|
|
37
|
+
getSnapshot(routeId, params) {
|
|
38
|
+
return this.#states.get(createRouteKey(routeId, params)) ?? IDLE_ROUTE_STATE;
|
|
39
|
+
}
|
|
40
|
+
hydrateRoute(routeId, data, params = {}) {
|
|
41
|
+
const routeKey = createRouteKey(routeId, params);
|
|
42
|
+
const currentState = this.#states.get(routeKey) ?? IDLE_ROUTE_STATE;
|
|
43
|
+
this.#setState(routeKey, {
|
|
44
|
+
...currentState,
|
|
45
|
+
loaderData: data,
|
|
46
|
+
loaderError: "",
|
|
47
|
+
loaderInitialized: true,
|
|
48
|
+
loaderState: "idle",
|
|
49
|
+
navigationState: currentState.actionState === "submitting" ? "submitting" : "idle",
|
|
50
|
+
unexpectedError: false
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
persist(routeId, params = {}) {
|
|
54
|
+
const routeKey = createRouteKey(routeId, params);
|
|
55
|
+
this.#persistentRoutes.add(routeKey);
|
|
56
|
+
const cleanupTimer = this.#cleanupTimers.get(routeKey);
|
|
57
|
+
if (!cleanupTimer) return;
|
|
58
|
+
clearTimeout(cleanupTimer);
|
|
59
|
+
this.#cleanupTimers.delete(routeKey);
|
|
60
|
+
}
|
|
61
|
+
disposeRoute(routeId, params = {}) {
|
|
62
|
+
this.#disposeRouteKey(createRouteKey(routeId, params));
|
|
63
|
+
}
|
|
64
|
+
#disposeRouteKey(routeKey) {
|
|
65
|
+
this.#persistentRoutes.delete(routeKey);
|
|
66
|
+
const cleanupTimer = this.#cleanupTimers.get(routeKey);
|
|
67
|
+
if (cleanupTimer) clearTimeout(cleanupTimer);
|
|
68
|
+
this.#cleanupTimers.delete(routeKey);
|
|
69
|
+
this.#controllers.get(routeKey)?.abort();
|
|
70
|
+
this.#controllers.get(`${routeKey}:action`)?.abort();
|
|
71
|
+
this.#controllers.delete(routeKey);
|
|
72
|
+
this.#controllers.delete(`${routeKey}:action`);
|
|
73
|
+
this.#inFlightLoads.delete(routeKey);
|
|
74
|
+
this.#states.delete(routeKey);
|
|
75
|
+
this.#notify(routeKey);
|
|
76
|
+
}
|
|
77
|
+
load(routeId, params = {}, force = false) {
|
|
78
|
+
const routeKey = createRouteKey(routeId, params);
|
|
79
|
+
const route = this.#routes.get(routeId);
|
|
80
|
+
if (!route?.loader) return Promise.resolve({
|
|
81
|
+
data: null,
|
|
82
|
+
error: "Route loader not found."
|
|
83
|
+
});
|
|
84
|
+
const currentState = this.#states.get(routeKey);
|
|
85
|
+
if (!force && currentState?.loaderInitialized) return Promise.resolve({
|
|
86
|
+
data: currentState?.loaderData,
|
|
87
|
+
error: currentState?.loaderError ?? ""
|
|
88
|
+
});
|
|
89
|
+
const currentLoad = this.#inFlightLoads.get(routeKey);
|
|
90
|
+
if (force && currentLoad) {
|
|
91
|
+
this.#controllers.get(routeKey)?.abort();
|
|
92
|
+
this.#inFlightLoads.delete(routeKey);
|
|
93
|
+
}
|
|
94
|
+
if (!force && currentLoad) return currentLoad;
|
|
95
|
+
const controller = this.#replaceController(routeKey);
|
|
96
|
+
this.#setState(routeKey, {
|
|
97
|
+
...currentState ?? IDLE_ROUTE_STATE,
|
|
98
|
+
loaderError: "",
|
|
99
|
+
loaderState: "loading",
|
|
100
|
+
navigationState: currentState?.actionState === "submitting" ? "submitting" : "loading",
|
|
101
|
+
unexpectedError: false
|
|
102
|
+
});
|
|
103
|
+
const load = this.#runLoader(route, routeKey, params, controller);
|
|
104
|
+
this.#inFlightLoads.set(routeKey, load);
|
|
105
|
+
return load;
|
|
106
|
+
}
|
|
107
|
+
subscribe(routeId, params, listener) {
|
|
108
|
+
const routeKey = createRouteKey(routeId, params);
|
|
109
|
+
const cleanupTimer = this.#cleanupTimers.get(routeKey);
|
|
110
|
+
if (cleanupTimer) {
|
|
111
|
+
clearTimeout(cleanupTimer);
|
|
112
|
+
this.#cleanupTimers.delete(routeKey);
|
|
113
|
+
}
|
|
114
|
+
const listeners = this.#listeners.get(routeKey) ?? /* @__PURE__ */ new Set();
|
|
115
|
+
listeners.add(listener);
|
|
116
|
+
this.#listeners.set(routeKey, listeners);
|
|
117
|
+
return () => {
|
|
118
|
+
if (!listeners.delete(listener) || listeners.size > 0) return;
|
|
119
|
+
this.#listeners.delete(routeKey);
|
|
120
|
+
if (this.#persistentRoutes.has(routeKey)) return;
|
|
121
|
+
const timer = setTimeout(() => this.#disposeRouteKey(routeKey), routeCleanupGraceMs);
|
|
122
|
+
this.#cleanupTimers.set(routeKey, timer);
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
async submit(routeId, input, params = {}) {
|
|
126
|
+
const routeKey = createRouteKey(routeId, params);
|
|
127
|
+
const route = this.#routes.get(routeId);
|
|
128
|
+
if (!route?.action) return {
|
|
129
|
+
data: null,
|
|
130
|
+
error: "Route action not found."
|
|
131
|
+
};
|
|
132
|
+
const controllerKey = `${routeKey}:action`;
|
|
133
|
+
const controller = this.#replaceController(controllerKey);
|
|
134
|
+
const currentState = this.#states.get(routeKey) ?? IDLE_ROUTE_STATE;
|
|
135
|
+
this.#setState(routeKey, {
|
|
136
|
+
...currentState,
|
|
137
|
+
actionError: "",
|
|
138
|
+
actionState: "submitting",
|
|
139
|
+
navigationState: "submitting",
|
|
140
|
+
unexpectedError: false
|
|
141
|
+
});
|
|
142
|
+
const { error, data: result } = await safeWrapAsync(() => route.action?.({
|
|
143
|
+
input,
|
|
144
|
+
params,
|
|
145
|
+
signal: controller.signal
|
|
146
|
+
}));
|
|
147
|
+
if (this.#controllers.get(controllerKey) === controller) this.#controllers.delete(controllerKey);
|
|
148
|
+
if (controller.signal.aborted) return {
|
|
149
|
+
data: null,
|
|
150
|
+
error: ""
|
|
151
|
+
};
|
|
152
|
+
const latestState = this.#states.get(routeKey) ?? IDLE_ROUTE_STATE;
|
|
153
|
+
if (error || !result) {
|
|
154
|
+
const publicError = "The request could not be completed.";
|
|
155
|
+
this.#setState(routeKey, {
|
|
156
|
+
...latestState,
|
|
157
|
+
actionData: null,
|
|
158
|
+
actionError: publicError,
|
|
159
|
+
actionState: "idle",
|
|
160
|
+
navigationState: latestState.loaderState === "loading" ? "loading" : "idle",
|
|
161
|
+
unexpectedError: true
|
|
162
|
+
});
|
|
163
|
+
return {
|
|
164
|
+
data: null,
|
|
165
|
+
error: publicError,
|
|
166
|
+
unexpected: true
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
this.#setState(routeKey, {
|
|
170
|
+
...latestState,
|
|
171
|
+
actionData: result.data,
|
|
172
|
+
actionError: result.error,
|
|
173
|
+
actionState: "idle",
|
|
174
|
+
navigationState: latestState.loaderState === "loading" ? "loading" : "idle",
|
|
175
|
+
unexpectedError: false
|
|
176
|
+
});
|
|
177
|
+
const actionResult = {
|
|
178
|
+
data: result.data,
|
|
179
|
+
error: result.error
|
|
180
|
+
};
|
|
181
|
+
const { error: revalidationError, data: shouldRevalidate } = await safeWrapAsync(() => route.shouldRevalidate?.({
|
|
182
|
+
actionResult: result,
|
|
183
|
+
defaultShouldRevalidate: Boolean(route.loader),
|
|
184
|
+
params
|
|
185
|
+
}) ?? Boolean(route.loader));
|
|
186
|
+
if (revalidationError) {
|
|
187
|
+
const publicError = "The request could not be completed.";
|
|
188
|
+
this.#setState(routeKey, {
|
|
189
|
+
...this.#states.get(routeKey) ?? IDLE_ROUTE_STATE,
|
|
190
|
+
actionError: publicError,
|
|
191
|
+
actionState: "idle",
|
|
192
|
+
unexpectedError: true
|
|
193
|
+
});
|
|
194
|
+
return {
|
|
195
|
+
data: null,
|
|
196
|
+
error: publicError,
|
|
197
|
+
unexpected: true
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
if (shouldRevalidate) await this.load(routeId, params, true);
|
|
201
|
+
return actionResult;
|
|
202
|
+
}
|
|
203
|
+
async fetchLoader(routeId, params, signal) {
|
|
204
|
+
const route = this.#routes.get(routeId);
|
|
205
|
+
if (!route?.loader) return {
|
|
206
|
+
data: null,
|
|
207
|
+
error: "Route loader not found."
|
|
208
|
+
};
|
|
209
|
+
const { error, data: result } = await safeWrapAsync(() => route.loader?.({
|
|
210
|
+
params,
|
|
211
|
+
signal
|
|
212
|
+
}));
|
|
213
|
+
if (signal.aborted) return {
|
|
214
|
+
data: null,
|
|
215
|
+
error: ""
|
|
216
|
+
};
|
|
217
|
+
if (error || !result) return {
|
|
218
|
+
data: null,
|
|
219
|
+
error: "The route could not be loaded.",
|
|
220
|
+
unexpected: true
|
|
221
|
+
};
|
|
222
|
+
return result;
|
|
223
|
+
}
|
|
224
|
+
async fetchAction(routeId, input, params, signal) {
|
|
225
|
+
const route = this.#routes.get(routeId);
|
|
226
|
+
if (!route?.action) return {
|
|
227
|
+
data: null,
|
|
228
|
+
error: "Route action not found."
|
|
229
|
+
};
|
|
230
|
+
const { error, data: result } = await safeWrapAsync(() => route.action?.({
|
|
231
|
+
input,
|
|
232
|
+
params,
|
|
233
|
+
signal
|
|
234
|
+
}));
|
|
235
|
+
if (signal.aborted) return {
|
|
236
|
+
data: null,
|
|
237
|
+
error: ""
|
|
238
|
+
};
|
|
239
|
+
if (error || !result) return {
|
|
240
|
+
data: null,
|
|
241
|
+
error: "The request could not be completed.",
|
|
242
|
+
unexpected: true
|
|
243
|
+
};
|
|
244
|
+
const { error: revalidationError, data: shouldRevalidate } = await safeWrapAsync(() => route.shouldRevalidate?.({
|
|
245
|
+
actionResult: result,
|
|
246
|
+
defaultShouldRevalidate: Boolean(route.loader),
|
|
247
|
+
params
|
|
248
|
+
}) ?? Boolean(route.loader));
|
|
249
|
+
if (signal.aborted) return {
|
|
250
|
+
data: null,
|
|
251
|
+
error: ""
|
|
252
|
+
};
|
|
253
|
+
if (revalidationError) return {
|
|
254
|
+
data: null,
|
|
255
|
+
error: "The request could not be completed.",
|
|
256
|
+
unexpected: true
|
|
257
|
+
};
|
|
258
|
+
if (shouldRevalidate) await this.load(routeId, params, true);
|
|
259
|
+
return result;
|
|
260
|
+
}
|
|
261
|
+
retry(routeId, params) {
|
|
262
|
+
const routeKey = createRouteKey(routeId, params);
|
|
263
|
+
this.#states.set(routeKey, IDLE_ROUTE_STATE);
|
|
264
|
+
this.#notify(routeKey);
|
|
265
|
+
return this.load(routeId, params, true);
|
|
266
|
+
}
|
|
267
|
+
reportUnexpected(routeId, params) {
|
|
268
|
+
const routeKey = createRouteKey(routeId, params);
|
|
269
|
+
const currentState = this.#states.get(routeKey) ?? IDLE_ROUTE_STATE;
|
|
270
|
+
this.#setState(routeKey, {
|
|
271
|
+
...currentState,
|
|
272
|
+
unexpectedError: true
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
async #runLoader(route, routeKey, params, controller) {
|
|
276
|
+
const { error, data: result } = await safeWrapAsync(() => route.loader?.({
|
|
277
|
+
params,
|
|
278
|
+
signal: controller.signal
|
|
279
|
+
}) ?? Promise.resolve({
|
|
280
|
+
data: null,
|
|
281
|
+
error: ""
|
|
282
|
+
}));
|
|
283
|
+
if (this.#controllers.get(routeKey) === controller) {
|
|
284
|
+
this.#controllers.delete(routeKey);
|
|
285
|
+
this.#inFlightLoads.delete(routeKey);
|
|
286
|
+
}
|
|
287
|
+
if (controller.signal.aborted) return {
|
|
288
|
+
data: null,
|
|
289
|
+
error: ""
|
|
290
|
+
};
|
|
291
|
+
if (error || !result) {
|
|
292
|
+
const publicError = "The route could not be loaded.";
|
|
293
|
+
this.#setState(routeKey, {
|
|
294
|
+
actionData: null,
|
|
295
|
+
actionError: "",
|
|
296
|
+
actionState: "idle",
|
|
297
|
+
loaderData: null,
|
|
298
|
+
loaderError: publicError,
|
|
299
|
+
loaderInitialized: true,
|
|
300
|
+
loaderState: "idle",
|
|
301
|
+
navigationState: "idle",
|
|
302
|
+
unexpectedError: true
|
|
303
|
+
});
|
|
304
|
+
return {
|
|
305
|
+
data: null,
|
|
306
|
+
error: publicError,
|
|
307
|
+
unexpected: true
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
const currentState = this.#states.get(routeKey) ?? IDLE_ROUTE_STATE;
|
|
311
|
+
this.#setState(routeKey, {
|
|
312
|
+
...currentState,
|
|
313
|
+
loaderData: result.data,
|
|
314
|
+
loaderError: result.error,
|
|
315
|
+
loaderInitialized: true,
|
|
316
|
+
loaderState: "idle",
|
|
317
|
+
navigationState: currentState.actionState === "submitting" ? "submitting" : "idle",
|
|
318
|
+
unexpectedError: false
|
|
319
|
+
});
|
|
320
|
+
return result;
|
|
321
|
+
}
|
|
322
|
+
#replaceController(key) {
|
|
323
|
+
this.#controllers.get(key)?.abort();
|
|
324
|
+
const controller = new AbortController();
|
|
325
|
+
this.#controllers.set(key, controller);
|
|
326
|
+
return controller;
|
|
327
|
+
}
|
|
328
|
+
#setState(routeKey, state) {
|
|
329
|
+
this.#states.set(routeKey, state);
|
|
330
|
+
this.#notify(routeKey);
|
|
331
|
+
}
|
|
332
|
+
#notify(routeKey) {
|
|
333
|
+
for (const listener of this.#listeners.get(routeKey) ?? []) listener();
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
function createResourceRouter(routes) {
|
|
337
|
+
return new ResourceRouter(routes);
|
|
338
|
+
}
|
|
339
|
+
function createRouteKey(routeId, params) {
|
|
340
|
+
const normalizedParams = Object.entries(params).sort(([left], [right]) => left.localeCompare(right));
|
|
341
|
+
return `${routeId}:${JSON.stringify(normalizedParams)}`;
|
|
342
|
+
}
|
|
343
|
+
var routeCleanupGraceMs = 100;
|
|
344
|
+
//#endregion
|
|
345
|
+
export { ResourceRouter, createResourceRouter };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface RouteError extends Error {
|
|
2
|
+
status: number;
|
|
3
|
+
statusText: string;
|
|
4
|
+
data?: unknown;
|
|
5
|
+
}
|
|
6
|
+
export interface DataResponse<T> {
|
|
7
|
+
value: T;
|
|
8
|
+
init: ResponseInit;
|
|
9
|
+
__routeData: true;
|
|
10
|
+
}
|
|
11
|
+
export declare function data<T>(value: T, init?: ResponseInit): DataResponse<T>;
|
|
12
|
+
export declare function redirect(location: string, init?: number | ResponseInit): Response;
|
|
13
|
+
export declare function isRouteErrorResponse(value: unknown): value is RouteError;
|
|
14
|
+
export declare function normalizeResult(value: unknown): Promise<unknown>;
|
|
15
|
+
export declare function resolveData(value: unknown): Promise<unknown>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//#region .generated/core/responses.ts
|
|
2
|
+
function data(value, init = {}) {
|
|
3
|
+
return {
|
|
4
|
+
value,
|
|
5
|
+
init,
|
|
6
|
+
__routeData: true
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
function redirect(location, init = 302) {
|
|
10
|
+
const options = typeof init === "number" ? { status: init } : init;
|
|
11
|
+
const headers = new Headers(options.headers);
|
|
12
|
+
headers.set("Location", location);
|
|
13
|
+
return new Response(null, {
|
|
14
|
+
...options,
|
|
15
|
+
status: options.status ?? 302,
|
|
16
|
+
headers
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function isRouteErrorResponse(value) {
|
|
20
|
+
return value instanceof Error && "status" in value;
|
|
21
|
+
}
|
|
22
|
+
async function normalizeResult(value) {
|
|
23
|
+
if (value && typeof value === "object" && "__routeError" in value) {
|
|
24
|
+
const error = value.__routeError;
|
|
25
|
+
return Object.assign(Error(error.message), error);
|
|
26
|
+
}
|
|
27
|
+
if (value instanceof Response) {
|
|
28
|
+
if (value.headers.has("Location")) return value;
|
|
29
|
+
if (!value.ok) return Object.assign(Error(await value.text()), {
|
|
30
|
+
status: value.status,
|
|
31
|
+
statusText: value.statusText
|
|
32
|
+
});
|
|
33
|
+
return value.json();
|
|
34
|
+
}
|
|
35
|
+
if (value && typeof value === "object" && "__routeData" in value) return resolveData(value.value);
|
|
36
|
+
return resolveData(value);
|
|
37
|
+
}
|
|
38
|
+
async function resolveData(value) {
|
|
39
|
+
const resolved = await value;
|
|
40
|
+
if (Array.isArray(resolved)) return Promise.all(resolved.map(resolveData));
|
|
41
|
+
if (resolved && typeof resolved === "object" && Object.getPrototypeOf(resolved) === Object.prototype) {
|
|
42
|
+
const entries = await Promise.all(Object.entries(resolved).map(async ([key, field]) => [key, await resolveData(field)]));
|
|
43
|
+
return Object.fromEntries(entries);
|
|
44
|
+
}
|
|
45
|
+
return resolved;
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
export { data, isRouteErrorResponse, normalizeResult, redirect, resolveData };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { Route, Router, RouterOptions } from "./types.ts";
|
|
2
2
|
/** Create an isolated router. Use browser: true for browser history, or loadRouter for SSR. */
|
|
3
|
-
export declare function
|
|
3
|
+
export declare function createDataRouter(routes: readonly Route[], options?: RouterOptions): Router;
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { safeWrapAsync } from "./errors.js";
|
|
2
|
+
import { matchRoutes } from "./match.js";
|
|
3
|
+
import { internalURL } from "./url.js";
|
|
4
|
+
//#region .generated/core/router.ts
|
|
5
|
+
/** Create an isolated router. Use browser: true for browser history, or loadRouter for SSR. */
|
|
6
|
+
function createDataRouter(routes, options = {}) {
|
|
7
|
+
const browser = options.browser && typeof window !== "undefined";
|
|
8
|
+
const hydrated = options.hydrationData?.state === "idle";
|
|
9
|
+
const initial = {
|
|
10
|
+
loaded: hydrated,
|
|
11
|
+
...options.hydrationData ?? {
|
|
12
|
+
url: new URL(options.url ?? (browser ? location.href : "/"), "http://localhost").href,
|
|
13
|
+
params: {},
|
|
14
|
+
state: "idle"
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const revalidators = /* @__PURE__ */ new Set();
|
|
18
|
+
const channel = (initial, local) => {
|
|
19
|
+
let snapshot = initial;
|
|
20
|
+
let loadedURL;
|
|
21
|
+
let submission;
|
|
22
|
+
let controller;
|
|
23
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
24
|
+
const emit = (state, next) => {
|
|
25
|
+
snapshot = {
|
|
26
|
+
...snapshot,
|
|
27
|
+
...next,
|
|
28
|
+
state
|
|
29
|
+
};
|
|
30
|
+
for (const listener of listeners) listener();
|
|
31
|
+
};
|
|
32
|
+
const run = async (href, body, operation = {}, navigation, redirects = 10) => {
|
|
33
|
+
controller?.abort();
|
|
34
|
+
controller = new AbortController();
|
|
35
|
+
const { signal } = controller;
|
|
36
|
+
const { method = "POST" } = operation;
|
|
37
|
+
revalidators.add(task);
|
|
38
|
+
submission = body;
|
|
39
|
+
const previous = snapshot;
|
|
40
|
+
const url = internalURL(main.getSnapshot().url, href);
|
|
41
|
+
const match = url && matchRoutes(routes, url.pathname);
|
|
42
|
+
const commit = () => {
|
|
43
|
+
if (local || !url) return;
|
|
44
|
+
const changed = snapshot.url !== url.href;
|
|
45
|
+
if (!changed && snapshot.path !== void 0) return;
|
|
46
|
+
if (browser && (navigation || body) && changed) history[operation.replace ? "replaceState" : "pushState"](null, "", url);
|
|
47
|
+
const sameRoute = snapshot.path === match?.route.path;
|
|
48
|
+
snapshot = {
|
|
49
|
+
...snapshot,
|
|
50
|
+
url: url.href,
|
|
51
|
+
path: match?.route.path,
|
|
52
|
+
params: match?.params ?? {},
|
|
53
|
+
actionData: void 0,
|
|
54
|
+
...!sameRoute && {
|
|
55
|
+
data: void 0,
|
|
56
|
+
loaded: false
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
if (!body) commit();
|
|
61
|
+
emit(body ? "submitting" : "loading", { error: void 0 });
|
|
62
|
+
const result = await safeWrapAsync(() => {
|
|
63
|
+
if (!match) return Error(url ? "No route" : "External URL");
|
|
64
|
+
if (navigation !== void 0 && previous.loaded && !previous.error && previous.path === match.route.path && match.route.shouldRevalidate?.({
|
|
65
|
+
url,
|
|
66
|
+
currentUrl: new URL(previous.url),
|
|
67
|
+
nextUrl: url,
|
|
68
|
+
defaultShouldRevalidate: true,
|
|
69
|
+
reason: "navigation"
|
|
70
|
+
}) === false) return previous.data;
|
|
71
|
+
const fn = match.route[body ? "clientAction" : "clientLoader"] || match.route[body ? "action" : "loader"];
|
|
72
|
+
if (!fn) return body && Error("No action");
|
|
73
|
+
return fn({
|
|
74
|
+
request: new Request(url, {
|
|
75
|
+
signal,
|
|
76
|
+
method: body && method,
|
|
77
|
+
body
|
|
78
|
+
}),
|
|
79
|
+
params: match.params,
|
|
80
|
+
signal
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
if (signal.aborted) return result;
|
|
84
|
+
const redirectTo = result.data instanceof Response && result.data.headers.get("Location");
|
|
85
|
+
if (redirectTo && redirects) {
|
|
86
|
+
await main.load(new URL(redirectTo, url).href, void 0, { replace: true }, true, redirects - 1);
|
|
87
|
+
if (!signal.aborted) {
|
|
88
|
+
submission = void 0;
|
|
89
|
+
emit("idle");
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
if (redirectTo) result.error = Error("Redirect loop");
|
|
94
|
+
if (!body && !result.error) loadedURL = url.href;
|
|
95
|
+
if (body && !result.error) commit();
|
|
96
|
+
emit(body && !result.error ? "loading" : "idle", {
|
|
97
|
+
[body && !local ? "actionData" : "data"]: result.data,
|
|
98
|
+
error: result.error,
|
|
99
|
+
loaded: body ? snapshot.loaded : !result.error
|
|
100
|
+
});
|
|
101
|
+
if (body && !result.error) {
|
|
102
|
+
await router.revalidate("action", {
|
|
103
|
+
formMethod: method,
|
|
104
|
+
formAction: url.href,
|
|
105
|
+
formData: body,
|
|
106
|
+
actionResult: result.data
|
|
107
|
+
}).finally(() => {
|
|
108
|
+
if (!signal.aborted) {
|
|
109
|
+
submission = void 0;
|
|
110
|
+
emit("idle");
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
if (!signal.aborted) submission = void 0;
|
|
116
|
+
return result;
|
|
117
|
+
};
|
|
118
|
+
const task = {
|
|
119
|
+
revalidate: async (args, targets) => {
|
|
120
|
+
if (local && (!loadedURL || submission)) return;
|
|
121
|
+
const url = new URL(local ? loadedURL : main.getSnapshot().url);
|
|
122
|
+
const route = matchRoutes(routes, url.pathname)?.route;
|
|
123
|
+
if (!route || targets && !targets.includes(route.id) && !targets.includes(route.path)) return;
|
|
124
|
+
if (route.shouldRevalidate?.({
|
|
125
|
+
...args,
|
|
126
|
+
url,
|
|
127
|
+
currentUrl: url,
|
|
128
|
+
nextUrl: url
|
|
129
|
+
}) === false) return;
|
|
130
|
+
return run(url.href);
|
|
131
|
+
},
|
|
132
|
+
getSnapshot: () => snapshot,
|
|
133
|
+
subscribe(listener) {
|
|
134
|
+
listeners.add(listener);
|
|
135
|
+
return () => {
|
|
136
|
+
listeners.delete(listener);
|
|
137
|
+
};
|
|
138
|
+
},
|
|
139
|
+
load: run,
|
|
140
|
+
submit: run,
|
|
141
|
+
dispose() {
|
|
142
|
+
controller?.abort();
|
|
143
|
+
listeners.clear();
|
|
144
|
+
revalidators.delete(task);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
revalidators.add(task);
|
|
148
|
+
return task;
|
|
149
|
+
};
|
|
150
|
+
const main = channel(initial);
|
|
151
|
+
let started = 0;
|
|
152
|
+
const pop = () => main.load(location.href, void 0, void 0, false);
|
|
153
|
+
const router = {
|
|
154
|
+
...main,
|
|
155
|
+
routes,
|
|
156
|
+
start() {
|
|
157
|
+
if (!started++ && browser) addEventListener("popstate", pop);
|
|
158
|
+
if (!hydrated && main.getSnapshot() === initial) main.load(initial.url);
|
|
159
|
+
return () => {
|
|
160
|
+
if (!--started && browser) removeEventListener("popstate", pop);
|
|
161
|
+
};
|
|
162
|
+
},
|
|
163
|
+
async navigate(url, operation) {
|
|
164
|
+
await main.load(url, void 0, operation, true);
|
|
165
|
+
},
|
|
166
|
+
async revalidate(options = {}, context) {
|
|
167
|
+
const { route, reason = "manual", ...details } = typeof options === "string" ? {
|
|
168
|
+
reason: options,
|
|
169
|
+
...context
|
|
170
|
+
} : options;
|
|
171
|
+
const url = new URL(main.getSnapshot().url);
|
|
172
|
+
const args = {
|
|
173
|
+
...details,
|
|
174
|
+
url,
|
|
175
|
+
currentUrl: url,
|
|
176
|
+
nextUrl: url,
|
|
177
|
+
defaultShouldRevalidate: true,
|
|
178
|
+
reason
|
|
179
|
+
};
|
|
180
|
+
const targets = typeof route === "string" ? [route] : route;
|
|
181
|
+
await Promise.all([...revalidators].map((refresh) => refresh.revalidate(args, targets)));
|
|
182
|
+
},
|
|
183
|
+
registerRevalidator(id, refresh) {
|
|
184
|
+
const task = { revalidate: async (args, targets) => {
|
|
185
|
+
if (!targets || targets.includes(id)) return refresh(args);
|
|
186
|
+
} };
|
|
187
|
+
revalidators.add(task);
|
|
188
|
+
return () => {
|
|
189
|
+
revalidators.delete(task);
|
|
190
|
+
};
|
|
191
|
+
},
|
|
192
|
+
createFetcher() {
|
|
193
|
+
return channel({ state: "idle" }, true);
|
|
194
|
+
},
|
|
195
|
+
dispose() {
|
|
196
|
+
for (const refresh of revalidators) refresh.dispose?.();
|
|
197
|
+
revalidators.clear();
|
|
198
|
+
if (browser) removeEventListener("popstate", pop);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
return router;
|
|
202
|
+
}
|
|
203
|
+
//#endregion
|
|
204
|
+
export { createDataRouter };
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { Snapshot } from "./types.ts";
|
|
2
2
|
/** Safe inside a script element. Data must be JSON serializable; error stacks and causes are omitted. */
|
|
3
3
|
export declare function serialize(snapshot: Snapshot): string;
|
|
4
|
+
/** Encode JSON for embedding inside an HTML script element. */
|
|
5
|
+
export declare function serializeData(value: unknown): string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region .generated/core/serialize.ts
|
|
2
|
+
/** Safe inside a script element. Data must be JSON serializable; error stacks and causes are omitted. */
|
|
3
|
+
function serialize(snapshot) {
|
|
4
|
+
return serializeData({
|
|
5
|
+
...snapshot,
|
|
6
|
+
error: snapshot.error && {
|
|
7
|
+
name: snapshot.error.name,
|
|
8
|
+
message: snapshot.error.message
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
/** Encode JSON for embedding inside an HTML script element. */
|
|
13
|
+
function serializeData(value) {
|
|
14
|
+
return JSON.stringify(value).replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { serialize, serializeData };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region .generated/core/store.ts
|
|
2
|
+
/** Small observable value shared by layout data and navigation state. */
|
|
3
|
+
function createStore(initial) {
|
|
4
|
+
let value = initial;
|
|
5
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
6
|
+
return {
|
|
7
|
+
getSnapshot: () => value,
|
|
8
|
+
subscribe(listener) {
|
|
9
|
+
listeners.add(listener);
|
|
10
|
+
return () => {
|
|
11
|
+
listeners.delete(listener);
|
|
12
|
+
};
|
|
13
|
+
},
|
|
14
|
+
update(next) {
|
|
15
|
+
value = next;
|
|
16
|
+
for (const listener of listeners) listener();
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
export { createStore };
|