##// END OF EJS Templates
`dojo.store.api` replaced `dojo.promise.Promise<T>` with `PromiseLike<T>` type
cin -
r12:1e87acba35f3 v1.0.3 default
parent child
Show More
@@ -1,33 +1,48
1 1 # dojo-typings
2 2
3 3 This project is forked from the original `dojo-typings` package to
4 4 improve it with more tight and customizable type definitions.
5 5
6 6 The primary aims are
7 7
8 8 * Parametrize collections with items type.
9 9 * Distinguish sync and async methods in stores.
10 10 * Parametrize widget typings with event and attribute maps.
11 11
12 12 ## Changes
13 13
14 ### 1.0.3
15
16 Maintenance release
17
18 * `dojo.store.api` replaced `dojo.promise.Promise<T>` with `PromiseLike<T>` type
19
20 ### 1.0.2
21
22 Maintenance release
23
24 * added type parameter to `Evented<T>`, `T` is the event map
25 * `_WidgetBase` `emit()` and `on()` are made more strict to accept only valid
26 event names, or explicit `any`.
27 * added `_setValueAttr` method to `_FormMixin`
28
14 29 ### 1.0.1
15 30
16 31 Maintenance release
17 32
18 33 * `NodeList-fx` added overloads to all methods to distinguish returned values
19 34 of different types (`Animation | this`).
20 35 * Added missing signatures to `NodeList` constructor
21 36 * Improved `dijit.form._FormWidgetMixin`
22 37 * Added module declarations `dijit/form/_FormWidgetMixin`,
23 38 `dijit/form/_FormValueMixin`, `dijit/_HasDropDown`
24 39
25 40 ### 1.0.0
26 41
27 42 Initial release
28 43
29 44 * Parametrized `dojo/Stateful`, `diji/_WidgetBase`
30 45 * split `dojo.store.api.Store` in two `SyncStore` and `AsyncStore` interfaces
31 46 * `dojo/store/Memory` implements `SyncStore` and can be used synchronously,
32 47 * `dojo/store/Rest` implements `AsyncStore` and all its methods are
33 48 returning explicit promises.
@@ -1,591 +1,590
1 /// <reference path="promise.d.ts" />
2 1 /// <reference path="data.d.ts" />
3 2
4 3
5 4 declare namespace dojo {
6 5 namespace store {
7 6 namespace api {
8 7
9 8 type Identity = number | string;
10 9
11 10 /* dojo/store/api/Store */
12 11
13 12 interface SortInformation {
14 13
15 14 /**
16 15 * The name of the attribute to sort on.
17 16 */
18 17 attribute: string;
19 18
20 19 /**
21 20 * The direction of the sort. Default is false.
22 21 */
23 22 descending?: boolean;
24 23 }
25 24
26 25 interface QueryOptions {
27 26 /**
28 27 * A list of attributes to sort on, as well as direction
29 28 * For example:
30 29 * | [{attribute:"price", descending: true}].
31 30 * If the sort parameter is omitted, then the natural order of the store may be
32 31 * applied if there is a natural order.
33 32 */
34 33 sort?: SortInformation[];
35 34
36 35 /**
37 36 * The first result to begin iteration on
38 37 */
39 38 start?: number;
40 39
41 40 /**
42 41 * The number of how many results should be returned.
43 42 */
44 43 count?: number;
45 44 }
46 45
47 46 interface QueryEngineFunction<T extends object> {
48 47 (array: T[]): T[];
49 48 matches(object: T): boolean;
50 49 }
51 50
52 51 type BaseQueryType = string | object | ((...params: any[]) => any);
53 52
54 53 interface QueryEngine<T extends object, Q extends BaseQueryType> {
55 54 <O extends QueryOptions>(query: Q, options?: O): QueryEngineFunction<T>;
56 55 }
57 56
58 57 interface PutDirectives<T extends object> {
59 58
60 59 /**
61 60 * Indicates the identity of the object if a new object is created
62 61 */
63 62 id?: Identity;
64 63
65 64 /**
66 65 * If the collection of objects in the store has a natural ordering,
67 66 * this indicates that the created or updated object should be placed before the
68 67 * object specified by the value of this property. A value of null indicates that the
69 68 * object should be last.
70 69 */
71 70 before?: T;
72 71
73 72 /**
74 73 * If the store is hierarchical (with single parenting) this property indicates the
75 74 * new parent of the created or updated object.
76 75 */
77 76 parent?: T;
78 77
79 78 /**
80 79 * If this is provided as a boolean it indicates that the object should or should not
81 80 * overwrite an existing object. A value of true indicates that a new object
82 81 * should not be created, the operation should update an existing object. A
83 82 * value of false indicates that an existing object should not be updated, a new
84 83 * object should be created (which is the same as an add() operation). When
85 84 * this property is not provided, either an update or creation is acceptable.
86 85 */
87 86 overwrite?: boolean;
88 87 }
89 88
90 89 interface SyncQueryResults<T extends object> extends Array<T> {
91 90 total?: number;
92 91 }
93 92
94 93 interface AsyncQueryResults<T extends object> extends PromiseLike<Array<T>> {
95 94 /**
96 95 * Iterates over the query results, based on
97 96 * https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/objects/Array/forEach.
98 97 * Note that this may executed asynchronously. The callback may be called
99 98 * after this function returns.
100 99 */
101 100 forEach(callback: (item: T, index: number, results: Array<T>) => void, thisObject?: object): PromiseLike<void>;
102 101
103 102 /**
104 103 * Filters the query results, based on
105 104 * https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/filter.
106 105 * Note that this may executed asynchronously. The callback may be called
107 106 * after this function returns.
108 107 */
109 108 filter(callback: (item: T, index: number, results: Array<T>) => boolean, thisObject?: object): PromiseLike<Array<T>>;
110 109
111 110 /**
112 111 * Maps the query results, based on
113 112 * https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/map.
114 113 * Note that this may executed asynchronously. The callback may be called
115 114 * after this function returns.
116 115 */
117 116 map<U>(callback: (item: T, index: number, results: Array<T>) => U, thisObject?: object): PromiseLike<Array<U>>;
118 117
119 118 /** Cancells the current query */
120 119 cancel(reason?: any): void;
121 120
122 121 total?: PromiseLike<number>;
123 122 }
124 123
125 124 type QueryResults<T extends object> = SyncQueryResults<T> | AsyncQueryResults<T>;
126 125
127 126 interface Transaction {
128 127 /**
129 128 * Commits the transaction. This may throw an error if it fails. Of if the operation
130 129 * is asynchronous, it may return a promise that represents the eventual success
131 130 * or failure of the commit.
132 131 */
133 132 commit(): void;
134 133
135 134 /**
136 135 * Aborts the transaction. This may throw an error if it fails. Of if the operation
137 136 * is asynchronous, it may return a promise that represents the eventual success
138 137 * or failure of the abort.
139 138 */
140 139 abort(): void;
141 140 }
142 141
143 142 interface Transacted {
144 143 /**
145 144 * Starts a new transaction.
146 145 * Note that a store user might not call transaction() prior to using put,
147 146 * delete, etc. in which case these operations effectively could be thought of
148 147 * as "auto-commit" style actions.
149 148 */
150 149 transaction(): Transaction;
151 150 }
152 151
153 152 interface MetadataProvider<T extends object> {
154 153 /**
155 154 * Returns any metadata about the object. This may include attribution,
156 155 * cache directives, history, or version information.
157 156 */
158 157 getMetadata(object: T): object;
159 158 }
160 159
161 160 interface IdentityProvider<T extends object> {
162 161 /**
163 162 * If the store has a single primary key, this indicates the property to use as the
164 163 * identity property. The values of this property should be unique.
165 164 */
166 165 idProperty: keyof T;
167 166
168 167 /**
169 168 * Returns an object's identity
170 169 */
171 170 getIdentity(object: T): Identity;
172 171 }
173 172
174 173 interface Queryable<
175 174 T extends object,
176 175 Q extends BaseQueryType,
177 176 O extends QueryOptions,
178 177 R extends QueryResults<T> = QueryResults<T>> {
179 178
180 179 /**
181 180 * Queries the store for objects. This does not alter the store, but returns a
182 181 * set of data from the store.
183 182 */
184 183 query(query?: Q, options?: O): R;
185 184 }
186 185
187 186 interface SyncStore<T extends object, Q extends BaseQueryType = Partial<T> | ((item: T) => boolean) | string, O extends QueryOptions = QueryOptions>
188 187 extends IdentityProvider<T>, Queryable<T, Q, O, SyncQueryResults<T>> {
189 188
190 189 /**
191 190 * If the store can be queried locally (on the client side in JS), this defines
192 191 * the query engine to use for querying the data store.
193 192 * This takes a query and query options and returns a function that can execute
194 193 * the provided query on a JavaScript array. The queryEngine may be replace to
195 194 * provide more sophisticated querying capabilities. For example:
196 195 * | var query = store.queryEngine({foo:"bar"}, {count:10});
197 196 * | query(someArray) -> filtered array
198 197 * The returned query function may have a "matches" property that can be
199 198 * used to determine if an object matches the query. For example:
200 199 * | query.matches({id:"some-object", foo:"bar"}) -> true
201 200 * | query.matches({id:"some-object", foo:"something else"}) -> false
202 201 */
203 202 queryEngine?: QueryEngine<T, Q>;
204 203
205 204 /**
206 205 * Retrieves an object by its identity
207 206 */
208 207 get(id: Identity): T;
209 208
210 209 /**
211 210 * Stores an object
212 211 */
213 212 put(object: T, directives?: PutDirectives<T>): any;
214 213
215 214 /**
216 215 * Creates an object, throws an error if the object already exists
217 216 */
218 217 add(object: T, directives?: PutDirectives<T>): any;
219 218
220 219 /**
221 220 * Deletes an object by its identity
222 221 */
223 222 remove(id: Identity): void;
224 223
225 224 }
226 225
227 226 interface AsyncStore<T extends object, Q extends BaseQueryType = Partial<T> | ((item: T) => boolean) | string, O extends QueryOptions = QueryOptions>
228 227 extends IdentityProvider<T>, Queryable<T, Q, O, AsyncQueryResults<T>> {
229 228
230 229 /**
231 230 * If the store can be queried locally (on the client side in JS), this defines
232 231 * the query engine to use for querying the data store.
233 232 * This takes a query and query options and returns a function that can execute
234 233 * the provided query on a JavaScript array. The queryEngine may be replace to
235 234 * provide more sophisticated querying capabilities. For example:
236 235 * | var query = store.queryEngine({foo:"bar"}, {count:10});
237 236 * | query(someArray) -> filtered array
238 237 * The returned query function may have a "matches" property that can be
239 238 * used to determine if an object matches the query. For example:
240 239 * | query.matches({id:"some-object", foo:"bar"}) -> true
241 240 * | query.matches({id:"some-object", foo:"something else"}) -> false
242 241 */
243 242 queryEngine?: QueryEngine<T, Q>;
244 243
245 244 /**
246 245 * Retrieves an object by its identity
247 246 */
248 get(id: Identity): promise.Promise<T>;
247 get(id: Identity): PromiseLike<T>;
249 248
250 249 /**
251 250 * Stores an object
252 251 */
253 put(object: T, directives?: PutDirectives<T>): promise.Promise<any>;
252 put(object: T, directives?: PutDirectives<T>): PromiseLike<any>;
254 253
255 254 /**
256 255 * Creates an object, throws an error if the object already exists
257 256 */
258 add(object: T, directives?: PutDirectives<T>): promise.Promise<any>;
257 add(object: T, directives?: PutDirectives<T>): PromiseLike<any>;
259 258
260 259 /**
261 260 * Deletes an object by its identity
262 261 */
263 remove(id: string | number): promise.Promise<void>;
262 remove(id: string | number): PromiseLike<void>;
264 263
265 264 }
266 265
267 266 interface HierarchicalStore<T extends object, O extends QueryOptions = QueryOptions, R extends QueryResults<T> = QueryResults<T>> {
268 267
269 268 /**
270 269 * Retrieves the children of an object.
271 270 */
272 271 getChildren(parent: T, options?: O): R;
273 272 }
274 273
275 274 interface Store<T extends object, Q extends BaseQueryType = Partial<T> | ((item: T) => boolean) | string, O extends QueryOptions = QueryOptions>
276 275 extends IdentityProvider<T>, Queryable<T, Q, O> {
277 276
278 277 /**
279 278 * If the store can be queried locally (on the client side in JS), this defines
280 279 * the query engine to use for querying the data store.
281 280 * This takes a query and query options and returns a function that can execute
282 281 * the provided query on a JavaScript array. The queryEngine may be replace to
283 282 * provide more sophisticated querying capabilities. For example:
284 283 * | var query = store.queryEngine({foo:"bar"}, {count:10});
285 284 * | query(someArray) -> filtered array
286 285 * The returned query function may have a "matches" property that can be
287 286 * used to determine if an object matches the query. For example:
288 287 * | query.matches({id:"some-object", foo:"bar"}) -> true
289 288 * | query.matches({id:"some-object", foo:"something else"}) -> false
290 289 */
291 290 queryEngine?: QueryEngine<T, Q>;
292 291
293 292 /**
294 293 * Retrieves an object by its identity
295 294 */
296 get(id: Identity): T | promise.Promise<T>;
295 get(id: Identity): T | PromiseLike<T>;
297 296
298 297 /**
299 298 * Stores an object
300 299 */
301 300 put(object: T, directives?: PutDirectives<T>): any;
302 301
303 302 /**
304 303 * Creates an object, throws an error if the object already exists
305 304 */
306 305 add(object: T, directives?: PutDirectives<T>): any;
307 306
308 307 /**
309 308 * Deletes an object by its identity
310 309 */
311 remove(id: Identity): void | promise.Promise<void>;
310 remove(id: Identity): void | PromiseLike<void>;
312 311
313 312 }
314 313
315 314 interface StoreConstructor {
316 315 new <T extends object, Q extends BaseQueryType, O extends QueryOptions>(): Store<T, Q, O>;
317 316 }
318 317 }
319 318
320 319 namespace util {
321 320
322 321 /* dojo/store/util/QueryResults */
323 322
324 323 interface QueryResultsFunction {
325 324 /**
326 325 * A function that wraps the results of a store query with additional
327 326 * methods.
328 327 */
329 328 <T extends object>(results: T[]): api.SyncQueryResults<T>;
330 329 <T extends object>(results: PromiseLike<T[]>): api.AsyncQueryResults<T>;
331 330 }
332 331
333 332 /* dojo/store/util/SimpleQueryEngine */
334 333 interface SimpleQueryEngine extends api.QueryEngine<object, api.BaseQueryType> { }
335 334 }
336 335
337 336 /* dojo/store/Cache */
338 337
339 338 interface CacheOptions<T extends object = any> {
340 339 /**
341 340 * This is a function that will be called for each item in a query response to determine
342 341 * if it is cacheable. If isLoaded returns true, the item will be cached, otherwise it
343 342 * will not be cached. If isLoaded is not provided, all items will be cached.
344 343 */
345 344 isLoaded?: (item: T) => boolean;
346 345 }
347 346
348 347 interface CacheMixin {
349 348 /**
350 349 * Remove the object with the specific id.
351 350 */
352 remove(id: api.Identity): promise.Promise<void>;
351 remove(id: api.Identity): PromiseLike<void>;
353 352
354 353 /**
355 354 * Remove the object with the given id from the underlying caching store.
356 355 */
357 evict(id: api.Identity): promise.Promise<void>;
356 evict(id: api.Identity): PromiseLike<void>;
358 357 }
359 358
360 359 interface Cache {
361 360 /**
362 361 * The Cache store wrapper takes a master store and a caching store,
363 362 * caches data from the master into the caching store for faster
364 363 * lookup. Normally one would use a memory store for the caching
365 364 * store and a server store like JsonRest for the master store.
366 365 */
367 366 <T extends object, Q extends api.BaseQueryType, O extends api.QueryOptions, S extends api.Store<T, Q, O>>(masterStore: S, cacheStore: api.Store<T, Q, O>, options?: CacheOptions): CacheMixin & S;
368 367 }
369 368
370 369 /* dojo/store/DataStore */
371 370
372 371 interface DataStoreOptions<T extends object> {
373 372 idProperty?: keyof T;
374 373 queryEngine?: api.QueryEngine<T, api.QueryOptions>;
375 374 store?: data.api.Read<T> | data.api.Write<T> | data.api.Identity<T>;
376 375 }
377 376
378 377 interface DataStore<T extends object> extends api.Store<T, api.BaseQueryType, api.QueryOptions> {
379 378 /**
380 379 * The object store to convert to a data store
381 380 */
382 381 store: data.api.Read<T> | data.api.Write<T> | data.api.Identity<T>;
383 382
384 383 /**
385 384 * Defines the query engine to use for querying the data store
386 385 */
387 386 queryEngine: api.QueryEngine<T, api.BaseQueryType>;
388 387
389 388 _objectConverter(callback: (item: T) => any): (item: T) => any;
390 389 }
391 390
392 391 interface DataStoreConstructor extends _base.DeclareConstructor<DataStore<object>> {
393 392 new <T extends object>(options?: DataStoreOptions<T>): DataStore<T>;
394 393 }
395 394
396 395 /* dojo/store/JsonRest */
397 396
398 397 interface Headers {
399 398 [header: string]: string;
400 399 }
401 400
402 401 interface JsonRestPutDirectives<T extends object> extends api.PutDirectives<T> {
403 402 headers?: Headers;
404 403
405 404 incremental?: boolean;
406 405
407 406 timeout?: number
408 407 }
409 408
410 409 interface JsonRestQueryOptions extends api.QueryOptions {
411 410 headers?: Headers;
412 411
413 412 timeout?: number;
414 413 }
415 414
416 415 interface JsonRestOptions<T extends object> {
417 416 idProperty?: keyof T;
418 417 queryEngine?: api.QueryEngine<T, JsonRestQueryOptions>;
419 418 headers?: Headers;
420 419 target?: string;
421 420 rangeParam?: string;
422 421 sortParam?: string;
423 422 ascendingPrefix?: string;
424 423 descendingPrefix?: string;
425 424 accepts?: string;
426 425 }
427 426
428 427 interface JsonRest<
429 428 T extends object,
430 429 Q extends api.BaseQueryType = (Partial<T> | string) & api.BaseQueryType,
431 430 O extends JsonRestQueryOptions = JsonRestQueryOptions>
432 431 extends api.AsyncStore<T, Q, O> {
433 432 /**
434 433 * Additional headers to pass in all requests to the server. These can be overridden
435 434 * by passing additional headers to calls to the store.
436 435 */
437 436 headers: Headers;
438 437
439 438 /**
440 439 * The target base URL to use for all requests to the server. This string will be
441 440 * prepended to the id to generate the URL (relative or absolute) for requests
442 441 * sent to the server
443 442 */
444 443 target: string;
445 444
446 445 /**
447 446 * Use a query parameter for the requested range. If this is omitted, than the
448 447 * Range header will be used. Independent of this, the X-Range header is always set.
449 448 */
450 449 rangeParam?: string;
451 450
452 451 /**
453 452 * The query parameter to used for holding sort information. If this is omitted, than
454 453 * the sort information is included in a functional query token to avoid colliding
455 454 * with the set of name/value pairs.
456 455 */
457 456 sortParam?: string;
458 457
459 458 /**
460 459 * The prefix to apply to sort attribute names that are ascending
461 460 */
462 461 ascendingPrefix: string;
463 462
464 463 /**
465 464 * The prefix to apply to sort attribute names that are descending
466 465 */
467 466 descendingPrefix: string;
468 467
469 468 /**
470 469 * If the target has no trailing '/', then append it.
471 470 */
472 471 _getTarget(id: api.Identity): string;
473 472
474 473 /**
475 474 * Retrieves an object by its identity. This will trigger a GET request to the server using
476 475 * the url `this.target + id`.
477 476 */
478 get(id: api.Identity, options?: { headers: Headers } | Headers): promise.Promise<T>;
477 get(id: api.Identity, options?: { headers: Headers } | Headers): PromiseLike<T>;
479 478
480 479 /**
481 480 * Defines the Accept header to use on HTTP requests
482 481 */
483 482 accepts: string;
484 483
485 484 /**
486 485 * Stores an object. This will trigger a PUT request to the server
487 486 * if the object has an id, otherwise it will trigger a POST request.
488 487 */
489 put(object: T, options?: JsonRestPutDirectives<T>): promise.Promise<any>;
488 put(object: T, options?: JsonRestPutDirectives<T>): PromiseLike<any>;
490 489
491 490 /**
492 491 * Adds an object. This will trigger a PUT request to the server
493 492 * if the object has an id, otherwise it will trigger a POST request.
494 493 */
495 add(object: T, options?: JsonRestPutDirectives<T>): promise.Promise<any>;
494 add(object: T, options?: JsonRestPutDirectives<T>): PromiseLike<any>;
496 495
497 496 /**
498 497 * Deletes an object by its identity. This will trigger a DELETE request to the server.
499 498 */
500 remove(id: api.Identity, options?: { headers?: Headers }): promise.Promise<void>;
499 remove(id: api.Identity, options?: { headers?: Headers }): PromiseLike<void>;
501 500
502 501 }
503 502
504 503 interface JsonRestConstructor extends _base.DeclareConstructor<JsonRest<object, api.BaseQueryType, JsonRestQueryOptions>> {
505 504 new <T extends object, Q extends api.BaseQueryType, O extends JsonRestQueryOptions>(options?: JsonRestOptions<T>): JsonRest<T, Q, O>;
506 505 }
507 506
508 507 /* dojo/store/Memory */
509 508
510 509 interface MemoryOptions<T extends object> {
511 510 data?: T[];
512 511 idProperty?: keyof T;
513 512 queryEngine?: api.QueryEngine<T, api.QueryOptions>;
514 513 setData?: (data: T[]) => void;
515 514 }
516 515
517 516 interface Memory<T extends object> extends api.SyncStore<T> {
518 517 /**
519 518 * The array of all the objects in the memory store
520 519 */
521 520 data: T[];
522 521
523 522 /**
524 523 * An index of data indices into the data array by id
525 524 */
526 525 index: { [id: string]: number };
527 526
528 527 /**
529 528 * Sets the given data as the source for this store, and indexes it
530 529 */
531 530 setData(data: T[]): void;
532 531 }
533 532
534 533 interface MemoryConstructor extends _base.DeclareConstructor<Memory<any>> {
535 534 /**
536 535 * This is a basic in-memory object store. It implements dojo/store/api/Store.
537 536 */
538 537 new <T extends object>(options?: MemoryOptions<T>): Memory<T>;
539 538 }
540 539
541 540 /* dojo/store/Observable */
542 541
543 542 interface ObservableQueryResultsMixin<T extends object> {
544 543 /**
545 544 * Allows observation of results
546 545 */
547 546 observe(listener: (object: T, previousIndex: number, newIndex: number) => void, includeUpdates?: boolean): {
548 547 remove(): void;
549 548 cancel(): void;
550 549 };
551 550 }
552 551
553 552 type ObservableQueryResults<T extends object> = ObservableQueryResultsMixin<T> & api.QueryResults<T>;
554 553
555 554 interface ObservableMixin<T extends object, Q extends api.BaseQueryType, O extends api.QueryOptions, R extends api.QueryResults<T> = api.QueryResults<T> > {
556 555 notify(object: T, existingId?: api.Identity): void;
557 556
558 557 /**
559 558 * Queries the store for objects. This does not alter the store, but returns a
560 559 * set of data from the store.
561 560 */
562 561 query(query: Q, options?: O): ObservableQueryResultsMixin<T> & R;
563 562 }
564 563
565 564 interface ResultsObserveMixin<T extends object> {
566 565 /**
567 566 * Allows observation of results
568 567 */
569 568 observe(listener: (object: T, previousIndex: number, newIndex: number) => void, includeUpdates?: boolean): {
570 569 remove(): void;
571 570 cancel(): void;
572 571 };
573 572 }
574 573
575 574 interface Notifiable<T> {
576 575 notify(object: T, existingId?: api.Identity): void;
577 576 }
578 577
579 578 type ResultItem<R> = R extends ArrayLike<infer I1> ? I1 :
580 579 R extends PromiseLike<ArrayLike<infer I2>> ? I2 : never;
581 580
582 581 type ObservableStore<S extends { query: (...params: any) => any }> =
583 582 S extends { query: (...params: infer P) => api.QueryResults<infer R> } ? {
584 583 [K in keyof S]: K extends "query" ? (...params: P) => ReturnType<S[K]> & ResultsObserveMixin<R> : S[K];
585 584 } & Notifiable<R> : S;
586 585
587 586 interface Observable {
588 587 new <S extends api.Store<any, any, any>>(store: S): ObservableStore<S>;
589 588 }
590 589 }
591 590 }
General Comments 0
You need to be logged in to leave comments. Login now