@muspellheim/shared 0.5.0 → 0.6.1
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.
- package/.prettierignore +3 -0
- package/.prettierrc +5 -0
- package/README.md +10 -5
- package/deno.json +4 -1
- package/deno.mk +68 -0
- package/eslint.config.js +23 -0
- package/lib/assert.js +2 -2
- package/lib/browser/index.js +0 -2
- package/lib/color.js +20 -22
- package/lib/health.js +3 -7
- package/lib/index.js +2 -1
- package/lib/lang.js +3 -5
- package/lib/logging.js +11 -10
- package/lib/long-polling-client.js +21 -10
- package/lib/message-client.js +0 -6
- package/lib/messages.js +68 -0
- package/lib/metrics.js +1 -1
- package/lib/node/actuator-controller.js +7 -8
- package/lib/node/configuration-properties.js +123 -86
- package/lib/node/handler.js +4 -2
- package/lib/node/index.js +1 -2
- package/lib/node/logging.js +5 -2
- package/lib/node/long-polling.js +2 -0
- package/lib/node/static-files-controller.js +15 -0
- package/lib/sse-client.js +21 -10
- package/lib/time.js +13 -11
- package/lib/util.js +45 -26
- package/lib/validation.js +13 -22
- package/lib/vector.js +1 -1
- package/lib/vitest/equality-testers.js +19 -0
- package/lib/vitest/index.js +1 -0
- package/lib/web-socket-client.js +26 -9
- package/package.json +12 -6
- package/tsconfig.json +13 -0
|
@@ -1,19 +1,31 @@
|
|
|
1
1
|
// Copyright (c) 2023-2024 Falko Schumann. All rights reserved. MIT license.
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Provide the configuration of an application.
|
|
5
|
+
*
|
|
6
|
+
* The sources in order of priority:
|
|
7
|
+
*
|
|
8
|
+
* 1. JSON file
|
|
9
|
+
* 2. Environment variables
|
|
10
|
+
* 3. Command line arguments
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
|
|
3
15
|
import fsPromises from 'node:fs/promises';
|
|
4
16
|
import path from 'node:path';
|
|
5
17
|
import process from 'node:process';
|
|
6
18
|
|
|
7
|
-
import { deepMerge } from '../util.js';
|
|
19
|
+
import { deepCopy, deepMerge } from '../util.js';
|
|
8
20
|
|
|
9
21
|
// TODO How to handle optional values? Cast to which type?
|
|
10
22
|
// TODO Use JSON schema to validate the configuration?
|
|
11
23
|
|
|
12
24
|
/**
|
|
13
|
-
*
|
|
25
|
+
* Reads the configuration from file and environment variables.
|
|
14
26
|
*
|
|
15
27
|
* The configuration is read from a JSON file `application.json` from the
|
|
16
|
-
* working directory.
|
|
28
|
+
* working directory. Can be configured with `config.name`, `config.location`.
|
|
17
29
|
*
|
|
18
30
|
* Example:
|
|
19
31
|
*
|
|
@@ -26,120 +38,111 @@ import { deepMerge } from '../util.js';
|
|
|
26
38
|
*
|
|
27
39
|
* ```javascript
|
|
28
40
|
* const configuration = ConfigurationProperties.create({
|
|
29
|
-
*
|
|
41
|
+
* defaultProperties: {
|
|
30
42
|
* port: 8080,
|
|
31
43
|
* database: { host: 'localhost', port: 5432 },
|
|
32
44
|
* },
|
|
33
45
|
* });
|
|
34
46
|
* const config = await configuration.get();
|
|
35
47
|
* ```
|
|
48
|
+
*
|
|
49
|
+
* @template T
|
|
36
50
|
*/
|
|
37
51
|
export class ConfigurationProperties {
|
|
38
52
|
/**
|
|
39
53
|
* Creates an instance of the application configuration.
|
|
40
54
|
*
|
|
55
|
+
* @template T
|
|
41
56
|
* @param {object} options The configuration options.
|
|
42
|
-
* @param {
|
|
57
|
+
* @param {T} [options.defaultProperties=null] The default configuration.
|
|
43
58
|
* @param {string} [options.prefix=""] The prefix of the properties to get.
|
|
44
|
-
* @
|
|
45
|
-
* configuration file.
|
|
46
|
-
* @param {string[]} [options.location=['.', 'config']] The locations where to
|
|
47
|
-
* search for the configuration file.
|
|
48
|
-
* @return {ConfigurationProperties} The new instance.
|
|
59
|
+
* @return {ConfigurationProperties<T>} The new instance.
|
|
49
60
|
*/
|
|
50
|
-
static create({
|
|
51
|
-
|
|
52
|
-
prefix = '',
|
|
53
|
-
name = 'application.json',
|
|
54
|
-
location = ['.', 'config'],
|
|
55
|
-
} = {}) {
|
|
56
|
-
return new ConfigurationProperties(
|
|
57
|
-
defaults,
|
|
58
|
-
prefix,
|
|
59
|
-
name,
|
|
60
|
-
location,
|
|
61
|
-
fsPromises,
|
|
62
|
-
);
|
|
61
|
+
static create({ defaultProperties = null, prefix = '' } = {}) {
|
|
62
|
+
return new ConfigurationProperties(defaultProperties, prefix, fsPromises);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
66
|
* Creates a nullable of the application configuration.
|
|
67
67
|
*
|
|
68
|
+
* @template T
|
|
68
69
|
* @param {object} options The configuration options.
|
|
69
|
-
* @param {
|
|
70
|
+
* @param {T} [options.defaultProperties=null] The default configuration.
|
|
70
71
|
* @param {string} [options.prefix=""] The prefix of the properties to get.
|
|
71
|
-
* @param {string} [options.name='application.json'] The name of the
|
|
72
|
-
* configuration file.
|
|
73
|
-
* @param {string[]} [options.location=['.', 'config']] The locations where to
|
|
74
|
-
* search for the configuration file.
|
|
75
72
|
* @param {object} [options.files={}] The files and file content that are
|
|
76
73
|
* available.
|
|
74
|
+
* @return {ConfigurationProperties<T>} The new instance.
|
|
77
75
|
*/
|
|
78
76
|
static createNull({
|
|
79
|
-
|
|
77
|
+
defaultProperties = null,
|
|
80
78
|
prefix = '',
|
|
81
|
-
name = 'application.json',
|
|
82
|
-
location = ['.', 'config'],
|
|
83
79
|
files = {},
|
|
84
80
|
} = {}) {
|
|
85
81
|
return new ConfigurationProperties(
|
|
86
|
-
|
|
82
|
+
defaultProperties,
|
|
87
83
|
prefix,
|
|
88
|
-
name,
|
|
89
|
-
location,
|
|
90
84
|
new FsStub(files),
|
|
91
85
|
);
|
|
92
86
|
}
|
|
93
87
|
|
|
94
|
-
|
|
88
|
+
/** @type {T} */
|
|
89
|
+
#defaultProperties;
|
|
90
|
+
|
|
91
|
+
/** @type {string} */
|
|
95
92
|
#prefix;
|
|
96
|
-
|
|
97
|
-
|
|
93
|
+
|
|
94
|
+
/** @type {fsPromises} */
|
|
98
95
|
#fs;
|
|
99
96
|
|
|
97
|
+
/** @type {T} */
|
|
98
|
+
#cached;
|
|
99
|
+
|
|
100
100
|
/**
|
|
101
101
|
* The constructor is for internal use. Use the factory methods instead.
|
|
102
102
|
*
|
|
103
|
+
* @param {T} defaultProperties
|
|
104
|
+
* @param {string} prefix
|
|
105
|
+
* @param {fsPromises} fs
|
|
103
106
|
* @see ConfigurationProperties.create
|
|
104
107
|
* @see ConfigurationProperties.createNull
|
|
105
108
|
*/
|
|
106
|
-
constructor(
|
|
107
|
-
|
|
108
|
-
/** @type {string} */ prefix,
|
|
109
|
-
/** @type {string} */ name,
|
|
110
|
-
/** @type {string[]} */ locations,
|
|
111
|
-
/** @type {fsPromises} */ fs,
|
|
112
|
-
) {
|
|
113
|
-
this.#defaults = defaults;
|
|
109
|
+
constructor(defaultProperties, prefix, fs) {
|
|
110
|
+
this.#defaultProperties = defaultProperties;
|
|
114
111
|
this.#prefix = prefix;
|
|
115
|
-
this.#name = name;
|
|
116
|
-
this.#locations = locations;
|
|
117
112
|
this.#fs = fs;
|
|
118
113
|
}
|
|
119
114
|
|
|
120
115
|
/**
|
|
121
116
|
* Loads the configuration from the file.
|
|
122
117
|
*
|
|
123
|
-
* @return {Promise<
|
|
118
|
+
* @return {Promise<T>} The configuration object.
|
|
124
119
|
*/
|
|
125
120
|
async get() {
|
|
121
|
+
if (this.#cached !== undefined) {
|
|
122
|
+
return this.#cached;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// TODO Interpret placeholders like ${foo.bar}
|
|
126
|
+
// TODO Interpret placeholders with default value like ${foo.bar:default}
|
|
127
|
+
|
|
126
128
|
let config = await this.#loadFile();
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
this.#
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
config = deepMerge(deepCopy(this.#defaultProperties), config);
|
|
130
|
+
// TODO apply environment variable APPLICATION_JSON as JSON string
|
|
131
|
+
this.#applyVariables(config, getEnv);
|
|
132
|
+
this.#applyVariables(config, getArg);
|
|
133
|
+
this.#cached = this.#getSubset(config, this.#prefix);
|
|
134
|
+
return this.#cached;
|
|
132
135
|
}
|
|
133
136
|
|
|
134
137
|
async #loadFile() {
|
|
135
|
-
|
|
136
|
-
for (const
|
|
138
|
+
const { name, location } = this.#getConfig();
|
|
139
|
+
for (const l of location) {
|
|
137
140
|
try {
|
|
138
|
-
const filePath = path.join(
|
|
141
|
+
const filePath = path.join(l, name);
|
|
139
142
|
const content = await this.#fs.readFile(filePath, 'utf-8');
|
|
140
|
-
|
|
141
|
-
break;
|
|
143
|
+
return JSON.parse(content);
|
|
142
144
|
} catch (err) {
|
|
145
|
+
// @ts-ignore NodeJS error code
|
|
143
146
|
if (err.code === 'ENOENT') {
|
|
144
147
|
// ignore file not found
|
|
145
148
|
continue;
|
|
@@ -148,54 +151,68 @@ export class ConfigurationProperties {
|
|
|
148
151
|
throw err;
|
|
149
152
|
}
|
|
150
153
|
}
|
|
151
|
-
return config;
|
|
152
154
|
}
|
|
153
155
|
|
|
154
|
-
#
|
|
156
|
+
#getConfig() {
|
|
157
|
+
const defaultConfig = {
|
|
158
|
+
config: {
|
|
159
|
+
name: 'application.json',
|
|
160
|
+
location: ['.', 'config'],
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
this.#applyVariables(defaultConfig, getEnv);
|
|
164
|
+
this.#applyVariables(defaultConfig, getArg);
|
|
165
|
+
const {
|
|
166
|
+
config: { name, location },
|
|
167
|
+
} = defaultConfig;
|
|
168
|
+
return { name, location };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
#applyVariables(config, getValue, path) {
|
|
155
172
|
// handle object
|
|
156
173
|
// handle array
|
|
157
174
|
// handle string
|
|
158
175
|
// handle number
|
|
159
176
|
// handle boolean (true, false)
|
|
160
|
-
// handle null (empty
|
|
161
|
-
// if
|
|
177
|
+
// handle null (empty string set the value to null)
|
|
178
|
+
// if value is undefined, keep the default value
|
|
162
179
|
for (const key in config) {
|
|
163
180
|
if (typeof config[key] === 'boolean') {
|
|
164
|
-
const value =
|
|
181
|
+
const value = getValue(key, path);
|
|
165
182
|
if (value === null) {
|
|
166
183
|
config[key] = null;
|
|
167
184
|
} else if (value) {
|
|
168
185
|
config[key] = value.toLowerCase() === 'true';
|
|
169
186
|
}
|
|
170
187
|
} else if (typeof config[key] === 'number') {
|
|
171
|
-
const value =
|
|
188
|
+
const value = getValue(key, path);
|
|
172
189
|
if (value === null) {
|
|
173
190
|
config[key] = null;
|
|
174
191
|
} else if (value) {
|
|
175
192
|
config[key] = Number(value);
|
|
176
193
|
}
|
|
177
194
|
} else if (typeof config[key] === 'string') {
|
|
178
|
-
const value =
|
|
195
|
+
const value = getValue(key, path);
|
|
179
196
|
if (value === null) {
|
|
180
197
|
config[key] = null;
|
|
181
198
|
} else if (value) {
|
|
182
199
|
config[key] = String(value);
|
|
183
200
|
}
|
|
184
201
|
} else if (config[key] === null) {
|
|
185
|
-
const value =
|
|
202
|
+
const value = getValue(key, path);
|
|
186
203
|
if (value === null) {
|
|
187
204
|
config[key] = null;
|
|
188
205
|
} else if (value) {
|
|
189
206
|
config[key] = value;
|
|
190
207
|
}
|
|
191
208
|
} else if (typeof config[key] === 'object') {
|
|
192
|
-
const value =
|
|
209
|
+
const value = getValue(key, path);
|
|
193
210
|
if (value === null) {
|
|
194
211
|
config[key] = null;
|
|
195
212
|
} else if (Array.isArray(config[key]) && value) {
|
|
196
213
|
config[key] = value.split(',');
|
|
197
214
|
} else {
|
|
198
|
-
this.#
|
|
215
|
+
this.#applyVariables(config[key], getValue, key);
|
|
199
216
|
}
|
|
200
217
|
} else {
|
|
201
218
|
throw new Error(`Unsupported type: ${typeof config[key]}`);
|
|
@@ -203,19 +220,6 @@ export class ConfigurationProperties {
|
|
|
203
220
|
}
|
|
204
221
|
}
|
|
205
222
|
|
|
206
|
-
#getEnv(key, path = '') {
|
|
207
|
-
let envKey = key;
|
|
208
|
-
if (path) {
|
|
209
|
-
envKey = `${path}_${envKey}`;
|
|
210
|
-
}
|
|
211
|
-
envKey = envKey.toUpperCase();
|
|
212
|
-
const value = process.env[envKey];
|
|
213
|
-
if (value === '') {
|
|
214
|
-
return null;
|
|
215
|
-
}
|
|
216
|
-
return value;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
223
|
#getSubset(config, prefix) {
|
|
220
224
|
if (prefix === '') {
|
|
221
225
|
return config;
|
|
@@ -230,9 +234,45 @@ export class ConfigurationProperties {
|
|
|
230
234
|
}
|
|
231
235
|
}
|
|
232
236
|
|
|
237
|
+
function getEnv(key, path = '') {
|
|
238
|
+
if (path) {
|
|
239
|
+
key = `${path}_${key}`;
|
|
240
|
+
}
|
|
241
|
+
key = key.toUpperCase();
|
|
242
|
+
const value = process.env[key];
|
|
243
|
+
if (value === '') {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
return value;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function getArg(key, path = '') {
|
|
250
|
+
if (path) {
|
|
251
|
+
key = `${path}.${key}`;
|
|
252
|
+
}
|
|
253
|
+
key = `--${key}=`;
|
|
254
|
+
const keyValue = process.argv.find((arg) => arg.startsWith(key));
|
|
255
|
+
if (keyValue == null) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const [, value] = keyValue.split('=');
|
|
260
|
+
if (value === '') {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
return value;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* @ignore
|
|
268
|
+
*/
|
|
233
269
|
class FsStub {
|
|
270
|
+
/** @type {Record<string, string>} */
|
|
234
271
|
#files;
|
|
235
272
|
|
|
273
|
+
/**
|
|
274
|
+
* @param {Record<string, string>} files
|
|
275
|
+
*/
|
|
236
276
|
constructor(files) {
|
|
237
277
|
this.#files = files;
|
|
238
278
|
}
|
|
@@ -240,15 +280,12 @@ class FsStub {
|
|
|
240
280
|
readFile(path) {
|
|
241
281
|
const fileContent = this.#files[path];
|
|
242
282
|
if (fileContent == null) {
|
|
243
|
-
const err = new Error(`
|
|
283
|
+
const err = new Error(`No such file or directory`);
|
|
284
|
+
// @ts-ignore NodeJS error code
|
|
244
285
|
err.code = 'ENOENT';
|
|
245
286
|
throw err;
|
|
246
287
|
}
|
|
247
288
|
|
|
248
|
-
if (typeof fileContent === 'string') {
|
|
249
|
-
return fileContent;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
289
|
return JSON.stringify(fileContent);
|
|
253
290
|
}
|
|
254
291
|
}
|
package/lib/node/handler.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
// Copyright (c) 2023-2024 Falko Schumann. All rights reserved. MIT license.
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @import
|
|
4
|
+
* @import express from 'express'
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
// TODO Remove dependency to express
|
|
8
|
+
|
|
7
9
|
export function runSafe(/** @type {express.RequestHandler} */ handler) {
|
|
8
10
|
// TODO runSafe is obsolete with with Express 5
|
|
9
11
|
return async (request, response, next) => {
|
|
10
12
|
try {
|
|
11
|
-
await handler(request, response);
|
|
13
|
+
await handler(request, response, next);
|
|
12
14
|
} catch (error) {
|
|
13
15
|
next(error);
|
|
14
16
|
}
|
package/lib/node/index.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
// Copyright (c) 2023-2024 Falko Schumann. All rights reserved. MIT license.
|
|
2
2
|
|
|
3
|
-
export * from '../index.js';
|
|
4
|
-
|
|
5
3
|
export * from './actuator-controller.js';
|
|
6
4
|
export * from './configuration-properties.js';
|
|
7
5
|
export * from './handler.js';
|
|
8
6
|
export * from './logging.js';
|
|
9
7
|
export * from './long-polling.js';
|
|
10
8
|
export * from './sse-emitter.js';
|
|
9
|
+
export * from './static-files-controller.js';
|
package/lib/node/logging.js
CHANGED
|
@@ -46,9 +46,12 @@ export class FileHandler extends Handler {
|
|
|
46
46
|
await fsPromises.rm(this.#filename);
|
|
47
47
|
}
|
|
48
48
|
} catch (error) {
|
|
49
|
-
// ignore error
|
|
50
|
-
if (error.code
|
|
49
|
+
// @ts-ignore NodeJS error code
|
|
50
|
+
if (error.code === 'ENOENT') {
|
|
51
|
+
// ignore error if file does not exist
|
|
51
52
|
console.error(error);
|
|
53
|
+
} else {
|
|
54
|
+
throw error;
|
|
52
55
|
}
|
|
53
56
|
}
|
|
54
57
|
}
|
package/lib/node/long-polling.js
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Copyright (c) 2024 Falko Schumann. All rights reserved. MIT license.
|
|
2
|
+
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import express from 'express';
|
|
5
|
+
|
|
6
|
+
export class StaticFilesController {
|
|
7
|
+
/**
|
|
8
|
+
* @param {express.Express} app
|
|
9
|
+
* @param {string} [directory="./public"]
|
|
10
|
+
* @param {string} [route="/"]
|
|
11
|
+
*/
|
|
12
|
+
constructor(app, directory = './public', route = '/') {
|
|
13
|
+
app.use(route, express.static(path.join(directory)));
|
|
14
|
+
}
|
|
15
|
+
}
|
package/lib/sse-client.js
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import { MessageClient } from './message-client.js';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @ignore @typedef {typeof EventSource} EventSourceConstructor
|
|
7
|
+
*/
|
|
8
|
+
|
|
5
9
|
/**
|
|
6
10
|
* A client for the server-sent events protocol.
|
|
7
11
|
*
|
|
@@ -32,29 +36,35 @@ export class SseClient extends MessageClient {
|
|
|
32
36
|
/**
|
|
33
37
|
* The constructor is for internal use. Use the factory methods instead.
|
|
34
38
|
*
|
|
39
|
+
* @param {EventSourceConstructor} eventSourceConstructor
|
|
35
40
|
* @see SseClient.create
|
|
36
41
|
* @see SseClient.createNull
|
|
37
42
|
*/
|
|
38
|
-
constructor(
|
|
43
|
+
constructor(eventSourceConstructor) {
|
|
39
44
|
super();
|
|
40
45
|
this.#eventSourceConstructor = eventSourceConstructor;
|
|
41
46
|
}
|
|
42
47
|
|
|
48
|
+
/**
|
|
49
|
+
* @override
|
|
50
|
+
*/
|
|
43
51
|
get isConnected() {
|
|
44
52
|
return this.#eventSource?.readyState === this.#eventSourceConstructor.OPEN;
|
|
45
53
|
}
|
|
46
54
|
|
|
55
|
+
/**
|
|
56
|
+
* @override
|
|
57
|
+
*/
|
|
47
58
|
get url() {
|
|
48
59
|
return this.#eventSource?.url;
|
|
49
60
|
}
|
|
50
61
|
|
|
51
62
|
/**
|
|
52
63
|
* Connects to the server.
|
|
53
|
-
*
|
|
54
64
|
* @param {URL | string} url The server URL to connect to.
|
|
55
|
-
* @param {string} [eventName
|
|
65
|
+
* @param {string} [eventName] The optional event type to listen to.
|
|
66
|
+
* @override
|
|
56
67
|
*/
|
|
57
|
-
|
|
58
68
|
async connect(url, eventName = 'message') {
|
|
59
69
|
await new Promise((resolve, reject) => {
|
|
60
70
|
if (this.isConnected) {
|
|
@@ -68,13 +78,11 @@ export class SseClient extends MessageClient {
|
|
|
68
78
|
this.#handleOpen(e);
|
|
69
79
|
resolve();
|
|
70
80
|
});
|
|
71
|
-
this.#eventSource.addEventListener(
|
|
72
|
-
|
|
73
|
-
(e) => this.#handleMessage(e),
|
|
81
|
+
this.#eventSource.addEventListener(eventName, (e) =>
|
|
82
|
+
this.#handleMessage(e),
|
|
74
83
|
);
|
|
75
|
-
this.#eventSource.addEventListener(
|
|
76
|
-
|
|
77
|
-
(e) => this.#handleError(e),
|
|
84
|
+
this.#eventSource.addEventListener('error', (e) =>
|
|
85
|
+
this.#handleError(e),
|
|
78
86
|
);
|
|
79
87
|
} catch (error) {
|
|
80
88
|
reject(error);
|
|
@@ -82,6 +90,9 @@ export class SseClient extends MessageClient {
|
|
|
82
90
|
});
|
|
83
91
|
}
|
|
84
92
|
|
|
93
|
+
/**
|
|
94
|
+
* @override
|
|
95
|
+
*/
|
|
85
96
|
async close() {
|
|
86
97
|
await new Promise((resolve, reject) => {
|
|
87
98
|
if (!this.isConnected) {
|
package/lib/time.js
CHANGED
|
@@ -25,12 +25,12 @@ export class Clock {
|
|
|
25
25
|
/**
|
|
26
26
|
* Creates a clock using a fixed date.
|
|
27
27
|
*
|
|
28
|
-
* @param {Date} [
|
|
28
|
+
* @param {Date} [date='2024-02-21T19:16:00Z'] The fixed date of the clock.
|
|
29
29
|
* @return {Clock} A clock that returns alaways a fixed date.
|
|
30
30
|
* @see Clock#add
|
|
31
31
|
*/
|
|
32
|
-
static fixed(
|
|
33
|
-
return new Clock(
|
|
32
|
+
static fixed(date = new Date('2024-02-21T19:16:00Z')) {
|
|
33
|
+
return new Clock(date);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
#date;
|
|
@@ -241,10 +241,11 @@ export class Duration {
|
|
|
241
241
|
* @readonly
|
|
242
242
|
*/
|
|
243
243
|
get secondsPart() {
|
|
244
|
-
const value =
|
|
245
|
-
this.
|
|
246
|
-
|
|
247
|
-
|
|
244
|
+
const value =
|
|
245
|
+
(this.millis -
|
|
246
|
+
this.daysPart * 86400000 -
|
|
247
|
+
this.hoursPart * 3600000 -
|
|
248
|
+
this.minutesPart * 60000) /
|
|
248
249
|
1000;
|
|
249
250
|
return this.isNegative() ? Math.ceil(value) : Math.floor(value);
|
|
250
251
|
}
|
|
@@ -256,7 +257,8 @@ export class Duration {
|
|
|
256
257
|
* @readonly
|
|
257
258
|
*/
|
|
258
259
|
get millisPart() {
|
|
259
|
-
const value =
|
|
260
|
+
const value =
|
|
261
|
+
this.millis -
|
|
260
262
|
this.daysPart * 86400000 -
|
|
261
263
|
this.hoursPart * 3600000 -
|
|
262
264
|
this.minutesPart * 60000 -
|
|
@@ -267,7 +269,7 @@ export class Duration {
|
|
|
267
269
|
/**
|
|
268
270
|
* Checks if the duration is zero.
|
|
269
271
|
*
|
|
270
|
-
* @
|
|
272
|
+
* @return {boolean}
|
|
271
273
|
*/
|
|
272
274
|
isZero() {
|
|
273
275
|
return this.millis === 0;
|
|
@@ -276,7 +278,7 @@ export class Duration {
|
|
|
276
278
|
/**
|
|
277
279
|
* Checks if the duration is negative.
|
|
278
280
|
*
|
|
279
|
-
* @
|
|
281
|
+
* @return {boolean}
|
|
280
282
|
*/
|
|
281
283
|
isNegative() {
|
|
282
284
|
return this.millis < 0;
|
|
@@ -285,7 +287,7 @@ export class Duration {
|
|
|
285
287
|
/**
|
|
286
288
|
* Checks if the duration is positive.
|
|
287
289
|
*
|
|
288
|
-
* @
|
|
290
|
+
* @return {boolean}
|
|
289
291
|
*/
|
|
290
292
|
isPositive() {
|
|
291
293
|
return this.millis > 0;
|