@fluixi/core 1.0.0-alpha.92 → 1.0.0-alpha.93
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/cdn/core.global.js +2 -2
- package/dist/src/cdn/{chunk-MDJLC3PE.mjs → chunk-7XM7Q5VR.mjs} +271 -229
- package/dist/src/cdn/core.cjs +360 -253
- package/dist/src/cdn/core.mjs +92 -20
- package/dist/src/cdn/{router-JYFZP3EZ.mjs → router-VOY43YNM.mjs} +7 -3
- package/dist/src/index.cjs +547 -232
- package/dist/src/index.mjs +547 -223
- package/dist/src/lib/client/chunk-QT3RFKH6.mjs +53 -0
- package/dist/src/lib/client/index.cjs +310 -176
- package/dist/src/lib/client/index.d.ts.map +1 -1
- package/dist/src/lib/client/index.js +33 -18
- package/dist/src/lib/client/index.mjs +86 -15
- package/dist/src/lib/client/{router-PHLUYVQU.mjs → router-ADTS55OP.mjs} +244 -99
- package/dist/src/lib/context/context.cjs +35 -72
- package/dist/src/lib/context/context.d.ts +45 -291
- package/dist/src/lib/context/context.d.ts.map +1 -1
- package/dist/src/lib/context/context.js +65 -397
- package/dist/src/lib/context/context.mjs +38 -73
- package/dist/src/lib/context/index.cjs +35 -72
- package/dist/src/lib/context/index.d.ts +2 -2
- package/dist/src/lib/context/index.d.ts.map +1 -1
- package/dist/src/lib/context/index.js +1 -5
- package/dist/src/lib/context/index.mjs +38 -73
- package/dist/src/lib/core.cjs +546 -231
- package/dist/src/lib/core.d.ts +3 -1
- package/dist/src/lib/core.d.ts.map +1 -1
- package/dist/src/lib/core.js +5 -3
- package/dist/src/lib/core.mjs +546 -222
- package/dist/src/lib/env/index.cjs +1 -17
- package/dist/src/lib/env/index.d.ts +2 -14
- package/dist/src/lib/env/index.d.ts.map +1 -1
- package/dist/src/lib/env/index.js +2 -26
- package/dist/src/lib/env/index.mjs +1 -17
- package/dist/src/lib/i18n/index.cjs +17 -2
- package/dist/src/lib/i18n/index.d.ts +10 -0
- package/dist/src/lib/i18n/index.d.ts.map +1 -1
- package/dist/src/lib/i18n/index.js +17 -1
- package/dist/src/lib/i18n/index.mjs +17 -2
- package/dist/src/lib/index.cjs +64 -99
- package/dist/src/lib/index.mjs +43 -76
- package/dist/src/lib/isomorphic.cjs +9 -4
- package/dist/src/lib/isomorphic.d.ts +9 -2
- package/dist/src/lib/isomorphic.d.ts.map +1 -1
- package/dist/src/lib/isomorphic.js +11 -4
- package/dist/src/lib/isomorphic.mjs +9 -4
- package/dist/src/lib/jsx-runtime/index.cjs +1 -1
- package/dist/src/lib/jsx-runtime/index.mjs +1 -1
- package/dist/src/lib/jsx-runtime/jsx-dev-runtime.cjs +1 -1
- package/dist/src/lib/jsx-runtime/jsx-dev-runtime.mjs +1 -1
- package/dist/src/lib/plugins/fluixi-routes-plugin.cjs +2 -2
- package/dist/src/lib/plugins/fluixi-routes-plugin.mjs +2 -2
- package/dist/src/lib/plugins/index.cjs +3 -3
- package/dist/src/lib/plugins/index.mjs +3 -3
- package/dist/src/lib/plugins/route-codegen.cjs +2 -2
- package/dist/src/lib/plugins/route-codegen.js +1 -1
- package/dist/src/lib/plugins/route-codegen.mjs +2 -2
- package/dist/src/lib/render/index.cjs +1 -1
- package/dist/src/lib/render/index.mjs +1 -1
- package/dist/src/lib/render/versions.cjs +1 -1
- package/dist/src/lib/render/versions.mjs +1 -1
- package/dist/src/lib/router/data.cjs +113 -115
- package/dist/src/lib/router/data.d.ts +63 -10
- package/dist/src/lib/router/data.d.ts.map +1 -1
- package/dist/src/lib/router/data.js +158 -68
- package/dist/src/lib/router/data.mjs +112 -112
- package/dist/src/lib/router/index.cjs +260 -163
- package/dist/src/lib/router/index.d.ts +22 -2
- package/dist/src/lib/router/index.d.ts.map +1 -1
- package/dist/src/lib/router/index.js +165 -22
- package/dist/src/lib/router/index.mjs +244 -143
- package/dist/src/lib/server/index.cjs +1 -1
- package/dist/src/lib/server/index.mjs +1 -1
- package/dist/src/lib/storage/cache.cjs +51 -0
- package/dist/src/lib/storage/cache.d.ts +18 -0
- package/dist/src/lib/storage/cache.d.ts.map +1 -0
- package/dist/src/lib/storage/cache.js +42 -0
- package/dist/src/lib/storage/cache.mjs +30 -0
- package/dist/src/lib/storage/index.cjs +246 -0
- package/dist/src/lib/storage/index.d.ts +83 -0
- package/dist/src/lib/storage/index.d.ts.map +1 -0
- package/dist/src/lib/storage/index.js +179 -0
- package/dist/src/lib/storage/index.mjs +231 -0
- package/dist/src/lib/storage/persisted.cjs +91 -0
- package/dist/src/lib/storage/persisted.d.ts +53 -0
- package/dist/src/lib/storage/persisted.d.ts.map +1 -0
- package/dist/src/lib/storage/persisted.js +75 -0
- package/dist/src/lib/storage/persisted.mjs +70 -0
- package/dist/src/lib/theme/color-scheme.cjs +281 -0
- package/dist/src/lib/theme/color-scheme.d.ts +61 -0
- package/dist/src/lib/theme/color-scheme.d.ts.map +1 -0
- package/dist/src/lib/theme/color-scheme.js +120 -0
- package/dist/src/lib/theme/color-scheme.mjs +265 -0
- package/dist/src/lib/theme/index.cjs +194 -37
- package/dist/src/lib/theme/index.d.ts +23 -11
- package/dist/src/lib/theme/index.d.ts.map +1 -1
- package/dist/src/lib/theme/index.js +73 -32
- package/dist/src/lib/theme/index.mjs +197 -35
- package/dist/src/lib/theme/provider.cjs +346 -0
- package/dist/src/lib/theme/provider.d.ts +34 -0
- package/dist/src/lib/theme/provider.d.ts.map +1 -0
- package/dist/src/lib/theme/provider.js +60 -0
- package/dist/src/lib/theme/provider.mjs +332 -0
- package/dist/src/routes.cjs +1 -1
- package/dist/src/routes.mjs +1 -1
- package/dist/src/utils.cjs +1 -1
- package/dist/src/utils.mjs +1 -1
- package/dist/src/version.generated.cjs +1 -1
- package/dist/src/version.generated.d.ts +1 -1
- package/dist/src/version.generated.js +1 -1
- package/dist/src/version.generated.mjs +1 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +18 -11
- package/dist/src/lib/client/chunk-VB567ABU.mjs +0 -133
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/*! @fluixi/core v1.0.0-alpha.93 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
|
|
2
|
+
|
|
3
|
+
// src/lib/storage/index.ts
|
|
4
|
+
import { appendResponseHeader, getRequestContext, getServerData } from "@fluixi/dom";
|
|
5
|
+
import { createContext, useContext } from "@fluixi/reactive/signal";
|
|
6
|
+
import {
|
|
7
|
+
createAsyncStorage,
|
|
8
|
+
createSyncStorage,
|
|
9
|
+
memoryStorage,
|
|
10
|
+
StorageUnavailableError
|
|
11
|
+
} from "@fluixi/storage";
|
|
12
|
+
import { documentCookieAdapter, localStorageAdapter, sessionStorageAdapter } from "@fluixi/storage/browser";
|
|
13
|
+
import { cookieStorage, requestStorage, toStorageRequest } from "@fluixi/storage/server";
|
|
14
|
+
|
|
15
|
+
// src/lib/storage/persisted.ts
|
|
16
|
+
import { getOwner, onCleanup, signal, untrack } from "@fluixi/reactive/signal";
|
|
17
|
+
var isAsync = (storage) => "encryption" in storage;
|
|
18
|
+
function persisted(storage, key, options = {}) {
|
|
19
|
+
const fallback = options.fallback ?? null;
|
|
20
|
+
const name = options.name ?? `storage:${storage.keyPrefix}${key}`;
|
|
21
|
+
const state = signal(isAsync(storage) ? fallback : storage.get(key) ?? fallback, { name });
|
|
22
|
+
const put = (value) => state.set(() => value);
|
|
23
|
+
const resolve = (next) => typeof next === "function" ? next(untrack(state)) : next;
|
|
24
|
+
const writeOptions = (extra) => ({
|
|
25
|
+
...options.ttl !== void 0 ? { ttl: options.ttl } : {},
|
|
26
|
+
...options.encrypt ? { encrypt: true } : {},
|
|
27
|
+
...extra
|
|
28
|
+
});
|
|
29
|
+
if (!isAsync(storage)) {
|
|
30
|
+
const off2 = storage.subscribe(key, (event) => {
|
|
31
|
+
if (event.type !== "set") return put(fallback);
|
|
32
|
+
if (event.value !== void 0) put(event.value);
|
|
33
|
+
});
|
|
34
|
+
if (getOwner()) onCleanup(off2);
|
|
35
|
+
return Object.assign(() => state(), {
|
|
36
|
+
set(next, extra) {
|
|
37
|
+
const value = resolve(next);
|
|
38
|
+
storage.set(key, value, writeOptions(extra));
|
|
39
|
+
put(value);
|
|
40
|
+
},
|
|
41
|
+
remove() {
|
|
42
|
+
storage.remove(key);
|
|
43
|
+
put(fallback);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
let written = false;
|
|
48
|
+
const reread = () => storage.get(key).then(
|
|
49
|
+
(value) => put(value ?? fallback),
|
|
50
|
+
() => void 0
|
|
51
|
+
);
|
|
52
|
+
const off = storage.subscribe(key, (event) => {
|
|
53
|
+
if (event.type !== "set") return put(fallback);
|
|
54
|
+
if (event.value !== void 0) put(event.value);
|
|
55
|
+
else void reread();
|
|
56
|
+
});
|
|
57
|
+
if (getOwner()) onCleanup(off);
|
|
58
|
+
const ready = storage.get(key).then((value) => {
|
|
59
|
+
if (!written) put(value ?? fallback);
|
|
60
|
+
});
|
|
61
|
+
return Object.assign(() => state(), {
|
|
62
|
+
ready,
|
|
63
|
+
async set(next, extra) {
|
|
64
|
+
const previous = untrack(state);
|
|
65
|
+
const value = resolve(next);
|
|
66
|
+
written = true;
|
|
67
|
+
put(value);
|
|
68
|
+
try {
|
|
69
|
+
await storage.set(key, value, writeOptions(extra));
|
|
70
|
+
} catch (error) {
|
|
71
|
+
put(previous);
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
async remove() {
|
|
76
|
+
written = true;
|
|
77
|
+
await storage.remove(key);
|
|
78
|
+
put(fallback);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/lib/storage/cache.ts
|
|
84
|
+
function storageCache(storage, options = {}) {
|
|
85
|
+
const inflight = /* @__PURE__ */ new Map();
|
|
86
|
+
return {
|
|
87
|
+
get(key) {
|
|
88
|
+
try {
|
|
89
|
+
const entry = storage.getEntry(key);
|
|
90
|
+
return entry ? { value: entry.value, timestamp: entry.createdAt } : void 0;
|
|
91
|
+
} catch {
|
|
92
|
+
return void 0;
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
set(key, entry) {
|
|
96
|
+
try {
|
|
97
|
+
storage.set(key, entry.value, options.ttl !== void 0 ? { ttl: options.ttl } : void 0);
|
|
98
|
+
} catch {
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
delete: (key) => storage.remove(key),
|
|
102
|
+
keys: () => storage.keys(),
|
|
103
|
+
clear() {
|
|
104
|
+
storage.clear();
|
|
105
|
+
inflight.clear();
|
|
106
|
+
},
|
|
107
|
+
inflight
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/lib/storage/index.ts
|
|
112
|
+
var StorageContext = createContext(void 0, "storage");
|
|
113
|
+
var AsyncStorageContext = createContext(void 0, "asyncStorage");
|
|
114
|
+
var isAsyncStorage = (storage) => "encryption" in storage;
|
|
115
|
+
function StorageProvider(props) {
|
|
116
|
+
const context = isAsyncStorage(props.storage) ? AsyncStorageContext : StorageContext;
|
|
117
|
+
return context.Provider({
|
|
118
|
+
value: props.storage,
|
|
119
|
+
get children() {
|
|
120
|
+
return props.children;
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
var isBrowser = () => typeof window !== "undefined" && typeof document !== "undefined";
|
|
125
|
+
var perRequest = /* @__PURE__ */ new WeakMap();
|
|
126
|
+
var perPage = /* @__PURE__ */ new Map();
|
|
127
|
+
function cached(scope, id, make) {
|
|
128
|
+
let adapter = scope.get(id);
|
|
129
|
+
if (!adapter) scope.set(id, adapter = make());
|
|
130
|
+
return adapter;
|
|
131
|
+
}
|
|
132
|
+
function requestRequired(scope) {
|
|
133
|
+
throw new StorageUnavailableError(
|
|
134
|
+
`useStorage({ scope: '${scope}' }) on the server needs a request: call it during a render, a loader or a server function, or provide a storage with <StorageProvider>.`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
function adapterFor(scope, cookie = {}) {
|
|
138
|
+
const ctx = getRequestContext();
|
|
139
|
+
const options = { httpOnly: false, ...cookie };
|
|
140
|
+
switch (scope) {
|
|
141
|
+
case "local":
|
|
142
|
+
return localStorageAdapter();
|
|
143
|
+
case "session":
|
|
144
|
+
return sessionStorageAdapter();
|
|
145
|
+
case "request":
|
|
146
|
+
if (isBrowser()) return cached(perPage, "request", memoryStorage);
|
|
147
|
+
if (!ctx) requestRequired(scope);
|
|
148
|
+
return cached(perRequest.get(ctx) ?? perRequest.set(ctx, /* @__PURE__ */ new Map()).get(ctx), "request", requestStorage);
|
|
149
|
+
case "cookie": {
|
|
150
|
+
const id = `cookie:${JSON.stringify(options)}`;
|
|
151
|
+
if (isBrowser()) {
|
|
152
|
+
const { httpOnly: _ignored, ...browser } = options;
|
|
153
|
+
return cached(perPage, id, () => documentCookieAdapter(browser));
|
|
154
|
+
}
|
|
155
|
+
if (!ctx) requestRequired(scope);
|
|
156
|
+
const raw = ctx.event.request;
|
|
157
|
+
const target = raw && typeof raw === "object" ? raw : void 0;
|
|
158
|
+
return cached(
|
|
159
|
+
perRequest.get(ctx) ?? perRequest.set(ctx, /* @__PURE__ */ new Map()).get(ctx),
|
|
160
|
+
id,
|
|
161
|
+
() => cookieStorage({
|
|
162
|
+
...options,
|
|
163
|
+
request: raw && typeof raw === "object" && ("headers" in raw || "getHeader" in raw) ? toStorageRequest(raw) : void 0,
|
|
164
|
+
response: {
|
|
165
|
+
appendHeader(name, value) {
|
|
166
|
+
if (!appendResponseHeader(name, value, target)) {
|
|
167
|
+
throw new StorageUnavailableError("The response headers were already sent, so this cookie cannot reach the browser. Write it before the page head goes out: in middleware, a loader, a server function or the render before its first chunk.");
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function useStorage(options = {}) {
|
|
177
|
+
const { scope = "auto", cookie, ...storageOptions } = options;
|
|
178
|
+
if (scope === "auto") {
|
|
179
|
+
const provided = useContext(StorageContext);
|
|
180
|
+
if (provided) return options.namespace ? provided.namespace(options.namespace) : provided;
|
|
181
|
+
}
|
|
182
|
+
return createSyncStorage({ ...storageOptions, adapter: resolveAdapter(scope, cookie) });
|
|
183
|
+
}
|
|
184
|
+
function useAsyncStorage(options = {}) {
|
|
185
|
+
const { scope = "auto", cookie, ...storageOptions } = options;
|
|
186
|
+
if (scope === "auto") {
|
|
187
|
+
const provided = useContext(AsyncStorageContext);
|
|
188
|
+
if (provided) return options.namespace ? provided.namespace(options.namespace) : provided;
|
|
189
|
+
}
|
|
190
|
+
return createAsyncStorage({ ...storageOptions, adapter: resolveAdapter(scope, cookie) });
|
|
191
|
+
}
|
|
192
|
+
function resolveAdapter(scope, cookie) {
|
|
193
|
+
if (scope !== "auto") return adapterFor(scope, cookie);
|
|
194
|
+
if (isBrowser()) return adapterFor("local");
|
|
195
|
+
if (!getRequestContext()) requestRequired(scope);
|
|
196
|
+
return adapterFor("request");
|
|
197
|
+
}
|
|
198
|
+
var SLOT = "__storage:";
|
|
199
|
+
var seeded = /* @__PURE__ */ new Set();
|
|
200
|
+
function hydrateStorage(storage, keys) {
|
|
201
|
+
const slot = SLOT + storage.keyPrefix;
|
|
202
|
+
if (!isBrowser()) {
|
|
203
|
+
const ctx = getRequestContext();
|
|
204
|
+
if (!ctx) return;
|
|
205
|
+
const entries2 = ctx.data.get(slot) ?? {};
|
|
206
|
+
for (const key of keys) {
|
|
207
|
+
const entry = storage.getEntry(key);
|
|
208
|
+
if (entry) entries2[key] = entry;
|
|
209
|
+
}
|
|
210
|
+
ctx.data.set(slot, entries2);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const entries = getServerData()?.[slot];
|
|
214
|
+
if (!entries) return;
|
|
215
|
+
const fresh = {};
|
|
216
|
+
for (const key of keys) {
|
|
217
|
+
const id = slot + key;
|
|
218
|
+
if (seeded.has(id) || !entries[key]) continue;
|
|
219
|
+
seeded.add(id);
|
|
220
|
+
fresh[key] = entries[key];
|
|
221
|
+
}
|
|
222
|
+
storage.restore(fresh, { overwrite: true });
|
|
223
|
+
}
|
|
224
|
+
export {
|
|
225
|
+
StorageProvider,
|
|
226
|
+
hydrateStorage,
|
|
227
|
+
persisted,
|
|
228
|
+
storageCache,
|
|
229
|
+
useAsyncStorage,
|
|
230
|
+
useStorage
|
|
231
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/lib/storage/persisted.ts
|
|
21
|
+
var persisted_exports = {};
|
|
22
|
+
__export(persisted_exports, {
|
|
23
|
+
persisted: () => persisted
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(persisted_exports);
|
|
26
|
+
var import_signal = require("@fluixi/reactive/signal");
|
|
27
|
+
var isAsync = (storage) => "encryption" in storage;
|
|
28
|
+
function persisted(storage, key, options = {}) {
|
|
29
|
+
const fallback = options.fallback ?? null;
|
|
30
|
+
const name = options.name ?? `storage:${storage.keyPrefix}${key}`;
|
|
31
|
+
const state = (0, import_signal.signal)(isAsync(storage) ? fallback : storage.get(key) ?? fallback, { name });
|
|
32
|
+
const put = (value) => state.set(() => value);
|
|
33
|
+
const resolve = (next) => typeof next === "function" ? next((0, import_signal.untrack)(state)) : next;
|
|
34
|
+
const writeOptions = (extra) => ({
|
|
35
|
+
...options.ttl !== void 0 ? { ttl: options.ttl } : {},
|
|
36
|
+
...options.encrypt ? { encrypt: true } : {},
|
|
37
|
+
...extra
|
|
38
|
+
});
|
|
39
|
+
if (!isAsync(storage)) {
|
|
40
|
+
const off2 = storage.subscribe(key, (event) => {
|
|
41
|
+
if (event.type !== "set") return put(fallback);
|
|
42
|
+
if (event.value !== void 0) put(event.value);
|
|
43
|
+
});
|
|
44
|
+
if ((0, import_signal.getOwner)()) (0, import_signal.onCleanup)(off2);
|
|
45
|
+
return Object.assign(() => state(), {
|
|
46
|
+
set(next, extra) {
|
|
47
|
+
const value = resolve(next);
|
|
48
|
+
storage.set(key, value, writeOptions(extra));
|
|
49
|
+
put(value);
|
|
50
|
+
},
|
|
51
|
+
remove() {
|
|
52
|
+
storage.remove(key);
|
|
53
|
+
put(fallback);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
let written = false;
|
|
58
|
+
const reread = () => storage.get(key).then(
|
|
59
|
+
(value) => put(value ?? fallback),
|
|
60
|
+
() => void 0
|
|
61
|
+
);
|
|
62
|
+
const off = storage.subscribe(key, (event) => {
|
|
63
|
+
if (event.type !== "set") return put(fallback);
|
|
64
|
+
if (event.value !== void 0) put(event.value);
|
|
65
|
+
else void reread();
|
|
66
|
+
});
|
|
67
|
+
if ((0, import_signal.getOwner)()) (0, import_signal.onCleanup)(off);
|
|
68
|
+
const ready = storage.get(key).then((value) => {
|
|
69
|
+
if (!written) put(value ?? fallback);
|
|
70
|
+
});
|
|
71
|
+
return Object.assign(() => state(), {
|
|
72
|
+
ready,
|
|
73
|
+
async set(next, extra) {
|
|
74
|
+
const previous = (0, import_signal.untrack)(state);
|
|
75
|
+
const value = resolve(next);
|
|
76
|
+
written = true;
|
|
77
|
+
put(value);
|
|
78
|
+
try {
|
|
79
|
+
await storage.set(key, value, writeOptions(extra));
|
|
80
|
+
} catch (error) {
|
|
81
|
+
put(previous);
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
async remove() {
|
|
86
|
+
written = true;
|
|
87
|
+
await storage.remove(key);
|
|
88
|
+
put(fallback);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type Accessor } from '@fluixi/reactive/signal';
|
|
2
|
+
import type { AsyncTypedStorage, SetOptions, StorageKey, StorageSchema, TypedStorage } from '@fluixi/storage';
|
|
3
|
+
type Next<T> = T | ((previous: T) => T);
|
|
4
|
+
export interface PersistedOptions<T> {
|
|
5
|
+
/** The value while the key is absent, expired or removed. Without it, null. */
|
|
6
|
+
fallback?: T;
|
|
7
|
+
/** Every write expires this long after it is made. */
|
|
8
|
+
ttl?: number;
|
|
9
|
+
/** Async storage only: store each write encrypted. */
|
|
10
|
+
encrypt?: boolean;
|
|
11
|
+
/** For devtools. Defaults to `storage:<prefix><key>`. */
|
|
12
|
+
name?: string;
|
|
13
|
+
}
|
|
14
|
+
/** A signal whose value lives in a storage key. Writes go to the storage first. */
|
|
15
|
+
export type Persisted<T> = Accessor<T> & {
|
|
16
|
+
/** Writes the storage, then the signal. Throws what the storage throws, leaving both as they were. */
|
|
17
|
+
set(next: Next<T>, options?: SetOptions): void;
|
|
18
|
+
/** Removes the key; the signal falls back. */
|
|
19
|
+
remove(): void;
|
|
20
|
+
};
|
|
21
|
+
/** The awaited form, over an async (or encrypted) storage. */
|
|
22
|
+
export type AsyncPersisted<T> = Accessor<T> & {
|
|
23
|
+
/** Sets the signal at once and writes the storage; on failure the signal goes back and this rejects. */
|
|
24
|
+
set(next: Next<T>, options?: SetOptions & {
|
|
25
|
+
encrypt?: boolean;
|
|
26
|
+
}): Promise<void>;
|
|
27
|
+
remove(): Promise<void>;
|
|
28
|
+
/** Settles once the stored value has been read. Until then the signal holds the fallback. */
|
|
29
|
+
readonly ready: Promise<void>;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* A signal bound to one key of a storage: it starts with the stored value, writes through on
|
|
33
|
+
* `set`, and follows changes made by any other storage over the same adapter, or by another
|
|
34
|
+
* tab where the adapter reports them. Created under an owner, it stops listening with it.
|
|
35
|
+
*
|
|
36
|
+
* Over a cookie storage it reads the same value in the server render and in the browser, so a
|
|
37
|
+
* stored preference renders without a flash. An async storage cannot answer during the render;
|
|
38
|
+
* the signal holds the fallback until `ready`.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* const theme = persisted(useStorage({ scope: 'cookie', namespace: 'ui' }), 'theme', { fallback: 'light' });
|
|
42
|
+
* theme.set((t) => (t === 'light' ? 'dark' : 'light'));
|
|
43
|
+
*/
|
|
44
|
+
export declare function persisted<S extends StorageSchema, K extends StorageKey<S>>(storage: TypedStorage<S>, key: K, options: PersistedOptions<S[K]> & {
|
|
45
|
+
fallback: S[K];
|
|
46
|
+
}): Persisted<S[K]>;
|
|
47
|
+
export declare function persisted<S extends StorageSchema, K extends StorageKey<S>>(storage: TypedStorage<S>, key: K, options?: PersistedOptions<S[K]>): Persisted<S[K] | null>;
|
|
48
|
+
export declare function persisted<S extends StorageSchema, K extends StorageKey<S>>(storage: AsyncTypedStorage<S>, key: K, options: PersistedOptions<S[K]> & {
|
|
49
|
+
fallback: S[K];
|
|
50
|
+
}): AsyncPersisted<S[K]>;
|
|
51
|
+
export declare function persisted<S extends StorageSchema, K extends StorageKey<S>>(storage: AsyncTypedStorage<S>, key: K, options?: PersistedOptions<S[K]>): AsyncPersisted<S[K] | null>;
|
|
52
|
+
export {};
|
|
53
|
+
//# sourceMappingURL=persisted.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persisted.d.ts","sourceRoot":"","sources":["../../../../src/lib/storage/persisted.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwC,KAAK,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAC9F,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE9G,KAAK,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;AAExC,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,CAAC,CAAC;IACb,sDAAsD;IACtD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,yDAAyD;IACzD,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,mFAAmF;AACnF,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,GAAG;IACvC,sGAAsG;IACtG,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/C,8CAA8C;IAC9C,MAAM,IAAI,IAAI,CAAC;CAChB,CAAC;AAEF,8DAA8D;AAC9D,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,GAAG;IAC5C,wGAAwG;IACxG,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChF,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,6FAA6F;IAC7F,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B,CAAC;AAIF;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,aAAa,EAAE,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,EACxE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EACxB,GAAG,EAAE,CAAC,EACN,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG;IAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;CAAE,GACnD,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACnB,wBAAgB,SAAS,CAAC,CAAC,SAAS,aAAa,EAAE,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,EACxE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EACxB,GAAG,EAAE,CAAC,EACN,OAAO,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAC/B,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;AAC1B,wBAAgB,SAAS,CAAC,CAAC,SAAS,aAAa,EAAE,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,EACxE,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAC7B,GAAG,EAAE,CAAC,EACN,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG;IAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;CAAE,GACnD,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACxB,wBAAgB,SAAS,CAAC,CAAC,SAAS,aAAa,EAAE,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,EACxE,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAC7B,GAAG,EAAE,CAAC,EACN,OAAO,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAC/B,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { getOwner, onCleanup, signal, untrack } from '@fluixi/reactive/signal';
|
|
2
|
+
const isAsync = (storage) => 'encryption' in storage;
|
|
3
|
+
export function persisted(storage, key, options = {}) {
|
|
4
|
+
const fallback = options.fallback ?? null;
|
|
5
|
+
const name = options.name ?? `storage:${storage.keyPrefix}${key}`;
|
|
6
|
+
// `set(() => v)`: a stored function value would otherwise be called as an updater.
|
|
7
|
+
const state = signal(isAsync(storage) ? fallback : (storage.get(key) ?? fallback), { name });
|
|
8
|
+
const put = (value) => state.set(() => value);
|
|
9
|
+
const resolve = (next) => (typeof next === 'function' ? next(untrack(state)) : next);
|
|
10
|
+
const writeOptions = (extra) => ({
|
|
11
|
+
...(options.ttl !== undefined ? { ttl: options.ttl } : {}),
|
|
12
|
+
...(options.encrypt ? { encrypt: true } : {}),
|
|
13
|
+
...extra,
|
|
14
|
+
});
|
|
15
|
+
if (!isAsync(storage)) {
|
|
16
|
+
const off = storage.subscribe(key, (event) => {
|
|
17
|
+
if (event.type !== 'set')
|
|
18
|
+
return put(fallback);
|
|
19
|
+
// An external write to an encrypted entry carries no value; reading it here would throw.
|
|
20
|
+
if (event.value !== undefined)
|
|
21
|
+
put(event.value);
|
|
22
|
+
});
|
|
23
|
+
if (getOwner())
|
|
24
|
+
onCleanup(off);
|
|
25
|
+
return Object.assign(() => state(), {
|
|
26
|
+
set(next, extra) {
|
|
27
|
+
const value = resolve(next);
|
|
28
|
+
storage.set(key, value, writeOptions(extra));
|
|
29
|
+
put(value);
|
|
30
|
+
},
|
|
31
|
+
remove() {
|
|
32
|
+
storage.remove(key);
|
|
33
|
+
put(fallback);
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
let written = false;
|
|
38
|
+
const reread = () => storage.get(key).then((value) => put(value ?? fallback), () => undefined);
|
|
39
|
+
const off = storage.subscribe(key, (event) => {
|
|
40
|
+
if (event.type !== 'set')
|
|
41
|
+
return put(fallback);
|
|
42
|
+
if (event.value !== undefined)
|
|
43
|
+
put(event.value);
|
|
44
|
+
else
|
|
45
|
+
void reread();
|
|
46
|
+
});
|
|
47
|
+
if (getOwner())
|
|
48
|
+
onCleanup(off);
|
|
49
|
+
const ready = storage.get(key).then((value) => {
|
|
50
|
+
// A write made before the first read finished is newer than what it found.
|
|
51
|
+
if (!written)
|
|
52
|
+
put(value ?? fallback);
|
|
53
|
+
});
|
|
54
|
+
return Object.assign(() => state(), {
|
|
55
|
+
ready,
|
|
56
|
+
async set(next, extra) {
|
|
57
|
+
const previous = untrack(state);
|
|
58
|
+
const value = resolve(next);
|
|
59
|
+
written = true;
|
|
60
|
+
put(value);
|
|
61
|
+
try {
|
|
62
|
+
await storage.set(key, value, writeOptions(extra));
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
put(previous);
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
async remove() {
|
|
70
|
+
written = true;
|
|
71
|
+
await storage.remove(key);
|
|
72
|
+
put(fallback);
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// src/lib/storage/persisted.ts
|
|
2
|
+
import { getOwner, onCleanup, signal, untrack } from "@fluixi/reactive/signal";
|
|
3
|
+
var isAsync = (storage) => "encryption" in storage;
|
|
4
|
+
function persisted(storage, key, options = {}) {
|
|
5
|
+
const fallback = options.fallback ?? null;
|
|
6
|
+
const name = options.name ?? `storage:${storage.keyPrefix}${key}`;
|
|
7
|
+
const state = signal(isAsync(storage) ? fallback : storage.get(key) ?? fallback, { name });
|
|
8
|
+
const put = (value) => state.set(() => value);
|
|
9
|
+
const resolve = (next) => typeof next === "function" ? next(untrack(state)) : next;
|
|
10
|
+
const writeOptions = (extra) => ({
|
|
11
|
+
...options.ttl !== void 0 ? { ttl: options.ttl } : {},
|
|
12
|
+
...options.encrypt ? { encrypt: true } : {},
|
|
13
|
+
...extra
|
|
14
|
+
});
|
|
15
|
+
if (!isAsync(storage)) {
|
|
16
|
+
const off2 = storage.subscribe(key, (event) => {
|
|
17
|
+
if (event.type !== "set") return put(fallback);
|
|
18
|
+
if (event.value !== void 0) put(event.value);
|
|
19
|
+
});
|
|
20
|
+
if (getOwner()) onCleanup(off2);
|
|
21
|
+
return Object.assign(() => state(), {
|
|
22
|
+
set(next, extra) {
|
|
23
|
+
const value = resolve(next);
|
|
24
|
+
storage.set(key, value, writeOptions(extra));
|
|
25
|
+
put(value);
|
|
26
|
+
},
|
|
27
|
+
remove() {
|
|
28
|
+
storage.remove(key);
|
|
29
|
+
put(fallback);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
let written = false;
|
|
34
|
+
const reread = () => storage.get(key).then(
|
|
35
|
+
(value) => put(value ?? fallback),
|
|
36
|
+
() => void 0
|
|
37
|
+
);
|
|
38
|
+
const off = storage.subscribe(key, (event) => {
|
|
39
|
+
if (event.type !== "set") return put(fallback);
|
|
40
|
+
if (event.value !== void 0) put(event.value);
|
|
41
|
+
else void reread();
|
|
42
|
+
});
|
|
43
|
+
if (getOwner()) onCleanup(off);
|
|
44
|
+
const ready = storage.get(key).then((value) => {
|
|
45
|
+
if (!written) put(value ?? fallback);
|
|
46
|
+
});
|
|
47
|
+
return Object.assign(() => state(), {
|
|
48
|
+
ready,
|
|
49
|
+
async set(next, extra) {
|
|
50
|
+
const previous = untrack(state);
|
|
51
|
+
const value = resolve(next);
|
|
52
|
+
written = true;
|
|
53
|
+
put(value);
|
|
54
|
+
try {
|
|
55
|
+
await storage.set(key, value, writeOptions(extra));
|
|
56
|
+
} catch (error) {
|
|
57
|
+
put(previous);
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
async remove() {
|
|
62
|
+
written = true;
|
|
63
|
+
await storage.remove(key);
|
|
64
|
+
put(fallback);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
export {
|
|
69
|
+
persisted
|
|
70
|
+
};
|