Skip to content

Commit 8d539d8

Browse files
authored
Merge pull request #2733 from sanderhahn/master
typescript version of store
2 parents 0bf9910 + e45aa0f commit 8d539d8

File tree

6 files changed

+187
-94
lines changed

6 files changed

+187
-94
lines changed

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ node_modules
66
/compiler.js
77
/index.js
88
/internal.*
9-
/store.js
9+
/store.*
1010
/easing.js
1111
/motion.*
1212
/transition.js

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,12 @@
2727
"precodecov": "npm run coverage",
2828
"lint": "eslint src test/*.js",
2929
"build": "rollup -c",
30-
"prepare": "npm run build",
30+
"prepare": "npm run build && npm run tsd",
3131
"dev": "rollup -cw",
3232
"pretest": "npm run build",
3333
"posttest": "agadoo src/internal/index.js",
34-
"prepublishOnly": "export PUBLISH=true && npm run lint && npm test"
34+
"prepublishOnly": "export PUBLISH=true && npm run lint && npm test",
35+
"tsd": "tsc -d src/store.ts --outDir ."
3536
},
3637
"repository": {
3738
"type": "git",

rollup.config.js

+30-1
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,37 @@ export default [
8787
external: id => id.startsWith('svelte/')
8888
},
8989

90+
/* store.mjs */
91+
{
92+
input: `src/store.ts`,
93+
output: [
94+
{
95+
file: `store.mjs`,
96+
format: 'esm',
97+
paths: id => id.startsWith('svelte/') && id.replace('svelte', '.')
98+
},
99+
{
100+
file: `store.js`,
101+
format: 'cjs',
102+
paths: id => id.startsWith('svelte/') && id.replace('svelte', '.')
103+
}
104+
],
105+
plugins: [
106+
is_publish
107+
? typescript({
108+
include: 'src/**',
109+
exclude: 'src/internal/**',
110+
typescript: require('typescript')
111+
})
112+
: sucrase({
113+
transforms: ['typescript']
114+
})
115+
],
116+
external: id => id.startsWith('svelte/')
117+
},
118+
90119
// everything else
91-
...['index', 'store', 'easing', 'transition', 'animate'].map(name => ({
120+
...['index', 'easing', 'transition', 'animate'].map(name => ({
92121
input: `${name}.mjs`,
93122
output: {
94123
file: `${name}.js`,

src/store.ts

+136
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import { run_all, noop, safe_not_equal } from './internal/utils';
2+
3+
type Subscriber<T> = (value: T) => void;
4+
5+
type Unsubscriber = () => void;
6+
7+
type Updater<T> = (value: T) => T;
8+
9+
type Invalidater<T> = (value?: T) => void;
10+
11+
type StartStopNotifier<T> = (set: Subscriber<T>) => Unsubscriber | void;
12+
13+
export interface Readable<T> {
14+
subscribe(run: Subscriber<T>, invalidate?: Invalidater<T>): Unsubscriber;
15+
}
16+
17+
export interface Writable<T> extends Readable<T> {
18+
set(value: T): void;
19+
update(updater: Updater<T>): void;
20+
}
21+
22+
type SubscribeInvalidateTuple<T> = [Subscriber<T>, Invalidater<T>];
23+
24+
export function readable<T>(value: T, start: StartStopNotifier<T>): Readable<T> {
25+
return {
26+
subscribe: writable(value, start).subscribe,
27+
};
28+
}
29+
30+
export function writable<T>(value: T, start: StartStopNotifier<T> = noop): Writable<T> {
31+
let stop: Unsubscriber;
32+
const subscribers: Array<SubscribeInvalidateTuple<T>> = [];
33+
34+
function set(new_value: T): void {
35+
if (safe_not_equal(value, new_value)) {
36+
value = new_value;
37+
if (!stop) {
38+
return; // not ready
39+
}
40+
subscribers.forEach((s) => s[1]());
41+
subscribers.forEach((s) => s[0](value));
42+
}
43+
}
44+
45+
function update(fn: Updater<T>): void {
46+
set(fn(value));
47+
}
48+
49+
function subscribe(run: Subscriber<T>, invalidate: Invalidater<T> = noop): Unsubscriber {
50+
const subscriber: SubscribeInvalidateTuple<T> = [run, invalidate];
51+
subscribers.push(subscriber);
52+
if (subscribers.length === 1) {
53+
stop = start(set) || noop;
54+
}
55+
run(value);
56+
57+
return () => {
58+
const index = subscribers.indexOf(subscriber);
59+
if (index !== -1) {
60+
subscribers.splice(index, 1);
61+
}
62+
if (subscribers.length === 0) {
63+
stop();
64+
}
65+
};
66+
}
67+
68+
return { set, update, subscribe };
69+
}
70+
71+
type Stores = Readable<any> | [Readable<any>, ...Array<Readable<any>>];
72+
73+
type StoresValues<T> = T extends Readable<infer U> ? U :
74+
{ [K in keyof T]: T[K] extends Readable<infer U> ? U : never };
75+
76+
export function derived<T, S extends Stores>(
77+
stores: S,
78+
fn: (values: StoresValues<S>, set?: Subscriber<T>) => T | Unsubscriber | void,
79+
initial_value?: T,
80+
): Readable<T> {
81+
82+
const single = !Array.isArray(stores);
83+
const stores_array: Array<Readable<any>> = single
84+
? [stores as Readable<any>]
85+
: stores as Array<Readable<any>>;
86+
87+
const auto = fn.length < 2;
88+
89+
return readable(initial_value, (set) => {
90+
let inited = false;
91+
const values: StoresValues<S> = [] as StoresValues<S>;
92+
93+
let pending = 0;
94+
let cleanup = noop;
95+
96+
const sync = () => {
97+
if (pending) {
98+
return;
99+
}
100+
cleanup();
101+
const result = fn(single ? values[0] : values, set);
102+
if (auto) {
103+
set(result as T);
104+
} else {
105+
cleanup = result as Unsubscriber || noop;
106+
}
107+
};
108+
109+
const unsubscribers = stores_array.map((store, i) => store.subscribe(
110+
(value) => {
111+
values[i] = value;
112+
pending &= ~(1 << i);
113+
if (inited) {
114+
sync();
115+
}
116+
},
117+
() => {
118+
pending |= (1 << i);
119+
}),
120+
);
121+
122+
inited = true;
123+
sync();
124+
125+
return function stop() {
126+
run_all(unsubscribers);
127+
cleanup();
128+
};
129+
});
130+
}
131+
132+
export function get<T>(store: Readable<T>): T {
133+
let value: T | undefined;
134+
store.subscribe((_: T) => value = _)();
135+
return value as T;
136+
}

store.mjs

-85
This file was deleted.

test/store/index.js test/store.ts

+17-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as assert from 'assert';
2-
import { readable, writable, derived, get } from '../../store.js';
2+
import { readable, writable, derived, get } from '../store';
33

44
describe('store', () => {
55
describe('writable', () => {
@@ -30,10 +30,10 @@ describe('store', () => {
3030
return () => called -= 1;
3131
});
3232

33-
const unsubscribe1 = store.subscribe(() => {});
33+
const unsubscribe1 = store.subscribe(() => { });
3434
assert.equal(called, 1);
3535

36-
const unsubscribe2 = store.subscribe(() => {});
36+
const unsubscribe2 = store.subscribe(() => { });
3737
assert.equal(called, 1);
3838

3939
unsubscribe1();
@@ -73,7 +73,7 @@ describe('store', () => {
7373
set(0);
7474

7575
return () => {
76-
tick = () => {};
76+
tick = () => { };
7777
running = false;
7878
};
7979
});
@@ -242,11 +242,23 @@ describe('store', () => {
242242

243243
assert.deepEqual(cleaned_up, [2, 3, 4]);
244244
});
245+
246+
it('allows derived with different types', () => {
247+
const a = writable('one');
248+
const b = writable(1);
249+
const c = derived([a, b], ([a, b]) => `${a} ${b}`);
250+
251+
assert.deepEqual(get(c), 'one 1');
252+
253+
a.set('two');
254+
b.set(2);
255+
assert.deepEqual(get(c), 'two 2');
256+
});
245257
});
246258

247259
describe('get', () => {
248260
it('gets the current value of a store', () => {
249-
const store = readable(42, () => {});
261+
const store = readable(42, () => { });
250262
assert.equal(get(store), 42);
251263
});
252264
});

0 commit comments

Comments
 (0)