##// END OF EJS Templates
linting
cin -
r109:4a375b9c654a default
parent child
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -0,0 +1,1
1 .eslintrc.js No newline at end of file
@@ -0,0 +1,39
1 module.exports = {
2 root: true,
3 extends: [
4 "plugin:react/recommended",
5 "eslint:recommended",
6 "plugin:@typescript-eslint/eslint-recommended",
7 "plugin:@typescript-eslint/recommended",
8 "plugin:@typescript-eslint/recommended-requiring-type-checking"
9 ],
10 parser: "@typescript-eslint/parser",
11 parserOptions: {
12 ecmaFeatures: {
13 jsx: true
14 },
15 ecmaVersion: 5,
16 tsconfigRootDir: __dirname + "/src",
17 project: [
18 "tsconfig.eslint.json",
19 "*/tsconfig.json"
20 ]
21 },
22 plugins: [
23 "react",
24 "@typescript-eslint"
25 ],
26 rules: {
27 "react/react-in-jsx-scope": "off",
28 "react/no-unknown-property": "off",
29 "@typescript-eslint/no-empty-function": "off",
30 "max-classes-per-file": [
31 "error",
32 { "ignoreExpressions": true, "max": 1 }
33 ],
34 "@typescript-eslint/prefer-readonly": ["error"],
35 "semi": "off",
36 "@typescript-eslint/semi": ["error"]
37
38 }
39 }
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,42 +1,42
1 1 {
2 2 "name": "@implab/djx",
3 3 "version": "0.0.1-dev",
4 4 "description": "Supports using dojo version 1 with typescript and .tsx files",
5 5 "keywords": [
6 6 "dojo",
7 7 "tsx",
8 8 "typescript",
9 9 "widgets"
10 10 ],
11 11 "author": "Implab team",
12 12 "license": "BSD-2-Clause",
13 13 "repository": "https://code.implab.org/implab/implabjs-djx",
14 14 "publishConfig": {
15 15 "access": "public"
16 16 },
17 17 "peerDependencies": {
18 18 "@implab/core-amd": "^1.4.0",
19 19 "dojo": "^1.10.0"
20 20 },
21 21 "devDependencies": {
22 22 "@implab/core-amd": "^1.4.0",
23 23 "@types/chai": "4.1.3",
24 24 "@types/requirejs": "2.1.31",
25 25 "@types/yaml": "1.2.0",
26 26 "@types/tap": "15.0.7",
27 27 "dojo": "1.16.0",
28 "@implab/dojo-typings": "1.0.0",
28 "@implab/dojo-typings": "1.0.3",
29 29 "@typescript-eslint/eslint-plugin": "^5.23.0",
30 30 "@typescript-eslint/parser": "^5.23.0",
31 31 "eslint": "^8.23.0",
32 32 "eslint-config-standard": "^17.0.0",
33 33 "eslint-plugin-import": "^2.26.0",
34 34 "eslint-plugin-n": "^15.2.0",
35 35 "eslint-plugin-promise": "^6.0.0",
36 36 "eslint-plugin-react": "^7.29.4",
37 37 "requirejs": "2.3.6",
38 38 "typescript": "4.8.3",
39 39 "yaml": "~1.7.2",
40 40 "tap": "16.3.0"
41 41 }
42 42 }
@@ -1,73 +1,58
1 1 import { MapOf, PromiseOrValue } from "@implab/core-amd/interfaces";
2 2 import { argumentNotEmptyString, isPromise, mixin } from "@implab/core-amd/safe";
3 3
4 4 export type LocaleProvider<T> = () => PromiseOrValue<T | { default: T }>;
5 5
6 type ResolveCallback<T> = () => PromiseOrValue<T>;
7
8 6 function when<T, T2>(value: PromiseOrValue<T>, cb: (v: T) => PromiseOrValue<T2>): PromiseOrValue<T2> {
9 7 return isPromise(value) ?
10 8 value.then(cb) :
11 9 cb(value);
12 10 }
13 11
14 function isCallback<T>(v: ResolveCallback<T> | PromiseOrValue<T>): v is ResolveCallback<T> {
15 return typeof v === "function";
16 }
12 const chainObjects = <T extends object, T2 extends object>(o1: T, o2: T2) =>
13 mixin(Object.create(o1) as T, o2);
14 export class NlsBundle<T extends object> {
15 private readonly _locales: MapOf<LocaleProvider<Partial<T>>>;
17 16
18 function chainObjects<T extends object>(o1: T, o2: T) {
19 if (!o1)
20 return o2;
21 if (!o2)
22 return o1;
23
24 return mixin(Object.create(o1) as T, o2);
25 }
26
27 export class NlsBundle<T extends object> {
28 private _locales: MapOf<LocaleProvider<Partial<T>>>;
29
30 private _default: T;
17 private readonly _default: T;
31 18
32 19 private _cache: MapOf<PromiseOrValue<T>>;
33 20
34 21 constructor(defNls: T, locales?: MapOf<LocaleProvider<Partial<T>>>) {
35 22 this._default = defNls;
36 23 this._locales = locales || {};
37 24 this._cache = {};
38 25 }
39 26
40 27 getLocale(locale: string) {
41 28 argumentNotEmptyString(locale, "locale");
42 29 const _loc = locale;
43 30
44 31 // en-US => ["en", "en-US"]
45 32 const locales = _loc.split(/-|_/).map((x, i, a) => a.slice(0, i + 1).join("-"));
46 33 return this._resolveLocale(locales);
47 34 }
48 35
49 36 _resolveLocale(locales: string[]): PromiseOrValue<T> {
50 37 if (!locales.length)
51 38 return this._default;
52 39
53 40 const locale = locales.pop();
54 41 if (!locale)
55 42 throw new Error("The locale can't be empty");
56 43
57 44 if (this._cache[locale])
58 45 return this._cache[locale];
59 46
60 47 const data = this._loadPackage(this._locales[locale]);
61 48 const parent = this._resolveLocale(locales);
62 49
63 50 return this._cache[locale] = when(data, x => {
64 51 return when(parent, y => this._cache[locale] = chainObjects(y, x));
65 52 });
66 53 }
67 54
68 _loadPackage(localeData: any) {
69 if (isCallback(localeData))
55 _loadPackage(localeData: LocaleProvider<Partial<T>>) {
70 56 return when(localeData(), data => data && "default" in data ? data.default : data);
71 return localeData;
72 57 }
73 58 }
@@ -1,18 +1,18
1 1 import inject from "./dom-inject";
2 2
3 3 interface OnLoad {
4 (result?: any): void;
5 error(err: any): void;
4 (result?: unknown): void;
5 error(err: unknown): void;
6 6 }
7 7
8 8 const plugin = {
9 9 load: (id: string, require: Require, cb: OnLoad, config: { isBuild?: boolean }) => {
10 10 if (config && config.isBuild) {
11 11 cb();
12 12 } else {
13 13 const url = require.toUrl(id);
14 14 inject.injectStylesheet(url).then(() => cb({ url }), e => cb.error(e));
15 15 }
16 16 }
17 17 };
18 18 export = plugin;
@@ -1,254 +1,255
1 1 import declare = require("dojo/_base/declare");
2 2 import { each } from "@implab/core-amd/safe";
3 3 import { Constructor } from "@implab/core-amd/interfaces";
4 4
5 5 // declare const declare: any;
6 6
7 7 type DeclareConstructor<T> = dojo._base.DeclareConstructor<T>;
8 8
9 9 export interface AbstractConstructor<T = object> {
10 10 prototype: T;
11 11 }
12 12
13 13 interface DjMockConstructor<T = object> {
14 14 new(...args: unknown[]): T;
15 15 mock: boolean;
16 16 bases: AbstractConstructor[];
17 17 }
18 18
19 19 export function djbase<T>(): DeclareConstructor<T>;
20 20 export function djbase<T>(
21 21 b0: AbstractConstructor<T>
22 22 ): DeclareConstructor<T>;
23 23
24 24 export function djbase<T0, T1>(
25 25 b0: AbstractConstructor<T0>,
26 26 b1: AbstractConstructor<T1>
27 27 ): DeclareConstructor<T0 & T1>;
28 28
29 29 export function djbase<T0, T1, T2>(
30 30 b0: AbstractConstructor<T0>,
31 31 b1: AbstractConstructor<T1>,
32 32 b2: AbstractConstructor<T2>
33 33 ): DeclareConstructor<T0 & T1 & T2>;
34 34
35 35 export function djbase<T0, T1, T2, T3>(
36 36 b0: AbstractConstructor<T0>,
37 37 b1: AbstractConstructor<T1>,
38 38 b2: AbstractConstructor<T2>,
39 39 b3: AbstractConstructor<T3>
40 40 ): DeclareConstructor<T0 & T1 & T2 & T3>;
41 41
42 42 export function djbase<T0, T1, T2, T3, T4>(
43 43 b0: AbstractConstructor<T0>,
44 44 b1: AbstractConstructor<T1>,
45 45 b2: AbstractConstructor<T2>,
46 46 b3: AbstractConstructor<T3>,
47 47 b4: AbstractConstructor<T4>
48 48 ): DeclareConstructor<T0 & T1 & T2 & T3 & T4>;
49 49
50 50 export function djbase<T0, T1, T2, T3, T4, T5>(
51 51 b0: AbstractConstructor<T0>,
52 52 b1: AbstractConstructor<T1>,
53 53 b2: AbstractConstructor<T2>,
54 54 b3: AbstractConstructor<T3>,
55 55 b4: AbstractConstructor<T4>,
56 56 b5: AbstractConstructor<T5>
57 57 ): DeclareConstructor<T0 & T1 & T2 & T3 & T4 & T5>;
58 58
59 59 export function djbase<T0, T1, T2, T3, T4, T5, T6>(
60 60 b0: AbstractConstructor<T0>,
61 61 b1: AbstractConstructor<T1>,
62 62 b2: AbstractConstructor<T2>,
63 63 b3: AbstractConstructor<T3>,
64 64 b4: AbstractConstructor<T4>,
65 65 b5: AbstractConstructor<T5>,
66 66 b6: AbstractConstructor<T6>
67 67 ): DeclareConstructor<T0 & T1 & T2 & T3 & T4 & T5 & T6>;
68 68
69 69 export function djbase<T0, T1, T2, T3, T4, T5, T6, T7>(
70 70 b0: AbstractConstructor<T0>,
71 71 b1: AbstractConstructor<T1>,
72 72 b2: AbstractConstructor<T2>,
73 73 b3: AbstractConstructor<T3>,
74 74 b4: AbstractConstructor<T4>,
75 75 b5: AbstractConstructor<T5>,
76 76 b6: AbstractConstructor<T6>,
77 77 b7: AbstractConstructor<T7>
78 78 ): DeclareConstructor<T0 & T1 & T2 & T3 & T4 & T5 & T6 & T7>;
79 79
80 80 /** Создает конструктор-заглушку из списка базовых классов, используется
81 81 * для объявления классов при помощи `dojo/_base/declare`.
82 82 *
83 83 * Создает пустой конструктор, с пустым стандартным прототипом, это нужно,
84 84 * поскольку в унаследованном классе конструктор обязательно должен вызвать
85 85 * `super(...)`, таким образом он вызовет пустую функцию.
86 86 *
87 87 * Созданный конструктор хранит в себе список базовых классов, который будет
88 88 * использован декоратором `djclass`, который вернет класс, объявленный при
89 89 * помощи `dojo/_base/declare`.
90 90 *
91 91 * @param bases список базовых классов, от которых требуется унаследовать
92 92 * новый класс.
93 93 *
94 94 */
95 95 export function djbase(...bases: AbstractConstructor[]): Constructor {
96 96
97 97 const t = class {
98 98 static mock: boolean;
99 99 static bases: AbstractConstructor[];
100 100 };
101 101
102 102 t.mock = true;
103 103 t.bases = bases;
104 104
105 105 return t as Constructor;
106 106 }
107 107
108 108 function isMockConstructor<T extends object>(v: AbstractConstructor<T>): v is DjMockConstructor<T> {
109 109 return v && "mock" in v;
110 110 }
111 111
112 112 /** Создает класс при помощи `dojo/_base/declare`. Для этого исходный класс
113 113 * должен быть унаследован от `djbase(...)`.
114 114 *
115 115 * @param target Класс, который нужно объявить при помощи `dojo/_base/declare`
116 116 */
117 117 export function djclass<T extends AbstractConstructor>(target: T): T {
118 118 // получаем базовый конструктор и его прототип
119 119 const bp = target && !!target.prototype && Object.getPrototypeOf(target.prototype) as object;
120 120 const bc = bp && bp.constructor;
121 121
122 122 // проверка того, что класс унаследован от специальной заглушки
123 123 if (isMockConstructor(bc)) {
124 124 // bc.bases - базовый класс, объявленный при помощи dojo/_base/declare
125 125 const cls = declare(bc.bases, target.prototype) as unknown as T;
126 126
127 127 // bc - базовый класс, bc.prototype используется как super
128 128 // при вызове базовых методов. Нужно создать bc.prototype
129 129 // таким образом, чтобы он вызывал this.inherited().
130 130
131 131 // создаем новый прототип, он не в цепочке прототипов у текущего
132 132 // класса, но super.some_method будет использовать именно его.
133 133 // в этом объекте будут размещены прокси для переопределенных
134 134 // методов.
135 135 const nbp = bc.prototype = Object.create(cls.prototype) as Record<string, unknown>;
136 136 nbp.constructor = bc;
137 137
138 138 // proxy - фабрика для создания прокси-методов, которые внутри
139 139 // себя вызовут this.inherited с правильными параметрами.
140 const proxy = (m: (...args: unknown[]) => unknown) => function (this: dojo._base.DeclareCreatedObject, ...args: unknown[]) {
141 const f = this.getInherited({ callee: m, ...args, length: args.length });
142 return f ? f.apply(this, args) as unknown : undefined;
140 const proxy = (m: (...args: unknown[]) => unknown) => function (this: dojo._base.DeclareCreatedObject) {
141 const f = this.getInherited({ callee: m } as unknown as IArguments);
142 // eslint-disable-next-line prefer-rest-params
143 return f ? f.apply(this, arguments) as unknown : undefined;
143 144
144 145 // так сделать можно только dojo 1.15+
145 146 // return this.inherited(m, arguments);
146 147 };
147 148
148 149 // у текущего класса прототип содержит методы, объявленные в этом
149 150 // классе и его конструктор. Нужно пройти по всем методам и
150 151 // создать для них прокси.
151 152 // При этом только те, методы, которые есть в базовых классах
152 153 // могут быть переопределены.
153 154 each(target.prototype, (m: unknown, p: string) => {
154 155 if (typeof m === "function" &&
155 156 p !== "constructor" &&
156 Object.prototype.hasOwnProperty.call(target, p)
157 Object.prototype.hasOwnProperty.call(target.prototype, p)
157 158 ) {
158 159 nbp[p] = proxy(m as (...args: unknown[]) => unknown);
159 160 }
160 161 });
161 162
162 163 // TODO mixin static members
163 164 return cls;
164 165 } else {
165 166 return target;
166 167 }
167 168 }
168 169
169 170 function makeSetterName(prop: string) {
170 171 return [
171 172 "_set",
172 173 prop.replace(/^./, x => x.toUpperCase()),
173 174 "Attr"
174 175 ].join("");
175 176 }
176 177
177 178 function makeGetterName(prop: string) {
178 179 return [
179 180 "_get",
180 181 prop.replace(/^./, x => x.toUpperCase()),
181 182 "Attr"
182 183 ].join("");
183 184 }
184 185
185 186 interface NodeBindSpec {
186 187 node: string;
187 188 type: "attribute" | "innerText" | "textContent" | "innerHTML" | "class" | "toggleClass";
188 189 attribute?: string;
189 190
190 191 className?: string;
191 192 }
192 193
193 194 /**
194 195 * Описание привязки свойства виджета к свойству внутреннего объекта.
195 196 */
196 197 interface MemberBindSpec {
197 198 /**
198 199 * Имя свойства со ссылкой на объект, к которому .
199 200 */
200 201 member: string;
201 202 /**
202 203 * Свойство объекта к которому нужно осуществить привязку.
203 204 */
204 205 property: string;
205 206
206 207 /**
207 208 * Привязка осуществляется не только на запись но и на чтение свойства.
208 209 */
209 210 getter?: boolean;
210 211 }
211 212
212 213 function isNodeBindSpec(v: object): v is NodeBindSpec {
213 214 return "node" in v;
214 215 }
215 216
216 217 /** Декорирует свойства виджета для привязки их к внутренним членам, либо DOM
217 218 * элементам, либо свойству внутреннего объекта.
218 219 *
219 220 * @param {NodeBindSpec | MemberBindSpec} params Параметры связывания.
220 221 */
221 222 export function bind(params: NodeBindSpec | MemberBindSpec) {
222 223 if (isNodeBindSpec(params)) {
223 224 return <K extends string>(target: Record<K, unknown>, name: K) => {
224 225 target[makeSetterName(name) as K /** hack to not go insane) */] = params;
225 226 };
226 227 } else {
227 return <K extends string,
228 T extends { [p in K]: p extends "_set" ? (name: p, v: unknown) => void : unknown; }> (target: T, name: K) => {
229 target[name] = undefined as T[K];
230 target[makeSetterName(name) as K] = function (this: T, v: unknown) {
231 this._set(name, v);
232 const inner = this[params.member] as Record<string, unknown>;
228 return <K extends string>(target: Record<K | "_set", unknown>, name: K) => {
229 target[name] = undefined;
230
231 target[makeSetterName(name) as K] = function (this: typeof target, v: unknown) {
232 (this._set as (n: K, v: unknown) => void)(name, v);
233 const inner = this[params.member as K] as Record<string, unknown>;
233 234 if (typeof inner.set === "function")
234 235 inner.set(params.property, v);
235 } as T[K];
236 };
236 237 if (params.getter)
237 target[makeGetterName(name)] = function () {
238 const inner = this[params.member] as Record<string, unknown>;
238 target[makeGetterName(name) as K] = function (this: typeof target) {
239 const inner = this[params.member as K] as Record<string, unknown>;
239 240 if (typeof inner.get === "function")
240 241 return inner.get(params.property) as unknown;
241 242 };
242 243 };
243 244 }
244 245 }
245 246
246 247 /** Создает в прототипе свойство с указанным значением.
247 248 * @param value Значение, которое будет указано в прототипе
248 249 */
249 250 export function prototype<T>(value: T): <P extends { [m in K]: T }, K extends keyof P>(p: P, name: K) => void;
250 251 export function prototype<T>(value?: T) {
251 252 return (p: { [m in string]: T | undefined }, name: string) => {
252 253 p[name] = value;
253 254 };
254 255 }
@@ -1,100 +1,100
1 1 import { id as mid } from "module";
2 2 import { TraceSource } from "@implab/core-amd/log/TraceSource";
3 3 import { MapOf } from "@implab/core-amd/interfaces";
4 4 import { mixin } from "@implab/core-amd/safe";
5 5
6 6 const trace = TraceSource.get(mid);
7 7
8 8
9 function on<T extends keyof HTMLElementEventMap>(node: HTMLElement, eventName: T, handler: (this: HTMLElement, ev: HTMLElementEventMap[T]) => any): () => void {
9 function on<T extends keyof HTMLElementEventMap>(node: HTMLElement, eventName: T, handler: (this: HTMLElement, ev: HTMLElementEventMap[T]) => unknown): () => void {
10 10 // Add an event listener to a DOM node
11 11 node.addEventListener(eventName, handler, false);
12 12
13 13 return () => node.removeEventListener(eventName, handler, false);
14 14 }
15 15
16 16 interface NodeLoadResult {
17 17 node: HTMLElement;
18 18 }
19 19
20 20 class DomInject {
21 21 injectionPoint?: HTMLElement;
22 22 injectAfter?: HTMLElement;
23 23
24 24 _map: MapOf<Promise<NodeLoadResult>> = {};
25 25
26 26 _inject<T extends keyof HTMLElementTagNameMap>(name: T, attr: Partial<HTMLElementTagNameMap[T]>) {
27 27 const node = document.createElement(name);
28 28
29 29 return new Promise<NodeLoadResult>((ok, fail) => {
30 30
31 31 const cleanup = () => {
32 32 noerr();
33 33 noload();
34 34 };
35 35
36 36 const noload = on(node, "load", () => {
37 37 ok({ node });
38 38 cleanup();
39 39 });
40 40
41 41 const noerr = on(node, "error", e => {
42 42 fail({
43 43 error: e,
44 44 node
45 45 });
46 46 cleanup();
47 47 });
48 48
49 49 mixin(node, attr);
50 50
51 51 const _injectionPoint = this.injectionPoint || document.head;
52 52 const _injectBefore = this.injectAfter ? this.injectAfter.nextSibling : null;
53 53
54 54 _injectionPoint.insertBefore(node, _injectBefore);
55 55 });
56 56 }
57 57
58 58 async injectScript(url: string) {
59 59 let d = this._map[url];
60 if (!d) {
60 if (d === undefined) {
61 61 trace.log("js {0}", url);
62 62 d = this._inject("script", {
63 63 type: "text/javascript",
64 64 charset: "utf-8",
65 65 src: url
66 66 });
67 67 this._map[url] = d;
68 68 }
69 69 try {
70 70 await d;
71 71 trace.log("done {0}", url);
72 72 } catch (e) {
73 73 trace.error("failed {0}: {1}", url, e);
74 74 throw e;
75 75 }
76 76 }
77 77
78 78 async injectStylesheet(url: string) {
79 79 let d = this._map[url];
80 if (!d) {
80 if (d === undefined) {
81 81 trace.log("js {0}", url);
82 82 d = this._inject("link", {
83 83 type: "text/css",
84 84 rel: "stylesheet",
85 85 href: url
86 86 });
87 87 this._map[url] = d;
88 88 }
89 89 try {
90 90 await d;
91 91 trace.log("done {0}", url);
92 92 } catch (e) {
93 93 trace.error("failed {0}: {1}", url, e);
94 94 throw e;
95 95 }
96 96 }
97 97 }
98 98
99 99 const instance = new DomInject();
100 100 export default instance;
@@ -1,51 +1,51
1 1 import { id as mid} from "module";
2 2 import { MapOf } from "@implab/core-amd/interfaces";
3 import { NlsBundle } from "./NlsBundle";
3 import { LocaleProvider, NlsBundle } from "./NlsBundle";
4 4 import { isPromise } from "@implab/core-amd/safe";
5 5 import { locale as sysLocale } from "dojo/_base/kernel";
6 6 import { TraceSource } from "@implab/core-amd/log/TraceSource";
7 7
8 8 const trace = TraceSource.get(mid);
9 9
10 10 trace.debug("Current sysLocale: {0}", sysLocale);
11 11
12 12 export interface OnLoad {
13 (result?: any): void;
14 error(err: any): void;
13 (result?: unknown): void;
14 error(err: unknown): void;
15 15 }
16 16
17 export function bundle<T extends object>(nls: T, locales?: MapOf<any>) {
17 export function bundle<T extends object>(nls: T, locales?: MapOf<LocaleProvider<object>>) {
18 18 const nlsBundle = new NlsBundle(nls, locales);
19 19
20 20 const fn = (_locale?: string) => {
21 21 const locale = _locale || sysLocale;
22 22 const result = nlsBundle.getLocale(locale);
23 23
24 24 if (isPromise(result))
25 25 throw new Error(`The bundle '${locale}' isn't loaded`);
26 26 else
27 27 return result;
28 28 };
29 29
30 30 fn.define = (pack: Partial<T>) => pack;
31 fn.load = async (id: string, require: Require, cb: OnLoad, config: any) => {
31 fn.load = async (id: string, require: Require, cb: OnLoad, config: {isBuild?: boolean}) => {
32 32 const locale = id || sysLocale;
33 33 if (config && config.isBuild) {
34 34 cb();
35 35 } else {
36 36 try {
37 37 await nlsBundle.getLocale(locale);
38 38 cb();
39 39 } catch (e) {
40 40 if(cb.error) {
41 41 cb.error(e);
42 42 } else {
43 43 // in case the loader doesn't support error reporting
44 44 trace.error("Error loading {0}: {1}", locale, e);
45 45 }
46 46 }
47 47 }
48 48 };
49 49
50 50 return fn;
51 51 }
@@ -1,147 +1,147
1 1 /**
2 2 * The interface for the consumer of an observable sequence
3 3 */
4 4 export interface Observer<T> {
5 5 /**
6 6 * Called for the next element in the sequence
7 7 */
8 8 next: (value: T) => void;
9 9
10 10 /**
11 11 * Called once when the error occurs in the sequence.
12 12 */
13 13 error: (e: unknown) => void;
14 14
15 15 /**
16 16 * Called once at the end of the sequence.
17 17 */
18 18 complete: () => void;
19 19 }
20 20
21 21 /**
22 22 * The group of functions to feed an observable. This methods are provided to
23 23 * the producer to generate a stream of events.
24 24 */
25 25 export type Sink<T> = {
26 26 [k in keyof Observer<T>]: (this: void, ...args: Parameters<Observer<T>[k]>) => void;
27 27 };
28 28
29 29 export type Producer<T> = (sink: Sink<T>) => (void | (() => void));
30 30
31 31 export interface Unsubscribable {
32 32 unsubscribe(): void;
33 33 }
34 34
35 35 export const isUnsubsribable = (v: unknown): v is Unsubscribable =>
36 36 v !== null && v !== undefined && typeof (v as Unsubscribable).unsubscribe === "function";
37 37
38 38 export const isSubsribable = <T = unknown>(v: unknown): v is Subscribable<T> =>
39 39 v !== null && v !== undefined && typeof (v as Subscribable<unknown>).subscribe === "function";
40 40
41 41 export interface Subscribable<T> {
42 42 subscribe(consumer: Partial<Observer<T>>): Unsubscribable;
43 43 }
44 44
45 45 /** The observable source of items. */
46 46 export interface Observable<T> extends Subscribable<T> {
47 47 /** Transforms elements of the sequence with the specified mapper
48 48 *
49 49 * @param mapper The mapper used to transform the values
50 50 */
51 51 map<T2>(mapper: (value: T) => T2): Observable<T2>;
52 52
53 53 /** Filters elements of the sequence. The resulting sequence will
54 54 * contain only elements which match the specified predicate.
55 55 *
56 56 * @param predicate The filter predicate.
57 57 */
58 58 filter(predicate: (value: T) => boolean): Observable<T>;
59 59
60 60 /** Applies accumulator to each value in the sequence and
61 61 * emits the accumulated value for each source element
62 62 *
63 63 * @param accumulator
64 64 * @param initial
65 65 */
66 66 scan<A>(accumulator: (acc: A, value: T) => A, initial: A): Observable<A>;
67 67 }
68 68
69 69 const noop = () => { };
70 70
71 71 const sink = <T>(consumer: Partial<Observer<T>>) => {
72 72 const { next, error, complete } = consumer;
73 73 return {
74 74 next: next ? next.bind(consumer) : noop,
75 75 error: error ? error.bind(consumer) : noop,
76 76 complete: complete ? complete.bind(consumer) : noop
77 }
77 };
78 78 };
79 79
80 80 const fuse = <T>({ next, error, complete }: Sink<T>) => {
81 81 let done = false;
82 82 return {
83 next: (value: T) => { !done && next(value) },
84 error: (e: unknown) => { !done && (done = true, error(e)) },
85 complete: () => { !done && (done = true, complete()) }
86 }
87 }
83 next: (value: T) => { !done && next(value); },
84 error: (e: unknown) => { !done && (done = true, error(e)); },
85 complete: () => { !done && (done = true, complete()); }
86 };
87 };
88 88
89 89 const _observe = <T>(producer: Producer<T>): Observable<T> => ({
90 90 subscribe: (consumer: Partial<Observer<T>>) => ({
91 91 unsubscribe: producer(sink(consumer)) ?? noop
92 92 }),
93 93 map: (mapper) => _observe(({ next, error, complete }) =>
94 94 producer({
95 95 next: next !== noop ? (v: T) => next(mapper(v)) : noop,
96 96 error,
97 97 complete
98 98 })
99 99 ),
100 100 filter: (predicate) => _observe(({ next, error, complete }) =>
101 101 producer({
102 102 next: next !== noop ?
103 103 (v: T) => predicate(v) ? next(v) : void (0) : noop,
104 104 error,
105 105 complete
106 106 })
107 107 ),
108 108 scan: (accumulator, initial) => _observe(({ next, error, complete }) => {
109 109 let _acc = initial;
110 110 return producer({
111 111 next: next !== noop ?
112 112 (v: T) => next(_acc = accumulator(_acc, v)) : noop,
113 113 error,
114 114 complete
115 115 });
116 116 })
117 117 });
118 118
119 119 export const observe = <T>(producer: Producer<T>): Observable<T> => ({
120 120 subscribe: (consumer: Partial<Observer<T>>) => ({
121 121 unsubscribe: producer(fuse(sink(consumer))) ?? noop
122 122 }),
123 123 map: (mapper) => _observe(({ next, error, complete }) =>
124 124 producer(fuse({
125 125 next: next !== noop ?
126 126 (v: T) => next(mapper(v)) : noop,
127 127 error,
128 128 complete
129 129 }))
130 130 ),
131 131 filter: (predicate) => _observe(({ next, error, complete }) =>
132 132 producer(fuse({
133 133 next: next !== noop ?
134 134 (v: T) => predicate(v) ? next(v) : void (0) : noop,
135 135 error,
136 136 complete
137 137 }))
138 138 ),
139 139 scan: (accumulator, initial) => observe(({ next, error, complete }) => {
140 140 let _acc = initial;
141 141 return producer(fuse({
142 142 next: next !== noop ? (v: T) => next(_acc = accumulator(_acc, v)) : noop,
143 143 error,
144 144 complete
145 145 }));
146 146 })
147 147 });
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now