@quiltt/core 5.0.0 → 5.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +19 -12
  3. package/dist/api/browser.cjs +14 -0
  4. package/dist/api/browser.d.ts +128 -0
  5. package/dist/api/browser.js +12 -0
  6. package/dist/api/graphql/SubscriptionLink-12s-ufJBKwu1.js +149 -0
  7. package/dist/api/graphql/SubscriptionLink-12s-wjkChfxO.cjs +150 -0
  8. package/dist/api/graphql/index.cjs +218 -0
  9. package/dist/api/graphql/index.d.ts +82 -0
  10. package/dist/api/graphql/index.js +184 -0
  11. package/dist/api/index.cjs +26 -0
  12. package/dist/api/index.d.ts +3 -0
  13. package/dist/api/index.js +3 -0
  14. package/dist/api/rest/index.cjs +225 -0
  15. package/dist/api/rest/index.d.ts +128 -0
  16. package/dist/api/rest/index.js +217 -0
  17. package/dist/auth/index.cjs +21 -0
  18. package/dist/auth/index.d.ts +29 -0
  19. package/dist/auth/index.js +19 -0
  20. package/dist/config/index.cjs +44 -0
  21. package/dist/config/index.d.ts +9 -0
  22. package/dist/config/index.js +36 -0
  23. package/dist/index.cjs +61 -0
  24. package/dist/index.d.ts +8 -524
  25. package/dist/index.js +8 -449
  26. package/dist/observables/index.cjs +30 -0
  27. package/dist/observables/index.d.ts +21 -0
  28. package/dist/observables/index.js +28 -0
  29. package/dist/storage/index.cjs +272 -0
  30. package/dist/storage/index.d.ts +91 -0
  31. package/dist/{SubscriptionLink-12s-C2VbF8Tf.js → storage/index.js} +2 -139
  32. package/dist/timing/index.cjs +30 -0
  33. package/dist/timing/index.d.ts +15 -0
  34. package/dist/timing/index.js +28 -0
  35. package/dist/types.cjs +1 -0
  36. package/dist/types.d.ts +28 -0
  37. package/dist/types.js +1 -0
  38. package/dist/utils/index.cjs +61 -0
  39. package/dist/utils/index.d.ts +18 -0
  40. package/dist/utils/index.js +57 -0
  41. package/package.json +62 -6
  42. package/src/api/graphql/client.ts +1 -1
  43. package/src/api/graphql/links/ActionCableLink.ts +7 -6
  44. package/src/api/graphql/links/AuthLink.ts +13 -9
  45. package/src/api/graphql/links/BatchHttpLink.ts +1 -1
  46. package/src/api/graphql/links/ErrorLink.ts +4 -0
  47. package/src/api/graphql/links/HttpLink.ts +1 -1
  48. package/src/api/graphql/links/VersionLink.ts +1 -1
  49. package/src/api/rest/auth.ts +1 -1
  50. package/src/api/rest/connectors.ts +1 -1
  51. package/src/auth/index.ts +1 -0
  52. package/src/{JsonWebToken.ts → auth/json-web-token.ts} +1 -1
  53. package/src/{configuration.ts → config/configuration.ts} +1 -1
  54. package/src/config/index.ts +1 -0
  55. package/src/index.ts +5 -5
  56. package/src/observables/index.ts +1 -0
  57. package/src/{Observable.ts → observables/observable.ts} +1 -1
  58. package/src/storage/Local.ts +1 -1
  59. package/src/storage/Memory.ts +2 -2
  60. package/src/storage/Storage.ts +1 -1
  61. package/src/timing/index.ts +1 -0
  62. package/src/{Timeoutable.ts → timing/timeoutable.ts} +1 -1
  63. package/src/utils/index.ts +1 -0
  64. package/src/utils/token-validation.ts +67 -0
package/dist/index.js CHANGED
@@ -1,449 +1,8 @@
1
- import { ApolloLink, ApolloClient } from '@apollo/client/core';
2
- export { gql } from '@apollo/client/core';
3
- import { G as GlobalStorage, e as endpointGraphQL, v as version, d as debugging, S as SubscriptionLink, a as endpointAuth, b as endpointRest } from './SubscriptionLink-12s-C2VbF8Tf.js';
4
- export { L as LocalStorage, M as MemoryStorage, O as Observable, c as Storage, f as cdnBase, g as endpointWebsockets } from './SubscriptionLink-12s-C2VbF8Tf.js';
5
- import { Observable } from 'rxjs';
6
- import { BatchHttpLink as BatchHttpLink$1 } from '@apollo/client/link/batch-http';
7
- import crossfetch from 'cross-fetch';
8
- import { ErrorLink as ErrorLink$1 } from '@apollo/client/link/error';
9
- import { HttpLink as HttpLink$1 } from '@apollo/client/link/http';
10
- import { RetryLink as RetryLink$1 } from '@apollo/client/link/retry';
11
- export { InMemoryCache } from '@apollo/client/cache';
12
- export { useMutation, useQuery, useSubscription } from '@apollo/client/react';
13
-
14
- /**
15
- * Enum representing the different types of events emitted by the Connector.
16
- */ var ConnectorSDKEventType = /*#__PURE__*/ function(ConnectorSDKEventType) {
17
- /** The Connector modal has been opened */ ConnectorSDKEventType["Open"] = "opened";
18
- /** The Connector has loaded successfully */ ConnectorSDKEventType["Load"] = "loaded";
19
- /** The end-user successfully completed the flow */ ConnectorSDKEventType["ExitSuccess"] = "exited.successful";
20
- /** The end-user exited the Connector before completing the flow */ ConnectorSDKEventType["ExitAbort"] = "exited.aborted";
21
- /** The end-user experienced an error during the flow */ ConnectorSDKEventType["ExitError"] = "exited.errored";
22
- return ConnectorSDKEventType;
23
- }({});
24
-
25
- /**
26
- * unauthorizedCallback only triggers in the event the token is present, and
27
- * returns the token; This allows sessions to be forgotten without race conditions
28
- * causing null sessions to kill valid sessions, or invalid sessions for killing
29
- * valid sessions during rotation and networking weirdness.
30
- */ class AuthLink extends ApolloLink {
31
- request(operation, forward) {
32
- const token = GlobalStorage.get('session');
33
- if (!token) {
34
- console.warn('QuilttLink attempted to send an unauthenticated Query');
35
- return new Observable((observer)=>{
36
- observer.error(new Error('No authentication token available'));
37
- });
38
- }
39
- operation.setContext(({ headers = {} })=>({
40
- headers: {
41
- ...headers,
42
- authorization: `Bearer ${token}`
43
- }
44
- }));
45
- return forward(operation);
46
- }
47
- }
48
-
49
- // Use `cross-fetch` only if `fetch` is not available on the `globalThis` object
50
- const effectiveFetch$2 = typeof fetch === 'undefined' ? crossfetch : fetch;
51
- const BatchHttpLink = new BatchHttpLink$1({
52
- uri: endpointGraphQL,
53
- fetch: effectiveFetch$2
54
- });
55
-
56
- const ErrorLink = new ErrorLink$1(({ error, result })=>{
57
- // In Apollo Client 4, errors are consolidated to the 'error' and 'result' properties
58
- // Handle GraphQL errors from result
59
- if (result?.errors) {
60
- result.errors.forEach((graphQLError)=>{
61
- const { message, path, extensions } = graphQLError;
62
- const formattedPath = Array.isArray(path) ? path.join('.') : path ?? 'N/A';
63
- const parts = [
64
- `[Quiltt][GraphQL Error]: ${message}`,
65
- `Path: ${formattedPath}`
66
- ];
67
- if (extensions) {
68
- if (extensions.code) parts.push(`Code: ${extensions.code}`);
69
- if (extensions.errorId) parts.push(`Error ID: ${extensions.errorId}`);
70
- }
71
- console.warn(parts.join(' | '));
72
- });
73
- }
74
- // Handle network/server errors
75
- if (error) {
76
- if ('statusCode' in error && error.statusCode === 401) {
77
- console.warn('[Quiltt][Authentication Error]:', error);
78
- GlobalStorage.set('session', null);
79
- } else if ('statusCode' in error) {
80
- console.warn('[Quiltt][Server Error]:', error);
81
- } else {
82
- console.warn('[Quiltt][Network Error]:', error);
83
- }
84
- }
85
- });
86
-
87
- const ForwardableLink = new ApolloLink((operation, forward)=>forward(operation));
88
-
89
- // Use `cross-fetch` only if `fetch` is not available on the `globalThis` object
90
- const effectiveFetch$1 = typeof fetch === 'undefined' ? crossfetch : fetch;
91
- const HttpLink = new HttpLink$1({
92
- uri: endpointGraphQL,
93
- fetch: effectiveFetch$1
94
- });
95
-
96
- const RetryLink = new RetryLink$1({
97
- attempts: {
98
- retryIf: (error, _operation)=>{
99
- if (!error) return false;
100
- const statusCode = 'statusCode' in error ? error.statusCode : undefined;
101
- return !statusCode || statusCode >= 500;
102
- }
103
- }
104
- });
105
-
106
- const TerminatingLink = new ApolloLink(()=>{
107
- return new Observable((observer)=>{
108
- observer.complete();
109
- });
110
- });
111
-
112
- /**
113
- * Extracts version number from formatted version string
114
- * @param formattedVersion - Formatted version like "@quiltt/core: v4.5.1"
115
- * @returns Version number like "4.5.1" or "unknown" if not found
116
- */ const extractVersionNumber = (formattedVersion)=>{
117
- // Find the 'v' prefix and extract version after it
118
- const vIndex = formattedVersion.indexOf('v');
119
- if (vIndex === -1) return 'unknown';
120
- const versionPart = formattedVersion.substring(vIndex + 1);
121
- const parts = versionPart.split('.');
122
- // Validate we have at least major.minor.patch
123
- if (parts.length < 3) return 'unknown';
124
- // Extract numeric parts (handles cases like "4.5.1-beta")
125
- const major = parts[0].match(/^\d+/)?.[0];
126
- const minor = parts[1].match(/^\d+/)?.[0];
127
- const patch = parts[2].match(/^\d+/)?.[0];
128
- if (!major || !minor || !patch) return 'unknown';
129
- return `${major}.${minor}.${patch}`;
130
- };
131
- /**
132
- * Generates a User-Agent string following standard format
133
- * Format: Quiltt/<version> (<platform-info>)
134
- */ const getUserAgent = (sdkVersion, platformInfo)=>{
135
- return `Quiltt/${sdkVersion} (${platformInfo})`;
136
- };
137
- /**
138
- * Detects browser information from user agent string
139
- * Returns browser name and version, or 'Unknown' if not detected
140
- */ const getBrowserInfo = ()=>{
141
- if (typeof navigator === 'undefined' || !navigator.userAgent) {
142
- return 'Unknown';
143
- }
144
- const ua = navigator.userAgent;
145
- // Edge (must be checked before Chrome)
146
- if (ua.includes('Edg/')) {
147
- const version = ua.match(/Edg\/(\d+)/)?.[1] || 'Unknown';
148
- return `Edge/${version}`;
149
- }
150
- // Chrome
151
- if (ua.includes('Chrome/') && !ua.includes('Edg/')) {
152
- const version = ua.match(/Chrome\/(\d+)/)?.[1] || 'Unknown';
153
- return `Chrome/${version}`;
154
- }
155
- // Safari (must be checked after Chrome)
156
- if (ua.includes('Safari/') && !ua.includes('Chrome/')) {
157
- const version = ua.match(/Version\/(\d+)/)?.[1] || 'Unknown';
158
- return `Safari/${version}`;
159
- }
160
- // Firefox
161
- if (ua.includes('Firefox/')) {
162
- const version = ua.match(/Firefox\/(\d+)/)?.[1] || 'Unknown';
163
- return `Firefox/${version}`;
164
- }
165
- return 'Unknown';
166
- };
167
-
168
- const createVersionLink = (platformInfo)=>{
169
- const versionNumber = extractVersionNumber(version);
170
- const userAgent = getUserAgent(versionNumber, platformInfo);
171
- return new ApolloLink((operation, forward)=>{
172
- operation.setContext(({ headers = {} })=>({
173
- headers: {
174
- ...headers,
175
- 'Quiltt-Client-Version': version,
176
- 'User-Agent': userAgent
177
- }
178
- }));
179
- return forward(operation);
180
- });
181
- };
182
-
183
- class QuilttClient extends ApolloClient {
184
- constructor(options){
185
- const finalOptions = {
186
- ...options,
187
- devtools: {
188
- enabled: options.devtools?.enabled ?? debugging
189
- }
190
- };
191
- const initialLinks = options.customLinks ? [
192
- ...options.customLinks
193
- ] : [];
194
- const isOperationDefinition = (def)=>def.kind === 'OperationDefinition';
195
- const isSubscriptionOperation = (operation)=>{
196
- return operation.query.definitions.some((definition)=>isOperationDefinition(definition) && definition.operation === 'subscription');
197
- };
198
- const isBatchable = (operation)=>{
199
- return operation.getContext().batchable ?? true;
200
- };
201
- const authLink = new AuthLink();
202
- const subscriptionsLink = new SubscriptionLink();
203
- const quilttLink = ApolloLink.from([
204
- ...initialLinks,
205
- options.versionLink,
206
- authLink,
207
- ErrorLink,
208
- RetryLink
209
- ]).split(isSubscriptionOperation, subscriptionsLink, ForwardableLink).split(isBatchable, BatchHttpLink, HttpLink);
210
- super({
211
- link: quilttLink,
212
- ...finalOptions
213
- });
214
- }
215
- }
216
-
217
- // Use `cross-fetch` only if `fetch` is not available on the `globalThis` object
218
- const effectiveFetch = typeof fetch === 'undefined' ? crossfetch : fetch;
219
- const RETRY_DELAY = 150 // ms
220
- ;
221
- const RETRIES = 10 // 150, 300, 450, 600, 750, 900, 1050, 1200, 1350, 1500 = 8.250s
222
- ;
223
- /**
224
- * A wrapper around the native `fetch` function that adds automatic retries on failure, including network errors and HTTP 429 responses.
225
- * Now treats any response with status < 500 as valid.
226
- */ const fetchWithRetry = async (url, options = {
227
- retry: false
228
- })=>{
229
- const { retry, retriesRemaining, validateStatus = (status)=>status >= 200 && status < 300, ...fetchOptions } = options;
230
- try {
231
- const response = await effectiveFetch(url, fetchOptions);
232
- const isResponseOk = validateStatus(response.status);
233
- if (isResponseOk) {
234
- return {
235
- data: await response.json().catch(()=>null),
236
- status: response.status,
237
- statusText: response.statusText,
238
- headers: response.headers,
239
- ok: isResponseOk
240
- };
241
- }
242
- // If validateStatus fails, and retry is enabled, prepare to retry for eligible status codes
243
- if (retry && (response.status >= 500 || response.status === 429)) {
244
- throw new Error('Retryable failure');
245
- }
246
- throw new Error(`HTTP error with status ${response.status}`);
247
- } catch (error) {
248
- if (retry) {
249
- const currentRetriesRemaining = retriesRemaining !== undefined ? retriesRemaining : RETRIES;
250
- if (currentRetriesRemaining > 0) {
251
- const delayTime = RETRY_DELAY * (RETRIES - currentRetriesRemaining);
252
- await new Promise((resolve)=>setTimeout(resolve, delayTime));
253
- return fetchWithRetry(url, {
254
- ...options,
255
- retriesRemaining: currentRetriesRemaining - 1
256
- });
257
- }
258
- }
259
- return Promise.reject(error);
260
- }
261
- };
262
-
263
- var AuthStrategies = /*#__PURE__*/ function(AuthStrategies) {
264
- AuthStrategies["Email"] = "email";
265
- AuthStrategies["Phone"] = "phone";
266
- return AuthStrategies;
267
- }({});
268
- // https://www.quiltt.dev/api-reference/auth
269
- class AuthAPI {
270
- constructor(clientId){
271
- /**
272
- * Response Statuses:
273
- * - 200: OK -> Session is Valid
274
- * - 401: Unauthorized -> Session is Invalid
275
- */ this.ping = async (token)=>{
276
- const response = await fetchWithRetry(endpointAuth, {
277
- method: 'GET',
278
- ...this.config(token)
279
- });
280
- return response;
281
- };
282
- /**
283
- * Response Statuses:
284
- * - 201: Created -> Profile Created, New Session Returned
285
- * - 202: Accepted -> Profile Found, MFA Code Sent for `authenticate`
286
- * - 422: Unprocessable Entity -> Invalid Payload
287
- */ this.identify = async (payload)=>{
288
- const response = await fetchWithRetry(endpointAuth, {
289
- method: 'POST',
290
- body: JSON.stringify(this.body(payload)),
291
- ...this.config()
292
- });
293
- return response;
294
- };
295
- /**
296
- * Response Statuses:
297
- * - 201: Created -> MFA Validated, New Session Returned
298
- * - 401: Unauthorized -> MFA Invalid
299
- * - 422: Unprocessable Entity -> Invalid Payload
300
- */ this.authenticate = async (payload)=>{
301
- const response = await fetchWithRetry(endpointAuth, {
302
- method: 'PUT',
303
- body: JSON.stringify(this.body(payload)),
304
- ...this.config()
305
- });
306
- return response;
307
- };
308
- /**
309
- * Response Statuses:
310
- * - 204: No Content -> Session Revoked
311
- * - 401: Unauthorized -> Session Not Found
312
- */ this.revoke = async (token)=>{
313
- const response = await fetchWithRetry(endpointAuth, {
314
- method: 'DELETE',
315
- ...this.config(token)
316
- });
317
- return response;
318
- };
319
- this.config = (token)=>{
320
- const headers = new Headers();
321
- headers.set('Content-Type', 'application/json');
322
- headers.set('Accept', 'application/json');
323
- if (token) {
324
- headers.set('Authorization', `Bearer ${token}`);
325
- }
326
- return {
327
- headers,
328
- validateStatus: this.validateStatus,
329
- retry: true
330
- };
331
- };
332
- this.validateStatus = (status)=>status < 500 && status !== 429;
333
- this.body = (payload)=>{
334
- if (!this.clientId) {
335
- console.error('Quiltt Client ID is not set. Unable to identify & authenticate');
336
- }
337
- return {
338
- session: {
339
- clientId: this.clientId,
340
- ...payload
341
- }
342
- };
343
- };
344
- this.clientId = clientId;
345
- }
346
- }
347
-
348
- class ConnectorsAPI {
349
- constructor(clientId, userAgent = getUserAgent(extractVersionNumber(version), 'Unknown')){
350
- /**
351
- * Response Statuses:
352
- * - 200: OK -> Institutions Found
353
- * - 401: Unauthorized -> Invalid Token
354
- * - 403: Forbidden -> Unsupported SDK
355
- * - 400: Bad Request -> Invalid Request
356
- */ this.searchInstitutions = async (token, connectorId, term, signal)=>{
357
- const params = new URLSearchParams();
358
- params.append('term', term);
359
- const response = await fetchWithRetry(`${endpointRest}/sdk/connectors/${connectorId}/institutions?${params}`, {
360
- method: 'GET',
361
- signal,
362
- ...this.config(token)
363
- });
364
- return response;
365
- };
366
- /**
367
- * Response Statuses:
368
- * - 200: OK -> Provider API ID is resolvable or not
369
- * - 401: Unauthorized -> Invalid Token
370
- * - 403: Forbidden -> Unsupported SDK
371
- * - 400: Bad Request -> Missing provider API ID parameter
372
- * - 404: Not Found -> Connector not found
373
- */ this.checkResolvable = async (token, connectorId, providerId, signal)=>{
374
- const params = new URLSearchParams();
375
- const providerKey = Object.keys(providerId)[0];
376
- if (providerKey && providerId[providerKey]) {
377
- params.append(providerKey, providerId[providerKey]);
378
- }
379
- const response = await fetchWithRetry(`${endpointRest}/sdk/connectors/${connectorId}/resolvable?${params}`, {
380
- method: 'GET',
381
- signal,
382
- ...this.config(token)
383
- });
384
- return response;
385
- };
386
- this.config = (token)=>{
387
- const headers = new Headers();
388
- headers.set('Content-Type', 'application/json');
389
- headers.set('Accept', 'application/json');
390
- headers.set('User-Agent', this.userAgent);
391
- headers.set('Authorization', `Bearer ${token}`);
392
- return {
393
- headers,
394
- validateStatus: this.validateStatus,
395
- retry: true
396
- };
397
- };
398
- this.validateStatus = (status)=>status < 500 && status !== 429;
399
- this.clientId = clientId;
400
- this.userAgent = userAgent;
401
- }
402
- }
403
-
404
- const MATCHER = /^(?:[\w-]+\.){2}[\w-]+$/;
405
- const JsonWebTokenParse = (token)=>{
406
- if (typeof token === 'undefined' || token === null) return token;
407
- if (!MATCHER.test(token)) {
408
- console.error(`Invalid Session Token: ${token}`);
409
- return;
410
- }
411
- const [_header, payload, _signature] = token.split('.');
412
- try {
413
- return {
414
- token,
415
- claims: JSON.parse(atob(payload))
416
- };
417
- } catch (error) {
418
- console.error(`Invalid Session Token: ${error}`);
419
- }
420
- };
421
-
422
- /**
423
- * This is designed to support singletons to timeouts that can broadcast
424
- * to any observers, preventing race conditions with multiple timeouts.
425
- */ class Timeoutable {
426
- constructor(){
427
- this.observers = [];
428
- this.set = (callback, delay)=>{
429
- if (this.timeout) {
430
- clearTimeout(this.timeout);
431
- }
432
- // Replace all observers with the new one
433
- this.observers = [
434
- callback
435
- ];
436
- this.timeout = setTimeout(this.broadcast.bind(this), delay);
437
- };
438
- this.clear = (observer)=>{
439
- this.observers = this.observers.filter((callback)=>callback !== observer);
440
- };
441
- // Only sends to the 1st listener, but ensures that someone is notified
442
- this.broadcast = ()=>{
443
- if (this.observers.length === 0) return;
444
- this.observers[0](undefined);
445
- };
446
- }
447
- }
448
-
449
- export { AuthAPI, AuthLink, AuthStrategies, BatchHttpLink, ConnectorSDKEventType, ConnectorsAPI, ErrorLink, ForwardableLink, GlobalStorage, HttpLink, JsonWebTokenParse, QuilttClient, RetryLink, SubscriptionLink, TerminatingLink, Timeoutable, createVersionLink, debugging, endpointAuth, endpointGraphQL, endpointRest, extractVersionNumber, getBrowserInfo, getUserAgent, version };
1
+ export * from './api/index.js';
2
+ export * from './auth/index.js';
3
+ export * from './config/index.js';
4
+ export * from './observables/index.js';
5
+ export * from './storage/index.js';
6
+ export * from './timing/index.js';
7
+ export * from './types.js';
8
+ export * from './utils/index.js';
@@ -0,0 +1,30 @@
1
+ Object.defineProperty(exports, '__esModule', { value: true });
2
+
3
+ /**
4
+ * This is designed to support singletons to share the memory states across all
5
+ * instance of hooks to ensure that updates only process once, by storing a value
6
+ * then notifying all subscribers when it's updated.
7
+ */ class Observable {
8
+ constructor(initialState){
9
+ this.observers = [];
10
+ this.get = ()=>{
11
+ return this.state;
12
+ };
13
+ this.set = (nextState)=>{
14
+ if (this.state === nextState) return;
15
+ this.state = nextState;
16
+ this.observers.forEach((update)=>{
17
+ update(nextState);
18
+ });
19
+ };
20
+ this.subscribe = (observer)=>{
21
+ this.observers.push(observer);
22
+ };
23
+ this.unsubscribe = (observer)=>{
24
+ this.observers = this.observers.filter((update)=>update !== observer);
25
+ };
26
+ this.state = initialState;
27
+ }
28
+ }
29
+
30
+ exports.Observable = Observable;
@@ -0,0 +1,21 @@
1
+ import { Dispatch, SetStateAction } from 'react';
2
+ import { Maybe } from '../types.js';
3
+
4
+ type Observer<T> = Dispatch<SetStateAction<Maybe<T> | undefined>>;
5
+ /**
6
+ * This is designed to support singletons to share the memory states across all
7
+ * instance of hooks to ensure that updates only process once, by storing a value
8
+ * then notifying all subscribers when it's updated.
9
+ */
10
+ declare class Observable<T> {
11
+ private state?;
12
+ private observers;
13
+ constructor(initialState?: Maybe<T>);
14
+ get: () => Maybe<T> | undefined;
15
+ set: (nextState: Maybe<T> | undefined) => void;
16
+ subscribe: (observer: Observer<T>) => void;
17
+ unsubscribe: (observer: Observer<T>) => void;
18
+ }
19
+
20
+ export { Observable };
21
+ export type { Observer };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * This is designed to support singletons to share the memory states across all
3
+ * instance of hooks to ensure that updates only process once, by storing a value
4
+ * then notifying all subscribers when it's updated.
5
+ */ class Observable {
6
+ constructor(initialState){
7
+ this.observers = [];
8
+ this.get = ()=>{
9
+ return this.state;
10
+ };
11
+ this.set = (nextState)=>{
12
+ if (this.state === nextState) return;
13
+ this.state = nextState;
14
+ this.observers.forEach((update)=>{
15
+ update(nextState);
16
+ });
17
+ };
18
+ this.subscribe = (observer)=>{
19
+ this.observers.push(observer);
20
+ };
21
+ this.unsubscribe = (observer)=>{
22
+ this.observers = this.observers.filter((update)=>update !== observer);
23
+ };
24
+ this.state = initialState;
25
+ }
26
+ }
27
+
28
+ export { Observable };