├── observable ├── share.js ├── static │ ├── create.js │ ├── return.js │ ├── throw.js │ ├── array.js │ ├── interval.js │ ├── fromevent.js │ └── range.js ├── map.js ├── filter.js ├── reduce.js ├── find.js ├── take.js ├── do.js ├── last.js ├── takeUntil.js ├── merge.js ├── flatmap.js ├── concat.js └── publish.js ├── rx.global.js ├── .gitignore ├── observer └── static │ └── create.js ├── utils └── extend.js ├── webpack.config.js ├── subscriber.js ├── package.json ├── .eslintrc.json ├── disposable.js ├── compositeobserver.js ├── observer.js ├── rx.js ├── observable.js ├── index.html └── README.md /observable/share.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rx.global.js: -------------------------------------------------------------------------------- 1 | import Rx from './rx'; 2 | 3 | window.Rx = Rx; 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | 3 | dist 4 | node_modules 5 | bower_modules 6 | -------------------------------------------------------------------------------- /observable/static/create.js: -------------------------------------------------------------------------------- 1 | import Observable from './../../observable'; 2 | 3 | export default function create(subscribeFn) { 4 | return new Observable(subscribeFn); 5 | } 6 | -------------------------------------------------------------------------------- /observer/static/create.js: -------------------------------------------------------------------------------- 1 | import Observer from './../../observer'; 2 | 3 | export default function create(onNext, onError, onComplete) { 4 | return new Observer(onNext, onError, onComplete); 5 | } 6 | -------------------------------------------------------------------------------- /observable/static/return.js: -------------------------------------------------------------------------------- 1 | import Observable from './../../observable'; 2 | 3 | export default function createFromReturn(value) { 4 | return new Observable(observer => { 5 | observer.next(value); 6 | observer.complete(); 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /observable/static/throw.js: -------------------------------------------------------------------------------- 1 | import Observable from './../../observable'; 2 | 3 | export default function createFromReturn(error) { 4 | return new Observable(observer => { 5 | observer.error(error instanceof Error ? error : new Error(error)); 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /observable/static/array.js: -------------------------------------------------------------------------------- 1 | import Observable from './../../observable'; 2 | 3 | export default function createFromArray(array) { 4 | return new Observable(observer => { 5 | array.forEach(item => observer.next(item)); 6 | observer.complete(); 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /utils/extend.js: -------------------------------------------------------------------------------- 1 | 2 | export default function extend(Class, BaseClass) { 3 | let Func = function() { }; 4 | Func.prototype = BaseClass.prototype; 5 | Class.prototype = new Func(); 6 | Class.prototype.constructor = Class; 7 | Class.superclass = BaseClass.prototype; 8 | } 9 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = { 4 | entry: './rx.global.js', 5 | output: { 6 | path: __dirname, 7 | filename: 'dist/bundle.js' 8 | }, 9 | module: { 10 | loaders: [{ 11 | test : /.js$/, 12 | loader: 'babel-loader', 13 | query: { 14 | presets: 'es2015' 15 | } 16 | }] 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /observable/map.js: -------------------------------------------------------------------------------- 1 | import Observable from './../observable'; 2 | import Subscriber from './../subscriber'; 3 | 4 | Observable.prototype.map = function(project) { 5 | return new Observable(observer => 6 | this.subscribe(new Subscriber( 7 | observer, 8 | value => observer.next(project(value)), 9 | error => observer.error(error), 10 | () => observer.complete()))); 11 | }; 12 | -------------------------------------------------------------------------------- /subscriber.js: -------------------------------------------------------------------------------- 1 | 2 | import extend from './utils/extend'; 3 | import Observer from './observer'; 4 | 5 | function Subscriber(parent, onNext, onError, onComplete) { 6 | Subscriber.superclass.constructor.call(this, onNext, onError, onComplete); 7 | 8 | this.parent_ = parent; 9 | this.parent_.unsubscribeFn = () => this.unsubscribe(); 10 | } 11 | extend(Subscriber, Observer); 12 | 13 | export default Subscriber; 14 | -------------------------------------------------------------------------------- /observable/filter.js: -------------------------------------------------------------------------------- 1 | import Observable from './../observable'; 2 | import Subscriber from './../subscriber'; 3 | 4 | Observable.prototype.filter = function(predicate) { 5 | return new Observable(observer => 6 | this.subscribe(new Subscriber( 7 | observer, 8 | value => predicate(value) && observer.next(value), 9 | error => observer.error(error), 10 | () => observer.complete()))); 11 | }; 12 | -------------------------------------------------------------------------------- /observable/static/interval.js: -------------------------------------------------------------------------------- 1 | import Disposable from './../../disposable'; 2 | import Observable from './../../observable'; 3 | 4 | export default function createFromInterval(interval) { 5 | return new Observable(observer => { 6 | let intervalId = window.setInterval(function() { 7 | observer.next(); 8 | }, interval); 9 | 10 | return new Disposable(() => { 11 | window.clearInterval(intervalId); 12 | }); 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rx-lite", 3 | "version": "1.0.0", 4 | "description": "", 5 | "dependencies": {}, 6 | "author": "", 7 | "license": "ISC", 8 | "devDependencies": { 9 | "babel-core": "^6.10.4", 10 | "babel-loader": "^6.2.4", 11 | "babel-preset-es2015": "^6.9.0", 12 | "webpack": "^1.13.1" 13 | }, 14 | "scripts": { 15 | "start": "python -m SimpleHTTPServer", 16 | "watch": "webpack --watch" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /observable/reduce.js: -------------------------------------------------------------------------------- 1 | import Observable from './../observable'; 2 | import Subscriber from './../subscriber'; 3 | 4 | Observable.prototype.reduce = function(accumulator, seed) { 5 | return new Observable(observer => 6 | this.subscribe(new Subscriber( 7 | observer, 8 | value => { seed = accumulator(value, seed); }, 9 | error => observer.error(error), 10 | () => { 11 | observer.next(seed); 12 | observer.complete(); 13 | }))); 14 | }; 15 | -------------------------------------------------------------------------------- /observable/static/fromevent.js: -------------------------------------------------------------------------------- 1 | import Disposable from './../../disposable'; 2 | import Observable from './../../observable'; 3 | 4 | export default function createFromEvent(element, eventName) { 5 | return new Observable(observer => { 6 | function handleEvent(e) { 7 | observer.next(e); 8 | } 9 | element.addEventListener(eventName, handleEvent, false); 10 | 11 | return new Disposable(() => { 12 | element.removeEventListener(eventName, handleEvent, false); 13 | }); 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /observable/static/range.js: -------------------------------------------------------------------------------- 1 | import Observable from './../../observable'; 2 | 3 | export default function createFromRange(start, count, step) { 4 | return new Observable(observer => { 5 | step = step || 1; 6 | let value = start; 7 | while (true) { 8 | if (count == 0) { 9 | observer.complete(); 10 | break; 11 | } 12 | observer.next(value); 13 | value += step; 14 | count -= 1; 15 | 16 | if (observer.isUnsubscribed) { 17 | break; 18 | } 19 | } 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /observable/find.js: -------------------------------------------------------------------------------- 1 | import Observable from './../observable'; 2 | import Subscriber from './../subscriber'; 3 | 4 | Observable.prototype.find = function(predicate) { 5 | return new Observable(observer => 6 | this.subscribe(new Subscriber( 7 | observer, 8 | value => { 9 | if (predicate(value)) { 10 | observer.next(value); 11 | observer.complete(); 12 | } 13 | }, 14 | error => observer.error(error), 15 | () => observer.complete()))); 16 | }; 17 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true 5 | }, 6 | "extends": "eslint:recommended", 7 | "parserOptions": { 8 | "sourceType": "module", 9 | "ecmaFeatures": { 10 | "modules": true 11 | } 12 | }, 13 | "rules": { 14 | "no-var": "error", 15 | "no-constant-condition": 0, 16 | "prefer-const": 0, 17 | "indent": ["error", 2], 18 | "linebreak-style": ["error", "unix"], 19 | "quotes": ["error", "single"], 20 | "semi": ["error", "always"], 21 | "no-new": "error" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /observable/take.js: -------------------------------------------------------------------------------- 1 | import Observable from './../observable'; 2 | import Subscriber from './../subscriber'; 3 | 4 | Observable.prototype.take = function(takeCount) { 5 | return new Observable(observer => 6 | this.subscribe(new Subscriber( 7 | observer, 8 | value => { 9 | if (takeCount > 0) { 10 | observer.next(value); 11 | 12 | takeCount -= 1; 13 | if (takeCount <= 0) { 14 | observer.complete(); 15 | } 16 | } 17 | }, 18 | error => observer.error(error), 19 | () => observer.complete()))); 20 | }; 21 | -------------------------------------------------------------------------------- /observable/do.js: -------------------------------------------------------------------------------- 1 | import Observable from './../observable'; 2 | import Subscriber from './../subscriber'; 3 | 4 | Observable.prototype.do = function(onNext, onError, onComplete) { 5 | return new Observable(observer => 6 | this.subscribe(new Subscriber( 7 | observer, 8 | value => { 9 | onNext && onNext(value); 10 | observer.next(value); 11 | }, 12 | error => { 13 | onError && onError(error); 14 | observer.error(error); 15 | }, 16 | () => { 17 | onComplete && onComplete(); 18 | observer.complete(); 19 | }))); 20 | }; 21 | -------------------------------------------------------------------------------- /observable/last.js: -------------------------------------------------------------------------------- 1 | import Observable from './../observable'; 2 | import Subscriber from './../subscriber'; 3 | 4 | Observable.prototype.last = function() { 5 | return new Observable(observer => { 6 | let lastValue; 7 | let hadAnyValue = false; 8 | return this.subscribe(new Subscriber( 9 | observer, 10 | value => { 11 | lastValue = value; 12 | hadAnyValue = true; 13 | }, 14 | error => observer.error(error), 15 | () => { 16 | if (hadAnyValue) { 17 | observer.next(lastValue); 18 | observer.complete(); 19 | } else { 20 | observer.error('Sequence is empty'); 21 | } 22 | })); 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /observable/takeUntil.js: -------------------------------------------------------------------------------- 1 | import Disposable from './../disposable'; 2 | import Observable from './../observable'; 3 | import Subscriber from './../subscriber'; 4 | 5 | Observable.prototype.takeUntil = function(untilObservable) { 6 | return new Observable(observer => { 7 | let subscription = this.subscribe( 8 | new Subscriber( 9 | observer, 10 | value => observer.next(value), 11 | error => observer.error(error), 12 | () => observer.complete())); 13 | let subscriptionUntil = untilObservable.subscribe( 14 | new Subscriber( 15 | observer, 16 | () => observer.complete(), 17 | error => observer.error(error), 18 | () => observer.complete())); 19 | return new Disposable(subscription, subscriptionUntil); 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /disposable.js: -------------------------------------------------------------------------------- 1 | 2 | // TODO: check ES6 arguments 3 | function Disposable() { 4 | let disposables = [].slice.call(arguments); 5 | this.disposables_ = []; 6 | disposables.forEach(disposable => { 7 | if (disposable instanceof Array) { 8 | this.disposables_ = this.disposables_.concat(disposable); 9 | } else { 10 | this.disposables_.push(disposable); 11 | } 12 | }); 13 | this.isDisposed_ = false; 14 | } 15 | 16 | Disposable.prototype.dispose = function() { 17 | if (this.disposables_.length && !this.isDisposed_) { 18 | this.isDisposed_ = true; 19 | this.disposables_.forEach(disposable => { 20 | if (disposable instanceof Disposable) { 21 | disposable.dispose(); 22 | } else { 23 | disposable(); 24 | } 25 | }); 26 | } 27 | }; 28 | 29 | export default Disposable; 30 | -------------------------------------------------------------------------------- /observable/merge.js: -------------------------------------------------------------------------------- 1 | import Disposable from './../disposable'; 2 | import Observable from './../observable'; 3 | import Subscriber from './../subscriber'; 4 | 5 | Observable.prototype.merge = function() { 6 | let observables = [this].concat([].slice.call(arguments)); 7 | 8 | return new Observable(observer => { 9 | let observablesCount = 0; 10 | let subscriptions = observables.map(observable => { 11 | observablesCount += 1; 12 | return observable.subscribe( 13 | new Subscriber( 14 | observer, 15 | value => observer.next(value), 16 | error => observer.error(error), 17 | () => { 18 | if (--observablesCount == 0) { 19 | observer.complete(); 20 | subscriptions.forEach(subscription => subscription.dispose()); 21 | } 22 | })); 23 | }); 24 | return new Disposable(subscriptions); 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /observable/flatmap.js: -------------------------------------------------------------------------------- 1 | import Disposable from './../disposable'; 2 | import Subscriber from './../subscriber'; 3 | import Observable from './../observable'; 4 | 5 | Observable.prototype.flatMap = function(project) { 6 | return new Observable(observer => { 7 | let observablesCount = 1; 8 | let subscriptions = []; 9 | function complete() { 10 | if (--observablesCount == 0) { 11 | observer.complete(); 12 | } 13 | } 14 | let subscription = this.subscribe(new Subscriber( 15 | observer, 16 | value => { 17 | let observable = project(value); 18 | observablesCount += 1; 19 | subscriptions.push(observable.subscribe( 20 | value => observer.next(value), 21 | error => observer.error(error), 22 | complete)); 23 | }, 24 | error => observer.error(error), 25 | complete)); 26 | return new Disposable(subscription, subscriptions); 27 | }); 28 | }; 29 | -------------------------------------------------------------------------------- /compositeobserver.js: -------------------------------------------------------------------------------- 1 | import extend from './utils/extend'; 2 | import Observer from './observer'; 3 | 4 | function CompositeObserver() { 5 | this.observers_ = []; 6 | CompositeObserver.superclass.constructor.call(this, 7 | value => this.observers_.forEach(observer => observer.next(value)), 8 | error => this.observers_.forEach(observer => observer.error(error)), 9 | () => this.observers_.forEach(observer => observer.complete())); 10 | } 11 | extend(CompositeObserver, Observer); 12 | 13 | CompositeObserver.prototype.add = function(observer) { 14 | this.observers_.push(observer); 15 | }; 16 | 17 | CompositeObserver.prototype.hasObservers = function() { 18 | return this.observers_.length > 0; 19 | }; 20 | 21 | CompositeObserver.prototype.getObserversLength = function() { 22 | return this.observers_.length; 23 | }; 24 | 25 | CompositeObserver.prototype.remove = function(observer) { 26 | this.observers_.splice(this.observers_.indexOf(observer), 1); 27 | }; 28 | 29 | CompositeObserver.prototype.unsubscribe = function() { 30 | if (!this.isUnsubscribed) { 31 | this.observers_.forEach(o => o.unsubscribe()); 32 | } 33 | }; 34 | 35 | export default CompositeObserver; 36 | -------------------------------------------------------------------------------- /observer.js: -------------------------------------------------------------------------------- 1 | 2 | function Observer(onNext, onError, onComplete) { 3 | this.onNext_ = onNext; 4 | this.onError_ = onError; 5 | this.onComplete_ = onComplete; 6 | this.isUnsubscribed = false; 7 | this.completed_ = false; 8 | this.unsubscribeFn = null; 9 | } 10 | 11 | Observer.prototype.next = function(value) { 12 | if (this.onNext_ && !this.isUnsubscribed) { 13 | try { 14 | this.onNext_(value); 15 | } catch(err) { 16 | this.unsubscribe(); 17 | throw err; 18 | } 19 | } 20 | }; 21 | 22 | Observer.prototype.error = function(error) { 23 | if (this.onError_ && !this.isUnsubscribed) { 24 | try { 25 | this.onError_(error); 26 | } finally { 27 | this.unsubscribe(); 28 | } 29 | } 30 | }; 31 | 32 | Observer.prototype.complete = function() { 33 | if (this.onComplete_ && !this.isUnsubscribed) { 34 | try { 35 | this.onComplete_(); 36 | } finally { 37 | this.unsubscribe(); 38 | } 39 | } 40 | }; 41 | 42 | Observer.prototype.unsubscribe = function() { 43 | if (!this.isUnsubscribed) { 44 | this.isUnsubscribed = true; 45 | 46 | if (this.unsubscribeFn) { 47 | this.unsubscribeFn(); 48 | } 49 | } 50 | }; 51 | 52 | export default Observer; 53 | -------------------------------------------------------------------------------- /rx.js: -------------------------------------------------------------------------------- 1 | import createFromRange from './observable/static/range'; 2 | import createFromArray from './observable/static/array'; 3 | import createFromEvent from './observable/static/fromevent'; 4 | import createFromInterval from './observable/static/interval'; 5 | import createFromReturn from './observable/static/return'; 6 | import createThrow from './observable/static/throw'; 7 | import createObservable from './observable/static/create'; 8 | 9 | import createObserver from './observer/static/create'; 10 | 11 | import './observable/map'; 12 | import './observable/find'; 13 | import './observable/filter'; 14 | import './observable/take'; 15 | import './observable/takeuntil'; 16 | import './observable/last'; 17 | import './observable/reduce'; 18 | import './observable/do'; 19 | import './observable/concat'; 20 | import './observable/flatmap'; 21 | import './observable/merge'; 22 | import './observable/publish'; 23 | 24 | let Rx = { 25 | Observable: { 26 | create: createObservable, 27 | fromRange: createFromRange, 28 | from: createFromArray, 29 | fromEvent: createFromEvent, 30 | interval: createFromInterval, 31 | return: createFromReturn, 32 | throw: createThrow 33 | }, 34 | Observer: { 35 | create: createObserver 36 | } 37 | }; 38 | 39 | export default Rx; 40 | -------------------------------------------------------------------------------- /observable/concat.js: -------------------------------------------------------------------------------- 1 | import Observable from './../observable'; 2 | import Subscriber from './../subscriber'; 3 | import Disposable from './../disposable'; 4 | 5 | Observable.prototype.concat = function() { 6 | let observables = [this].concat([].slice.call(arguments)); 7 | return new Observable(observer => { 8 | let observableIndex = 0; 9 | let buffer = Array(observables.length).fill([]); 10 | let subscriptions = observables.map( 11 | (observable, index) => observable.subscribe( 12 | new Subscriber( 13 | observer, 14 | value => { 15 | if (index == observableIndex) { 16 | observer.next(value); 17 | } else if (index > observableIndex) { 18 | buffer[index].push(value); 19 | } 20 | }, 21 | error => observer.error(error), 22 | () => { 23 | observableIndex++; 24 | if (observableIndex == observables.length) { 25 | observer.complete(); 26 | } else { 27 | buffer[observableIndex].forEach(observer.next); 28 | buffer[observableIndex] = null; 29 | } 30 | }))); 31 | 32 | return new Disposable(subscriptions); 33 | }); 34 | }; 35 | -------------------------------------------------------------------------------- /observable.js: -------------------------------------------------------------------------------- 1 | import Disposable from './disposable'; 2 | import Observer from './observer'; 3 | 4 | // TODO: Figure out how hot vs cold observables work. 5 | function Observable(subscribeFn) { 6 | this.subscribeFn_ = subscribeFn; 7 | } 8 | 9 | Observable.prototype.subscribe = function() { 10 | let observer = (arguments.length == 1 && arguments[0] instanceof Observer) ? 11 | arguments[0] : new Observer(arguments[0], arguments[1], arguments[2]); 12 | 13 | return this.subscribeInternal(observer); 14 | }; 15 | 16 | Observable.prototype.subscribeInternal = function(observer) { 17 | let subscription = this.subscribeFn_(observer); 18 | if (subscription instanceof Disposable) { 19 | observer.unsubscribeFn = function() { 20 | subscription.dispose(); 21 | }; 22 | } 23 | 24 | return new Disposable(() => observer.unsubscribe()); 25 | }; 26 | 27 | // function Observable(subscribeFn) { 28 | // this.observer_ = new CompositeObserver(); 29 | // this.subscribed_ = false; 30 | // this.subscribeFn_ = subscribeFn; 31 | // this.unsubscribeFn_ = null; 32 | // } 33 | // Observable.prototype.subscribe = function() { 34 | // let observer = (arguments.length == 1 && arguments[0] instanceof Observer) ? 35 | // arguments[0] : new Observer(arguments[0], arguments[1], arguments[2]); 36 | // this.observer_.add(observer); 37 | // 38 | // // let disposable = 39 | // // observer.addOnDispose(() => { 40 | // // 41 | // // }); 42 | // if (!this.subscribed_) { 43 | // this.unsubscribeFn_ = this.subscribeFn_(this.observer_); 44 | // this.subscribed_ = true; 45 | // } 46 | // return new Disposable(() => { 47 | // // // TODO: Unsubscription does not work for takeUntil with interval 48 | // if (this.unsubscribeFn_ && !this.observer_.hasObservers()) { 49 | // // this.unsubscribeFn_(); 50 | // } 51 | // this.observer_.remove(observer); 52 | // observer.unsubscribe(); 53 | // }); 54 | // }; 55 | 56 | export default Observable; 57 | -------------------------------------------------------------------------------- /observable/publish.js: -------------------------------------------------------------------------------- 1 | 2 | import extend from './../utils/extend'; 3 | import Disposable from './../disposable'; 4 | import Observable from './../observable'; 5 | import Subscriber from './../subscriber'; 6 | import CompositeObserver from './../compositeobserver'; 7 | 8 | Observable.prototype.publish = function() { 9 | return new SharedObservable(observer => 10 | this.subscribe(new Subscriber( 11 | observer, 12 | value => observer.next(value), 13 | error => observer.error(error), 14 | () => observer.complete()))); 15 | }; 16 | 17 | function SharedObservable(subscribeFn) { 18 | SharedObservable.superclass.constructor.call(this, subscribeFn); 19 | 20 | this.observer_ = new CompositeObserver(); 21 | this.subscription_ = null; 22 | } 23 | extend(SharedObservable, Observable); 24 | 25 | SharedObservable.prototype.subscribeInternal = function(observer) { 26 | this.observer_.add(observer); 27 | observer.unsubscribeFn_ = function() { 28 | this.observer_.remove(observer); 29 | }; 30 | 31 | if (!this.subscribed_) { 32 | this.subscription_ = this.subscribeFn_(this.observer_); 33 | this.subscribed_ = true; 34 | } 35 | 36 | return new Disposable(() => { 37 | if (this.subscription_) { 38 | this.subscription_.dispose(); 39 | } 40 | this.observer_.unsubscribe(); 41 | }); 42 | }; 43 | 44 | // SharedObservable.prototype.subscribe = function() { 45 | // // let observer = (arguments.length == 1 && arguments[0] instanceof Observer) ? 46 | // // arguments[0] : new Observer(arguments[0], arguments[1], arguments[2]); 47 | // this.observer_.add(observer); 48 | // 49 | // // let disposable = 50 | // // observer.addOnDispose(() => { 51 | // // 52 | // // }); 53 | // if (!this.subscribed_) { 54 | // this.unsubscribeFn_ = this.subscribeFn_(this.observer_); 55 | // this.subscribed_ = true; 56 | // } 57 | // return new Disposable(() => { 58 | // // // TODO: Unsubscription does not work for takeUntil with interval 59 | // if (this.unsubscribeFn_ && !this.observer_.hasObservers()) { 60 | // // this.unsubscribeFn_(); 61 | // } 62 | // this.observer_.remove(observer); 63 | // observer.unsubscribe(); 64 | // }); 65 | // }; 66 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |