##// END OF EJS Templates
Merge
cin -
r154:2a5720a0816e merge default
parent child
Show More
@@ -1,56 +1,57
1 1 fc9f82c082ef432137da086a1fe9c37a12ba16a2 v1.0.0-rc2
2 2 6d80d7901b4c8ffe8728e4a7bf5f4b7e7a669bb5 v1.0.0-rc3
3 3 5a2c44d8e1f34dd30c2b50f92b7dc2e8f3247c43 v1.0.0-rc5
4 4 6c01fabe9ea9fb5e753fbeae8b0d2664e7072a66 v1.0.0-rc6
5 5 9e546fe36fdddc8324f1098ee950fa1a7ba19b93 v1.0.0-rc7
6 6 8f4d5e2c719a20ae6d65f1f4b5e2141ed765e975 v1.0.0-rc8
7 7 a1ab2b5975ad4b19599fb61538e7aaf329fb528c v1.0.0-rc10
8 8 9b77ac3bf8f200876450ad50c308a9441a7f39c7 v1.0.0-rc11
9 9 32b72f33756d3d10553b743f0b9f4504148cf97d v1.0.0-rc12
10 10 b88fac0e76c0e61e397e2995f468f7cf342afbc9 v1.0.0-rc13
11 11 a46488b209e8aac583c1634043147d87740c63b4 v1.0.0-rc14
12 12 1174538197f6796384e643f62100292f1377b137 v1.0.0-rc15
13 13 e8012fdf09ae442094f3831abe70649f8520659e 1.0.0-rc16
14 14 a1a1ef050ecc9d2a780e308cbffc2ad915a5c13d 1.0.0-rc17
15 15 5c6c7e16919cff4019a55661725789d287439b75 v1.0.0-rc18
16 16 3b6c4159c66cecf6c5957d33ad474919608489c6 v1.0.0
17 17 18383b2dcc1ae97fe3673242c84fe8d4b50a55f0 v1.0.1
18 18 ed3c20c09b000386b5204b483955eb61ee662eff v1.0.2
19 19 030ea350f98bb7a06e636251ea5cad403217e868 v1.0.3
20 20 346ba910a5425ed95f7a67ef4db1dbbf599e2cab v1.0.5
21 21 deb0ed6fb68015d89ba1287f6b766d1f80b6e4ff v1.0.7
22 22 dd0d589acfbbe938a5630547e83ad934ab597b64 v1.0.8
23 23 f2499237b5bf0ac2d73a8e437b8f15fa8a6306a5 v1.0.9
24 24 d4f0cdae9577caa605d9317d0ceb93f7b67895f3 v1.0.10
25 25 1a0018655d1c3dafc16ae50971cb535b8555e246 v1.1.0
26 26 ff3695b0a48f00155ad6c3d7e000fe185ee6f95a v1.1.1
27 27 c276b9b7fa833242d1bf75eeeaae9924feee51e8 v1.1.2
28 28 fdde09e66c009f4950ec69a393a8e789725da758 v1.2.0
29 29 16678c6055f20ce1a49bdca5e6dabce88ee8deed v1.2.1
30 30 bc7556143fe536e3df374bc0070146311884284e v1.2.2
31 31 e5bb5e80ce96fc4ca0fbbf0b5390443a616e99fa v1.2.3
32 32 cc5be30e84f8a0d9a8e25447651576d9e46ab154 v1.2.4
33 33 2807ab11174c7446830d91d5f1b652a18c6ecae5 v1.2.5
34 34 35a7b6319ebe24973fe10b8d82c19d3d86857b4e v1.2.6
35 35 70058deb750dc18fcd9be83c28cb8371530fffd8 v1.2.7
36 36 367f8caa5bf8fd2d4a56a6cff40be31ab82a2727 v1.2.8
37 37 15c829aa08a2cf65cc074640199e79c5e49d2c93 v1.3.0
38 38 1a190b3a757dc650c2c612073cbffaaa720b6090 v1.4.0
39 39 2ccfaae984e9a458580664923c87f258cff9890f v1.4.4
40 40 e9a9ed6d7647848e9c7f28e6e9f92d009259607d v1.5.0
41 41 aac297dda27dac1e86bc93617ba172a2c92415f5 v1.6.0
42 42 e07418577cbcaa6e31ccbf87c4495324ecbd88bd v1.6.1
43 43 bc1b4dd8ca1a03af095ea1a61169c6025ef37fce v1.6.2
44 44 fb2ea4d6aabac2b374f3b2bfddaca6a83dedf34f v1.6.3
45 45 cede47727a1b0867d19be1d168aceb00b5e74e3f v1.7.0
46 46 8095aad89415099d8e1942a8e67050c22e111d8b v1.7.1
47 47 66546e70973248de01601aa364eadf11dd2f3a9f v1.8.0
48 48 c7d9ad82b374c2a20710eba8fa40a9b1472ddc77 v1.8.1
49 49 f139e2153e0da5649558b1ed9369da860d442c0d v1.9.0-rc1
50 50 435ce00ba2452d565f1ed3bb884643b4b88b9cde v1.9.0-rc2
51 51 98b2c550c676ff6acf16f5bd56ee3804342f80b7 v1.9.0-rc3
52 52 515d1b83ebdfaa93599451175055a94f711c079f v1.9.0-rc4
53 53 894b8239b953c0384cf32cab46cf41dac97ea03b v1.9.0-rc5
54 54 63215d91ae4b711eeb2145b9685240e1afada904 v1.9.0-rc6
55 55 af4f8424e83d56e89a64f39e19514ca10dbd43c6 v1.9.0
56 56 63f3ad8e6cffcf32fc5a555e55e8544227293d4c v1.10.0
57 d9c99ae7dec87ef47aded05def6a094a892df0e6 v1.10.1
@@ -1,152 +1,171
1 1 import { PromiseOrValue } from "@implab/core-amd/interfaces";
2 2 import { isCancellable, isPromise } from "@implab/core-amd/safe";
3 3 import { observe, Observable, empty } from "./observable";
4 4 import { after } from "dojo/aspect";
5 5
6 6 export interface OrderedUpdate<T> {
7 7 /** The item is being updated */
8 8 readonly item: T;
9 9
10 10 /** The previous index of the item, -1 in case it is inserted */
11 11 readonly prevIndex: number;
12 12
13 13 /** The new index of the item, -1 in case it is deleted */
14 14 readonly newIndex: number;
15 15
16 16 }
17 17
18 18 export type QueryResults<T> = Observable<OrderedUpdate<T>>;
19 19
20 20 interface DjObservableResults<T> {
21 21 /**
22 22 * Allows observation of results
23 23 */
24 24 observe(listener: (object: T, previousIndex: number, newIndex: number) => void, includeUpdates?: boolean): {
25 25 remove(): void;
26 26 };
27 27 }
28 28
29 29 interface Queryable<T, Q, O> {
30 30 query(query?: Q, options?: O): PromiseOrValue<T[]>;
31 31 }
32 32
33 33 export const isDjObservableResults = <T>(v: object): v is DjObservableResults<T> =>
34 34 v && (typeof (v as { observe?: unknown; }).observe === "function");
35 35
36 36 export const query = <T, Q, O>(store: Queryable<T, Q, O>, includeUpdates = true) => {
37 37 const q = queryEx(store, includeUpdates);
38 38 return (query?: Q, options?: O & { observe?: boolean }) => {
39 39 const [data, updates] = q(query, options);
40 40
41 41 return options?.observe === false ? data : data.cat(updates);
42 42 };
43 43 };
44 44
45 /**
46 * Wraps the query method of the store, the resulting method takes a query
47 * expression and returns two observable sequences. The first sequence represents
48 * the results of the query, the second sequence provides the updates to the
49 * query results.
50 *
51 * @param store The store used to query data
52 * @param includeUpdates The flag to include item updates not only additions and
53 * deletions. By default this flag is set to true.
54 * @returns Two observable sequences
55 */
45 56 export const queryEx = <T, Q, O>(store: Queryable<T, Q, O>, includeUpdates = true) =>
46 57 (query?: Q, options?: O): [data: QueryResults<T>, updates: QueryResults<T>] => {
47 58
48 const pending: T[] = [];
49
50 let results: PromiseOrValue<T[]> = pending;
59 /** count active observers */
60 let listeners = 0;
61 let results: PromiseOrValue<T[]> = [];
51 62
52 63 const data = observe<OrderedUpdate<T>>(({ next, complete, error }) => {
53 64 const processResults = (items: T[]) =>
54 65 items.forEach((item, newIndex) => next({ item, newIndex, prevIndex: -1 }));
55 66
56 67 try {
57 if (results === pending)
68 // is there are no active observers here, we need to query actual
69 // data from the store.
70 if (listeners === 0)
58 71 results = store.query(query, options);
59 72
60 73 if (isPromise(results)) {
61 74 results.then(processResults).then(complete, error);
62 75
63 76 if (isCancellable(results))
64 77 return results.cancel.bind(results);
65 78 } else {
66 79 processResults(results);
67 80 complete();
68 81 }
69 82 } catch (e) {
70 83 error(e);
71 84 }
72 85 });
73 86
74 87 const updates = observe<OrderedUpdate<T>>(({ next, complete, error, isClosed }) => {
75 88 try {
76 89 if (!isClosed() && isDjObservableResults<T>(results)) {
90 // subscribe fot the changes
91 listeners++;
77 92 const h = results.observe((item, prevIndex, newIndex) => next({ item, prevIndex, newIndex }), includeUpdates);
78 return () => h.remove();
93 return () => {
94 // unsubscribe from changes
95 listeners--;
96 h.remove();
97 };
79 98 } else {
80 99 complete();
81 100 }
82 101 } catch (e) {
83 102 error(e);
84 103 }
85 104 });
86 105
87 106 return [data, updates];
88 107 };
89 108
90 109
91 110 interface IndexedStore<T> {
92 111 get(id: string | number): PromiseLike<T> | T | null | undefined;
93 112 }
94 113
95 114 interface Notifications<T> {
96 115 notify(item: T | undefined, id: string | number | undefined): void;
97 116 }
98 117
99 118 const hasNotifications = <T>(x: unknown): x is Notifications<T> =>
100 119 typeof x === "object" && x !== null && (typeof (x as Notifications<T>).notify === "function");
101 120
102 121 interface GetOpts {
103 122 observe?: boolean;
104 123 }
105 124
106 125 export type ItemUpdate<T> = [item: NonNullable<T>, id: string | number | undefined] |
107 126 [item: undefined | null, id: string | number];
108 127
109 128 const filterItem = (itemId: string | number) =>
110 129 <T>(source: Observable<ItemUpdate<T>>) =>
111 130 observe<T>(({ next, complete, error }) => {
112 131 const subscription = source
113 132 .filter(([, id]) => id === itemId)
114 133 .subscribe({
115 134 next: ([item]) => item !== null && item !== undefined ? next(item) : complete(),
116 135 complete,
117 136 error
118 137 });
119 138 return () => subscription.unsubscribe();
120 139 });
121 140
122 141 export const get = <T>(store: IndexedStore<T>) => {
123 142 const changes = hasNotifications<T>(store) ?
124 143 observe<ItemUpdate<T>>(({ next }) => {
125 144 const handle = after(store, "notify", (...args: ItemUpdate<T>) => next(args), true);
126 145 return () => handle.remove();
127 146 }) : empty;
128 147
129 148 return (id: string | number, opts: GetOpts = {}) =>
130 149 observe<T>(({ next, complete, error }) => {
131 150 try {
132 151 const result = store.get(id);
133 152
134 153 const handle = (x: T | null | undefined) => {
135 154 if (x !== null && x !== undefined)
136 155 next(x);
137 156 complete();
138 157 };
139 158
140 159 if (isPromise(result)) {
141 160 result.then(handle).then(undefined, error);
142 161
143 162 if (isCancellable(result))
144 163 return () => result.cancel();
145 164 } else {
146 165 handle(result);
147 166 }
148 167 } catch (e) {
149 168 error(e);
150 169 }
151 170 }).cat(opts.observe !== false ? changes.pipe(filterItem(id)) : empty);
152 171 }; No newline at end of file
@@ -1,173 +1,191
1 1 import { empty, observe, of } from "./observable";
2 2 import * as tap from "tap";
3 3 import { Cancellation } from "@implab/core-amd/Cancellation";
4 4 import { delay } from "@implab/core-amd/safe";
5 5
6 6 const subj1 = observe<number>(({ next, complete }) => {
7 7 next(1);
8 8 complete();
9 9 next(2);
10 10 });
11 11
12 12 const consumer1 = {
13 13 sum: 0,
14 14 next(v: number) {
15 15 this.sum += v;
16 16 }
17 17 };
18 18
19 19 subj1.subscribe(consumer1);
20 20 tap.equal(consumer1.sum, 1, "Should get only one value");
21 21
22 22 subj1.subscribe(consumer1);
23 23 tap.equal(consumer1.sum, 2, "Should get the value again");
24 24
25 25 const consumer2 = {
26 26 value: 0,
27 27 completed: false,
28 28 next(v: number) { this.value = v; },
29 29 complete() { this.completed = true; }
30 30 };
31 31
32 32 let maps = 0;
33 33
34 34 subj1
35 35 .map(v => {
36 36 tap.comment(`map1: ${v * 2}`);
37 37 maps++;
38 38 return v * 2;
39 39 })
40 40 .map(v => {
41 41 tap.comment(`map2: ${v * 2}`);
42 42 maps++;
43 43 return v * 2;
44 44 })
45 45 .map(v => {
46 46 tap.comment(`map3: ${v * 2}`);
47 47 maps++;
48 48 return v * 2;
49 49 })
50 50 .subscribe(consumer2);
51 51
52 52 tap.equal(consumer2.value, 8, "Should map");
53 53 tap.equal(maps, 3, "The map chain should not be executed after completion");
54 54 tap.ok(consumer2.completed, "The completion signal should pass through");
55 55
56 56 const subj2 = observe<number>(({ next, complete }) => {
57 57 [1, 2, 3, 4, 5].forEach(next);
58 58 complete();
59 59 return () => {
60 60 tap.comment("subj2: unsubscribe");
61 61 };
62 62 });
63 63
64 64 const consumer3 = {
65 65 even: 0,
66 66 odd: 0,
67 67 completed: false,
68 68 subscribed: 0,
69 69 unsubscribed: 0,
70 70 next(v: "even" | "odd") {
71 71 this[v]++;
72 72 },
73 73 complete() {
74 74 this.completed = true;
75 75 },
76 76 subscribe() {
77 77 this.subscribed++;
78 78 },
79 79 unsubscribe() {
80 80 this.unsubscribed++;
81 81 }
82 82 };
83 83
84 84
85 85 const subj3 = subj2.pipe<"even" | "odd">(self => observe(({ next, complete, error }) => {
86 86 consumer3.subscribe();
87 87 let count = 0;
88 88 const h = self.subscribe({
89 89 next: val => {
90 90 if (val % 2 === 0)
91 91 next("odd");
92 92 else
93 93 next("even");
94 94 if (++count === 4)
95 95 complete();
96 96 },
97 97 complete,
98 98 error
99 99 });
100 100 return () => {
101 101 consumer3.unsubscribe();
102 102 h.unsubscribe();
103 103 };
104 104 }));
105 105
106 106 subj3.subscribe(consumer3);
107 107
108 108 tap.equal(consumer3.odd, 2, "Should get 2 odd elements");
109 109 tap.equal(consumer3.even, 2, "Should get 2 even elements");
110 110 tap.ok(consumer3.completed, "The sequence should completed");
111 111 tap.equal(consumer3.subscribed, 1, "The subscription should be done once");
112 112 tap.equal(consumer3.unsubscribed, 1, "The cleanup should be done after completion");
113 113
114 114 subj2.reduce((a, b) => a + b).subscribe({
115 115 next: val => tap.comment("subj2: reduce =", val),
116 116 complete: () => tap.comment("subj2: complete")
117 117 });
118 118
119 119 tap.test("of(...) tests", async t => {
120 120 await subj2.reduce((a, b) => a + b).next()
121 121 .then(value => t.comment("subj2: next reduce=", value));
122 122
123 123 await subj2.next().then(val => t.equal(val, 1, "Should peek the first element"));
124 124
125 125 const cancelled = new Cancellation(cancel => cancel());
126 126 await t.rejects(subj2.next(cancelled), "Cancelled next() method should fail");
127 127
128 128 await t.rejects(empty.next(), "Empty sequence should fail to get next element");
129 129
130 130 await of(delay(1).then(() => 1), Promise.resolve(2), 3)
131 131 .reduce<number[]>((a, x) => [...a, x], [])
132 132 .next()
133 133 .then(res => t.same(res, [1, 2, 3], "of(...) should keep the order"));
134 134
135 135 const rejected = Promise.reject("DIE!");
136 136 rejected.catch(() => { }); // SAFE AND SOUND
137 137
138 138 await t.resolves(
139 139 of(Promise.resolve(1), rejected).next(),
140 140 "of(...) should emit non-rejected items"
141 141 );
142 142 await t.rejects(
143 143 of(1, Promise.reject("DIE!")).reduce((a) => a).next(),
144 144 "of(...) should terminate with error when a parameter is rejected"
145 145 );
146 146
147 147 t.same(await of(1,2,3).collect(), [1,2,3], ".collect() should return the collected sequence");
148 148 await t.rejects(of(1,2,3).collect(cancelled), ".collect() should support cancellation");
149 149
150 150 }).catch(() => { });
151 151
152 152 tap.test(".tap() tests", async t => {
153 153 const side: number[] = [];
154 154
155 155 of(1,2)
156 156 .tap({next: v => side.push(v), complete: () => side.push(0)})
157 157 .tap({next: v => side.push(v*v)})
158 158 .subscribe({});
159 159
160 160 t.same(side, [1,1,2,4,0], ".tap() should be called in the order of registration");
161 161
162 162 side.length = 0;
163 163
164 164 await new Promise<void>(resolve => {
165 165 of(1,2,delay(1).then(() => 3))
166 166 .tap({next: v => side.push(v)})
167 167 .tap({ next: v => v === 1 && resolve()})
168 168 .subscribe({});
169 169 });
170 170
171 171 t.same(side, [1,2], ".tap() should be processed synchronously");
172 172
173 }).catch(() => { });
174
175 tap.test(".while() tests", async t => {
176
177 const seq = of(1, 2, 3, 4).while(v => v <= 2);
178
179 t.same(await seq.collect(), [1, 2], "Should collect only taken elements");
180
181 const data: number[] = [];
182 let complete = 0;
183 seq.subscribe({
184 next: v => data.push(v),
185 complete: () => complete++
186 });
187
188 t.same(data, [1, 2], "Should receive only taken elements");
189 t.equal(complete, 1, "Complete should run once");
190
173 191 }).catch(() => {}); No newline at end of file
@@ -1,133 +1,133
1 1 plugins {
2 2 id "org.implab.gradle-typescript" version "1.3.4"
3 3 id "ivy-publish"
4 4 }
5 5
6 6 def container = "djx-playground"
7 7
8 8 configurations {
9 9 npmLocal
10 10 }
11 11
12 12 dependencies {
13 13 npmLocal project(":djx")
14 14 }
15 15
16 16 def bundleDir = fileTree(layout.buildDirectory.dir("bundle")) {
17 17 builtBy "bundle"
18 18 }
19 19
20 20 typescript {
21 21 compilerOptions {
22 22 lib = ["es5", "dom", "scripthost", "es2015.promise", "es2015.symbol", "es2015.iterable"]
23 23 // listFiles = true
24 24 strict = true
25 25 types = ["requirejs", "@implab/dojo-typings", "@implab/djx"]
26 26 module = "amd"
27 27 it.target = "es5"
28 28 experimentalDecorators = true
29 29 noUnusedLocals = false
30 30 jsx = "react"
31 31 jsxFactory = "createElement"
32 32 moduleResolution = "node"
33 33 // dojo-typings are sick
34 34 skipLibCheck = true
35 35 // traceResolution = true
36 36 // baseUrl = "./"
37 37 // paths = [ "*": [ "$projectDir/src/typings/*" ] ]
38 38 // baseUrl = "$projectDir/src/typings"
39 39 // typeRoots = ["$projectDir/src/typings"]
40 40 }
41 41 tscCmd = "$projectDir/node_modules/.bin/tsc"
42 42 tsLintCmd = "$projectDir/node_modules/.bin/tslint"
43 43 esLintCmd = "$projectDir/node_modules/.bin/eslint"
44 44 }
45 45
46 46 tasks.matching{ it.name =~ /^configureTs/ }.configureEach {
47 47 compilerOptions {
48 48 if (symbols != 'none') {
49 49 sourceMap = true
50 50 switch(symbols) {
51 51 case "local":
52 52 sourceRoot = ( isWindows ? "file:///" : "file://" ) + it.rootDir
53 53 break;
54 54 }
55 55 }
56 56 }
57 57 }
58 58
59 npmInstall {
60 //npmInstall.dependsOn it
59 task npmInstallLocalDeps {
60 npmInstall.dependsOn it
61 61 dependsOn configurations.npmLocal
62 62
63 63 doFirst {
64 64 configurations.npmLocal.each { f ->
65 65 exec {
66 66 commandLine "npm", "install", f, "--save-dev"
67 67 }
68 68 }
69 69 }
70 70 }
71 71
72 72 clean {
73 73 doFirst {
74 74 delete "$buildDir/bundle"
75 75 }
76 76 }
77 77
78 78
79 79 task processResourcesBundle(type: Copy) {
80 80 from "src/bundle"
81 81 into layout.buildDirectory.dir("bundle")
82 82 }
83 83
84 84 task copyModules(type: Copy) {
85 85 dependsOn npmInstall
86 86 into layout.buildDirectory.dir("bundle/js");
87 87
88 88 def pack = { String jsmod ->
89 89 into(jsmod) {
90 90 from npm.module(jsmod)
91 91 }
92 92 }
93 93
94 94
95 95 pack("@implab/djx")
96 96 pack("@implab/core-amd")
97 97 into("@js-joda/core") {
98 98 from(npm.module("@js-joda/core/dist"))
99 99 }
100 100 pack("dojo")
101 101 pack("dijit")
102 102 into("rxjs") {
103 103 from(npm.module("rxjs/dist/bundles"))
104 104 }
105 105 from npm.module("requirejs/require.js")
106 106 }
107 107
108 108 npmPublish {
109 109 enabled = false
110 110 }
111 111
112 112 task copyApp(type: Copy) {
113 113 dependsOn assemble
114 114 from typescript.assemblyDir
115 115 into layout.buildDirectory.dir("bundle/js/app")
116 116 }
117 117
118 118 task bundle {
119 119 dependsOn copyModules, processResourcesBundle, copyApp
120 120 }
121 121
122 122 task up(type: Exec) {
123 123 dependsOn bundle
124 124 commandLine "podman", "run", "--rm", "-d",
125 125 "--name", container,
126 126 "-p", "2078:80",
127 127 "-v", "$buildDir/bundle:/srv/www/htdocs",
128 128 "registry.implab.org/implab/apache2:latest"
129 129 }
130 130
131 131 task stop(type: Exec) {
132 132 commandLine "podman", "stop", container
133 133 } No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now