@de./sdk 1.0.0

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 (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +4391 -0
  3. package/dist/allend/Access.d.ts +15 -0
  4. package/dist/allend/Access.js +164 -0
  5. package/dist/allend/Access.js.map +1 -0
  6. package/dist/allend/DAuth/index.d.ts +20 -0
  7. package/dist/allend/DAuth/index.js +92 -0
  8. package/dist/allend/DAuth/index.js.map +1 -0
  9. package/dist/allend/DClient/Client.d.ts +10 -0
  10. package/dist/allend/DClient/Client.js +144 -0
  11. package/dist/allend/DClient/Client.js.map +1 -0
  12. package/dist/allend/DClient/Event.d.ts +18 -0
  13. package/dist/allend/DClient/Event.js +100 -0
  14. package/dist/allend/DClient/Event.js.map +1 -0
  15. package/dist/allend/DClient/Order.d.ts +49 -0
  16. package/dist/allend/DClient/Order.js +601 -0
  17. package/dist/allend/DClient/Order.js.map +1 -0
  18. package/dist/allend/IoTClient.d.ts +29 -0
  19. package/dist/allend/IoTClient.js +59 -0
  20. package/dist/allend/IoTClient.js.map +1 -0
  21. package/dist/allend/MSI/Controls.d.ts +256 -0
  22. package/dist/allend/MSI/Controls.js +763 -0
  23. package/dist/allend/MSI/Controls.js.map +1 -0
  24. package/dist/allend/MSI/Handles.d.ts +88 -0
  25. package/dist/allend/MSI/Handles.js +445 -0
  26. package/dist/allend/MSI/Handles.js.map +1 -0
  27. package/dist/allend/MSI/Plugins.d.ts +22 -0
  28. package/dist/allend/MSI/Plugins.js +38 -0
  29. package/dist/allend/MSI/Plugins.js.map +1 -0
  30. package/dist/allend/MSI/index.d.ts +55 -0
  31. package/dist/allend/MSI/index.js +155 -0
  32. package/dist/allend/MSI/index.js.map +1 -0
  33. package/dist/allend/index.d.ts +12 -0
  34. package/dist/allend/index.js +18 -0
  35. package/dist/allend/index.js.map +1 -0
  36. package/dist/backend/Auth.d.ts +38 -0
  37. package/dist/backend/Auth.js +286 -0
  38. package/dist/backend/Auth.js.map +1 -0
  39. package/dist/baseUrl.d.ts +12 -0
  40. package/dist/baseUrl.js +16 -0
  41. package/dist/baseUrl.js.map +1 -0
  42. package/dist/bundle.js +1 -0
  43. package/dist/index.d.ts +13 -0
  44. package/dist/index.js +20 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/types/access.d.ts +8 -0
  47. package/dist/types/auth.d.ts +27 -0
  48. package/dist/types/index.d.ts +281 -0
  49. package/dist/utils/index.d.ts +5 -0
  50. package/dist/utils/index.js +8 -0
  51. package/dist/utils/index.js.map +1 -0
  52. package/dist/utils/stream.d.ts +20 -0
  53. package/dist/utils/stream.js +63 -0
  54. package/dist/utils/stream.js.map +1 -0
  55. package/package.json +66 -0
  56. package/src/allend/Access.ts +78 -0
  57. package/src/allend/DAuth/index.ts +39 -0
  58. package/src/allend/DClient/Client.ts +76 -0
  59. package/src/allend/DClient/Event.ts +76 -0
  60. package/src/allend/DClient/Order.ts +452 -0
  61. package/src/allend/IoTClient.ts +68 -0
  62. package/src/allend/MSI/Controls.ts +760 -0
  63. package/src/allend/MSI/Handles.ts +387 -0
  64. package/src/allend/MSI/Plugins.ts +52 -0
  65. package/src/allend/MSI/index.ts +160 -0
  66. package/src/allend/index.ts +18 -0
  67. package/src/backend/Auth.ts +201 -0
  68. package/src/baseUrl.ts +13 -0
  69. package/src/index.ts +19 -0
  70. package/src/types/access.d.ts +8 -0
  71. package/src/types/auth.d.ts +27 -0
  72. package/src/types/index.d.ts +281 -0
  73. package/src/utils/index.ts +4 -0
  74. package/src/utils/stream.ts +68 -0
@@ -0,0 +1,201 @@
1
+ import { API_SERVER_BASEURL } from '../baseUrl'
2
+ import type { AuthCredentials, AuthOptions, AuthRequestOptions } from '../types/auth'
3
+
4
+ const ACCESS_TOKEN_EXPIRY = 3.75 // in 3 minutes 45 seconds
5
+
6
+ type AuthResponse = {
7
+ error: boolean
8
+ message: string
9
+ token: string
10
+ }
11
+
12
+ export default class Auth {
13
+ private version: number
14
+ private options: AuthOptions
15
+ protected creds: AuthCredentials
16
+ private refreshTimer?: NodeJS.Timeout
17
+ private autorefresh?: boolean
18
+ private onNewToken?: (token: string) => void
19
+ private baseURL: string
20
+ private isRotating: boolean = false
21
+ public accessToken?: string
22
+
23
+ constructor( creds: AuthCredentials, options?: AuthOptions ){
24
+ if( !creds ) throw new Error('Undefined Credentials. Check https://doc.dedot.io/sdk/auth')
25
+ if( !creds.context ) throw new Error('Undefined Context Reference. Check https://doc.dedot.io/sdk/auth')
26
+ if( !creds.remoteOrigin ) throw new Error('Undefined Remote Origin. Check https://doc.dedot.io/sdk/auth')
27
+ if( !creds.cid ) throw new Error('Undefined Connector ID. Check https://doc.dedot.io/sdk/auth')
28
+ if( !creds.secret ) throw new Error('Undefined Connector Secret. Check https://doc.dedot.io/sdk/auth')
29
+
30
+ this.creds = creds
31
+ this.options = options || { env: 'dev' }
32
+
33
+ this.version = this.options.version || 1
34
+ this.baseURL = API_SERVER_BASEURL[ this.options.env || 'dev' ]
35
+ this.autorefresh = this.options.autorefresh || false
36
+ this.onNewToken = this.options.onNewToken
37
+ }
38
+
39
+ private async request<T>({ url, ...options }: AuthRequestOptions ): Promise<T> {
40
+ const rawOptions: any = {
41
+ method: 'GET',
42
+ headers: {
43
+ /**
44
+ * Default User agent for SDK request calls
45
+ *
46
+ * NOTE: Later replace by latest SDK version
47
+ */
48
+ 'origin': this.creds.remoteOrigin,
49
+ 'de-user-agent': `De.remote/${this.version}.0`
50
+ }
51
+ }
52
+
53
+ if( this.accessToken )
54
+ rawOptions.headers.authorization = `Bearer ${this.accessToken}`
55
+
56
+ if( options.body ){
57
+ rawOptions.headers['content-type'] = 'application/json'
58
+ if( typeof options.body === 'object' )
59
+ options.body = JSON.stringify( options.body )
60
+ }
61
+
62
+ options = { ...rawOptions, ...options }
63
+
64
+ this.debug('Auth request', `${this.baseURL}/v${this.version}/${url.replace(/^\//, '')}`, options )
65
+ const response = await fetch(`${this.baseURL}/v${this.version}/${url.replace(/^\//, '')}`, options )
66
+
67
+ return await response.json() as T
68
+ }
69
+
70
+ private debug( ...args: any[] ){
71
+ this.options?.env === 'dev' && console.debug('[Auth]', ...args )
72
+ }
73
+ private error( ...args: any[] ){
74
+ console.error('[Auth]', ...args )
75
+ }
76
+
77
+ /**
78
+ * Schedule next token rotation
79
+ */
80
+ private scheduleRotation(){
81
+ if( !this.autorefresh ) return
82
+
83
+ this.clearRotation()
84
+ this.refreshTimer = setTimeout( () => this.rotateToken(), ACCESS_TOKEN_EXPIRY * 60 * 1000 )
85
+ }
86
+
87
+ /**
88
+ * Clear rotation timer
89
+ */
90
+ private clearRotation(){
91
+ if( !this.refreshTimer ) return
92
+
93
+ clearTimeout( this.refreshTimer )
94
+ this.refreshTimer = undefined
95
+ }
96
+
97
+ async getToken(): Promise<string>{
98
+ const
99
+ options: AuthRequestOptions = {
100
+ url: '/access/token',
101
+ method: 'POST',
102
+ body: this.creds
103
+ },
104
+ { error, message, token } = await this.request<AuthResponse>( options )
105
+ if( error ) throw new Error( message )
106
+
107
+ this.accessToken = token
108
+ this.scheduleRotation() // Schedule auto-refresh
109
+
110
+ return token
111
+ }
112
+
113
+ async rotateToken(): Promise<string> {
114
+ // Prevent concurrent rotation attempts
115
+ if( this.isRotating ){
116
+ this.debug('Token rotation already in progress, skipping')
117
+ return this.accessToken!
118
+ }
119
+
120
+ if( !this.accessToken )
121
+ throw new Error('No access token found')
122
+
123
+ this.isRotating = true
124
+
125
+ try {
126
+ const
127
+ options: AuthRequestOptions = {
128
+ url: '/access/token/rotate',
129
+ method: 'PATCH',
130
+ body: { secret: this.creds.secret }
131
+ },
132
+ { error, message, token } = await this.request<AuthResponse>( options )
133
+ if( error ) throw new Error( message )
134
+
135
+ this.accessToken = token
136
+
137
+ // Notify callback listener
138
+ if( typeof this.onNewToken === 'function' )
139
+ try { this.onNewToken( token ) }
140
+ catch( callbackError ){ this.error('[Auth] Error in onNewToken callback:', callbackError) }
141
+
142
+ // Schedule next rotation
143
+ this.scheduleRotation()
144
+
145
+ this.debug('Token rotated successfully')
146
+ return token
147
+ }
148
+ catch( error: any ){
149
+ this.debug('Refresh access token failed:', error.message)
150
+
151
+ try {
152
+ // Fallback: Get new token instead
153
+ const newToken = await this.getToken()
154
+ this.debug('Fallback to new token successful')
155
+
156
+ // Notify callback listener about new token
157
+ if( this.onNewToken )
158
+ try { this.onNewToken( newToken ) }
159
+ catch( callbackError ){ this.error('Error in onNewToken callback:', callbackError) }
160
+
161
+ return newToken
162
+ }
163
+ catch( fallbackError: any ){
164
+ this.error('Fallback to new token also failed:', fallbackError.message)
165
+ throw fallbackError
166
+ }
167
+ }
168
+ finally { this.isRotating = false }
169
+ }
170
+
171
+ /**
172
+ * Stop auto-refresh and clean up resources
173
+ */
174
+ stopAutoRefresh(){
175
+ this.debug('Stopping auto-refresh')
176
+
177
+ this.clearRotation()
178
+ this.autorefresh = false
179
+ }
180
+
181
+ /**
182
+ * Start auto-refresh
183
+ */
184
+ startAutoRefresh(){
185
+ this.debug('Starting auto-refresh')
186
+
187
+ this.autorefresh = true
188
+ this.scheduleRotation()
189
+ }
190
+
191
+ /**
192
+ * Clean up all resources
193
+ */
194
+ destroy(){
195
+ this.debug('Destroying Auth instance')
196
+ this.stopAutoRefresh()
197
+
198
+ this.accessToken = undefined
199
+ this.onNewToken = undefined
200
+ }
201
+ }
package/src/baseUrl.ts ADDED
@@ -0,0 +1,13 @@
1
+
2
+ export const ASI_SERVER_BASEURL = {
3
+ dev: 'http://localhost:44000',
4
+ prod: 'https://auth.dedot.io'
5
+ }
6
+ export const API_SERVER_BASEURL = {
7
+ dev: 'http://localhost:24800',
8
+ prod: 'https://api.dedot.io'
9
+ }
10
+ export const IOT_SERVER_BASEURL = {
11
+ dev: 'http://localhost:11011',
12
+ prod: 'https://iot.dedot.io'
13
+ }
package/src/index.ts ADDED
@@ -0,0 +1,19 @@
1
+ import Utils from './utils'
2
+ import MSI, { type MSIInterface } from './allend/MSI'
3
+ import IoTClient, { type IoTClientOptions } from './allend/IoTClient'
4
+ import Order from './allend/DClient/Order'
5
+ import Event from './allend/DClient/Event'
6
+ import Client from './allend/DClient/Client'
7
+ import Auth from './backend/Auth'
8
+
9
+ const DClient = { Client, Order, Event }
10
+
11
+ export {
12
+ Auth,
13
+ MSI,
14
+ type MSIInterface,
15
+ Utils,
16
+ DClient,
17
+ IoTClient,
18
+ type IoTClientOptions
19
+ }
@@ -0,0 +1,8 @@
1
+ export type AccessOptions = {
2
+ env: 'dev' | 'prod'
3
+ platform: 'web' | 'mobile' | 'server' | 'proxy'
4
+ version?: number
5
+ context: string
6
+ accessToken: string
7
+ remoteOrigin?: string
8
+ }
@@ -0,0 +1,27 @@
1
+ export type AuthOptions = {
2
+ env: 'dev' | 'prod'
3
+ version?: number
4
+ autorefresh?: boolean
5
+ onNewToken?: (token: string) => void
6
+ }
7
+
8
+ export type AuthCredentials = {
9
+ context: string
10
+ remoteOrigin: string
11
+ cid: string
12
+ secret: string
13
+ }
14
+
15
+ export type SocketAuthCredentials = {
16
+ utype: string
17
+ id: string
18
+ remoteOrigin: string
19
+ accessToken: string
20
+ }
21
+
22
+ export type AuthRequestOptions = {
23
+ url: string
24
+ method: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE'
25
+ headers?: { [index: string]: string }
26
+ body?: any
27
+ }
@@ -0,0 +1,281 @@
1
+ export type HTTPRequestOptions = {
2
+ url: string
3
+ method: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE'
4
+ headers?: { [index: string]: string }
5
+ body?: any
6
+ }
7
+ export type HTTPResponse = {
8
+ error: boolean
9
+ message?: string
10
+ }
11
+
12
+ export type LngLat = [number, number]
13
+ export type Coordinates = {
14
+ lng: number
15
+ lat: number
16
+ }
17
+ export type PickedLocation = {
18
+ point: {
19
+ x: number,
20
+ y: number
21
+ }
22
+ coordinates: Coordinates
23
+ }
24
+ export type RTLocation = Coordinates & {
25
+ heading?: number
26
+ }
27
+ export type ActivePosition = {
28
+ id: string
29
+ position: RTLocation
30
+ caption?: Caption
31
+ }
32
+
33
+ /**
34
+ * Configuration options for AnimatedPolyline
35
+ */
36
+ export type AnimatedRouteNativePathType = 'dot' | 'solid'
37
+ export type AnimatedRouteNativeMethods = 'dot:flow'
38
+ | 'dot:fade'
39
+ | 'dot:pulse'
40
+ | 'dot:directional'
41
+ | 'solid:flow'
42
+ | 'solid:fade'
43
+ | 'solid:pulse'
44
+ | 'solid:directional'
45
+ export interface AnimatedRouteRules {
46
+ styles?: any;
47
+ speed?: number; // pixels per frame
48
+ fadeLength?: number; // length of fade effect in pixels
49
+ }
50
+ export interface AnimatedRoute {
51
+ key: number | null
52
+ currentOffset: number
53
+ rules: Required<AnimatedRouteRules>
54
+ startTime?: number;
55
+ polyline?: any
56
+ engine: any
57
+ path: Coordinates[]
58
+
59
+ /**
60
+ * Create the polyline
61
+ */
62
+ create( pathType?: AnimatedRouteNativePathType ): void;
63
+
64
+ /**
65
+ * Animation methods
66
+ */
67
+ apply: Record<string, () => void>;
68
+
69
+ /**
70
+ * Stop the current animation
71
+ */
72
+ stop(): void;
73
+
74
+ /**
75
+ * Remove the polyline from the map and stop animation
76
+ */
77
+ remove(): void;
78
+ }
79
+ export type AnimatedRouteOptions = AnimatedRouteNativeMethods | {
80
+ handler?: new (engine: Engine, path: Coordinates[], rules?: AnimatedRouteRules) => AnimatedRoute
81
+ method?: string
82
+ rules?: AnimatedRouteRules
83
+ }
84
+
85
+ export type Journey = {
86
+ routeId: string | number
87
+ origin?: MapWaypoint
88
+ destination?: MapWaypoint
89
+ waypoints?: MapWaypoint[]
90
+ options?: RouteOptions
91
+ }
92
+ export type ActiveDirection<RouteSpec> = {
93
+ routeId: string | number
94
+ profile: string
95
+ origin?: Coordinates
96
+ destination?: Coordinates
97
+ waypoints: Coordinates[]
98
+ route: RouteSpec | null
99
+ }
100
+
101
+ export type RouteID = string | number
102
+ export type RoutesFitBoundsOptions = {
103
+ includes?: RouteID[]
104
+ margin?: number
105
+ }
106
+ export type RouteOptions = {
107
+ id?: string | number
108
+ mode?: 'default' | 'navigation'
109
+ profile?: 'driving-traffic' | 'driving' | 'cycling' | 'biking' | 'walking' | 'transit'
110
+ unit?: 'metric' | 'imperial',
111
+ preference?: 'TRAFFIC_AWARE' | 'TRAFFIC_UNAWARE'
112
+ pointless?: boolean
113
+ styles?: any
114
+ animation?: AnimatedRouteOptions
115
+ }
116
+
117
+ export type SearchPlace = {
118
+ name: string
119
+ location: Coordinates,
120
+ address: string
121
+ }
122
+
123
+ export type Waypoint = {
124
+ no: number
125
+ type: 'pickup' | 'dropoff'
126
+ description: string
127
+ coordinates: Coordinates
128
+ address?: string
129
+ contact: {
130
+ type: string
131
+ reference: string
132
+ phone?: string
133
+ email?: string
134
+ }
135
+ }
136
+ export type WaypointIndex = 'origin' | 'destination' | number
137
+ export type WaypointOptions = {
138
+ no?: number
139
+ type?: 'pickup' | 'dropoff'
140
+ description?: string
141
+ coordinates?: Coordinates
142
+ address?: string
143
+ 'contact.type'?: string
144
+ 'contact.reference'?: string
145
+ 'contact.phone'?: string
146
+ 'contact.email'?: string
147
+ }
148
+
149
+ export type Package = {
150
+ waypointNo: number
151
+ careLevel: number
152
+ category: string
153
+ weight: number
154
+ note?: string
155
+ }
156
+ export type PackageOptions = {
157
+ waypointNo?: number
158
+ careLevel?: number
159
+ category?: string
160
+ weight?: number
161
+ note?: string
162
+ }
163
+
164
+ export type PaymentMode = 'cash' | 'card' | 'momo' | 'wigo'
165
+ export type OrderService = {
166
+ fees: {
167
+ total: {
168
+ amount: number
169
+ currency: string
170
+ },
171
+ tax: number
172
+ discount: number
173
+ }
174
+ payment: {
175
+ mode: PaymentMode
176
+ paid: boolean
177
+ }
178
+ xpress: string
179
+ }
180
+ export type OrderServiceOptions = {
181
+ 'fees.total.amount'?: number
182
+ 'fees.total.currency'?: string
183
+ 'fees.tax'?: string
184
+ 'fees.discount'?: string
185
+ 'payment.mode'?: PaymentMode
186
+ 'payment.option'?: string
187
+ 'payment.paid'?: boolean
188
+ xpress?: string
189
+ }
190
+ export type OrderOperator = {}
191
+ export type OrderStage = {
192
+ current: string
193
+ status: string
194
+ }
195
+
196
+ export type Message = {
197
+ type: 'text' | 'location' | 'media'
198
+ sender: string
199
+ content: string
200
+ timestamp: string
201
+ }
202
+ export type Caption = {
203
+ duration?: number
204
+ unit?: string
205
+ label?: string
206
+ }
207
+ export type Peer = {
208
+ utype: string
209
+ id: string
210
+ }
211
+
212
+ export type MapOptions = {
213
+ element: string
214
+ accessToken: string
215
+ version?: number
216
+ env?: 'dev' | 'prod'
217
+ }
218
+ export type MapLayerStyle = 'streets' | 'outdoors' | 'light' | 'dark' | 'satellite'
219
+ export type MapWaypoint = {
220
+ index?: number
221
+ coords: Coordinates
222
+ caption?: Caption
223
+ }
224
+
225
+ export type Entity = {
226
+ id: string
227
+ status: 'ACTIVE' | 'BUSY'
228
+ grade: '1H' | '2H' | '3H'
229
+ currentLocation: RTLocation
230
+ static?: boolean
231
+ type: 'moto' | 'car' | 'bike' | 'truck' | 'plane' | 'ship' | 'restaurant' | 'hotel' | 'store' | 'office' | 'warehouse'
232
+ }
233
+ export type EntitySpecs = {
234
+ id: string
235
+ status: 'ACTIVE' | 'BUSY'
236
+ grade: '1H' | '2H' | '3H'
237
+ currentLocation: RTLocation
238
+ static?: boolean
239
+ type: 'moto' | 'car' | 'bike' | 'truck' | 'plane' | 'ship' | 'restaurant' | 'hotel' | 'store' | 'office' | 'warehouse'
240
+ }
241
+
242
+ export interface UserLocationOptions {
243
+ // Base point styling options
244
+ borderRadius?: number
245
+ borderColor?: string
246
+ borderOpacity?: number
247
+ dotColor?: string
248
+ showInnerDot?: boolean
249
+ noRing?: boolean
250
+
251
+ // User location specific options
252
+ showDirectionArrow?: boolean
253
+ arrowColor?: string
254
+ arrowSize?: number
255
+ pulseAnimation?: boolean
256
+ accuracyCircle?: boolean
257
+ accuracyColor?: string
258
+ accuracyOpacity?: number
259
+
260
+ // Callbacks
261
+ onLocationUpdate?: ( location: RTLocation ) => void
262
+ onLocationError?: ( error: GeolocationPositionError ) => void
263
+ }
264
+ export type DragPickOptions = {
265
+ snapToRoad?: boolean
266
+ pinPoints?: boolean
267
+ pointOptions?: CustomPointOptions
268
+ }
269
+ export type DragPickContentType = 'duration' | 'distance' | 'preloader'
270
+ export type DragPickContent = {
271
+ time?: number
272
+ unit?: 'min' | 'sec' | 'hr' | 'km' | 'mi' | 'm'
273
+ distance?: number
274
+ preloader?: boolean
275
+ }
276
+ export type DragPickEvent = 'dragstart' | 'dragend' | 'zoom_changed' | 'idle'
277
+ export interface DragPickInterface {
278
+ enable( origin?: Coordinates ): void
279
+ disable(): void
280
+ content( type: DragPickContentType, content: DragPickContent ): void
281
+ }
@@ -0,0 +1,4 @@
1
+
2
+ import Stream from './stream'
3
+
4
+ export default { Stream }
@@ -0,0 +1,68 @@
1
+
2
+ export type StreamDataListener = ( data: string ) => void
3
+
4
+ export default class Stream {
5
+ private _STALED: boolean = false
6
+ private _CLOSED: boolean = false
7
+ private _fns: StreamDataListener[] = []
8
+ private _exitFn = (() => {})
9
+ private _errorFn = (( error: Error ) => {})
10
+ private _downStream?: Stream
11
+ private _upStream?: Stream
12
+
13
+ private _upstream( stream: Stream ){
14
+ this._upStream = stream
15
+ return this
16
+ }
17
+ private _backpressure( data: any ){
18
+ !this._CLOSED && this._fns.map( fn => fn( data ) )
19
+ }
20
+
21
+ sync( data: any ){
22
+ !this._CLOSED && this._downStream?._backpressure( data )
23
+ }
24
+
25
+ on( _event: 'data', fn: StreamDataListener ){
26
+ if( this._CLOSED ) throw new Error('Stream closed')
27
+
28
+ this._fns.push( fn )
29
+ return this
30
+ }
31
+
32
+ pipe( stream: Stream ){
33
+ stream._upstream( this )
34
+ this._downStream = stream
35
+ return this
36
+ }
37
+
38
+ error( error: Error | string ){
39
+ // Throw error to self
40
+ this._errorFn( typeof error == 'string' ? new Error( error ) : error )
41
+ // Throw error to downstream pipe
42
+ this._downStream?.error( error )
43
+
44
+ return this
45
+ }
46
+
47
+ close(){
48
+ if( this._CLOSED ) return
49
+ this._CLOSED = true
50
+
51
+ this._fns = []
52
+ this._exitFn()
53
+
54
+ this._downStream?.close() // Close down streams
55
+ // this._upStream?.close() // Close up streams: Recursive loop call effect
56
+ }
57
+
58
+ isActive(){ return !this._CLOSED }
59
+
60
+ onerror( fn: ( error: Error ) => void ){
61
+ this._errorFn = fn
62
+ return this
63
+ }
64
+ onclose( fn: () => void ){
65
+ this._exitFn = fn
66
+ return this
67
+ }
68
+ }