@peerbit/time 2.1.0 → 2.2.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.
@@ -4,6 +4,7 @@ export declare const debounceFixedInterval: <T extends (...args: any[]) => any |
4
4
  }) => {
5
5
  call: (...args: Parameters<T>) => Promise<void>;
6
6
  close: () => void;
7
+ flush: () => Promise<void>;
7
8
  };
8
9
  export declare const debounceAccumulator: <K, T, V>(fn: (args: V) => any, create: () => {
9
10
  delete: (key: K) => void;
@@ -17,8 +18,9 @@ export declare const debounceAccumulator: <K, T, V>(fn: (args: V) => any, create
17
18
  add: (value: T) => Promise<void>;
18
19
  delete: (key: K) => void;
19
20
  size: () => number;
21
+ has: (key: K) => boolean;
20
22
  invoke: () => Promise<void>;
21
23
  close: () => void;
22
- has: (key: K) => boolean;
24
+ flush: () => Promise<void>;
23
25
  };
24
26
  //# sourceMappingURL=aggregators.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"aggregators.d.ts","sourceRoot":"","sources":["../../src/aggregators.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,GACjC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,EAEhD,IAAI,CAAC,EACL,OAAO,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,EAC9B,UAAU;IAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,KAC/D;IAAE,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,KAAK,EAAE,MAAM,IAAI,CAAA;CAsFtE,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAC1C,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,GAAG,EACpB,QAAQ,MAAM;IACb,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC;IACzB,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IACxB,IAAI,EAAE,MAAM,MAAM,CAAC;IACnB,KAAK,EAAE,CAAC,CAAC;IACT,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC;CACzB,EACD,OAAO,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,EAC9B,UAAU;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE;iBAiBjB,CAAC,KAAG,OAAO,CAAC,IAAI,CAAC;kBAKhB,CAAC;;;;eAMJ,CAAC;CAEb,CAAC"}
1
+ {"version":3,"file":"aggregators.d.ts","sourceRoot":"","sources":["../../src/aggregators.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,GACjC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,EAEhD,IAAI,CAAC,EACL,OAAO,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,EAC9B,UAAU;IAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,KAC/D;IACF,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAuJ3B,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAC1C,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,GAAG,EACpB,QAAQ,MAAM;IACb,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC;IACzB,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IACxB,IAAI,EAAE,MAAM,MAAM,CAAC;IACnB,KAAK,EAAE,CAAC,CAAC;IACT,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC;CACzB,EACD,OAAO,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,EAC9B,UAAU;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE;iBAajB,CAAC,KAAG,OAAO,CAAC,IAAI,CAAC;kBAKhB,CAAC;;eAIJ,CAAC;kBAGM,OAAO,CAAC,IAAI,CAAC;iBAMpB,IAAI;iBAMJ,OAAO,CAAC,IAAI,CAAC;CAEzB,CAAC"}
@@ -1,110 +1,175 @@
1
1
  export const debounceFixedInterval = (fn, delay, options) => {
2
- // A debounce function that waits for the delay after the async function finishes
3
- // before invoking the function again, and returns a promise that resolves when the invocation is done.
4
- let delayFn = typeof delay === "number" ? () => delay : delay;
5
- const onError = options?.onError ||
6
- ((error) => {
7
- throw error;
2
+ const delayFn = typeof delay === "number" ? () => delay : delay;
3
+ const onError = options?.onError ??
4
+ ((e) => {
5
+ throw e;
8
6
  });
9
- // When leading is true, the first call is immediate. Otherwise, it's deferred.
10
- const leading = options?.leading !== undefined ? options.leading : true;
7
+ const leading = options?.leading ?? true;
11
8
  let timeout = null;
12
9
  let lastArgs = null;
13
10
  let lastThis;
14
- let pendingCall = false;
15
- let isRunning = false;
16
- // Array of resolvers for the promises returned by each debounced call.
17
- let waitingResolvers = [];
18
- // Track when the last invocation finished.
11
+ let pendingCall = false; // there is queued work for the *next* run
12
+ let isRunning = false; // fn is executing right now
13
+ let waitingResolvers = []; // resolve when *a* run completes
19
14
  let lastInvokeTime = null;
20
- const invoke = async () => {
21
- timeout = null;
22
- if (!lastArgs) {
15
+ let forceNextImmediate = false;
16
+ // Completed run counter + precise run waiters
17
+ let completedRuns = 0;
18
+ let runWaiters = [];
19
+ const resolveRunWaiters = () => {
20
+ if (runWaiters.length === 0)
23
21
  return;
22
+ const remaining = [];
23
+ for (const w of runWaiters) {
24
+ if (completedRuns >= w.target) {
25
+ w.resolve();
26
+ }
27
+ else {
28
+ remaining.push(w);
29
+ }
24
30
  }
31
+ runWaiters = remaining;
32
+ };
33
+ const waitForRun = (target) => new Promise((resolve) => {
34
+ if (completedRuns >= target)
35
+ return resolve();
36
+ runWaiters.push({ target, resolve });
37
+ });
38
+ const invoke = async () => {
39
+ timeout = null;
40
+ if (!lastArgs)
41
+ return; // nothing to invoke
25
42
  const args = lastArgs;
26
- lastArgs = null;
27
- pendingCall = false;
43
+ const ctx = lastThis;
44
+ lastArgs = null; // consume current args
45
+ pendingCall = false; // this run is for those args
28
46
  isRunning = true;
29
47
  try {
30
- await Promise.resolve(fn.apply(lastThis, args));
48
+ await Promise.resolve(fn.apply(ctx, args));
31
49
  }
32
- catch (error) {
33
- onError(error);
50
+ catch (err) {
51
+ onError(err);
34
52
  }
35
53
  finally {
36
54
  isRunning = false;
37
55
  lastInvokeTime = Date.now();
38
- // Resolve all waiting promises.
39
- waitingResolvers.forEach((resolve) => resolve());
56
+ // Resolve all call() promises queued for this completed run
57
+ const resolvers = waitingResolvers;
40
58
  waitingResolvers = [];
41
- // If calls came in during the invocation, schedule the next run.
59
+ for (const r of resolvers)
60
+ r();
61
+ // Mark completion and resolve any run-target waiters that are due
62
+ completedRuns++;
63
+ resolveRunWaiters();
64
+ // If new calls arrived during this run, schedule the next one
42
65
  if (pendingCall) {
43
- const timeSinceInvoke = Date.now() - (lastInvokeTime || 0);
44
- const delayRemaining = Math.max(delayFn() - timeSinceInvoke, 0);
45
- timeout = setTimeout(invoke, delayRemaining);
66
+ if (forceNextImmediate) {
67
+ forceNextImmediate = false;
68
+ timeout = setTimeout(invoke, 0);
69
+ }
70
+ else {
71
+ const elapsed = Date.now() - (lastInvokeTime ?? 0);
72
+ const remaining = Math.max(delayFn() - elapsed, 0);
73
+ timeout = setTimeout(invoke, remaining);
74
+ }
46
75
  }
47
76
  }
48
77
  };
49
- const debounced = (...args) => {
78
+ // Use a normal function to preserve `this` from the call site
79
+ function debounced(...args) {
50
80
  lastArgs = args;
51
81
  lastThis = this;
52
82
  pendingCall = true;
53
- // Create a new promise that will resolve when the associated invocation completes.
83
+ // Resolve after the next completed run
54
84
  const p = new Promise((resolve) => {
55
85
  waitingResolvers.push(resolve);
56
86
  });
57
87
  const now = Date.now();
58
88
  if (!isRunning && !timeout) {
59
89
  if (leading) {
60
- // If leading is enabled, trigger immediately if it's the very first call
61
- // or if enough time has passed since the last invocation.
62
90
  if (lastInvokeTime === null || now - lastInvokeTime >= delayFn()) {
63
91
  invoke();
64
92
  }
65
93
  else {
66
- const delayRemaining = delayFn() - (now - lastInvokeTime);
67
- timeout = setTimeout(invoke, delayRemaining);
94
+ const remaining = delayFn() - (now - lastInvokeTime);
95
+ timeout = setTimeout(invoke, remaining);
68
96
  }
69
97
  }
70
98
  else {
71
- // If not leading, always schedule a trailing invocation.
72
99
  timeout = setTimeout(invoke, delayFn());
73
100
  }
74
101
  }
75
102
  return p;
76
- };
77
- return {
78
- call: debounced,
79
- close: () => {
80
- isRunning = false;
81
- if (timeout !== null) {
82
- clearTimeout(timeout);
103
+ }
104
+ const flush = () => {
105
+ if (isRunning) {
106
+ // If there are pending args, ensure a trailing run and make it immediate
107
+ const hadPendingArgs = !!lastArgs;
108
+ if (hadPendingArgs) {
109
+ pendingCall = true;
110
+ forceNextImmediate = true;
83
111
  }
84
- },
112
+ // Wait for the current run (+1) and, if needed, the immediate trailing run (+1)
113
+ const target = completedRuns + 1 + (hadPendingArgs ? 1 : 0);
114
+ return waitForRun(target);
115
+ }
116
+ // Not running
117
+ if (timeout) {
118
+ clearTimeout(timeout);
119
+ timeout = null;
120
+ }
121
+ if (lastArgs) {
122
+ const target = completedRuns + 1; // we'll trigger a run now
123
+ invoke();
124
+ return waitForRun(target);
125
+ }
126
+ else {
127
+ // nothing to flush
128
+ return Promise.resolve();
129
+ }
85
130
  };
131
+ const close = () => {
132
+ if (timeout !== null) {
133
+ clearTimeout(timeout);
134
+ timeout = null;
135
+ }
136
+ isRunning = false;
137
+ forceNextImmediate = false;
138
+ // no auto-resolving of pending promises on close()
139
+ };
140
+ return { call: debounced, close, flush };
86
141
  };
87
142
  export const debounceAccumulator = (fn, create, delay, options) => {
88
143
  let accumulator = create();
89
- const invoke = async () => {
144
+ const innerInvoke = async () => {
90
145
  const toSend = accumulator.value;
91
146
  accumulator = create();
92
147
  await fn(toSend);
93
148
  };
94
- const { call: debounced, close } = debounceFixedInterval(invoke, delay, options);
149
+ const deb = debounceFixedInterval(innerInvoke, delay, options);
95
150
  return {
96
151
  add: (value) => {
97
152
  accumulator.add(value);
98
- // Return a promise that resolves when the debounced (accumulated) call is executed.
99
- return debounced();
153
+ // resolves when the batch (which includes this value) runs
154
+ return deb.call();
100
155
  },
101
156
  delete: (key) => {
102
157
  accumulator.delete(key);
103
158
  },
104
159
  size: () => accumulator.size(),
105
- invoke,
106
- close: () => close(),
107
160
  has: (key) => accumulator.has(key),
161
+ // Run immediately, and **cancel** any pending scheduled run to avoid a trailing empty run.
162
+ invoke: async () => {
163
+ deb.close(); // cancel any pending timeout
164
+ await innerInvoke();
165
+ },
166
+ // Cancel pending schedule AND reset accumulator so size() === 0 afterward.
167
+ close: () => {
168
+ deb.close();
169
+ accumulator = create();
170
+ },
171
+ // If you exposed flush() before, keep passing it through:
172
+ flush: () => deb.flush?.() ?? Promise.resolve(),
108
173
  };
109
174
  };
110
175
  //# sourceMappingURL=aggregators.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"aggregators.js","sourceRoot":"","sources":["../../src/aggregators.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"aggregators.js","sourceRoot":"","sources":["../../src/aggregators.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peerbit/time",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Utility functions for time",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -4,60 +4,100 @@ export const debounceFixedInterval = <
4
4
  fn: T,
5
5
  delay: number | (() => number),
6
6
  options?: { onError?: (error: Error) => void; leading?: boolean },
7
- ): { call: (...args: Parameters<T>) => Promise<void>; close: () => void } => {
8
- // A debounce function that waits for the delay after the async function finishes
9
- // before invoking the function again, and returns a promise that resolves when the invocation is done.
10
- let delayFn: () => number = typeof delay === "number" ? () => delay : delay;
7
+ ): {
8
+ call: (...args: Parameters<T>) => Promise<void>;
9
+ close: () => void;
10
+ flush: () => Promise<void>;
11
+ } => {
12
+ const delayFn: () => number = typeof delay === "number" ? () => delay : delay;
11
13
  const onError =
12
- options?.onError ||
13
- ((error: Error) => {
14
- throw error;
14
+ options?.onError ??
15
+ ((e: Error) => {
16
+ throw e;
15
17
  });
16
- // When leading is true, the first call is immediate. Otherwise, it's deferred.
17
- const leading = options?.leading !== undefined ? options.leading : true;
18
- let timeout: NodeJS.Timeout | null = null;
18
+ const leading = options?.leading ?? true;
19
+
20
+ let timeout: ReturnType<typeof setTimeout> | null = null;
19
21
  let lastArgs: any[] | null = null;
20
22
  let lastThis: any;
21
- let pendingCall = false;
22
- let isRunning = false;
23
- // Array of resolvers for the promises returned by each debounced call.
24
- let waitingResolvers: Array<() => void> = [];
25
- // Track when the last invocation finished.
23
+ let pendingCall = false; // there is queued work for the *next* run
24
+ let isRunning = false; // fn is executing right now
25
+ let waitingResolvers: Array<() => void> = []; // resolve when *a* run completes
26
26
  let lastInvokeTime: number | null = null;
27
+ let forceNextImmediate = false;
28
+
29
+ // Completed run counter + precise run waiters
30
+ let completedRuns = 0;
31
+ type RunWaiter = { target: number; resolve: () => void };
32
+ let runWaiters: RunWaiter[] = [];
33
+
34
+ const resolveRunWaiters = () => {
35
+ if (runWaiters.length === 0) return;
36
+ const remaining: RunWaiter[] = [];
37
+ for (const w of runWaiters) {
38
+ if (completedRuns >= w.target) {
39
+ w.resolve();
40
+ } else {
41
+ remaining.push(w);
42
+ }
43
+ }
44
+ runWaiters = remaining;
45
+ };
46
+
47
+ const waitForRun = (target: number) =>
48
+ new Promise<void>((resolve) => {
49
+ if (completedRuns >= target) return resolve();
50
+ runWaiters.push({ target, resolve });
51
+ });
27
52
 
28
53
  const invoke = async () => {
29
54
  timeout = null;
30
- if (!lastArgs) {
31
- return;
32
- }
55
+ if (!lastArgs) return; // nothing to invoke
56
+
33
57
  const args = lastArgs;
34
- lastArgs = null;
35
- pendingCall = false;
58
+ const ctx = lastThis;
59
+ lastArgs = null; // consume current args
60
+ pendingCall = false; // this run is for those args
36
61
  isRunning = true;
62
+
37
63
  try {
38
- await Promise.resolve(fn.apply(lastThis, args));
39
- } catch (error) {
40
- onError(error as Error);
64
+ await Promise.resolve(fn.apply(ctx, args));
65
+ } catch (err) {
66
+ onError(err as Error);
41
67
  } finally {
42
68
  isRunning = false;
43
69
  lastInvokeTime = Date.now();
44
- // Resolve all waiting promises.
45
- waitingResolvers.forEach((resolve) => resolve());
70
+
71
+ // Resolve all call() promises queued for this completed run
72
+ const resolvers = waitingResolvers;
46
73
  waitingResolvers = [];
47
- // If calls came in during the invocation, schedule the next run.
74
+ for (const r of resolvers) r();
75
+
76
+ // Mark completion and resolve any run-target waiters that are due
77
+ completedRuns++;
78
+ resolveRunWaiters();
79
+
80
+ // If new calls arrived during this run, schedule the next one
48
81
  if (pendingCall) {
49
- const timeSinceInvoke = Date.now() - (lastInvokeTime || 0);
50
- const delayRemaining = Math.max(delayFn() - timeSinceInvoke, 0);
51
- timeout = setTimeout(invoke, delayRemaining);
82
+ if (forceNextImmediate) {
83
+ forceNextImmediate = false;
84
+ timeout = setTimeout(invoke, 0);
85
+ } else {
86
+ const elapsed = Date.now() - (lastInvokeTime ?? 0);
87
+ const remaining = Math.max(delayFn() - elapsed, 0);
88
+ timeout = setTimeout(invoke, remaining);
89
+ }
52
90
  }
53
91
  }
54
92
  };
55
93
 
56
- const debounced = (...args: Parameters<T>): Promise<void> => {
94
+ // Use a normal function to preserve `this` from the call site
95
+ function debounced(this: any, ...args: Parameters<T>): Promise<void> {
57
96
  lastArgs = args;
58
97
  lastThis = this;
59
98
  pendingCall = true;
60
- // Create a new promise that will resolve when the associated invocation completes.
99
+
100
+ // Resolve after the next completed run
61
101
  const p = new Promise<void>((resolve) => {
62
102
  waitingResolvers.push(resolve);
63
103
  });
@@ -65,31 +105,59 @@ export const debounceFixedInterval = <
65
105
  const now = Date.now();
66
106
  if (!isRunning && !timeout) {
67
107
  if (leading) {
68
- // If leading is enabled, trigger immediately if it's the very first call
69
- // or if enough time has passed since the last invocation.
70
108
  if (lastInvokeTime === null || now - lastInvokeTime >= delayFn()) {
71
109
  invoke();
72
110
  } else {
73
- const delayRemaining = delayFn() - (now - lastInvokeTime);
74
- timeout = setTimeout(invoke, delayRemaining);
111
+ const remaining = delayFn() - (now - lastInvokeTime);
112
+ timeout = setTimeout(invoke, remaining);
75
113
  }
76
114
  } else {
77
- // If not leading, always schedule a trailing invocation.
78
115
  timeout = setTimeout(invoke, delayFn());
79
116
  }
80
117
  }
81
118
  return p;
82
- };
119
+ }
83
120
 
84
- return {
85
- call: debounced,
86
- close: () => {
87
- isRunning = false;
88
- if (timeout !== null) {
89
- clearTimeout(timeout);
121
+ const flush = (): Promise<void> => {
122
+ if (isRunning) {
123
+ // If there are pending args, ensure a trailing run and make it immediate
124
+ const hadPendingArgs = !!lastArgs;
125
+ if (hadPendingArgs) {
126
+ pendingCall = true;
127
+ forceNextImmediate = true;
90
128
  }
91
- },
129
+ // Wait for the current run (+1) and, if needed, the immediate trailing run (+1)
130
+ const target = completedRuns + 1 + (hadPendingArgs ? 1 : 0);
131
+ return waitForRun(target);
132
+ }
133
+
134
+ // Not running
135
+ if (timeout) {
136
+ clearTimeout(timeout);
137
+ timeout = null;
138
+ }
139
+
140
+ if (lastArgs) {
141
+ const target = completedRuns + 1; // we'll trigger a run now
142
+ invoke();
143
+ return waitForRun(target);
144
+ } else {
145
+ // nothing to flush
146
+ return Promise.resolve();
147
+ }
148
+ };
149
+
150
+ const close = () => {
151
+ if (timeout !== null) {
152
+ clearTimeout(timeout);
153
+ timeout = null;
154
+ }
155
+ isRunning = false;
156
+ forceNextImmediate = false;
157
+ // no auto-resolving of pending promises on close()
92
158
  };
159
+
160
+ return { call: debounced, close, flush };
93
161
  };
94
162
 
95
163
  export const debounceAccumulator = <K, T, V>(
@@ -106,30 +174,39 @@ export const debounceAccumulator = <K, T, V>(
106
174
  ) => {
107
175
  let accumulator = create();
108
176
 
109
- const invoke = async () => {
177
+ const innerInvoke = async () => {
110
178
  const toSend = accumulator.value;
111
179
  accumulator = create();
112
180
  await fn(toSend);
113
181
  };
114
182
 
115
- const { call: debounced, close } = debounceFixedInterval(
116
- invoke,
117
- delay,
118
- options,
119
- );
183
+ const deb = debounceFixedInterval(innerInvoke, delay, options);
120
184
 
121
185
  return {
122
186
  add: (value: T): Promise<void> => {
123
187
  accumulator.add(value);
124
- // Return a promise that resolves when the debounced (accumulated) call is executed.
125
- return debounced();
188
+ // resolves when the batch (which includes this value) runs
189
+ return deb.call();
126
190
  },
127
191
  delete: (key: K) => {
128
192
  accumulator.delete(key);
129
193
  },
130
194
  size: () => accumulator.size(),
131
- invoke,
132
- close: () => close(),
133
195
  has: (key: K) => accumulator.has(key),
196
+
197
+ // Run immediately, and **cancel** any pending scheduled run to avoid a trailing empty run.
198
+ invoke: async (): Promise<void> => {
199
+ deb.close(); // cancel any pending timeout
200
+ await innerInvoke();
201
+ },
202
+
203
+ // Cancel pending schedule AND reset accumulator so size() === 0 afterward.
204
+ close: (): void => {
205
+ deb.close();
206
+ accumulator = create();
207
+ },
208
+
209
+ // If you exposed flush() before, keep passing it through:
210
+ flush: (): Promise<void> => deb.flush?.() ?? Promise.resolve(),
134
211
  };
135
212
  };