From 3b4c6ed4ebb31e2cd662548024967100783100ad Mon Sep 17 00:00:00 2001 From: Sander Hahn Date: Fri, 10 May 2019 18:56:54 +0200 Subject: [PATCH 1/4] typescript version of store --- .gitignore | 2 +- package.json | 3 +- rollup.config.js | 31 ++++++++++- src/store.ts | 130 +++++++++++++++++++++++++++++++++++++++++++++++ store.mjs | 85 ------------------------------- tsconfig.json | 3 +- 6 files changed, 165 insertions(+), 89 deletions(-) create mode 100644 src/store.ts delete mode 100644 store.mjs diff --git a/.gitignore b/.gitignore index 06671edc2b..7aa75b29f4 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ node_modules /compiler.js /index.js /internal.* -/store.js +/store.* /easing.js /motion.* /transition.js diff --git a/package.json b/package.json index ccbedde168..e0364bfa3a 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,8 @@ "dev": "rollup -cw", "pretest": "npm run build", "posttest": "agadoo src/internal/index.js", - "prepublishOnly": "export PUBLISH=true && npm run lint && npm test" + "prepublishOnly": "export PUBLISH=true && npm run lint && npm test", + "tsd": "tsc -d src/store.ts --outDir ." }, "repository": { "type": "git", diff --git a/rollup.config.js b/rollup.config.js index f7b2d07d4b..0d19e59d4a 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -87,8 +87,37 @@ export default [ external: id => id.startsWith('svelte/') }, + /* store.mjs */ + { + input: `src/store.ts`, + output: [ + { + file: `store.mjs`, + format: 'esm', + paths: id => id.startsWith('svelte/') && id.replace('svelte', '.') + }, + { + file: `store.js`, + format: 'cjs', + paths: id => id.startsWith('svelte/') && id.replace('svelte', '.') + } + ], + plugins: [ + is_publish + ? typescript({ + include: 'src/**', + exclude: 'src/internal/**', + typescript: require('typescript') + }) + : sucrase({ + transforms: ['typescript'] + }) + ], + external: id => id.startsWith('svelte/') + }, + // everything else - ...['index', 'store', 'easing', 'transition', 'animate'].map(name => ({ + ...['index', 'easing', 'transition', 'animate'].map(name => ({ input: `${name}.mjs`, output: { file: `${name}.js`, diff --git a/src/store.ts b/src/store.ts new file mode 100644 index 0000000000..e27059a4d7 --- /dev/null +++ b/src/store.ts @@ -0,0 +1,130 @@ +import { run_all, noop, safe_not_equal } from './internal/utils'; + +type Subscriber = (value: T) => void; + +type Unsubscriber = () => void; + +type Updater = (value: T) => T; + +type Invalidater = (value?: T) => void; + +type StartStopNotifier = (set: Subscriber) => Unsubscriber | void; + +export interface ReadableStore { + subscribe(run: Subscriber, invalidate?: Invalidater): Unsubscriber; +} + +export interface WritableStore extends ReadableStore { + set(value: T): void; + update(updater: Updater): void; +} + +type SubscribeInvalidateTuple = [Subscriber, Invalidater]; + +export function readable(value: T, start: StartStopNotifier): ReadableStore { + return { + subscribe: writable(value, start).subscribe, + }; +} + +export function writable(value: T, start: StartStopNotifier = noop): WritableStore { + let stop: Unsubscriber; + const subscribers: Array> = []; + + function set(new_value: T): void { + if (safe_not_equal(value, new_value)) { + value = new_value; + if (!stop) { + return; // not ready + } + subscribers.forEach((s) => s[1]()); + subscribers.forEach((s) => s[0](value)); + } + } + + function update(fn: Updater): void { + set(fn(value)); + } + + function subscribe(run: Subscriber, invalidate: Invalidater = noop): Unsubscriber { + const subscriber: SubscribeInvalidateTuple = [run, invalidate]; + subscribers.push(subscriber); + if (subscribers.length === 1) { + stop = start(set) || noop; + } + run(value); + + return () => { + const index = subscribers.indexOf(subscriber); + if (index !== -1) { + subscribers.splice(index, 1); + } + if (subscribers.length === 0) { + stop(); + } + }; + } + + return { set, update, subscribe }; +} + +export function derived( + stores: ReadableStore | Array>, + fn: (values: T | T[], set?: Subscriber) => T | Unsubscriber | void, + initial_value: T): ReadableStore { + + const single = !Array.isArray(stores); + const stores_array: Array> = single + ? [stores as ReadableStore] + : stores as Array>; + + const auto = fn.length < 2; + + return readable(initial_value, (set) => { + let inited = false; + const values: T[] = []; + + let pending = 0; + let cleanup = noop; + + const sync = () => { + if (pending) { + return; + } + cleanup(); + const result = fn(single ? values[0] : values, set); + if (auto) { + set(result as T); + } else { + cleanup = result as Unsubscriber || noop; + } + }; + + const unsubscribers = stores_array.map((store, i) => store.subscribe( + (value) => { + values[i] = value; + pending &= ~(1 << i); + if (inited) { + sync(); + } + }, + () => { + pending |= (1 << i); + }), + ); + + inited = true; + sync(); + + return function stop() { + run_all(unsubscribers); + cleanup(); + }; + }); +} + +export function get(store: ReadableStore): T { + let value: T | undefined; + store.subscribe((_: T) => value = _)(); + return value as T; +} diff --git a/store.mjs b/store.mjs deleted file mode 100644 index 624ede3dde..0000000000 --- a/store.mjs +++ /dev/null @@ -1,85 +0,0 @@ -import { run_all, noop, get_store_value, safe_not_equal } from './internal'; - -export function readable(value, start) { - return { - subscribe: writable(value, start).subscribe - }; -} - -export function writable(value, start = noop) { - let stop; - const subscribers = []; - - function set(new_value) { - if (safe_not_equal(value, new_value)) { - value = new_value; - if (!stop) return; // not ready - subscribers.forEach(s => s[1]()); - subscribers.forEach(s => s[0](value)); - } - } - - function update(fn) { - set(fn(value)); - } - - function subscribe(run, invalidate = noop) { - const subscriber = [run, invalidate]; - subscribers.push(subscriber); - if (subscribers.length === 1) stop = start(set) || noop; - run(value); - - return () => { - const index = subscribers.indexOf(subscriber); - if (index !== -1) subscribers.splice(index, 1); - if (subscribers.length === 0) stop(); - }; - } - - return { set, update, subscribe }; -} - -export function derived(stores, fn, initial_value) { - const single = !Array.isArray(stores); - if (single) stores = [stores]; - - const auto = fn.length < 2; - let value = {}; - - return readable(initial_value, set => { - let inited = false; - const values = []; - - let pending = 0; - let cleanup = noop; - - const sync = () => { - if (pending) return; - cleanup(); - const result = fn(single ? values[0] : values, set); - if (auto) set(result); - else cleanup = result || noop; - }; - - const unsubscribers = stores.map((store, i) => store.subscribe( - value => { - values[i] = value; - pending &= ~(1 << i); - if (inited) sync(); - }, - () => { - pending |= (1 << i); - }) - ); - - inited = true; - sync(); - - return function stop() { - run_all(unsubscribers); - cleanup(); - }; - }); -} - -export { get_store_value as get }; diff --git a/tsconfig.json b/tsconfig.json index fdb7367e05..5da2d13a01 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,8 @@ "allowJs": true, "lib": ["es5", "es6", "dom"], "importHelpers": true, - "moduleResolution": "node" + "moduleResolution": "node", + "strict": true }, "include": [ "src" From b6b7c621d062752121aced2a9108b1cc3c777179 Mon Sep 17 00:00:00 2001 From: Sander Hahn Date: Mon, 13 May 2019 00:25:32 +0200 Subject: [PATCH 2/4] run tsd on prepare --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e0364bfa3a..24feedd1e7 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "precodecov": "npm run coverage", "lint": "eslint src test/*.js", "build": "rollup -c", - "prepare": "npm run build", + "prepare": "npm run build && npm run tsd", "dev": "rollup -cw", "pretest": "npm run build", "posttest": "agadoo src/internal/index.js", From 52eda23a53061a951fee06050733556a7ad42773 Mon Sep 17 00:00:00 2001 From: Sander Hahn Date: Wed, 15 May 2019 07:34:06 +0200 Subject: [PATCH 3/4] advanced type for derived --- src/store.ts | 32 ++++++++++++++++++------------- test/{store/index.js => store.ts} | 28 ++++++++++++++++++++++----- tsconfig.json | 3 +-- 3 files changed, 43 insertions(+), 20 deletions(-) rename test/{store/index.js => store.ts} (88%) diff --git a/src/store.ts b/src/store.ts index e27059a4d7..462f155c2a 100644 --- a/src/store.ts +++ b/src/store.ts @@ -10,24 +10,24 @@ type Invalidater = (value?: T) => void; type StartStopNotifier = (set: Subscriber) => Unsubscriber | void; -export interface ReadableStore { +export interface Readable { subscribe(run: Subscriber, invalidate?: Invalidater): Unsubscriber; } -export interface WritableStore extends ReadableStore { +export interface Writable extends Readable { set(value: T): void; update(updater: Updater): void; } type SubscribeInvalidateTuple = [Subscriber, Invalidater]; -export function readable(value: T, start: StartStopNotifier): ReadableStore { +export function readable(value: T, start: StartStopNotifier): Readable { return { subscribe: writable(value, start).subscribe, }; } -export function writable(value: T, start: StartStopNotifier = noop): WritableStore { +export function writable(value: T, start: StartStopNotifier = noop): Writable { let stop: Unsubscriber; const subscribers: Array> = []; @@ -68,21 +68,27 @@ export function writable(value: T, start: StartStopNotifier = noop): Writa return { set, update, subscribe }; } -export function derived( - stores: ReadableStore | Array>, - fn: (values: T | T[], set?: Subscriber) => T | Unsubscriber | void, - initial_value: T): ReadableStore { +type Stores = Readable | [Readable, ...Array>]; + +type StoresValues = T extends Readable ? U : + { [K in keyof T]: T[K] extends Readable ? U : never }; + +export function derived( + stores: S, + fn: (values: StoresValues, set?: Subscriber) => T | Unsubscriber | void, + initial_value?: T, +): Readable { const single = !Array.isArray(stores); - const stores_array: Array> = single - ? [stores as ReadableStore] - : stores as Array>; + const stores_array: Array> = single + ? [stores as Readable] + : stores as Array>; const auto = fn.length < 2; return readable(initial_value, (set) => { let inited = false; - const values: T[] = []; + const values: StoresValues = [] as StoresValues; let pending = 0; let cleanup = noop; @@ -123,7 +129,7 @@ export function derived( }); } -export function get(store: ReadableStore): T { +export function get(store: Readable): T { let value: T | undefined; store.subscribe((_: T) => value = _)(); return value as T; diff --git a/test/store/index.js b/test/store.ts similarity index 88% rename from test/store/index.js rename to test/store.ts index 5f9176cffb..9b3d5d1788 100644 --- a/test/store/index.js +++ b/test/store.ts @@ -1,5 +1,5 @@ import * as assert from 'assert'; -import { readable, writable, derived, get } from '../../store.js'; +import { readable, writable, derived, get } from '../store'; describe('store', () => { describe('writable', () => { @@ -30,10 +30,10 @@ describe('store', () => { return () => called -= 1; }); - const unsubscribe1 = store.subscribe(() => {}); + const unsubscribe1 = store.subscribe(() => { }); assert.equal(called, 1); - const unsubscribe2 = store.subscribe(() => {}); + const unsubscribe2 = store.subscribe(() => { }); assert.equal(called, 1); unsubscribe1(); @@ -73,7 +73,7 @@ describe('store', () => { set(0); return () => { - tick = () => {}; + tick = () => { }; running = false; }; }); @@ -242,11 +242,29 @@ describe('store', () => { assert.deepEqual(cleaned_up, [2, 3, 4]); }); + + it('allows derived with different types', () => { + const a = writable('one'); + const b = writable(1); + const c = derived([a, b], ([a, b]) => `${a} ${b}`); + + const values: string[] = []; + + const unsubscribe = c.subscribe(value => { + values.push(value); + }); + + a.set('two'); + b.set(2); + assert.deepEqual(values, 'two 2'); + + unsubscribe(); + }); }); describe('get', () => { it('gets the current value of a store', () => { - const store = readable(42, () => {}); + const store = readable(42, () => { }); assert.equal(get(store), 42); }); }); diff --git a/tsconfig.json b/tsconfig.json index 5da2d13a01..fdb7367e05 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,8 +7,7 @@ "allowJs": true, "lib": ["es5", "es6", "dom"], "importHelpers": true, - "moduleResolution": "node", - "strict": true + "moduleResolution": "node" }, "include": [ "src" From e45aa0f85f0833ce2daab593425b717baf84149b Mon Sep 17 00:00:00 2001 From: Sander Hahn Date: Wed, 15 May 2019 09:26:54 +0200 Subject: [PATCH 4/4] simplify test --- test/store.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/test/store.ts b/test/store.ts index 9b3d5d1788..f931a841d7 100644 --- a/test/store.ts +++ b/test/store.ts @@ -248,17 +248,11 @@ describe('store', () => { const b = writable(1); const c = derived([a, b], ([a, b]) => `${a} ${b}`); - const values: string[] = []; - - const unsubscribe = c.subscribe(value => { - values.push(value); - }); + assert.deepEqual(get(c), 'one 1'); a.set('two'); b.set(2); - assert.deepEqual(values, 'two 2'); - - unsubscribe(); + assert.deepEqual(get(c), 'two 2'); }); });