From 3b84cacbaa11c128f95c2deab8b6bc45f4a67607 Mon Sep 17 00:00:00 2001 From: Timothy Farrell Date: Thu, 9 Nov 2017 16:59:45 -0600 Subject: [PATCH] Rename observable to prop(erty) to distinguish from TC39 --- README.md | 2 +- packages/frptools/README.md | 75 +++++++++---------- packages/frptools/package.json | 4 +- packages/frptools/spec/bundle.spec.js | 16 ++-- packages/frptools/spec/computed.spec.js | 16 ++-- .../{observable.spec.js => property.spec.js} | 14 ++-- packages/frptools/src/bundle.js | 10 +-- packages/frptools/src/index.js | 2 +- .../src/{observable.js => property.js} | 4 +- packages/gallery/package.json | 2 +- packages/gallery/src/interface/image.js | 4 +- packages/gallery/src/utils/comparators.js | 2 +- packages/gallery/src/utils/livearray.js | 6 +- 13 files changed, 75 insertions(+), 82 deletions(-) rename packages/frptools/spec/{observable.spec.js => property.spec.js} (89%) rename packages/frptools/src/{observable.js => property.js} (80%) diff --git a/README.md b/README.md index 016bd99..c97e722 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ These are my personal projects. ## [FRP tools](./packages/frptools/README.md) -Observable and computed value stores designed to work together for storing real state and derived +Observable property and computed value stores designed to work together for storing real and derived state. ## [Portal](./packages/portal/README.md) diff --git a/packages/frptools/README.md b/packages/frptools/README.md index 9bbe686..080b250 100644 --- a/packages/frptools/README.md +++ b/packages/frptools/README.md @@ -1,21 +1,13 @@ # FRP tools -Observer and Computed value stores designed to work together for storing real state and derived -state. +Property and Computed value stores designed to work together for storing real and derived state. -# [observable](./src/observable.js) +# [property](./src/property.js) -`observable` is a simple value store that can report when its value changes. It is good for wrapping -external props passed into a component so compute types can dependent on them. It can also be used -to receive events such as _window.onresize_ to always provide the current viewport size. - -NOTE: Javascript has a proposal for a thing called an -[Observable](https://github.com/tc39/proposal-observable). This is not an implementation of that. -They do serve similar functions (provide a way to communicate when a value changes) but the tc39 -proposal is more about event input sources and can communicate errors and be extended. This -implementation is designed to be as small and simple as possible. Extending it is done via -[Computed] instances depending on them. I may rename `observable` to `property` in a future major -release to avoid this confusion. +A `property` is a simple value store that can report when its value changes. It is good for wrapping +external values passed into a component so compute types can dependent on them and only recompute +when these values change. It can also be used to receive events such as _window.onresize_ to always +provide the current viewport size. ## Usage @@ -24,7 +16,7 @@ release to avoid this confusion. Creates and sets initial value to `true` ```js -const inViewport = observable(true); +const inViewport = prop(true); ``` ### Read @@ -48,15 +40,15 @@ inViewport(false); ### Subscribe to changes -Call the `subscribe` method with a callback that will be called when the observable is changed to a -different value. The returned function can be called to unsubscribe from the observable. When called -it will provide the count of remaining subscriptions. +Call the `subscribe` method with a callback that will be called when the property value changes. The +returned function can be called to unsubscribe from the property. When called it will provide the +count of remaining subscriptions. ```js const unsubscribe = inViewport.subscribe(console.log.bind(console)); const remainingSubscriptionCount = unsubscribe(); -inViewport.unsubscribeAll(); // Call unsubscribeAll to remove child observables/computeds. +inViewport.unsubscribeAll(); // Call unsubscribeAll to remove child property/computed subscriptions. ``` ### Provide a comparator for complex types @@ -74,13 +66,13 @@ function setEquals(a, b) { ); } -const a = observable(new Set([1, 2]), setEquals); +const a = prop(new Set([1, 2]), setEquals); ``` # [computed](./src/computed.js) -`computed` is a functional store that depends on the values of observables or other computeds. They -derive value from observables rather than store value and hence cannot be set directly. +`computed` is a functional store that depends on the values of properties or other computeds. They +derive value from properties rather than store value and hence cannot be set directly. ## Behavior @@ -95,7 +87,7 @@ is set, otherwise it just return the stored result from the last time it compute ```js const showDialog = computed( (inVP, shouldShow) => inVP && shouldShow, // computation function - [inViewport, shouldShow] // array of dependencies, can be either observable or computed + [inViewport, shouldShow] // array of dependencies, can be either a property or computed ); ``` @@ -112,7 +104,7 @@ Call it to receive the stored value, recomputing if necessary. ### Subscribe to changes Call the subscribe method with a callback that will be called when the computed result changes to a -different value. The returned function can be called to unsubscribe from the observable. When called +different value. The returned function can be called to unsubscribe from the property. When called it will provide the count of remaining subscriptions. ```js @@ -125,8 +117,8 @@ changes. This could negatively performance if it depends on multiple values that and the computation function is non-trivial. For example: ```js -const inViewport = observable(false); -const shouldShow = observable(false); +const inViewport = prop(false); +const shouldShow = prop(false); const showDialog = computed((inVP, shouldShow) => inVP && shouldShow, [inViewport, shouldShow]); @@ -142,7 +134,7 @@ shouldShow(false); // showDialog result recomputed, console.log is not called. showDialog(); // showDialog does not recompute, console.log is not called. `false` is returned. showDialog.detach(); // Call detach to remove this computed from the logic tree. -showDialog.unsubscribeAll(); // Call unsubscribeAll to remove child observables/computeds. +showDialog.unsubscribeAll(); // Call unsubscribeAll to remove child property/computed subscriptions. ``` ### Provide a comparator for complex types @@ -164,25 +156,26 @@ function _intersection(a, b) { return new Set([...a].filter(x => b.has(x))); } -const a = observable(new Set([1, 2]), setEquals); -const b = observable(new Set([2, 3]), setEquals); +const a = prop(new Set([1, 2]), setEquals); +const b = prop(new Set([2, 3]), setEquals); const intersection = computed(_intersection, [a, b], setEquals); ``` # [bundle](./src/bundle.js) -`bundle` is a wrapper around a group of `observables` for the purpose of applying changes to all of -them at once without having to trigger a subscription that may depend on more than observable in the +`bundle` is a wrapper around a group of properties for the purpose of applying changes to all of +them at once without having to trigger a subscription that may depend on more than property in the group. -Another way to think of a `bundle` is an `observable` that takes an object and exposes the -properties as individual observables. +Another way to think of a `bundle` is a `property` that takes an object and exposes the object's +properties as individual `property` instances. ## Behavior -A `bundle` wraps observables to intercept dependency hooks in such a way that updating all -observables can happen at once before any downstream `computeds` are evaluated. A bundle returns a -function that can be called with an object to set values for the mapped member observables. +A `bundle` wraps properties to intercept dependency hooks in such a way that updating all `property` +instances can happen at once before any downstream `computed` instances are evaluated. A bundle +returns a function that can be called with an object to set values for the mapped member `property` +instances. ## Usage @@ -190,14 +183,14 @@ function that can be called with an object to set values for the mapped member o ```js const layoutEventBundle = bundle({ - width: observable(1), - height: observable(2) + width: prop(1), + height: prop(2) }); const ratio = computed((a, b) => a / b, [layoutEventBundle.width, layoutEventBundle.height]); ratio.subscribe(render); ``` -### Change Member Observables atomically +### Change Member Properties atomically ```js layoutEventBundle({ width: 640, height: 480 }); @@ -207,11 +200,11 @@ layoutEventBundle({ width: 640, height: 480 }); change. But bundle allows both values to change and `ratio` will only be evaluated once and `render` called once. -### Change Member Observables individually +### Change Member Properties individually ```js layoutEventBundle.width(640); layoutEventBundle.height(480); ``` -The observables exposed by the bundle can also be updated apart from their grouping. +The properties exposed by the bundle can also be updated apart from their grouping. diff --git a/packages/frptools/package.json b/packages/frptools/package.json index d527a1a..1b9f4de 100644 --- a/packages/frptools/package.json +++ b/packages/frptools/package.json @@ -1,7 +1,7 @@ { "name": "frptools", - "version": "1.2.0", - "description": "Observable and Computed data streams", + "version": "2.0.0", + "description": "Observable Property and Computed data streams", "main": "lib/index.js", "jsnext:main": "src/index.js", "files": ["dist", "lib", "src"], diff --git a/packages/frptools/spec/bundle.spec.js b/packages/frptools/spec/bundle.spec.js index db7d39e..12a5a13 100644 --- a/packages/frptools/spec/bundle.spec.js +++ b/packages/frptools/spec/bundle.spec.js @@ -1,4 +1,4 @@ -const { observable, computed, bundle } = require('../lib/index.js'); +const { prop, computed, bundle } = require('../lib/index.js'); describe('bundle', () => { const methods = { @@ -13,10 +13,10 @@ describe('bundle', () => { spyOn(methods, 'getVal').and.callThrough(); }); - it('bundles observable changes together', () => { + it('bundles property changes together', () => { const a = bundle({ - a: observable(0), - b: observable(10) + a: prop(0), + b: prop(10) }); const b = computed(methods.square, [a.a]); const c = computed(methods.add, [a.a, a.b]); @@ -47,8 +47,8 @@ describe('bundle', () => { }); it('unbundled changes are less efficient', () => { - const a = observable(0); - const _b = observable(10); + const a = prop(0); + const _b = prop(10); const b = computed(methods.square, [a]); const c = computed(methods.add, [a, _b]); @@ -80,8 +80,8 @@ describe('bundle', () => { it('allows individual members to be updated', () => { const a = bundle({ - a: observable(0), - b: observable(10) + a: prop(0), + b: prop(10) }); const b = computed(methods.square, [a.a]); const c = computed(methods.add, [a.a, a.b]); diff --git a/packages/frptools/spec/computed.spec.js b/packages/frptools/spec/computed.spec.js index d6ff30e..cf6cb50 100644 --- a/packages/frptools/spec/computed.spec.js +++ b/packages/frptools/spec/computed.spec.js @@ -1,11 +1,11 @@ -const { observable, computed } = require('../lib/index.js'); +const { prop, computed } = require('../lib/index.js'); describe('computed', () => { const add = (a, b) => a + b; const square = a => a * a; it('returns the value computed from its dependencies', () => { - const a = observable(0); + const a = prop(0); const b = computed(square, [a]); const c = computed(add, [a, b]); @@ -28,7 +28,7 @@ describe('computed', () => { it('only computes when called', () => { let runCount = 0; let currentValue = 1; - const a = observable(0); + const a = prop(0); const b = computed( val => { runCount += 1; @@ -59,7 +59,7 @@ describe('computed', () => { let runCount = 0; let subRunCount = 0; let currentValue = 1; - const a = observable(0); + const a = prop(0); const b = computed( val => { runCount += 1; @@ -99,7 +99,7 @@ describe('computed', () => { let runCount = 0; let subRunCount = 0; let currentValue = 1; - const a = observable(0); + const a = prop(0); const b = computed( val => { runCount += 1; @@ -142,7 +142,7 @@ describe('computed', () => { }); it('can be detached', () => { - const a = observable(2); + const a = prop(2); const b = computed(square, [a]); const c = computed(add, [a, b]); @@ -173,8 +173,8 @@ describe('computed', () => { return new Set([...a].filter(x => b.has(x))); } - const a = observable(new Set([1, 2]), setEquals); - const b = observable(new Set([2, 3]), setEquals); + const a = prop(new Set([1, 2]), setEquals); + const b = prop(new Set([2, 3]), setEquals); const ABintersection = computed(intersection, [a, b], setEquals); expect(runCount).toEqual(0); diff --git a/packages/frptools/spec/observable.spec.js b/packages/frptools/spec/property.spec.js similarity index 89% rename from packages/frptools/spec/observable.spec.js rename to packages/frptools/spec/property.spec.js index fa1c8cc..05a49e0 100644 --- a/packages/frptools/spec/observable.spec.js +++ b/packages/frptools/spec/property.spec.js @@ -1,13 +1,13 @@ -const { observable } = require('../lib/index.js'); +const { prop } = require('../lib/index.js'); -describe('observable', () => { +describe('A property', () => { it('returns its initialized value', () => { - const a = observable(true); + const a = prop(true); expect(a()).toEqual(true); }); it('returns its set value', () => { - const a = observable(); + const a = prop(); expect(a()).toEqual(undefined); expect(a(true)).toEqual(true); }); @@ -15,7 +15,7 @@ describe('observable', () => { it('returns notifies dependents of updates', () => { let runCount = 0; let currentValue = 1; - const a = observable(); + const a = prop(); a.subscribe(val => { runCount += 1; expect(val).toEqual(currentValue); @@ -39,7 +39,7 @@ describe('observable', () => { it('honors cancelled subscriptions', () => { let runCount = 0; let currentValue = 1; - const a = observable(); + const a = prop(); const cancelSubscription = a.subscribe(val => { runCount += 1; expect(val).toEqual(currentValue); @@ -74,7 +74,7 @@ describe('observable', () => { let runCount = 0; - const a = observable(new Set([1, 2]), setEquals); + const a = prop(new Set([1, 2]), setEquals); a.subscribe(() => (runCount += 1)); expect([...a()]).toEqual([1, 2]); expect(runCount).toEqual(0); diff --git a/packages/frptools/src/bundle.js b/packages/frptools/src/bundle.js index 3169467..6e482dc 100644 --- a/packages/frptools/src/bundle.js +++ b/packages/frptools/src/bundle.js @@ -1,4 +1,4 @@ -export function bundle(observables) { +export function bundle(props) { const activeSubscribers = new Set(); let activeUpdate = false; @@ -6,9 +6,9 @@ export function bundle(observables) { const result = {}; activeUpdate = true; Object.keys(values) - .filter(k => typeof observables[k] === 'function') + .filter(k => typeof props[k] === 'function') .forEach(k => { - result[k] = observables[k](values[k]); + result[k] = props[k](values[k]); }); const subscribers = Array.from(activeSubscribers); @@ -32,8 +32,8 @@ export function bundle(observables) { }); }; - Object.keys(observables).forEach(k => { - const obs = observables[k]; + Object.keys(props).forEach(k => { + const obs = props[k]; accessor[k] = obs; obs._d = subscriptionFactory(obs._d); diff --git a/packages/frptools/src/index.js b/packages/frptools/src/index.js index 089f3ba..666042a 100644 --- a/packages/frptools/src/index.js +++ b/packages/frptools/src/index.js @@ -1,3 +1,3 @@ -export { observable } from './observable'; +export { prop } from './property'; export { computed } from './computed'; export { bundle } from './bundle'; diff --git a/packages/frptools/src/observable.js b/packages/frptools/src/property.js similarity index 80% rename from packages/frptools/src/observable.js rename to packages/frptools/src/property.js index 6c6180f..c3719c3 100644 --- a/packages/frptools/src/observable.js +++ b/packages/frptools/src/property.js @@ -1,9 +1,9 @@ import { eq } from './util.js'; -export function observable(store, comparator = eq) { +export function prop(store, comparator = eq) { const subscribers = new Set(); - const accessor = function _observable(newVal) { + const accessor = function _prop(newVal) { if (newVal !== undefined && !comparator(store, newVal)) { store = newVal; subscribers.forEach(s => s(store)); diff --git a/packages/gallery/package.json b/packages/gallery/package.json index 0c361e1..d5246e0 100644 --- a/packages/gallery/package.json +++ b/packages/gallery/package.json @@ -16,7 +16,7 @@ "domvm": "~3.2.1", "exif-parser": "~0.1.9", "extract-text-webpack-plugin": "^3.0.2", - "frptools": "1.2.0", + "frptools": "2.0.0", "pica": "~2.0.8", "pouchdb-adapter-http": "~6.3.4", "pouchdb-adapter-idb": "~6.3.4", diff --git a/packages/gallery/src/interface/image.js b/packages/gallery/src/interface/image.js index 8f8795c..4b39d2d 100644 --- a/packages/gallery/src/interface/image.js +++ b/packages/gallery/src/interface/image.js @@ -1,11 +1,11 @@ import { defineView, defineElement as el } from 'domvm'; -import { observable, computed } from 'frptools'; +import { prop, computed } from 'frptools'; import * as image from '../data/image.js'; export function ImageView(vm, model) { const { addTag } = model; - const imageData = observable(null); + const imageData = prop(null); let imageId = null; function onAddTag(image_id) { diff --git a/packages/gallery/src/utils/comparators.js b/packages/gallery/src/utils/comparators.js index ecbb658..36e73ac 100644 --- a/packages/gallery/src/utils/comparators.js +++ b/packages/gallery/src/utils/comparators.js @@ -2,7 +2,7 @@ import { extractID } from './conversion.js'; import { equals } from './set.js'; export function pouchDocArrayComparator(a, b) { - if (!Array.isArray(b)) { + if (!Array.isArray(a)) { return false; } const aIDs = a.map(extractID); diff --git a/packages/gallery/src/utils/livearray.js b/packages/gallery/src/utils/livearray.js index daffbef..3e09d81 100644 --- a/packages/gallery/src/utils/livearray.js +++ b/packages/gallery/src/utils/livearray.js @@ -1,4 +1,4 @@ -import { observable, computed } from 'frptools'; +import { prop, computed } from 'frptools'; import { matchesSelector } from 'pouchdb-selector-core'; import { getDatabase } from '../services/db.js'; @@ -54,8 +54,8 @@ export function LiveArray(db, selector, watcher) { const _watcher = watcher || Watcher(db, selector); let changeSub = null; - const ready = observable(false); - const data = observable({ docs: [] }); + const ready = prop(false); + const data = prop({ docs: [] }); const docs = computed(r => r.docs, [data], pouchDocArrayComparator); const idSet = () => docs().reduce((acc, d) => acc.add(d._id), new Set());