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 +58 -0
- package/dist/Maybe.d.ts +2 -0
- package/dist/Maybe.js +1 -0
- package/dist/async/Async.d.ts +11 -0
- package/dist/async/Async.js +1 -0
- package/dist/async/Async.svelte +35 -0
- package/dist/async/Async.svelte.d.ts +18 -0
- package/dist/async/index.d.ts +1 -0
- package/dist/async/index.js +1 -0
- package/dist/collection.d.ts +63 -0
- package/dist/collection.js +165 -0
- package/dist/combined.d.ts +6 -0
- package/dist/combined.js +24 -0
- package/dist/ensure.d.ts +3 -0
- package/dist/ensure.js +7 -0
- package/dist/having.d.ts +7 -0
- package/dist/having.js +22 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +9 -0
- package/dist/ok.d.ts +1 -0
- package/dist/ok.js +3 -0
- package/dist/sync.d.ts +9 -0
- package/dist/sync.js +14 -0
- package/dist/value.d.ts +36 -0
- package/dist/value.js +57 -0
- package/dist/values.d.ts +79 -0
- package/dist/values.js +148 -0
- package/package.json +57 -0
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
|
+
```
|
package/dist/Maybe.d.ts
ADDED
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 {};
|
package/dist/combined.js
ADDED
|
@@ -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
|
+
}
|
package/dist/ensure.d.ts
ADDED
package/dist/ensure.js
ADDED
package/dist/having.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -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
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
|
+
}
|
package/dist/value.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/values.d.ts
ADDED
|
@@ -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
|
+
}
|