@bquery/bquery 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +586 -546
- package/dist/component/component.d.ts +13 -5
- package/dist/component/component.d.ts.map +1 -1
- package/dist/component/html.d.ts +40 -3
- package/dist/component/html.d.ts.map +1 -1
- package/dist/component/index.d.ts +2 -2
- package/dist/component/index.d.ts.map +1 -1
- package/dist/component/library.d.ts.map +1 -1
- package/dist/component/types.d.ts +131 -16
- package/dist/component/types.d.ts.map +1 -1
- package/dist/component-BEQgt5hl.js +600 -0
- package/dist/component-BEQgt5hl.js.map +1 -0
- package/dist/component.es.mjs +7 -6
- package/dist/config-DRmZZno3.js.map +1 -1
- package/dist/core-BGQJVw0-.js +35 -0
- package/dist/core-BGQJVw0-.js.map +1 -0
- package/dist/{core-CK2Mfpf4.js → core-CCEabVHl.js} +2 -2
- package/dist/{core-CK2Mfpf4.js.map → core-CCEabVHl.js.map} +1 -1
- package/dist/core.es.mjs +1 -1
- package/dist/effect-AFRW_Plg.js +84 -0
- package/dist/effect-AFRW_Plg.js.map +1 -0
- package/dist/full.d.ts +4 -4
- package/dist/full.d.ts.map +1 -1
- package/dist/full.es.mjs +98 -94
- package/dist/full.iife.js +14 -14
- package/dist/full.iife.js.map +1 -1
- package/dist/full.umd.js +14 -14
- package/dist/full.umd.js.map +1 -1
- package/dist/index.es.mjs +143 -139
- package/dist/{motion-C5DRdPnO.js → motion-D9TcHxOF.js} +1 -1
- package/dist/{motion-C5DRdPnO.js.map → motion-D9TcHxOF.js.map} +1 -1
- package/dist/motion.es.mjs +1 -1
- package/dist/{platform-B7JhGBc7.js → platform-Dr9b6fsq.js} +21 -20
- package/dist/platform-Dr9b6fsq.js.map +1 -0
- package/dist/platform.es.mjs +1 -1
- package/dist/{reactive-BDya-ia8.js → reactive-DSkct0dO.js} +51 -50
- package/dist/reactive-DSkct0dO.js.map +1 -0
- package/dist/reactive.es.mjs +19 -17
- package/dist/{router-CijiICxt.js → router-CbDhl8rS.js} +3 -3
- package/dist/{router-CijiICxt.js.map → router-CbDhl8rS.js.map} +1 -1
- package/dist/router.es.mjs +1 -1
- package/dist/{sanitize-jyJ2ryE2.js → sanitize-Bs2dkMby.js} +94 -83
- package/dist/sanitize-Bs2dkMby.js.map +1 -0
- package/dist/security/index.d.ts +4 -2
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/sanitize.d.ts +4 -1
- package/dist/security/sanitize.d.ts.map +1 -1
- package/dist/security/trusted-html.d.ts +53 -0
- package/dist/security/trusted-html.d.ts.map +1 -0
- package/dist/security.es.mjs +10 -9
- package/dist/store/define-store.d.ts +1 -1
- package/dist/store/define-store.d.ts.map +1 -1
- package/dist/store/mapping.d.ts +1 -1
- package/dist/store/mapping.d.ts.map +1 -1
- package/dist/store/persisted.d.ts +1 -1
- package/dist/store/persisted.d.ts.map +1 -1
- package/dist/store/types.d.ts +2 -2
- package/dist/store/types.d.ts.map +1 -1
- package/dist/store/watch.d.ts +1 -1
- package/dist/store/watch.d.ts.map +1 -1
- package/dist/{store-CPK9E62U.js → store-BwDvI45q.js} +49 -48
- package/dist/{store-CPK9E62U.js.map → store-BwDvI45q.js.map} +1 -1
- package/dist/store.es.mjs +1 -1
- package/dist/storybook/index.d.ts +37 -0
- package/dist/storybook/index.d.ts.map +1 -0
- package/dist/storybook.es.mjs +151 -0
- package/dist/storybook.es.mjs.map +1 -0
- package/dist/untrack-B0rVscTc.js +7 -0
- package/dist/untrack-B0rVscTc.js.map +1 -0
- package/dist/{view-Cdi0g-qo.js → view-C70lA3vf.js} +29 -28
- package/dist/{view-Cdi0g-qo.js.map → view-C70lA3vf.js.map} +1 -1
- package/dist/view.es.mjs +9 -8
- package/package.json +141 -136
- package/src/component/component.ts +259 -54
- package/src/component/html.ts +153 -53
- package/src/component/index.ts +10 -2
- package/src/component/library.ts +42 -28
- package/src/component/types.ts +184 -19
- package/src/full.ts +8 -2
- package/src/motion/transition.ts +97 -97
- package/src/motion/types.ts +208 -208
- package/src/platform/announcer.ts +208 -208
- package/src/platform/config.ts +163 -163
- package/src/platform/cookies.ts +165 -165
- package/src/platform/index.ts +39 -39
- package/src/platform/meta.ts +168 -168
- package/src/reactive/async-data.ts +486 -486
- package/src/reactive/index.ts +37 -37
- package/src/reactive/signal.ts +29 -29
- package/src/security/constants.ts +211 -211
- package/src/security/index.ts +17 -10
- package/src/security/sanitize.ts +70 -66
- package/src/security/trusted-html.ts +71 -0
- package/src/store/define-store.ts +49 -48
- package/src/store/mapping.ts +74 -73
- package/src/store/persisted.ts +62 -61
- package/src/store/types.ts +92 -94
- package/src/store/watch.ts +53 -52
- package/src/storybook/index.ts +479 -0
- package/dist/component-CY5MVoYN.js +0 -531
- package/dist/component-CY5MVoYN.js.map +0 -1
- package/dist/core-DPdbItcq.js +0 -112
- package/dist/core-DPdbItcq.js.map +0 -1
- package/dist/platform-B7JhGBc7.js.map +0 -1
- package/dist/reactive-BDya-ia8.js.map +0 -1
- package/dist/sanitize-jyJ2ryE2.js.map +0 -1
package/src/security/sanitize.ts
CHANGED
|
@@ -1,66 +1,70 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Security utilities for HTML sanitization.
|
|
3
|
-
* All DOM writes are sanitized by default to prevent XSS attacks.
|
|
4
|
-
*
|
|
5
|
-
* @module bquery/security
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { sanitizeHtmlCore } from './sanitize-core';
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* @
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
'
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
export
|
|
1
|
+
/**
|
|
2
|
+
* Security utilities for HTML sanitization.
|
|
3
|
+
* All DOM writes are sanitized by default to prevent XSS attacks.
|
|
4
|
+
*
|
|
5
|
+
* @module bquery/security
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { sanitizeHtmlCore } from './sanitize-core';
|
|
9
|
+
import { toSanitizedHtml } from './trusted-html';
|
|
10
|
+
import type { SanitizedHtml } from './trusted-html';
|
|
11
|
+
import type { SanitizeOptions } from './types';
|
|
12
|
+
export { generateNonce } from './csp';
|
|
13
|
+
export { isTrustedTypesSupported } from './trusted-types';
|
|
14
|
+
export { trusted } from './trusted-html';
|
|
15
|
+
export type { SanitizedHtml, TrustedHtml } from './trusted-html';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Sanitize HTML string, removing dangerous elements and attributes.
|
|
19
|
+
* Uses Trusted Types when available for CSP compliance.
|
|
20
|
+
*
|
|
21
|
+
* @param html - The HTML string to sanitize
|
|
22
|
+
* @param options - Sanitization options
|
|
23
|
+
* @returns Sanitized HTML string
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* const safe = sanitizeHtml('<div onclick="alert(1)">Hello</div>');
|
|
28
|
+
* // Returns: '<div>Hello</div>'
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export const sanitizeHtml = (html: string, options: SanitizeOptions = {}): SanitizedHtml => {
|
|
32
|
+
return toSanitizedHtml(sanitizeHtmlCore(html, options));
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Escape HTML entities to prevent XSS.
|
|
37
|
+
* Use this for displaying user content as text.
|
|
38
|
+
*
|
|
39
|
+
* @param text - The text to escape
|
|
40
|
+
* @returns Escaped HTML string
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* escapeHtml('<script>alert(1)</script>');
|
|
45
|
+
* // Returns: '<script>alert(1)</script>'
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export const escapeHtml = (text: string): string => {
|
|
49
|
+
const escapeMap: Record<string, string> = {
|
|
50
|
+
'&': '&',
|
|
51
|
+
'<': '<',
|
|
52
|
+
'>': '>',
|
|
53
|
+
'"': '"',
|
|
54
|
+
"'": ''',
|
|
55
|
+
'`': '`',
|
|
56
|
+
};
|
|
57
|
+
return text.replace(/[&<>"'`]/g, (char) => escapeMap[char]);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Strip all HTML tags and return plain text.
|
|
62
|
+
*
|
|
63
|
+
* @param html - The HTML string to strip
|
|
64
|
+
* @returns Plain text content
|
|
65
|
+
*/
|
|
66
|
+
export const stripTags = (html: string): string => {
|
|
67
|
+
return sanitizeHtmlCore(html, { stripAllTags: true });
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export type { SanitizeOptions } from './types';
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
declare const sanitizedHtmlBrand: unique symbol;
|
|
2
|
+
const trustedHtmlBrand: unique symbol = Symbol('bquery.trusted-html.brand');
|
|
3
|
+
const TRUSTED_HTML_VALUE = Symbol('bquery.trusted-html');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Branded HTML string produced by bQuery's sanitization or escaping template helpers.
|
|
7
|
+
*
|
|
8
|
+
* Values returned from {@link sanitizeHtml} carry sanitized markup. Values returned from
|
|
9
|
+
* {@link safeHtml} preserve the template's static markup while escaping normal interpolations
|
|
10
|
+
* and splicing {@link trusted} fragments verbatim. This brand is not intended for arbitrary
|
|
11
|
+
* strings or manual concatenation outside those helpers.
|
|
12
|
+
*/
|
|
13
|
+
export type SanitizedHtml = string & { readonly [sanitizedHtmlBrand]: true };
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Marker object that safeHtml can splice into templates without escaping again.
|
|
17
|
+
*/
|
|
18
|
+
export type TrustedHtml = { readonly [trustedHtmlBrand]: true; toString(): string };
|
|
19
|
+
|
|
20
|
+
type TrustedHtmlValue = TrustedHtml & { readonly [TRUSTED_HTML_VALUE]: string };
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Apply the internal {@link SanitizedHtml} brand to helper output.
|
|
24
|
+
*
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export const toSanitizedHtml = (html: string): SanitizedHtml => html as SanitizedHtml;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Mark a sanitized HTML string for verbatim splicing into safeHtml templates.
|
|
31
|
+
*
|
|
32
|
+
* @param html - HTML previously produced by sanitizeHtml, safeHtml, or another trusted bQuery helper
|
|
33
|
+
* @returns Trusted HTML marker object for safeHtml interpolations
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* const badge = trusted(sanitizeHtml('<strong onclick="alert(1)">New</strong>'));
|
|
38
|
+
* const markup = safeHtml`<span>${badge}</span>`;
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export const trusted = (html: SanitizedHtml): TrustedHtml => {
|
|
42
|
+
const value = String(html);
|
|
43
|
+
return Object.freeze({
|
|
44
|
+
[trustedHtmlBrand]: true as const,
|
|
45
|
+
[TRUSTED_HTML_VALUE]: value,
|
|
46
|
+
toString: () => value,
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Check whether a value is a trusted HTML marker created by trusted().
|
|
52
|
+
*
|
|
53
|
+
* @internal
|
|
54
|
+
*/
|
|
55
|
+
export const isTrustedHtml = (value: unknown): value is TrustedHtml => {
|
|
56
|
+
return (
|
|
57
|
+
typeof value === 'object' &&
|
|
58
|
+
value !== null &&
|
|
59
|
+
trustedHtmlBrand in value &&
|
|
60
|
+
TRUSTED_HTML_VALUE in value
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Unwrap the raw HTML string stored inside a trusted HTML marker.
|
|
66
|
+
*
|
|
67
|
+
* @internal
|
|
68
|
+
*/
|
|
69
|
+
export const unwrapTrustedHtml = (value: TrustedHtml): string => {
|
|
70
|
+
return (value as TrustedHtmlValue)[TRUSTED_HTML_VALUE];
|
|
71
|
+
};
|
|
@@ -1,48 +1,49 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Store factory helpers.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { createStore } from './create-store';
|
|
6
|
-
import { getStore, hasStore } from './registry';
|
|
7
|
-
import type { Store, StoreDefinition } from './types';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Creates a store factory that returns the store instance.
|
|
11
|
-
*
|
|
12
|
-
* The store is lazily created on first call and cached in the global store
|
|
13
|
-
* registry. Subsequent calls return the same instance. After calling
|
|
14
|
-
* `destroyStore(id)`, the next factory call will create a fresh store.
|
|
15
|
-
*
|
|
16
|
-
* @param id - Store identifier
|
|
17
|
-
* @param definition - Store definition without id
|
|
18
|
-
* @returns A function that returns the store instance
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* ```ts
|
|
22
|
-
* const useCounter = defineStore('counter', {
|
|
23
|
-
* state: () => ({ count: 0 }),
|
|
24
|
-
* actions: { increment() { this.count++; } },
|
|
25
|
-
* });
|
|
26
|
-
*
|
|
27
|
-
* const counter = useCounter();
|
|
28
|
-
* counter.increment();
|
|
29
|
-
* ```
|
|
30
|
-
*/
|
|
31
|
-
export const defineStore = <
|
|
32
|
-
S extends Record<string, unknown>,
|
|
33
|
-
G extends Record<string, unknown> = Record<string, never>,
|
|
34
|
-
|
|
35
|
-
>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Store factory helpers.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { createStore } from './create-store';
|
|
6
|
+
import { getStore, hasStore } from './registry';
|
|
7
|
+
import type { Store, StoreDefinition } from './types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Creates a store factory that returns the store instance.
|
|
11
|
+
*
|
|
12
|
+
* The store is lazily created on first call and cached in the global store
|
|
13
|
+
* registry. Subsequent calls return the same instance. After calling
|
|
14
|
+
* `destroyStore(id)`, the next factory call will create a fresh store.
|
|
15
|
+
*
|
|
16
|
+
* @param id - Store identifier
|
|
17
|
+
* @param definition - Store definition without id
|
|
18
|
+
* @returns A function that returns the store instance
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* const useCounter = defineStore('counter', {
|
|
23
|
+
* state: () => ({ count: 0 }),
|
|
24
|
+
* actions: { increment() { this.count++; } },
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* const counter = useCounter();
|
|
28
|
+
* counter.increment();
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export const defineStore = <
|
|
32
|
+
S extends Record<string, unknown>,
|
|
33
|
+
G extends Record<string, unknown> = Record<string, never>,
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- actions may declare specific parameter types
|
|
35
|
+
A extends Record<string, (...args: any[]) => any> = Record<string, never>,
|
|
36
|
+
>(
|
|
37
|
+
id: string,
|
|
38
|
+
definition: Omit<StoreDefinition<S, G, A>, 'id'>
|
|
39
|
+
): (() => Store<S, G, A>) => {
|
|
40
|
+
// Check registry first to avoid noisy warnings from createStore()
|
|
41
|
+
// when the factory is called multiple times (intended usage pattern).
|
|
42
|
+
// createStore() only called when store doesn't exist or was destroyed.
|
|
43
|
+
return () => {
|
|
44
|
+
if (hasStore(id)) {
|
|
45
|
+
return getStore(id) as Store<S, G, A>;
|
|
46
|
+
}
|
|
47
|
+
return createStore({ id, ...definition });
|
|
48
|
+
};
|
|
49
|
+
};
|
package/src/store/mapping.ts
CHANGED
|
@@ -1,73 +1,74 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mapping helpers for store state and actions.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Maps store state properties to a reactive object for use in components.
|
|
7
|
-
*
|
|
8
|
-
* @param store - The store instance
|
|
9
|
-
* @param keys - State keys to map
|
|
10
|
-
* @returns Object with mapped properties
|
|
11
|
-
*/
|
|
12
|
-
export const mapState = <S extends Record<string, unknown>, K extends keyof S>(
|
|
13
|
-
store: S,
|
|
14
|
-
keys: K[]
|
|
15
|
-
): Pick<S, K> => {
|
|
16
|
-
const mapped = {} as Pick<S, K>;
|
|
17
|
-
|
|
18
|
-
for (const key of keys) {
|
|
19
|
-
Object.defineProperty(mapped, key, {
|
|
20
|
-
get: () => store[key],
|
|
21
|
-
enumerable: true,
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return mapped;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Maps store getters to a reactive object for use in components.
|
|
30
|
-
*
|
|
31
|
-
* @param store - The store instance
|
|
32
|
-
* @param keys - Getter keys to map
|
|
33
|
-
* @returns Object with mapped getters
|
|
34
|
-
*/
|
|
35
|
-
export const mapGetters = <G extends Record<string, unknown>, K extends keyof G>(
|
|
36
|
-
store: G,
|
|
37
|
-
keys: K[]
|
|
38
|
-
): Pick<G, K> => {
|
|
39
|
-
const mapped = {} as Pick<G, K>;
|
|
40
|
-
|
|
41
|
-
for (const key of keys) {
|
|
42
|
-
Object.defineProperty(mapped, key, {
|
|
43
|
-
get: () => store[key],
|
|
44
|
-
enumerable: true,
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return mapped;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Maps store actions to an object for easier destructuring.
|
|
53
|
-
*
|
|
54
|
-
* @param store - The store instance
|
|
55
|
-
* @param keys - Action keys to map
|
|
56
|
-
* @returns Object with mapped actions
|
|
57
|
-
*/
|
|
58
|
-
export const mapActions = <
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Mapping helpers for store state and actions.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Maps store state properties to a reactive object for use in components.
|
|
7
|
+
*
|
|
8
|
+
* @param store - The store instance
|
|
9
|
+
* @param keys - State keys to map
|
|
10
|
+
* @returns Object with mapped properties
|
|
11
|
+
*/
|
|
12
|
+
export const mapState = <S extends Record<string, unknown>, K extends keyof S>(
|
|
13
|
+
store: S,
|
|
14
|
+
keys: K[]
|
|
15
|
+
): Pick<S, K> => {
|
|
16
|
+
const mapped = {} as Pick<S, K>;
|
|
17
|
+
|
|
18
|
+
for (const key of keys) {
|
|
19
|
+
Object.defineProperty(mapped, key, {
|
|
20
|
+
get: () => store[key],
|
|
21
|
+
enumerable: true,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return mapped;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Maps store getters to a reactive object for use in components.
|
|
30
|
+
*
|
|
31
|
+
* @param store - The store instance
|
|
32
|
+
* @param keys - Getter keys to map
|
|
33
|
+
* @returns Object with mapped getters
|
|
34
|
+
*/
|
|
35
|
+
export const mapGetters = <G extends Record<string, unknown>, K extends keyof G>(
|
|
36
|
+
store: G,
|
|
37
|
+
keys: K[]
|
|
38
|
+
): Pick<G, K> => {
|
|
39
|
+
const mapped = {} as Pick<G, K>;
|
|
40
|
+
|
|
41
|
+
for (const key of keys) {
|
|
42
|
+
Object.defineProperty(mapped, key, {
|
|
43
|
+
get: () => store[key],
|
|
44
|
+
enumerable: true,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return mapped;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Maps store actions to an object for easier destructuring.
|
|
53
|
+
*
|
|
54
|
+
* @param store - The store instance
|
|
55
|
+
* @param keys - Action keys to map
|
|
56
|
+
* @returns Object with mapped actions
|
|
57
|
+
*/
|
|
58
|
+
export const mapActions = <
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- actions may declare specific parameter types
|
|
60
|
+
A extends Record<string, (...args: any[]) => any>,
|
|
61
|
+
K extends keyof A,
|
|
62
|
+
>(
|
|
63
|
+
store: A,
|
|
64
|
+
keys: K[]
|
|
65
|
+
): Pick<A, K> => {
|
|
66
|
+
const mapped = {} as Pick<A, K>;
|
|
67
|
+
|
|
68
|
+
for (const key of keys) {
|
|
69
|
+
(mapped as Record<string, unknown>)[key as string] = (...args: unknown[]) =>
|
|
70
|
+
(store[key] as (...args: unknown[]) => unknown)(...args);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return mapped;
|
|
74
|
+
};
|
package/src/store/persisted.ts
CHANGED
|
@@ -1,61 +1,62 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Store persistence helpers.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { createStore } from './create-store';
|
|
6
|
-
import type { Store, StoreDefinition } from './types';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Creates a store with automatic persistence to localStorage.
|
|
10
|
-
*
|
|
11
|
-
* @param definition - Store definition
|
|
12
|
-
* @param storageKey - Optional custom storage key
|
|
13
|
-
* @returns The reactive store instance
|
|
14
|
-
*/
|
|
15
|
-
export const createPersistedStore = <
|
|
16
|
-
S extends Record<string, unknown>,
|
|
17
|
-
G extends Record<string, unknown> = Record<string, never>,
|
|
18
|
-
|
|
19
|
-
>
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Store persistence helpers.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { createStore } from './create-store';
|
|
6
|
+
import type { Store, StoreDefinition } from './types';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Creates a store with automatic persistence to localStorage.
|
|
10
|
+
*
|
|
11
|
+
* @param definition - Store definition
|
|
12
|
+
* @param storageKey - Optional custom storage key
|
|
13
|
+
* @returns The reactive store instance
|
|
14
|
+
*/
|
|
15
|
+
export const createPersistedStore = <
|
|
16
|
+
S extends Record<string, unknown>,
|
|
17
|
+
G extends Record<string, unknown> = Record<string, never>,
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- actions may declare specific parameter types
|
|
19
|
+
A extends Record<string, (...args: any[]) => any> = Record<string, never>,
|
|
20
|
+
>(
|
|
21
|
+
definition: StoreDefinition<S, G, A>,
|
|
22
|
+
storageKey?: string
|
|
23
|
+
): Store<S, G, A> => {
|
|
24
|
+
const key = storageKey ?? `bquery-store-${definition.id}`;
|
|
25
|
+
|
|
26
|
+
const originalStateFactory = definition.state;
|
|
27
|
+
|
|
28
|
+
const wrappedDefinition: StoreDefinition<S, G, A> = {
|
|
29
|
+
...definition,
|
|
30
|
+
state: () => {
|
|
31
|
+
const defaultState = originalStateFactory();
|
|
32
|
+
|
|
33
|
+
if (typeof window !== 'undefined') {
|
|
34
|
+
try {
|
|
35
|
+
const saved = localStorage.getItem(key);
|
|
36
|
+
if (saved) {
|
|
37
|
+
return { ...defaultState, ...JSON.parse(saved) } as S;
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
// Ignore parse errors
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return defaultState;
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const store = createStore(wrappedDefinition);
|
|
49
|
+
|
|
50
|
+
// Subscribe to save changes
|
|
51
|
+
store.$subscribe((state) => {
|
|
52
|
+
if (typeof window !== 'undefined') {
|
|
53
|
+
try {
|
|
54
|
+
localStorage.setItem(key, JSON.stringify(state));
|
|
55
|
+
} catch {
|
|
56
|
+
// Ignore quota errors
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return store;
|
|
62
|
+
};
|