##// END OF EJS Templates
Fixed queryEx: query fresh results every time when no active observers are exist
cin -
r150:d9c99ae7dec8 v1.10.1 default
parent child
Show More
@@ -1,152 +1,171
1 import { PromiseOrValue } from "@implab/core-amd/interfaces";
1 import { PromiseOrValue } from "@implab/core-amd/interfaces";
2 import { isCancellable, isPromise } from "@implab/core-amd/safe";
2 import { isCancellable, isPromise } from "@implab/core-amd/safe";
3 import { observe, Observable, empty } from "./observable";
3 import { observe, Observable, empty } from "./observable";
4 import { after } from "dojo/aspect";
4 import { after } from "dojo/aspect";
5
5
6 export interface OrderedUpdate<T> {
6 export interface OrderedUpdate<T> {
7 /** The item is being updated */
7 /** The item is being updated */
8 readonly item: T;
8 readonly item: T;
9
9
10 /** The previous index of the item, -1 in case it is inserted */
10 /** The previous index of the item, -1 in case it is inserted */
11 readonly prevIndex: number;
11 readonly prevIndex: number;
12
12
13 /** The new index of the item, -1 in case it is deleted */
13 /** The new index of the item, -1 in case it is deleted */
14 readonly newIndex: number;
14 readonly newIndex: number;
15
15
16 }
16 }
17
17
18 export type QueryResults<T> = Observable<OrderedUpdate<T>>;
18 export type QueryResults<T> = Observable<OrderedUpdate<T>>;
19
19
20 interface DjObservableResults<T> {
20 interface DjObservableResults<T> {
21 /**
21 /**
22 * Allows observation of results
22 * Allows observation of results
23 */
23 */
24 observe(listener: (object: T, previousIndex: number, newIndex: number) => void, includeUpdates?: boolean): {
24 observe(listener: (object: T, previousIndex: number, newIndex: number) => void, includeUpdates?: boolean): {
25 remove(): void;
25 remove(): void;
26 };
26 };
27 }
27 }
28
28
29 interface Queryable<T, Q, O> {
29 interface Queryable<T, Q, O> {
30 query(query?: Q, options?: O): PromiseOrValue<T[]>;
30 query(query?: Q, options?: O): PromiseOrValue<T[]>;
31 }
31 }
32
32
33 export const isDjObservableResults = <T>(v: object): v is DjObservableResults<T> =>
33 export const isDjObservableResults = <T>(v: object): v is DjObservableResults<T> =>
34 v && (typeof (v as { observe?: unknown; }).observe === "function");
34 v && (typeof (v as { observe?: unknown; }).observe === "function");
35
35
36 export const query = <T, Q, O>(store: Queryable<T, Q, O>, includeUpdates = true) => {
36 export const query = <T, Q, O>(store: Queryable<T, Q, O>, includeUpdates = true) => {
37 const q = queryEx(store, includeUpdates);
37 const q = queryEx(store, includeUpdates);
38 return (query?: Q, options?: O & { observe?: boolean }) => {
38 return (query?: Q, options?: O & { observe?: boolean }) => {
39 const [data, updates] = q(query, options);
39 const [data, updates] = q(query, options);
40
40
41 return options?.observe === false ? data : data.cat(updates);
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 export const queryEx = <T, Q, O>(store: Queryable<T, Q, O>, includeUpdates = true) =>
56 export const queryEx = <T, Q, O>(store: Queryable<T, Q, O>, includeUpdates = true) =>
46 (query?: Q, options?: O): [data: QueryResults<T>, updates: QueryResults<T>] => {
57 (query?: Q, options?: O): [data: QueryResults<T>, updates: QueryResults<T>] => {
47
58
48 const pending: T[] = [];
59 /** count active observers */
49
60 let listeners = 0;
50 let results: PromiseOrValue<T[]> = pending;
61 let results: PromiseOrValue<T[]> = [];
51
62
52 const data = observe<OrderedUpdate<T>>(({ next, complete, error }) => {
63 const data = observe<OrderedUpdate<T>>(({ next, complete, error }) => {
53 const processResults = (items: T[]) =>
64 const processResults = (items: T[]) =>
54 items.forEach((item, newIndex) => next({ item, newIndex, prevIndex: -1 }));
65 items.forEach((item, newIndex) => next({ item, newIndex, prevIndex: -1 }));
55
66
56 try {
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 results = store.query(query, options);
71 results = store.query(query, options);
59
72
60 if (isPromise(results)) {
73 if (isPromise(results)) {
61 results.then(processResults).then(complete, error);
74 results.then(processResults).then(complete, error);
62
75
63 if (isCancellable(results))
76 if (isCancellable(results))
64 return results.cancel.bind(results);
77 return results.cancel.bind(results);
65 } else {
78 } else {
66 processResults(results);
79 processResults(results);
67 complete();
80 complete();
68 }
81 }
69 } catch (e) {
82 } catch (e) {
70 error(e);
83 error(e);
71 }
84 }
72 });
85 });
73
86
74 const updates = observe<OrderedUpdate<T>>(({ next, complete, error, isClosed }) => {
87 const updates = observe<OrderedUpdate<T>>(({ next, complete, error, isClosed }) => {
75 try {
88 try {
76 if (!isClosed() && isDjObservableResults<T>(results)) {
89 if (!isClosed() && isDjObservableResults<T>(results)) {
90 // subscribe fot the changes
91 listeners++;
77 const h = results.observe((item, prevIndex, newIndex) => next({ item, prevIndex, newIndex }), includeUpdates);
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 } else {
98 } else {
80 complete();
99 complete();
81 }
100 }
82 } catch (e) {
101 } catch (e) {
83 error(e);
102 error(e);
84 }
103 }
85 });
104 });
86
105
87 return [data, updates];
106 return [data, updates];
88 };
107 };
89
108
90
109
91 interface IndexedStore<T> {
110 interface IndexedStore<T> {
92 get(id: string | number): PromiseLike<T> | T | null | undefined;
111 get(id: string | number): PromiseLike<T> | T | null | undefined;
93 }
112 }
94
113
95 interface Notifications<T> {
114 interface Notifications<T> {
96 notify(item: T | undefined, id: string | number | undefined): void;
115 notify(item: T | undefined, id: string | number | undefined): void;
97 }
116 }
98
117
99 const hasNotifications = <T>(x: unknown): x is Notifications<T> =>
118 const hasNotifications = <T>(x: unknown): x is Notifications<T> =>
100 typeof x === "object" && x !== null && (typeof (x as Notifications<T>).notify === "function");
119 typeof x === "object" && x !== null && (typeof (x as Notifications<T>).notify === "function");
101
120
102 interface GetOpts {
121 interface GetOpts {
103 observe?: boolean;
122 observe?: boolean;
104 }
123 }
105
124
106 export type ItemUpdate<T> = [item: NonNullable<T>, id: string | number | undefined] |
125 export type ItemUpdate<T> = [item: NonNullable<T>, id: string | number | undefined] |
107 [item: undefined | null, id: string | number];
126 [item: undefined | null, id: string | number];
108
127
109 const filterItem = (itemId: string | number) =>
128 const filterItem = (itemId: string | number) =>
110 <T>(source: Observable<ItemUpdate<T>>) =>
129 <T>(source: Observable<ItemUpdate<T>>) =>
111 observe<T>(({ next, complete, error }) => {
130 observe<T>(({ next, complete, error }) => {
112 const subscription = source
131 const subscription = source
113 .filter(([, id]) => id === itemId)
132 .filter(([, id]) => id === itemId)
114 .subscribe({
133 .subscribe({
115 next: ([item]) => item !== null && item !== undefined ? next(item) : complete(),
134 next: ([item]) => item !== null && item !== undefined ? next(item) : complete(),
116 complete,
135 complete,
117 error
136 error
118 });
137 });
119 return () => subscription.unsubscribe();
138 return () => subscription.unsubscribe();
120 });
139 });
121
140
122 export const get = <T>(store: IndexedStore<T>) => {
141 export const get = <T>(store: IndexedStore<T>) => {
123 const changes = hasNotifications<T>(store) ?
142 const changes = hasNotifications<T>(store) ?
124 observe<ItemUpdate<T>>(({ next }) => {
143 observe<ItemUpdate<T>>(({ next }) => {
125 const handle = after(store, "notify", (...args: ItemUpdate<T>) => next(args), true);
144 const handle = after(store, "notify", (...args: ItemUpdate<T>) => next(args), true);
126 return () => handle.remove();
145 return () => handle.remove();
127 }) : empty;
146 }) : empty;
128
147
129 return (id: string | number, opts: GetOpts = {}) =>
148 return (id: string | number, opts: GetOpts = {}) =>
130 observe<T>(({ next, complete, error }) => {
149 observe<T>(({ next, complete, error }) => {
131 try {
150 try {
132 const result = store.get(id);
151 const result = store.get(id);
133
152
134 const handle = (x: T | null | undefined) => {
153 const handle = (x: T | null | undefined) => {
135 if (x !== null && x !== undefined)
154 if (x !== null && x !== undefined)
136 next(x);
155 next(x);
137 complete();
156 complete();
138 };
157 };
139
158
140 if (isPromise(result)) {
159 if (isPromise(result)) {
141 result.then(handle).then(undefined, error);
160 result.then(handle).then(undefined, error);
142
161
143 if (isCancellable(result))
162 if (isCancellable(result))
144 return () => result.cancel();
163 return () => result.cancel();
145 } else {
164 } else {
146 handle(result);
165 handle(result);
147 }
166 }
148 } catch (e) {
167 } catch (e) {
149 error(e);
168 error(e);
150 }
169 }
151 }).cat(opts.observe !== false ? changes.pipe(filterItem(id)) : empty);
170 }).cat(opts.observe !== false ? changes.pipe(filterItem(id)) : empty);
152 }; No newline at end of file
171 };
@@ -1,133 +1,133
1 plugins {
1 plugins {
2 id "org.implab.gradle-typescript" version "1.3.4"
2 id "org.implab.gradle-typescript" version "1.3.4"
3 id "ivy-publish"
3 id "ivy-publish"
4 }
4 }
5
5
6 def container = "djx-playground"
6 def container = "djx-playground"
7
7
8 configurations {
8 configurations {
9 npmLocal
9 npmLocal
10 }
10 }
11
11
12 dependencies {
12 dependencies {
13 npmLocal project(":djx")
13 npmLocal project(":djx")
14 }
14 }
15
15
16 def bundleDir = fileTree(layout.buildDirectory.dir("bundle")) {
16 def bundleDir = fileTree(layout.buildDirectory.dir("bundle")) {
17 builtBy "bundle"
17 builtBy "bundle"
18 }
18 }
19
19
20 typescript {
20 typescript {
21 compilerOptions {
21 compilerOptions {
22 lib = ["es5", "dom", "scripthost", "es2015.promise", "es2015.symbol", "es2015.iterable"]
22 lib = ["es5", "dom", "scripthost", "es2015.promise", "es2015.symbol", "es2015.iterable"]
23 // listFiles = true
23 // listFiles = true
24 strict = true
24 strict = true
25 types = ["requirejs", "@implab/dojo-typings", "@implab/djx"]
25 types = ["requirejs", "@implab/dojo-typings", "@implab/djx"]
26 module = "amd"
26 module = "amd"
27 it.target = "es5"
27 it.target = "es5"
28 experimentalDecorators = true
28 experimentalDecorators = true
29 noUnusedLocals = false
29 noUnusedLocals = false
30 jsx = "react"
30 jsx = "react"
31 jsxFactory = "createElement"
31 jsxFactory = "createElement"
32 moduleResolution = "node"
32 moduleResolution = "node"
33 // dojo-typings are sick
33 // dojo-typings are sick
34 skipLibCheck = true
34 skipLibCheck = true
35 // traceResolution = true
35 // traceResolution = true
36 // baseUrl = "./"
36 // baseUrl = "./"
37 // paths = [ "*": [ "$projectDir/src/typings/*" ] ]
37 // paths = [ "*": [ "$projectDir/src/typings/*" ] ]
38 // baseUrl = "$projectDir/src/typings"
38 // baseUrl = "$projectDir/src/typings"
39 // typeRoots = ["$projectDir/src/typings"]
39 // typeRoots = ["$projectDir/src/typings"]
40 }
40 }
41 tscCmd = "$projectDir/node_modules/.bin/tsc"
41 tscCmd = "$projectDir/node_modules/.bin/tsc"
42 tsLintCmd = "$projectDir/node_modules/.bin/tslint"
42 tsLintCmd = "$projectDir/node_modules/.bin/tslint"
43 esLintCmd = "$projectDir/node_modules/.bin/eslint"
43 esLintCmd = "$projectDir/node_modules/.bin/eslint"
44 }
44 }
45
45
46 tasks.matching{ it.name =~ /^configureTs/ }.configureEach {
46 tasks.matching{ it.name =~ /^configureTs/ }.configureEach {
47 compilerOptions {
47 compilerOptions {
48 if (symbols != 'none') {
48 if (symbols != 'none') {
49 sourceMap = true
49 sourceMap = true
50 switch(symbols) {
50 switch(symbols) {
51 case "local":
51 case "local":
52 sourceRoot = ( isWindows ? "file:///" : "file://" ) + it.rootDir
52 sourceRoot = ( isWindows ? "file:///" : "file://" ) + it.rootDir
53 break;
53 break;
54 }
54 }
55 }
55 }
56 }
56 }
57 }
57 }
58
58
59 npmInstall {
59 task npmInstallLocalDeps {
60 //npmInstall.dependsOn it
60 npmInstall.dependsOn it
61 dependsOn configurations.npmLocal
61 dependsOn configurations.npmLocal
62
62
63 doFirst {
63 doFirst {
64 configurations.npmLocal.each { f ->
64 configurations.npmLocal.each { f ->
65 exec {
65 exec {
66 commandLine "npm", "install", f, "--save-dev"
66 commandLine "npm", "install", f, "--save-dev"
67 }
67 }
68 }
68 }
69 }
69 }
70 }
70 }
71
71
72 clean {
72 clean {
73 doFirst {
73 doFirst {
74 delete "$buildDir/bundle"
74 delete "$buildDir/bundle"
75 }
75 }
76 }
76 }
77
77
78
78
79 task processResourcesBundle(type: Copy) {
79 task processResourcesBundle(type: Copy) {
80 from "src/bundle"
80 from "src/bundle"
81 into layout.buildDirectory.dir("bundle")
81 into layout.buildDirectory.dir("bundle")
82 }
82 }
83
83
84 task copyModules(type: Copy) {
84 task copyModules(type: Copy) {
85 dependsOn npmInstall
85 dependsOn npmInstall
86 into layout.buildDirectory.dir("bundle/js");
86 into layout.buildDirectory.dir("bundle/js");
87
87
88 def pack = { String jsmod ->
88 def pack = { String jsmod ->
89 into(jsmod) {
89 into(jsmod) {
90 from npm.module(jsmod)
90 from npm.module(jsmod)
91 }
91 }
92 }
92 }
93
93
94
94
95 pack("@implab/djx")
95 pack("@implab/djx")
96 pack("@implab/core-amd")
96 pack("@implab/core-amd")
97 into("@js-joda/core") {
97 into("@js-joda/core") {
98 from(npm.module("@js-joda/core/dist"))
98 from(npm.module("@js-joda/core/dist"))
99 }
99 }
100 pack("dojo")
100 pack("dojo")
101 pack("dijit")
101 pack("dijit")
102 into("rxjs") {
102 into("rxjs") {
103 from(npm.module("rxjs/dist/bundles"))
103 from(npm.module("rxjs/dist/bundles"))
104 }
104 }
105 from npm.module("requirejs/require.js")
105 from npm.module("requirejs/require.js")
106 }
106 }
107
107
108 npmPublish {
108 npmPublish {
109 enabled = false
109 enabled = false
110 }
110 }
111
111
112 task copyApp(type: Copy) {
112 task copyApp(type: Copy) {
113 dependsOn assemble
113 dependsOn assemble
114 from typescript.assemblyDir
114 from typescript.assemblyDir
115 into layout.buildDirectory.dir("bundle/js/app")
115 into layout.buildDirectory.dir("bundle/js/app")
116 }
116 }
117
117
118 task bundle {
118 task bundle {
119 dependsOn copyModules, processResourcesBundle, copyApp
119 dependsOn copyModules, processResourcesBundle, copyApp
120 }
120 }
121
121
122 task up(type: Exec) {
122 task up(type: Exec) {
123 dependsOn bundle
123 dependsOn bundle
124 commandLine "podman", "run", "--rm", "-d",
124 commandLine "podman", "run", "--rm", "-d",
125 "--name", container,
125 "--name", container,
126 "-p", "2078:80",
126 "-p", "2078:80",
127 "-v", "$buildDir/bundle:/srv/www/htdocs",
127 "-v", "$buildDir/bundle:/srv/www/htdocs",
128 "registry.implab.org/implab/apache2:latest"
128 "registry.implab.org/implab/apache2:latest"
129 }
129 }
130
130
131 task stop(type: Exec) {
131 task stop(type: Exec) {
132 commandLine "podman", "stop", container
132 commandLine "podman", "stop", container
133 } No newline at end of file
133 }
General Comments 0
You need to be logged in to leave comments. Login now