valyrian.js 7.2.12 → 8.0.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/README.md +6 -6
- package/dist/flux-store/index.d.ts +32 -0
- package/dist/flux-store/index.d.ts.map +1 -0
- package/dist/flux-store/index.js +267 -0
- package/dist/flux-store/index.js.map +7 -0
- package/dist/flux-store/index.min.js +1 -0
- package/dist/flux-store/index.min.js.map +1 -0
- package/dist/flux-store/index.mjs +246 -0
- package/dist/flux-store/index.mjs.map +7 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +35 -51
- package/dist/hooks/index.js.map +3 -3
- package/dist/hooks/index.min.js +1 -0
- package/dist/hooks/index.min.js.map +1 -0
- package/dist/hooks/index.mjs +36 -52
- package/dist/hooks/index.mjs.map +3 -3
- package/dist/index.d.ts +18 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +107 -88
- package/dist/index.js.map +3 -3
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/index.mjs +107 -88
- package/dist/index.mjs.map +3 -3
- package/dist/native-store/index.d.ts +14 -0
- package/dist/native-store/index.d.ts.map +1 -0
- package/dist/native-store/index.js +103 -0
- package/dist/native-store/index.js.map +7 -0
- package/dist/native-store/index.min.js +1 -0
- package/dist/native-store/index.min.js.map +1 -0
- package/dist/native-store/index.mjs +82 -0
- package/dist/native-store/index.mjs.map +7 -0
- package/dist/node/index.d.ts +1 -1
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +125 -10
- package/dist/node/index.js.map +4 -4
- package/dist/node/index.mjs +125 -10
- package/dist/node/index.mjs.map +4 -4
- package/dist/node/node.sw.js +152 -0
- package/dist/node/utils/icons.d.ts +4 -5
- package/dist/node/utils/icons.d.ts.map +1 -1
- package/dist/node/utils/inline.d.ts +1 -1
- package/dist/node/utils/inline.d.ts.map +1 -1
- package/dist/node/utils/node.sw.js +152 -0
- package/dist/node/utils/session-storage.d.ts +22 -0
- package/dist/node/utils/session-storage.d.ts.map +1 -0
- package/dist/node/utils/sw.d.ts.map +1 -1
- package/dist/node/utils/tree-adapter.d.ts +5 -1
- package/dist/node/utils/tree-adapter.d.ts.map +1 -1
- package/dist/pulse-store/index.d.ts +16 -0
- package/dist/pulse-store/index.d.ts.map +1 -0
- package/dist/pulse-store/index.js +143 -0
- package/dist/pulse-store/index.js.map +7 -0
- package/dist/pulse-store/index.min.js +1 -0
- package/dist/pulse-store/index.min.js.map +1 -0
- package/dist/pulse-store/index.mjs +122 -0
- package/dist/pulse-store/index.mjs.map +7 -0
- package/dist/request/index.d.ts +11 -11
- package/dist/request/index.d.ts.map +1 -1
- package/dist/request/index.js +63 -84
- package/dist/request/index.js.map +2 -2
- package/dist/request/index.min.js +1 -0
- package/dist/request/index.min.js.map +1 -0
- package/dist/request/index.mjs +63 -84
- package/dist/request/index.mjs.map +2 -2
- package/dist/router/index.d.ts +36 -33
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +247 -96
- package/dist/router/index.js.map +3 -3
- package/dist/router/index.min.js +1 -0
- package/dist/router/index.min.js.map +1 -0
- package/dist/router/index.mjs +247 -96
- package/dist/router/index.mjs.map +3 -3
- package/dist/signals/index.d.ts +6 -0
- package/dist/signals/index.d.ts.map +1 -0
- package/dist/signals/index.js +92 -0
- package/dist/signals/index.js.map +7 -0
- package/dist/signals/index.min.js +1 -0
- package/dist/signals/index.min.js.map +1 -0
- package/dist/signals/index.mjs +71 -0
- package/dist/signals/index.mjs.map +7 -0
- package/dist/suspense/index.d.ts +6 -0
- package/dist/suspense/index.d.ts.map +1 -0
- package/dist/suspense/index.js +67 -0
- package/dist/suspense/index.js.map +7 -0
- package/dist/suspense/index.min.js +1 -0
- package/dist/suspense/index.min.js.map +1 -0
- package/dist/suspense/index.mjs +46 -0
- package/dist/suspense/index.mjs.map +7 -0
- package/dist/sw/index.min.js +1 -0
- package/dist/sw/index.min.js.map +1 -0
- package/dist/translate/index.d.ts +19 -0
- package/dist/translate/index.d.ts.map +1 -0
- package/dist/translate/index.js +150 -0
- package/dist/translate/index.js.map +7 -0
- package/dist/translate/index.min.js +1 -0
- package/dist/translate/index.min.js.map +1 -0
- package/dist/translate/index.mjs +129 -0
- package/dist/translate/index.mjs.map +7 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils/deep-freeze.d.ts +3 -0
- package/dist/utils/deep-freeze.d.ts.map +1 -0
- package/dist/utils/getter-setter.d.ts +3 -0
- package/dist/utils/getter-setter.d.ts.map +1 -0
- package/dist/utils/has-changed.d.ts +2 -0
- package/dist/utils/has-changed.d.ts.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +138 -0
- package/dist/utils/index.js.map +7 -0
- package/dist/utils/index.min.js +1 -0
- package/dist/utils/index.min.js.map +1 -0
- package/dist/utils/index.mjs +115 -0
- package/dist/utils/index.mjs.map +7 -0
- package/lib/flux-store/index.ts +312 -0
- package/lib/hooks/index.ts +39 -57
- package/lib/index.ts +135 -118
- package/lib/native-store/index.ts +106 -0
- package/lib/node/index.ts +3 -1
- package/lib/node/utils/icons.ts +4 -4
- package/lib/node/utils/inline.ts +2 -0
- package/lib/node/utils/node.sw.js +152 -0
- package/lib/node/utils/session-storage.ts +117 -0
- package/lib/node/utils/sw.ts +34 -10
- package/lib/node/utils/tree-adapter.ts +19 -1
- package/lib/pulse-store/index.ts +188 -0
- package/lib/request/index.ts +92 -122
- package/lib/router/index.ts +353 -164
- package/lib/signals/index.ts +98 -0
- package/lib/suspense/index.ts +57 -0
- package/lib/translate/index.ts +156 -0
- package/lib/utils/deep-freeze.ts +54 -0
- package/lib/utils/getter-setter.ts +40 -0
- package/lib/utils/has-changed.ts +43 -0
- package/lib/utils/index.ts +3 -0
- package/package.json +40 -57
- package/tsconfig.json +5 -4
- package/dist/dataset/index.d.ts +0 -24
- package/dist/dataset/index.d.ts.map +0 -1
- package/dist/dataset/index.js +0 -178
- package/dist/dataset/index.js.map +0 -7
- package/dist/dataset/index.mjs +0 -157
- package/dist/dataset/index.mjs.map +0 -7
- package/dist/node/node.sw.tpl +0 -133
- package/dist/node/utils/node.sw.tpl +0 -133
- package/dist/proxy-signal/index.d.ts +0 -23
- package/dist/proxy-signal/index.d.ts.map +0 -1
- package/dist/proxy-signal/index.js +0 -138
- package/dist/proxy-signal/index.js.map +0 -7
- package/dist/proxy-signal/index.mjs +0 -117
- package/dist/proxy-signal/index.mjs.map +0 -7
- package/dist/signal/index.d.ts +0 -9
- package/dist/signal/index.d.ts.map +0 -1
- package/dist/signal/index.js +0 -76
- package/dist/signal/index.js.map +0 -7
- package/dist/signal/index.mjs +0 -55
- package/dist/signal/index.mjs.map +0 -7
- package/dist/store/index.d.ts +0 -16
- package/dist/store/index.d.ts.map +0 -1
- package/dist/store/index.js +0 -93
- package/dist/store/index.js.map +0 -7
- package/dist/store/index.mjs +0 -72
- package/dist/store/index.mjs.map +0 -7
- package/lib/dataset/index.ts +0 -193
- package/lib/node/utils/node.sw.tpl +0 -133
- package/lib/proxy-signal/index.ts +0 -187
- package/lib/signal/index.ts +0 -86
- package/lib/store/index.ts +0 -101
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// eslint-disable-next-line no-console
|
|
2
|
+
const Log = console.log;
|
|
3
|
+
|
|
4
|
+
const config = {
|
|
5
|
+
version: "v1.2::",
|
|
6
|
+
name: "Valyrian.js",
|
|
7
|
+
// Critical resources and offline page
|
|
8
|
+
urls: ["/"]
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const cacheName = config.version + config.name;
|
|
12
|
+
const MAX_CACHE_SIZE = 50; // Max cache size
|
|
13
|
+
|
|
14
|
+
// Send messages to clients (controlled pages)
|
|
15
|
+
function sendMessageToClients(message) {
|
|
16
|
+
self.clients.matchAll().then((clients) => {
|
|
17
|
+
clients.forEach((client) => {
|
|
18
|
+
client.postMessage(message);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Limit the cache size by deleting the oldest entries
|
|
24
|
+
function limitCacheSize(name, size) {
|
|
25
|
+
caches.open(name).then((cache) => {
|
|
26
|
+
cache.keys().then((keys) => {
|
|
27
|
+
if (keys.length > size) {
|
|
28
|
+
cache.delete(keys[0]).then(() => {
|
|
29
|
+
limitCacheSize(name, size);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function fetchRequest(event) {
|
|
37
|
+
Log("WORKER: fetch event for " + event.request.url);
|
|
38
|
+
try {
|
|
39
|
+
// Clone the request to store it in the cache
|
|
40
|
+
const fetchRequest = event.request.clone();
|
|
41
|
+
const response = await fetch(fetchRequest);
|
|
42
|
+
|
|
43
|
+
// Verify if the response is valid
|
|
44
|
+
if (response && response.status === 200 && response.type === "basic") {
|
|
45
|
+
// Clone the response to store it in the cache
|
|
46
|
+
const responseToCache = response.clone();
|
|
47
|
+
const cache = await caches.open(cacheName);
|
|
48
|
+
cache.put(event.request, responseToCache);
|
|
49
|
+
|
|
50
|
+
// Limit the cache size
|
|
51
|
+
limitCacheSize(cacheName, MAX_CACHE_SIZE);
|
|
52
|
+
|
|
53
|
+
Log("WORKER: fetch response stored in cache.", event.request.url);
|
|
54
|
+
}
|
|
55
|
+
return response;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
Log("WORKER: fetch request failed.", error);
|
|
58
|
+
|
|
59
|
+
// Try to serve the cached response if available
|
|
60
|
+
const cachedResponse = await caches.match(event.request);
|
|
61
|
+
if (cachedResponse) {
|
|
62
|
+
Log("WORKER: fetch request failed, responding with cache.");
|
|
63
|
+
return cachedResponse;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Send the offline page if no cache is available
|
|
67
|
+
const cache = await caches.open(cacheName);
|
|
68
|
+
const offlineResponse = await cache.match("/offline.html");
|
|
69
|
+
if (offlineResponse) {
|
|
70
|
+
return offlineResponse;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Generic offline page
|
|
74
|
+
return new Response("<h1>Offline</h1>", {
|
|
75
|
+
status: 503,
|
|
76
|
+
statusText: "Service Unavailable",
|
|
77
|
+
headers: new Headers({
|
|
78
|
+
"Content-Type": "text/html"
|
|
79
|
+
})
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
self.addEventListener("fetch", (event) => {
|
|
85
|
+
// Ignore requests with the "only-if-cached" cache mode
|
|
86
|
+
if (event.request.cache === "only-if-cached" && event.request.mode !== "same-origin") {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
Log("WORKER: fetch event in progress.", event.request.url);
|
|
91
|
+
|
|
92
|
+
// Ignore requests that are not GET
|
|
93
|
+
if (event.request.method !== "GET") {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// If the request is for the API, use the network first
|
|
98
|
+
if (event.request.url.includes("/api/")) {
|
|
99
|
+
event.respondWith(fetchRequest(event));
|
|
100
|
+
} else {
|
|
101
|
+
// Use the cache first for other requests
|
|
102
|
+
event.respondWith(
|
|
103
|
+
caches.match(event.request).then((response) => {
|
|
104
|
+
if (response) {
|
|
105
|
+
Log("WORKER: returning from cache.", event.request.url);
|
|
106
|
+
return response;
|
|
107
|
+
}
|
|
108
|
+
return fetchRequest(event);
|
|
109
|
+
})
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
self.addEventListener("install", (event) => {
|
|
115
|
+
Log("WORKER: installing version", cacheName);
|
|
116
|
+
event.waitUntil(
|
|
117
|
+
caches
|
|
118
|
+
.open(cacheName)
|
|
119
|
+
.then((cache) => {
|
|
120
|
+
return cache.addAll(config.urls);
|
|
121
|
+
})
|
|
122
|
+
.then(() => {
|
|
123
|
+
Log("WORKER: install completed.");
|
|
124
|
+
})
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
self.addEventListener("activate", (event) => {
|
|
129
|
+
Log("WORKER: activating new version", cacheName);
|
|
130
|
+
|
|
131
|
+
event.waitUntil(
|
|
132
|
+
caches.keys().then((keys) =>
|
|
133
|
+
Promise.all(
|
|
134
|
+
keys
|
|
135
|
+
// Filter the caches that belong to this app version
|
|
136
|
+
.filter((key) => key !== cacheName)
|
|
137
|
+
.map((key) => caches.delete(key))
|
|
138
|
+
).then(() => {
|
|
139
|
+
Log("WORKER: old caches cleared.");
|
|
140
|
+
// Notify clients about the new version
|
|
141
|
+
sendMessageToClients({ type: "NEW_VERSION" });
|
|
142
|
+
})
|
|
143
|
+
)
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Listen for messages from clients
|
|
148
|
+
self.addEventListener("message", (event) => {
|
|
149
|
+
if (event.data && event.data.type === "SKIP_WAITING") {
|
|
150
|
+
self.skipWaiting();
|
|
151
|
+
}
|
|
152
|
+
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
export class SessionStorage {
|
|
5
|
+
private storage: { [key: string]: string };
|
|
6
|
+
private limit: number;
|
|
7
|
+
private persist: boolean;
|
|
8
|
+
private filePath: string;
|
|
9
|
+
private directory: string = ".session-storage";
|
|
10
|
+
|
|
11
|
+
constructor({ persist = false, filePath = "./sessionData.json" }: { persist?: boolean; filePath?: string } = {}) {
|
|
12
|
+
this.storage = {};
|
|
13
|
+
this.limit = 5 * 1024 * 1024; // 5MB storage limit
|
|
14
|
+
this.persist = persist;
|
|
15
|
+
this.filePath = path.resolve(this.directory, filePath);
|
|
16
|
+
|
|
17
|
+
if (!fs.existsSync(this.directory)) {
|
|
18
|
+
fs.mkdirSync(this.directory, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Load data from file if persistence is enabled
|
|
22
|
+
if (this.persist) {
|
|
23
|
+
this.loadFromFile();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Calculate total size in bytes of stored data
|
|
28
|
+
private getStorageSize(): number {
|
|
29
|
+
return new TextEncoder().encode(JSON.stringify(this.storage)).length;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Check if storage limit is exceeded
|
|
33
|
+
private checkSizeLimit(): void {
|
|
34
|
+
const size = this.getStorageSize();
|
|
35
|
+
if (size > this.limit) {
|
|
36
|
+
throw new DOMException("Storage limit exceeded", "QuotaExceededError");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Store value under the specified key
|
|
41
|
+
setItem(key: string | null | undefined, value: string | null | undefined): void {
|
|
42
|
+
if (key === null || key === undefined) {
|
|
43
|
+
throw new TypeError("Failed to execute 'setItem' on 'Storage': 1 argument required, but only 0 present.");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (value === null) {
|
|
47
|
+
value = "null"; // Convert null to "null"
|
|
48
|
+
} else if (value === undefined) {
|
|
49
|
+
value = "undefined"; // Convert undefined to "undefined"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this.storage[key] = String(value); // Store as string
|
|
53
|
+
this.checkSizeLimit(); // Check storage limit
|
|
54
|
+
if (this.persist) {
|
|
55
|
+
this.saveToFile(); // Save to file if persistence is enabled
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Retrieve value stored under the specified key
|
|
60
|
+
getItem(key: string | null | undefined): string | null {
|
|
61
|
+
if (key === null || key === undefined) {
|
|
62
|
+
throw new TypeError("Failed to execute 'getItem' on 'Storage': 1 argument required, but only 0 present.");
|
|
63
|
+
}
|
|
64
|
+
return this.storage[key] || null; // Return null if key doesn't exist
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Remove the value under the specified key
|
|
68
|
+
removeItem(key: string | null | undefined): void {
|
|
69
|
+
if (key === null || key === undefined) {
|
|
70
|
+
throw new TypeError("Failed to execute 'removeItem' on 'Storage': 1 argument required, but only 0 present.");
|
|
71
|
+
}
|
|
72
|
+
delete this.storage[key];
|
|
73
|
+
if (this.persist) {
|
|
74
|
+
this.saveToFile(); // Save to file if persistence is enabled
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Clear all stored values
|
|
79
|
+
clear(): void {
|
|
80
|
+
this.storage = {};
|
|
81
|
+
if (this.persist) {
|
|
82
|
+
this.saveToFile(); // Save to file if persistence is enabled
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Return the number of stored items
|
|
87
|
+
get length(): number {
|
|
88
|
+
return Object.keys(this.storage).length;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Return the key at the specified index
|
|
92
|
+
key(index: number): string | null {
|
|
93
|
+
const keys = Object.keys(this.storage);
|
|
94
|
+
return keys[index] || null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Save data to a file (only if persistence is enabled)
|
|
98
|
+
private saveToFile(): void {
|
|
99
|
+
try {
|
|
100
|
+
fs.writeFileSync(this.filePath, JSON.stringify(this.storage), "utf-8");
|
|
101
|
+
} catch (error) {
|
|
102
|
+
throw new Error(`Error saving data to file: ${(error as any).message}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Load data from a file (only if persistence is enabled)
|
|
107
|
+
private loadFromFile(): void {
|
|
108
|
+
try {
|
|
109
|
+
if (fs.existsSync(this.filePath)) {
|
|
110
|
+
const data = fs.readFileSync(this.filePath, "utf-8");
|
|
111
|
+
this.storage = JSON.parse(data || "{}");
|
|
112
|
+
}
|
|
113
|
+
} catch (error) {
|
|
114
|
+
throw new Error(`Error loading data from file: ${(error as any).message}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
package/lib/node/utils/sw.ts
CHANGED
|
@@ -2,17 +2,15 @@ import fs from "fs";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
|
|
4
4
|
export function sw(file: string, options = {}) {
|
|
5
|
-
const swfiletemplate = path.resolve(__dirname, "./node.sw.
|
|
5
|
+
const swfiletemplate = path.resolve(__dirname, "./node.sw.js");
|
|
6
6
|
const swTpl = fs.readFileSync(swfiletemplate, "utf8");
|
|
7
|
-
const opt =
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
options
|
|
15
|
-
);
|
|
7
|
+
const opt = {
|
|
8
|
+
version: "v1::",
|
|
9
|
+
name: "Valyrian.js",
|
|
10
|
+
urls: ["/"],
|
|
11
|
+
debug: false,
|
|
12
|
+
...options
|
|
13
|
+
};
|
|
16
14
|
let contents = swTpl
|
|
17
15
|
.replace("v1::", "v" + opt.version + "::")
|
|
18
16
|
.replace("Valyrian.js", opt.name)
|
|
@@ -24,3 +22,29 @@ export function sw(file: string, options = {}) {
|
|
|
24
22
|
|
|
25
23
|
fs.writeFileSync(file, contents, "utf8");
|
|
26
24
|
}
|
|
25
|
+
|
|
26
|
+
/*
|
|
27
|
+
sw("sw.js", {
|
|
28
|
+
version: "1",
|
|
29
|
+
name: "Valyrian.js",
|
|
30
|
+
urls: ["/", "/index.html"],
|
|
31
|
+
debug: false
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
// On the client side
|
|
36
|
+
if ("serviceWorker" in navigator) {
|
|
37
|
+
navigator.serviceWorker.register("/service-worker.js");
|
|
38
|
+
|
|
39
|
+
navigator.serviceWorker.addEventListener("message", (event) => {
|
|
40
|
+
if (event.data && event.data.type === "NEW_VERSION") {
|
|
41
|
+
// Notify the user about the new version and ask if they want to update
|
|
42
|
+
if (confirm("Hay una nueva versión disponible. ¿Deseas actualizar?")) {
|
|
43
|
+
// Send a message to the service worker to skip the waiting
|
|
44
|
+
navigator.serviceWorker.controller.postMessage({ type: "SKIP_WAITING" });
|
|
45
|
+
window.location.reload();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
*/
|
|
@@ -53,6 +53,16 @@ export class Node implements Node {
|
|
|
53
53
|
this.parent_node = node;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
#dataset: Record<string | number, any> = {};
|
|
57
|
+
|
|
58
|
+
get dataset() {
|
|
59
|
+
return this.#dataset;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
set dataset(value) {
|
|
63
|
+
this.#dataset = value;
|
|
64
|
+
}
|
|
65
|
+
|
|
56
66
|
constructor() {}
|
|
57
67
|
|
|
58
68
|
appendChild<T extends Node>(node: T): T {
|
|
@@ -112,6 +122,11 @@ export class Node implements Node {
|
|
|
112
122
|
node.setAttribute(this.attributes[i].nodeName, this.attributes[i].nodeValue);
|
|
113
123
|
}
|
|
114
124
|
}
|
|
125
|
+
|
|
126
|
+
for (const key in this.dataset) {
|
|
127
|
+
node.dataset[key] = this.dataset[key];
|
|
128
|
+
}
|
|
129
|
+
|
|
115
130
|
if (deep) {
|
|
116
131
|
for (let i = 0, l = this.childNodes.length; i < l; i++) {
|
|
117
132
|
node.appendChild(this.childNodes[i].cloneNode(deep));
|
|
@@ -431,8 +446,11 @@ export class Document extends Element {
|
|
|
431
446
|
super();
|
|
432
447
|
this.nodeType = 9;
|
|
433
448
|
this.nodeName = "#document";
|
|
449
|
+
this.body = this.createElement("body");
|
|
434
450
|
}
|
|
435
451
|
|
|
452
|
+
body: Element;
|
|
453
|
+
|
|
436
454
|
createDocumentFragment(): DocumentFragment {
|
|
437
455
|
return new DocumentFragment();
|
|
438
456
|
}
|
|
@@ -472,7 +490,7 @@ const selfClosingTags = [
|
|
|
472
490
|
"!doctype"
|
|
473
491
|
];
|
|
474
492
|
|
|
475
|
-
export function domToHtml(dom: Element): string {
|
|
493
|
+
export function domToHtml(dom: Element | Text | DocumentFragment): string {
|
|
476
494
|
if (dom.nodeType === 3) {
|
|
477
495
|
return dom.textContent;
|
|
478
496
|
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import { updateVnode, Vnode, VnodeWithDom, current } from "valyrian.js";
|
|
3
|
+
import { deepCloneUnfreeze, deepFreeze, hasChanged } from "valyrian.js/utils";
|
|
4
|
+
|
|
5
|
+
type State = Record<string, any>;
|
|
6
|
+
|
|
7
|
+
// An action or pulse type definition
|
|
8
|
+
// eslint-disable-next-line no-unused-vars
|
|
9
|
+
export type Pulse<StateType> = (state: StateType, ...args: any[]) => void | Promise<void>;
|
|
10
|
+
|
|
11
|
+
// A collection of pulses
|
|
12
|
+
export type Pulses<StateType> = {
|
|
13
|
+
[key: string]: Pulse<StateType>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// A proxy state
|
|
17
|
+
// This is a state that is proxied to automatically subscribe to changes
|
|
18
|
+
// And is used internally to update the vnode when the state changes
|
|
19
|
+
type ProxyState<StateType> = StateType & {
|
|
20
|
+
[key: string]: any;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// The effect stack
|
|
24
|
+
const effectStack: Function[] = [];
|
|
25
|
+
|
|
26
|
+
type StorePulses<PulsesType> = {
|
|
27
|
+
[K in keyof PulsesType]: PulsesType[K] extends (state: any, ...args: infer Args) => infer R
|
|
28
|
+
? (...args: Args) => R
|
|
29
|
+
: never;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Creates the store
|
|
33
|
+
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
34
|
+
function createStore<StateType extends State, PulsesType extends Pulses<StateType>>(
|
|
35
|
+
initialState: StateType | (() => StateType) | null,
|
|
36
|
+
pulses: PulsesType,
|
|
37
|
+
immutable = false
|
|
38
|
+
): () => [ProxyState<StateType>, StorePulses<PulsesType>] {
|
|
39
|
+
const subscribers = new Set<Function>();
|
|
40
|
+
const vnodesToUpdate = new WeakSet<Vnode>();
|
|
41
|
+
|
|
42
|
+
// Initialize the localState for this store
|
|
43
|
+
const localState: StateType =
|
|
44
|
+
(typeof initialState === "function" ? initialState() : initialState) || ({} as StateType);
|
|
45
|
+
|
|
46
|
+
function isMutable() {
|
|
47
|
+
if (immutable) {
|
|
48
|
+
throw new Error("You need to call a pulse to modify the state");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// We create a proxy for the state
|
|
53
|
+
const proxyState = new Proxy(localState, {
|
|
54
|
+
get: (state, prop: string) => {
|
|
55
|
+
const currentEffect = effectStack[effectStack.length - 1];
|
|
56
|
+
if (currentEffect && !subscribers.has(currentEffect)) {
|
|
57
|
+
subscribers.add(currentEffect);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const currentVnode = current.vnode as VnodeWithDom;
|
|
61
|
+
if (currentVnode && !vnodesToUpdate.has(currentVnode)) {
|
|
62
|
+
const subscription = () => {
|
|
63
|
+
if (!currentVnode.dom) {
|
|
64
|
+
subscribers.delete(subscription);
|
|
65
|
+
vnodesToUpdate.delete(currentVnode);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
updateVnode(currentVnode);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
subscribers.add(subscription);
|
|
73
|
+
vnodesToUpdate.add(currentVnode);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return state[prop];
|
|
77
|
+
},
|
|
78
|
+
// If the user tries to set directly it will throw an error
|
|
79
|
+
set: (state, prop: string, value: any) => {
|
|
80
|
+
isMutable();
|
|
81
|
+
Reflect.set(state, prop, value);
|
|
82
|
+
return true;
|
|
83
|
+
},
|
|
84
|
+
// If the user tries to delete directly it will throw an error
|
|
85
|
+
deleteProperty: (state, prop: string) => {
|
|
86
|
+
isMutable();
|
|
87
|
+
Reflect.deleteProperty(state, prop);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
function syncState(newState: StateType) {
|
|
93
|
+
for (const key in newState) {
|
|
94
|
+
localState[key] = immutable ? deepFreeze(newState[key]) : newState[key];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for (const key in localState) {
|
|
98
|
+
if (!(key in newState)) {
|
|
99
|
+
Reflect.deleteProperty(localState, key);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function setState(newState: StateType) {
|
|
105
|
+
if (!hasChanged(localState, newState)) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
syncState(newState);
|
|
110
|
+
|
|
111
|
+
subscribers.forEach((subscriber) => subscriber());
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function getPulseMethod(key: string) {
|
|
115
|
+
return (...args: any[]) => {
|
|
116
|
+
const currentState = deepCloneUnfreeze(localState);
|
|
117
|
+
const pulse = pulses[key](currentState, ...args);
|
|
118
|
+
|
|
119
|
+
if (pulse instanceof Promise) {
|
|
120
|
+
return pulse
|
|
121
|
+
.then(() => setState(currentState))
|
|
122
|
+
.catch((error) => {
|
|
123
|
+
console.error("Error in pulse:", error);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
setState(currentState);
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const boundPulses: Record<string, Pulse<StateType>> = {};
|
|
132
|
+
for (const key in pulses) {
|
|
133
|
+
if (typeof pulses[key] !== "function") {
|
|
134
|
+
throw new Error(`Pulse '${key}' must be a function`);
|
|
135
|
+
}
|
|
136
|
+
boundPulses[key] = getPulseMethod(key);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const pulsesProxy = new Proxy(boundPulses, {
|
|
140
|
+
get: (pulses, prop: string) => {
|
|
141
|
+
if (!(prop in pulses)) {
|
|
142
|
+
throw new Error(`Pulse '${prop}' does not exist`);
|
|
143
|
+
}
|
|
144
|
+
return pulses[prop];
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
function usePulseStore(): [ProxyState<StateType>, StorePulses<PulsesType>] {
|
|
149
|
+
return [proxyState, pulsesProxy as StorePulses<PulsesType>];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
syncState(localState);
|
|
153
|
+
|
|
154
|
+
return usePulseStore;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Creates a pulse store with an immutable state by default
|
|
158
|
+
export function createPulseStore<StateType extends State, PulsesType extends Pulses<StateType>>(
|
|
159
|
+
initialState: StateType,
|
|
160
|
+
pulses: PulsesType
|
|
161
|
+
): () => [ProxyState<StateType>, StorePulses<PulsesType>] {
|
|
162
|
+
return createStore(initialState, pulses, true);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Creates a mutable store, useful for performance, but not recommended
|
|
166
|
+
export function createMutableStore<StateType extends State, PulsesType extends Pulses<StateType>>(
|
|
167
|
+
initialState: StateType,
|
|
168
|
+
pulses: PulsesType
|
|
169
|
+
): () => [ProxyState<StateType>, StorePulses<PulsesType>] {
|
|
170
|
+
console.warn(
|
|
171
|
+
"Warning: You are working with a mutable state. This can lead to unpredictable behavior. All state changes made outside of a pulse will not trigger a re-render."
|
|
172
|
+
);
|
|
173
|
+
return createStore(initialState, pulses, false);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Creates an effect
|
|
177
|
+
export function createEffect(effect: Function) {
|
|
178
|
+
const runEffect = () => {
|
|
179
|
+
try {
|
|
180
|
+
effectStack.push(runEffect);
|
|
181
|
+
effect();
|
|
182
|
+
} finally {
|
|
183
|
+
effectStack.pop();
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
runEffect();
|
|
188
|
+
}
|