svas 0.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 ADDED
@@ -0,0 +1,58 @@
1
+ # Svelte library
2
+
3
+ Everything you need to build a Svelte library, powered by [`sv`](https://npmjs.com/package/sv).
4
+
5
+ Read more about creating a library [in the docs](https://svelte.dev/docs/kit/packaging).
6
+
7
+ ## Creating a project
8
+
9
+ If you're seeing this, you've probably already done this step. Congrats!
10
+
11
+ ```bash
12
+ # create a new project in the current directory
13
+ npx sv create
14
+
15
+ # create a new project in my-app
16
+ npx sv create my-app
17
+ ```
18
+
19
+ ## Developing
20
+
21
+ Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
22
+
23
+ ```bash
24
+ npm run dev
25
+
26
+ # or start the server and open the app in a new browser tab
27
+ npm run dev -- --open
28
+ ```
29
+
30
+ Everything inside `src/lib` is part of your library, everything inside `src/routes` can be used as a showcase or preview app.
31
+
32
+ ## Building
33
+
34
+ To build your library:
35
+
36
+ ```bash
37
+ npm run package
38
+ ```
39
+
40
+ To create a production version of your showcase app:
41
+
42
+ ```bash
43
+ npm run build
44
+ ```
45
+
46
+ You can preview the production build with `npm run preview`.
47
+
48
+ > To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
49
+
50
+ ## Publishing
51
+
52
+ Go into the `package.json` and give your package the desired name through the `"name"` option. Also consider adding a `"license"` field and point it to a `LICENSE` file which you can create from a template (one popular option is the [MIT license](https://opensource.org/license/mit/)).
53
+
54
+ To publish your library to [npm](https://www.npmjs.com):
55
+
56
+ ```bash
57
+ npm publish
58
+ ```
@@ -0,0 +1,2 @@
1
+ export type Maybe<T, E extends Error = Error> = null | T | E;
2
+ export type Just<T extends Maybe<unknown>> = Exclude<T, null | Error>;
package/dist/Maybe.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,11 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { Readable } from 'svelte/store';
3
+ export interface Props<T> {
4
+ store: Readable<T | null | Error>;
5
+ waiting?: Snippet;
6
+ awaited: Snippet<[T]>;
7
+ error?: Snippet<[Error]>;
8
+ silent?: boolean;
9
+ class?: string;
10
+ }
11
+ export { default as Async } from './Async.svelte';
@@ -0,0 +1 @@
1
+ export { default as Async } from './Async.svelte';
@@ -0,0 +1,35 @@
1
+ <script lang="ts" generics="T">
2
+ import { LoaderCircle } from "@lucide/svelte";
3
+ import type { Props } from "./Async";
4
+
5
+ const {
6
+ store,
7
+ waiting,
8
+ awaited,
9
+ error,
10
+ silent = false,
11
+ class: classes,
12
+ }: Props<T> = $props();
13
+ </script>
14
+
15
+ <div class={classes}>
16
+ {#if $store === null}
17
+ {#if waiting}
18
+ {@render waiting()}
19
+ {:else if !silent}
20
+ <div class="w-full h-full flex justify-center items-center">
21
+ <LoaderCircle class="animate-spin text-gray-400" />
22
+ </div>
23
+ {/if}
24
+ {:else if $store instanceof Error}
25
+ {#if error}
26
+ {@render error($store)}
27
+ {:else if !silent}
28
+ <div class="w-full h-full flex justify-center items-center">
29
+ Something went terribly wrong
30
+ </div>
31
+ {/if}
32
+ {:else}
33
+ {@render awaited($store)}
34
+ {/if}
35
+ </div>
@@ -0,0 +1,18 @@
1
+ import type { Props } from "./Async";
2
+ declare class __sveltets_Render<T> {
3
+ props(): Props<T>;
4
+ events(): {};
5
+ slots(): {};
6
+ bindings(): "";
7
+ exports(): {};
8
+ }
9
+ interface $$IsomorphicComponent {
10
+ new <T>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
11
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
12
+ } & ReturnType<__sveltets_Render<T>['exports']>;
13
+ <T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
14
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
15
+ }
16
+ declare const Async: $$IsomorphicComponent;
17
+ type Async<T> = InstanceType<typeof Async<T>>;
18
+ export default Async;
@@ -0,0 +1 @@
1
+ export { Async } from './Async';
@@ -0,0 +1 @@
1
+ export { Async } from './Async';
@@ -0,0 +1,63 @@
1
+ import { type Readable, type Subscriber, type Unsubscriber } from 'svelte/store';
2
+ import type { Maybe } from './Maybe';
3
+ import type { GetOptions, Values } from './values';
4
+ export declare class Collection<T extends Identifiable, E extends Error = Error> implements Readable<Maybe<T[], E>> {
5
+ private readonly store;
6
+ private readonly values?;
7
+ private readonly fetch;
8
+ private readonly map;
9
+ private readonly sort;
10
+ private readonly revalidate;
11
+ private readonly stale;
12
+ private timestamp;
13
+ constructor(options: Options<T, E>);
14
+ subscribe(run: Subscriber<Maybe<T[], E>>, invalidate?: () => void): Unsubscriber;
15
+ add<I = T>(item: Input<T, typeof this.map, I>): void;
16
+ get(id: string, options?: GetOptions): Readable<Maybe<T, E>>;
17
+ extract(id: string): T | null;
18
+ delete(id: string): void;
19
+ set<I = T>(item: Input<T, typeof this.map, I>, options?: SetOptions): Readable<T | E>;
20
+ preset<I = T>(item: Input<T, typeof this.map, I>): void;
21
+ reset(id: string): void;
22
+ update(id: string, update: (item: T) => T | void): void;
23
+ sync(): this;
24
+ replace<I = T>(items: Array<Input<T, typeof this.map, I>>): void;
25
+ private refresh;
26
+ private persist;
27
+ private bind;
28
+ private clear;
29
+ }
30
+ type Input<T, M, Default> = M extends (arg: infer P) => T ? P : Default;
31
+ interface Options<T = unknown, E extends Error = Error> {
32
+ /** Fetches the collection. */
33
+ get?: () => Promise<any[] | E>;
34
+ /** Maps the fetched item to the type of the collection. */
35
+ map?: (item: any) => T;
36
+ /** Sorting function applied on updates. */
37
+ sort?: (a: T, b: T) => number;
38
+ /** Time in milliseconds before revalidating the collection. */
39
+ revalidate?: number;
40
+ /** Whether to keep the collection while revalidating. */
41
+ stale?: boolean;
42
+ /** Values store. */
43
+ values?: Values<T, E>;
44
+ /** Key to persist the collection. */
45
+ persist?: string;
46
+ /** Nullifies the collection when the bound store is null. */
47
+ bind?: Readable<unknown | null>;
48
+ }
49
+ export interface Identifiable {
50
+ id: string;
51
+ }
52
+ export interface SetOptions {
53
+ /** Whether to sort the collection after setting the item. Defaults to true. */
54
+ sort?: boolean;
55
+ /** Whether to add the item to the collection if it does not exist. Defaults to false. */
56
+ add?: boolean;
57
+ /** Whether value is transient. Defaults to false. */
58
+ stash?: boolean;
59
+ /** Whether to sync the collection after setting the item. Defaults to true. */
60
+ bind?: boolean;
61
+ }
62
+ export declare function collection<T extends Identifiable, E extends Error = Error>(options: Options<T, E>): Collection<T, E>;
63
+ export {};
@@ -0,0 +1,165 @@
1
+ import { writable } from 'svelte/store';
2
+ import { browser } from '$app/environment';
3
+ export class Collection {
4
+ store;
5
+ values;
6
+ fetch;
7
+ map;
8
+ sort;
9
+ revalidate;
10
+ stale;
11
+ timestamp = 0;
12
+ constructor(options) {
13
+ this.store = writable(null);
14
+ this.values = options.values;
15
+ this.fetch = options.get;
16
+ this.map = options.map;
17
+ this.sort = options.sort;
18
+ this.revalidate = options.revalidate ?? DEFAULTS.revalidate;
19
+ this.stale = options.stale ?? DEFAULTS.stale;
20
+ if (browser) {
21
+ if (options.persist !== undefined)
22
+ this.persist(options.persist);
23
+ if (options.bind !== undefined)
24
+ this.bind(options.bind);
25
+ }
26
+ }
27
+ subscribe(run, invalidate) {
28
+ this.sync();
29
+ return this.store.subscribe(run, invalidate);
30
+ }
31
+ add(item) {
32
+ const value = this.map?.(item) ?? item;
33
+ this.values?.set(value.id, value);
34
+ this.store.update((items) => {
35
+ if (items === null || items instanceof Error)
36
+ return [value];
37
+ else {
38
+ if (items.find((item) => item.id === value.id) !== undefined)
39
+ return items;
40
+ items.unshift(value);
41
+ if (this.sort !== undefined)
42
+ items.sort(this.sort);
43
+ return items;
44
+ }
45
+ });
46
+ }
47
+ get(id, options) {
48
+ if (this.values === undefined)
49
+ throw new Error('Collection: values is not defined');
50
+ return this.values.get(id, options);
51
+ }
52
+ extract(id) {
53
+ if (this.values === undefined)
54
+ throw new Error('Collection: values is not defined');
55
+ return this.values.extract(id);
56
+ }
57
+ delete(id) {
58
+ this.values?.delete(id);
59
+ this.store.update((items) => {
60
+ if (items === null || items instanceof Error)
61
+ return items;
62
+ return items.filter((item) => item.id !== id);
63
+ });
64
+ }
65
+ set(item, options) {
66
+ const value = this.map?.(item) ?? item;
67
+ this.store.update((items) => {
68
+ if (items === null || items instanceof Error)
69
+ return [item];
70
+ const index = items.findIndex((i) => i.id === value.id);
71
+ if (index === -1)
72
+ return options?.add === true ? [value, ...items] : items;
73
+ items[index] = value;
74
+ if (this.sort !== undefined && options?.sort !== false)
75
+ items.sort(this.sort);
76
+ return items;
77
+ });
78
+ if (options?.bind === false)
79
+ return this.values?.get(value.id);
80
+ else
81
+ return this.values?.set(value.id, value, options);
82
+ }
83
+ preset(item) {
84
+ this.set(item, { stash: true });
85
+ }
86
+ reset(id) {
87
+ if (this.values === undefined)
88
+ throw new Error('Collection: values is not defined');
89
+ const value = this.values?.reset(id);
90
+ if (value === null || value instanceof Error)
91
+ return;
92
+ this.set(value, { bind: false });
93
+ }
94
+ update(id, update) {
95
+ if (this.values === undefined)
96
+ throw new Error('Collection: values is not defined');
97
+ const asis = this.values.extract(id);
98
+ if (asis === null)
99
+ return;
100
+ const tobe = update(asis) ?? asis;
101
+ this.set(tobe);
102
+ }
103
+ sync() {
104
+ const stale = this.timestamp === 0 || this.timestamp + this.revalidate < Date.now();
105
+ if (stale) {
106
+ if (!this.stale)
107
+ this.clear();
108
+ this.refresh();
109
+ }
110
+ return this;
111
+ }
112
+ replace(items) {
113
+ const values = this.map === undefined ? items : items.map((item) => this.map(item));
114
+ this.store.set(values);
115
+ this.timestamp = Date.now();
116
+ if (this.values !== undefined)
117
+ for (const value of values)
118
+ this.values.set(value.id, value);
119
+ }
120
+ refresh() {
121
+ if (this.fetch === undefined)
122
+ return;
123
+ this.fetch().then((items) => {
124
+ if (!(items instanceof Error))
125
+ this.replace(items);
126
+ });
127
+ this.timestamp = Date.now();
128
+ }
129
+ persist(key) {
130
+ const stored = localStorage.getItem(key);
131
+ if (stored !== null) {
132
+ const items = JSON.parse(stored);
133
+ this.store.set(items);
134
+ if (this.values?.persistent === false)
135
+ for (const item of items)
136
+ this.values.set(item.id, item);
137
+ }
138
+ this.store.subscribe((items) => {
139
+ if (items instanceof Error)
140
+ return;
141
+ if (items === null)
142
+ localStorage.removeItem(key);
143
+ else
144
+ localStorage.setItem(key, JSON.stringify(items));
145
+ });
146
+ }
147
+ bind(store) {
148
+ store.subscribe((value) => {
149
+ if (value === null)
150
+ this.clear();
151
+ });
152
+ }
153
+ clear() {
154
+ this.store.set(null);
155
+ this.values?.clear();
156
+ this.timestamp = 0;
157
+ }
158
+ }
159
+ export function collection(options) {
160
+ return new Collection(options);
161
+ }
162
+ const DEFAULTS = {
163
+ revalidate: 300_000,
164
+ stale: false
165
+ };
@@ -0,0 +1,6 @@
1
+ import { type Readable } from 'svelte/store';
2
+ import type { Maybe } from './Maybe';
3
+ export declare function combined<T extends Readable<unknown>[]>(...stores: T): Readable<Maybe<Values<T>>>;
4
+ type Value<T> = T extends Readable<infer U | null | Error> ? U : never;
5
+ type Values<T extends Readable<Maybe<unknown>>[]> = T extends [infer U, ...infer R] ? [Value<U>, ...Values<R extends Readable<unknown | null | Error>[] ? R : []>] : [];
6
+ export {};
@@ -0,0 +1,24 @@
1
+ import { readable } from 'svelte/store';
2
+ export function combined(...stores) {
3
+ return readable(null, (set) => {
4
+ const values = new Array(stores.length).fill(null);
5
+ const unsubs = stores.map((store, index) => store.subscribe((value) => {
6
+ if (value instanceof Error) {
7
+ values[index] = null;
8
+ set(value);
9
+ }
10
+ else if (value === null) {
11
+ values[index] = value;
12
+ set(value);
13
+ }
14
+ else {
15
+ values[index] = value;
16
+ if (values.every((value) => value !== null))
17
+ set(values);
18
+ }
19
+ }));
20
+ return () => {
21
+ unsubs.forEach((unsub) => unsub());
22
+ };
23
+ });
24
+ }
@@ -0,0 +1,3 @@
1
+ import type { Maybe } from './reflection/Maybe';
2
+ import { type Readable } from 'svelte/store';
3
+ export declare function ensure<T>(store: Readable<Maybe<T>>): T;
package/dist/ensure.js ADDED
@@ -0,0 +1,7 @@
1
+ import { get } from 'svelte/store';
2
+ export function ensure(store) {
3
+ const value = get(store);
4
+ if (value === null || value instanceof Error)
5
+ throw new Error('Store contains null or an error');
6
+ return value;
7
+ }
@@ -0,0 +1,7 @@
1
+ import type { Readable } from 'svelte/store';
2
+ import type { Maybe } from './Maybe';
3
+ /**
4
+ * Execute a callback once the store has a non-null and non-error value.
5
+ */
6
+ export declare function having<T>(store: Readable<Maybe<T>>): Promise<T>;
7
+ export declare function having<T>(store: Readable<Maybe<T>>, callback: (value: T) => unknown | Promise<unknown>): void;
package/dist/having.js ADDED
@@ -0,0 +1,22 @@
1
+ export function having(store, callback) {
2
+ const promise = new Promise((resolve) => {
3
+ let completed = false;
4
+ let unsubscribe = null;
5
+ unsubscribe = store.subscribe((value) => {
6
+ if (value === null || value instanceof Error)
7
+ return;
8
+ // A
9
+ unsubscribe?.();
10
+ completed = true;
11
+ resolve(value);
12
+ });
13
+ // B
14
+ if (completed)
15
+ unsubscribe?.();
16
+ // the execution order of A and B is uncertain
17
+ });
18
+ if (callback === undefined)
19
+ return promise;
20
+ else
21
+ promise.then(callback);
22
+ }
@@ -0,0 +1,10 @@
1
+ export { collection, type Collection } from './collection';
2
+ export { values, type Values } from './values';
3
+ export { value } from './value';
4
+ export { having } from './having';
5
+ export { ensure } from './ensure';
6
+ export { sync } from './sync';
7
+ export { combined } from './combined';
8
+ export { ok } from './ok';
9
+ export { Async } from './async';
10
+ export type { Maybe } from './Maybe';
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export { collection } from './collection';
2
+ export { values } from './values';
3
+ export { value } from './value';
4
+ export { having } from './having';
5
+ export { ensure } from './ensure';
6
+ export { sync } from './sync';
7
+ export { combined } from './combined';
8
+ export { ok } from './ok';
9
+ export { Async } from './async';
package/dist/ok.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function ok<T>(value: T): value is Exclude<T, null | Error>;
package/dist/ok.js ADDED
@@ -0,0 +1,3 @@
1
+ export function ok(value) {
2
+ return value !== null && !(value instanceof Error);
3
+ }
package/dist/sync.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import type { Collection, Identifiable, SetOptions } from './collection';
2
+ export declare function sync<T extends Comparable, I = T>(input: Input<T, Parameters<Collection<T>['add']>[0], I>, collection: Collection<T>, options?: Options): void;
3
+ type Input<T, M, Default> = M extends (arg: infer P) => T ? P : Default;
4
+ interface Comparable extends Identifiable {
5
+ _version: number;
6
+ _deleted?: number | null;
7
+ }
8
+ type Options = SetOptions;
9
+ export {};
package/dist/sync.js ADDED
@@ -0,0 +1,14 @@
1
+ export function sync(input, collection, options) {
2
+ const tobe = input;
3
+ if (tobe._deleted !== null && tobe._deleted !== undefined) {
4
+ collection.delete(tobe.id);
5
+ return;
6
+ }
7
+ const asis = collection.extract(tobe.id);
8
+ if (asis === null)
9
+ collection.add(tobe);
10
+ else if (asis._version < tobe._version)
11
+ collection.set(tobe, { add: true, ...options });
12
+ else
13
+ console.debug('Sync skipped', tobe);
14
+ }
@@ -0,0 +1,36 @@
1
+ import { type Readable, type Subscriber, type Unsubscriber, type Updater, type Writable } from 'svelte/store';
2
+ declare class Value<T> implements Writable<T | null> {
3
+ private readonly store;
4
+ private readonly map?;
5
+ constructor(options: Options<T>);
6
+ set(value: T | null): void;
7
+ update(updater: Updater<T | null>): void;
8
+ subscribe(run: Subscriber<T | null>, invalidate?: () => void): Unsubscriber;
9
+ extract(): T | null;
10
+ private bind;
11
+ }
12
+ export declare function value<T>(options?: Options<T>): Value<T>;
13
+ interface Options<T> {
14
+ /**
15
+ * Key to persist the collection.
16
+ */
17
+ persist?: string;
18
+ /**
19
+ * Persist the collection in the session storage.
20
+ * If `persist` is not set, `session` is ignored.
21
+ */
22
+ session?: boolean;
23
+ /**
24
+ * Nullify values when the bound store is null.
25
+ */
26
+ bind?: Readable<unknown | null>;
27
+ /**
28
+ * Maps values on set.
29
+ */
30
+ map?: (item: T) => T;
31
+ /**
32
+ * Default value.
33
+ */
34
+ default?: T;
35
+ }
36
+ export {};
package/dist/value.js ADDED
@@ -0,0 +1,57 @@
1
+ import { get, writable } from 'svelte/store';
2
+ import { browser } from '$app/environment';
3
+ class Value {
4
+ store;
5
+ map;
6
+ constructor(options) {
7
+ const persist = browser && options.persist !== undefined;
8
+ const def = options.default ?? null;
9
+ this.store = persist
10
+ ? persistent(options.persist, def, options.session)
11
+ : writable(def);
12
+ if (browser) {
13
+ if (options.bind)
14
+ this.bind(options.bind);
15
+ if (options.map)
16
+ this.map = options.map;
17
+ }
18
+ }
19
+ set(value) {
20
+ if (value !== null && this.map)
21
+ value = this.map(value);
22
+ this.store.set(value);
23
+ }
24
+ update(updater) {
25
+ this.store.update(updater);
26
+ }
27
+ subscribe(run, invalidate) {
28
+ return this.store.subscribe(run, invalidate);
29
+ }
30
+ extract() {
31
+ return get(this.store);
32
+ }
33
+ bind(store) {
34
+ store.subscribe((value) => {
35
+ if (value === null)
36
+ this.store.set(null);
37
+ });
38
+ }
39
+ }
40
+ export function value(options = {}) {
41
+ return new Value(options);
42
+ }
43
+ function persistent(key, def, session) {
44
+ const store = writable(load(key) ?? def);
45
+ const type = session ? 'sessionStorage' : 'localStorage';
46
+ store.subscribe((value) => {
47
+ if (value === null)
48
+ window[type].removeItem(key);
49
+ else
50
+ window[type].setItem(key, JSON.stringify(value));
51
+ });
52
+ return store;
53
+ }
54
+ function load(key) {
55
+ const json = window.localStorage.getItem(key);
56
+ return json === null ? null : JSON.parse(json);
57
+ }
@@ -0,0 +1,79 @@
1
+ import { type Readable } from 'svelte/store';
2
+ import type { Maybe } from './Maybe';
3
+ declare class Values<T, E extends Error = Error> {
4
+ readonly persistent: boolean;
5
+ private readonly values;
6
+ private readonly fetch;
7
+ private readonly map;
8
+ private readonly revalidate;
9
+ private readonly permanent;
10
+ private readonly stale;
11
+ private readonly persist?;
12
+ private ready;
13
+ private dumping;
14
+ constructor(options: Options<T, E>);
15
+ set<I = T>(key: string, item: Input<T, typeof this.map, I> | E, options?: SetOptions): Readable<T | E>;
16
+ /**
17
+ * Restores persistent value.
18
+ */
19
+ reset(key: string): Maybe<T | E>;
20
+ get(key: string, options?: GetOptions): Readable<Maybe<T, E>>;
21
+ extract(key: string): T | null;
22
+ delete(key: string): void;
23
+ clear(): void;
24
+ private create;
25
+ private stash;
26
+ private dump;
27
+ private load;
28
+ private bind;
29
+ }
30
+ type Input<T, M, Default> = M extends (arg: infer P) => T ? P : Default;
31
+ interface Options<T = unknown, E extends Error = Error> {
32
+ /**
33
+ * Fetches the value
34
+ */
35
+ get?: (key: string) => Promise<Exclude<any, Error> | E>;
36
+ /**
37
+ * Maps the fetched value
38
+ */
39
+ map?: (item: any) => T;
40
+ /**
41
+ * Time in milliseconds before revalidating the value.
42
+ * Default is 60 seconds.
43
+ */
44
+ revalidate?: number;
45
+ /**
46
+ * Whether to keep the values while revalidating.
47
+ * Default is false.
48
+ */
49
+ stale?: boolean;
50
+ /**
51
+ * Key to persist values.
52
+ */
53
+ persist?: string;
54
+ /**
55
+ * Do not refresh persistent values on application restart.
56
+ * Default is false.
57
+ *
58
+ * { permanent: true, revalidate: Infinity } prevents revalidation entirely.
59
+ */
60
+ permanent?: boolean;
61
+ /**
62
+ * Nullify values when the bound store is null.
63
+ */
64
+ bind?: Readable<unknown | null>;
65
+ }
66
+ export interface GetOptions {
67
+ /**
68
+ * Whether to fetch the value if it's not in the store. Defaults to true.
69
+ */
70
+ fetch?: boolean;
71
+ }
72
+ export interface SetOptions {
73
+ /**
74
+ * Whether value is transient. Defaults to false.
75
+ */
76
+ stash?: boolean;
77
+ }
78
+ declare function values<T, E extends Error = Error>(options?: Options<T, E>): Values<T, E>;
79
+ export { values, type Values };
package/dist/values.js ADDED
@@ -0,0 +1,148 @@
1
+ import { browser } from '$app/environment';
2
+ import { get, writable } from 'svelte/store';
3
+ class Values {
4
+ persistent;
5
+ values = {};
6
+ fetch;
7
+ map;
8
+ revalidate;
9
+ permanent;
10
+ stale;
11
+ persist;
12
+ ready = false;
13
+ dumping = null;
14
+ constructor(options) {
15
+ this.fetch = options.get;
16
+ this.map = options.map;
17
+ this.revalidate = options.revalidate ?? DEFAULTS.revalidate;
18
+ this.permanent = options.permanent ?? DEFAULTS.permanent;
19
+ this.stale = options.stale ?? DEFAULTS.stale;
20
+ this.persist = options.persist;
21
+ this.persistent = this.persist !== undefined;
22
+ if (browser) {
23
+ this.load();
24
+ if (options.bind !== undefined)
25
+ this.bind(options.bind);
26
+ }
27
+ }
28
+ set(key, item, options) {
29
+ const value = this.map?.(item) ?? item;
30
+ const entry = this.values[key] ?? (this.values[key] = this.create(value));
31
+ if (options?.stash === true)
32
+ this.stash(key);
33
+ else
34
+ delete entry.stash;
35
+ entry.store.set(value);
36
+ if (!(value instanceof Error) && this.ready && options?.stash !== true)
37
+ this.dump();
38
+ return this.values[key].store;
39
+ }
40
+ /**
41
+ * Restores persistent value.
42
+ */
43
+ reset(key) {
44
+ const stash = this.values[key]?.stash;
45
+ if (stash === undefined)
46
+ return this.extract(key);
47
+ if (stash === null)
48
+ this.delete(key);
49
+ else
50
+ this.set(key, stash);
51
+ return stash;
52
+ }
53
+ get(key, options) {
54
+ this.ready = true;
55
+ const value = (this.values[key] ??= this.create());
56
+ if (this.fetch === undefined || options?.fetch === false)
57
+ return value.store;
58
+ const stale = value.timestamp === 0 || value.timestamp + this.revalidate < Date.now();
59
+ if (stale) {
60
+ if (!this.stale)
61
+ value.store.set(null);
62
+ value.timestamp = Date.now();
63
+ this.fetch(key).then((data) => this.set(key, data));
64
+ }
65
+ return value.store;
66
+ }
67
+ extract(key) {
68
+ if (this.values[key] === undefined)
69
+ return null;
70
+ const value = get(this.values[key].store);
71
+ return value instanceof Error ? null : value;
72
+ }
73
+ delete(key) {
74
+ if (!(key in this.values))
75
+ return;
76
+ const value = this.values[key];
77
+ value.store.set(null);
78
+ value.timestamp = 0;
79
+ delete value.stash;
80
+ this.dump();
81
+ }
82
+ clear() {
83
+ for (const key of Object.keys(this.values))
84
+ this.delete(key);
85
+ this.ready = false;
86
+ }
87
+ create(value) {
88
+ return {
89
+ store: writable(value ?? null),
90
+ timestamp: this.permanent && value !== undefined ? Date.now() : 0
91
+ };
92
+ }
93
+ stash(key) {
94
+ const entry = this.values[key];
95
+ if (entry.stash !== undefined) {
96
+ console.warn('Stash overlap, skipping');
97
+ return;
98
+ }
99
+ const store = this.get(key, { fetch: false });
100
+ entry.stash ??= get(store);
101
+ }
102
+ dump(delay = true) {
103
+ if (this.persist === undefined || !browser)
104
+ return;
105
+ if (delay) {
106
+ this.dumping ??= setTimeout(() => this.dump(false), DUMP_GAP);
107
+ return;
108
+ }
109
+ else
110
+ this.dumping = null;
111
+ const data = {};
112
+ for (const [key, value] of Object.entries(this.values)) {
113
+ const state = value.stash ?? get(value.store);
114
+ if (state !== null && !(state instanceof Error))
115
+ data[key] = state;
116
+ }
117
+ if (Object.keys(data).length === 0)
118
+ localStorage.removeItem(this.persist);
119
+ else
120
+ localStorage.setItem(this.persist, JSON.stringify(data));
121
+ }
122
+ load() {
123
+ if (this.persist === undefined)
124
+ return;
125
+ const data = localStorage.getItem(this.persist);
126
+ if (data === null)
127
+ return;
128
+ const values = JSON.parse(data);
129
+ for (const [key, value] of Object.entries(values))
130
+ this.set(key, value);
131
+ }
132
+ bind(store) {
133
+ store.subscribe((value) => {
134
+ if (value === null)
135
+ this.clear();
136
+ });
137
+ }
138
+ }
139
+ const DUMP_GAP = 100; // should not be 0 to prevent unnecessary disk writes
140
+ const DEFAULTS = {
141
+ revalidate: 300_000,
142
+ permanent: false,
143
+ stale: false
144
+ };
145
+ function values(options = {}) {
146
+ return new Values(options);
147
+ }
148
+ export { values };
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "svas",
3
+ "version": "0.0.1",
4
+ "scripts": {
5
+ "dev": "vite dev",
6
+ "build": "vite build && npm run prepack",
7
+ "preview": "vite preview",
8
+ "prepare": "svelte-kit sync || echo ''",
9
+ "prepack": "svelte-kit sync && svelte-package && publint",
10
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
11
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
12
+ "lint": "eslint ."
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "!dist/**/*.test.*",
17
+ "!dist/**/*.spec.*"
18
+ ],
19
+ "sideEffects": [
20
+ "**/*.css"
21
+ ],
22
+ "svelte": "./dist/index.js",
23
+ "types": "./dist/index.d.ts",
24
+ "type": "module",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "svelte": "./dist/index.js"
29
+ }
30
+ },
31
+ "peerDependencies": {
32
+ "@lucide/svelte": "^0.510.0",
33
+ "svelte": "^5.0.0"
34
+ },
35
+ "devDependencies": {
36
+ "@eslint/compat": "^1.2.5",
37
+ "@eslint/js": "^9.18.0",
38
+ "@sveltejs/adapter-auto": "^6.0.0",
39
+ "@sveltejs/kit": "^2.16.0",
40
+ "@sveltejs/package": "^2.0.0",
41
+ "@sveltejs/vite-plugin-svelte": "^5.0.0",
42
+ "autoprefixer": "^10.4.20",
43
+ "eslint": "^9.18.0",
44
+ "eslint-plugin-svelte": "^3.0.0",
45
+ "globals": "^16.0.0",
46
+ "publint": "^0.3.2",
47
+ "svelte": "^5.0.0",
48
+ "svelte-check": "^4.0.0",
49
+ "tailwindcss": "^3.4.17",
50
+ "typescript": "^5.0.0",
51
+ "typescript-eslint": "^8.20.0",
52
+ "vite": "^6.2.6"
53
+ },
54
+ "keywords": [
55
+ "svelte"
56
+ ]
57
+ }