@aidc-toolkit/core 1.0.31-beta → 1.0.33-beta

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 (73) hide show
  1. package/README.md +98 -87
  2. package/dist/app-data-storage.d.ts +118 -0
  3. package/dist/app-data-storage.d.ts.map +1 -0
  4. package/dist/app-data-storage.js +117 -0
  5. package/dist/app-data-storage.js.map +1 -0
  6. package/dist/app-data.d.ts +26 -0
  7. package/dist/app-data.d.ts.map +1 -0
  8. package/dist/app-data.js +79 -0
  9. package/dist/app-data.js.map +1 -0
  10. package/dist/browser-app-data-storage.d.ts +26 -0
  11. package/dist/browser-app-data-storage.d.ts.map +1 -0
  12. package/dist/browser-app-data-storage.js +34 -0
  13. package/dist/browser-app-data-storage.js.map +1 -0
  14. package/dist/cache.d.ts +58 -0
  15. package/dist/cache.d.ts.map +1 -0
  16. package/dist/cache.js +12 -0
  17. package/dist/cache.js.map +1 -0
  18. package/dist/file-app-data-storage.d.ts +26 -0
  19. package/dist/file-app-data-storage.d.ts.map +1 -0
  20. package/dist/file-app-data-storage.js +40 -0
  21. package/dist/file-app-data-storage.js.map +1 -0
  22. package/dist/hyperlink.d.ts +18 -0
  23. package/dist/hyperlink.d.ts.map +1 -0
  24. package/dist/hyperlink.js +2 -0
  25. package/dist/hyperlink.js.map +1 -0
  26. package/dist/index.d.ts +6 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +5 -0
  29. package/dist/index.js.map +1 -1
  30. package/dist/local-app-data-storage.d.ts +8 -0
  31. package/dist/local-app-data-storage.d.ts.map +1 -0
  32. package/dist/local-app-data-storage.js +11 -0
  33. package/dist/local-app-data-storage.js.map +1 -0
  34. package/dist/locale/en/locale-resources.d.ts +10 -0
  35. package/dist/locale/en/locale-resources.d.ts.map +1 -0
  36. package/dist/locale/en/locale-resources.js +9 -0
  37. package/dist/locale/en/locale-resources.js.map +1 -0
  38. package/dist/locale/fr/locale-resources.d.ts +10 -0
  39. package/dist/locale/fr/locale-resources.d.ts.map +1 -0
  40. package/dist/locale/fr/locale-resources.js +9 -0
  41. package/dist/locale/fr/locale-resources.js.map +1 -0
  42. package/dist/locale/i18n.d.ts +33 -6
  43. package/dist/locale/i18n.d.ts.map +1 -1
  44. package/dist/locale/i18n.js +76 -31
  45. package/dist/locale/i18n.js.map +1 -1
  46. package/dist/logger.d.ts +108 -4
  47. package/dist/logger.d.ts.map +1 -1
  48. package/dist/logger.js +227 -12
  49. package/dist/logger.js.map +1 -1
  50. package/dist/remote-app-data-storage.d.ts +18 -0
  51. package/dist/remote-app-data-storage.d.ts.map +1 -0
  52. package/dist/remote-app-data-storage.js +37 -0
  53. package/dist/remote-app-data-storage.js.map +1 -0
  54. package/dist/type-helper.js.map +1 -1
  55. package/dist/type.d.ts +5 -0
  56. package/dist/type.d.ts.map +1 -1
  57. package/package.json +10 -8
  58. package/src/app-data-storage.ts +166 -0
  59. package/src/app-data.ts +94 -0
  60. package/src/browser-app-data-storage.ts +37 -0
  61. package/src/cache.ts +64 -0
  62. package/src/file-app-data-storage.ts +49 -0
  63. package/src/hyperlink.ts +19 -0
  64. package/src/index.ts +10 -0
  65. package/src/local-app-data-storage.ts +12 -0
  66. package/src/locale/en/locale-resources.ts +8 -0
  67. package/src/locale/fr/locale-resources.ts +8 -0
  68. package/src/locale/i18n.ts +102 -36
  69. package/src/locale/i18next.d.ts +16 -0
  70. package/src/logger.ts +250 -13
  71. package/src/remote-app-data-storage.ts +38 -0
  72. package/src/type-helper.ts +2 -2
  73. package/src/type.ts +6 -0
package/src/logger.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { Logger } from "tslog";
1
+ import { type ISettingsParam, Logger } from "tslog";
2
+ import { i18nextCore } from "./locale/i18n.js";
2
3
 
3
4
  /**
4
5
  * Log levels.
@@ -24,29 +25,265 @@ export type LogLevelKey = keyof typeof LogLevels;
24
25
  export type LogLevel = typeof LogLevels[LogLevelKey];
25
26
 
26
27
  /**
27
- * Get a simple logger with an optional log level.
28
+ * Get the log level enumeration value corresponding to a string or number.
28
29
  *
29
- * @param logLevel
30
- * Log level as enumeration value or string.
30
+ * @param untypedLogLevel
31
+ * Untyped log level.
31
32
  *
32
33
  * @returns
33
- * Logger.
34
+ * Typed log level or default `LogLevels.Info` if untyped log level not provided..
34
35
  */
35
- export function getLogger(logLevel?: string | number): Logger<unknown> {
36
- let minLevel: number;
36
+ export function logLevelOf(untypedLogLevel?: string | number): LogLevel {
37
+ let typedLogLevel: LogLevel;
37
38
 
38
- if (typeof logLevel === "string") {
39
- if (logLevel in LogLevels) {
39
+ if (typeof untypedLogLevel === "string") {
40
+ if (untypedLogLevel in LogLevels) {
40
41
  // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- String exists as a key.
41
- minLevel = LogLevels[logLevel as LogLevelKey];
42
+ typedLogLevel = LogLevels[untypedLogLevel as LogLevelKey];
43
+ } else {
44
+ throw new RangeError(i18nextCore.t("Logger.unknownLogLevel", {
45
+ logLevel: untypedLogLevel
46
+ }));
47
+ }
48
+ } else if (untypedLogLevel !== undefined) {
49
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Assume that valid log level has been provided.
50
+ if (Object.values(LogLevels).includes(untypedLogLevel as LogLevel)) {
51
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Valid log level has been provided.
52
+ typedLogLevel = untypedLogLevel as LogLevel;
42
53
  } else {
43
- throw new Error(`Unknown log level ${logLevel}`);
54
+ throw new RangeError(i18nextCore.t("Logger.unknownLogLevel", {
55
+ logLevel: untypedLogLevel
56
+ }));
44
57
  }
45
58
  } else {
46
- minLevel = logLevel ?? LogLevels.Info;
59
+ typedLogLevel = LogLevels.Info;
47
60
  }
48
61
 
62
+ return typedLogLevel;
63
+ }
64
+
65
+ /**
66
+ * Get a logger with an optional log level. The underlying implementation is
67
+ * [`tslog`](https://tslog.js.org/).
68
+ *
69
+ * @param logLevel
70
+ * Log level as enumeration value or string. Mapped to `minLevel` and sets `hideLogPositionForProduction` to true in
71
+ * settings if at {@linkcode LogLevels.Info} or higher. Default is {@linkcode LogLevels.Info}.
72
+ *
73
+ * @param settings
74
+ * Detailed settings. See [`tslog`](https://tslog.js.org/#/?id=settings) documentation for details. The `minLevel` is
75
+ * ignored in favour of `logLevel` but `hideLogPositionForProduction` will override the default logic.
76
+ *
77
+ * @param logObj
78
+ * Default log object. See [`tslog`](https://tslog.js.org/#/?id=defining-and-accessing-logobj) documentation for
79
+ * details.
80
+ *
81
+ * @returns
82
+ * Logger.
83
+ *
84
+ * @template T
85
+ * Log object type.
86
+ */
87
+ export function getLogger<T extends object = object>(logLevel?: string | number, settings?: ISettingsParam<T>, logObj?: T): Logger<T> {
88
+ const minLevel = logLevelOf(logLevel);
89
+
49
90
  return new Logger({
91
+ // Hiding log position for production can be overridden in settings parameter.
92
+ hideLogPositionForProduction: minLevel >= LogLevels.Info,
93
+ ...settings ?? {},
94
+ // Minimum log level overrides settings parameter.
50
95
  minLevel
51
- });
96
+ }, logObj);
97
+ }
98
+
99
+ /**
100
+ * Get a loggable representation of a value. Values are returned unmodified, except as follows:
101
+ *
102
+ * - Big integers are converted to whole numbers where possible, otherwise as their decimal string representations.
103
+ * - Arrays are limited to a maximum of ten elements. Any array longer than ten elements is replaced with the first four
104
+ * elements, a string of three dots, and the last four elements. This may still create large results for
105
+ * multidimensional arrays.
106
+ * - Errors are converted to objects with `name`, `message`, and `stack` properties.
107
+ * - Symbols are converted to their string representations.
108
+ * - Functions are converted to strings of the form `Function(name)`.
109
+ *
110
+ * @param value
111
+ * Value.
112
+ *
113
+ * @returns
114
+ * Loggable value.
115
+ */
116
+ export function loggableValue(value: unknown): unknown {
117
+ let replacementValue: unknown;
118
+
119
+ switch (typeof value) {
120
+ case "string":
121
+ case "number":
122
+ case "boolean":
123
+ case "undefined":
124
+ replacementValue = value;
125
+ break;
126
+
127
+ case "bigint":
128
+ // Big integers not supported in JSON.
129
+ replacementValue = value >= Number.MIN_SAFE_INTEGER && value <= Number.MAX_SAFE_INTEGER ? Number(value) : value.toString(10);
130
+ break;
131
+
132
+ case "object":
133
+ if (value === null) {
134
+ replacementValue = value;
135
+ } else if (Array.isArray(value)) {
136
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- Slicing array is necessary to keep log size down.
137
+ replacementValue = (value.length <= 10 ? value : [...value.slice(0, 4), "...", ...value.slice(-4)]).map(entry => loggableValue(entry));
138
+ } else if (value instanceof Error) {
139
+ replacementValue = loggableValue({
140
+ name: value.name,
141
+ message: value.message,
142
+ stack: value.stack?.split("\n")
143
+ });
144
+ } else {
145
+ // Apply recursively to all properties of the object.
146
+ replacementValue = Object.fromEntries(Object.entries(value).map(([k, v]) => [k, loggableValue(v)]));
147
+ }
148
+ break;
149
+
150
+ case "symbol":
151
+ replacementValue = value.toString();
152
+ break;
153
+
154
+ case "function":
155
+ replacementValue = `Function(${value.name})`;
156
+ break;
157
+ }
158
+
159
+ return replacementValue;
160
+ }
161
+
162
+ /**
163
+ * Logger transport that stores messages in memory.
164
+ */
165
+ export class MemoryTransport<T extends object> {
166
+ /**
167
+ * Notification callbacks map.
168
+ */
169
+ readonly #notificationCallbacksMap = new Map<string, (message: string | undefined, messages: readonly string[]) => void>();
170
+
171
+ /**
172
+ * Messages.
173
+ */
174
+ readonly #messages: string[] = [];
175
+
176
+ /**
177
+ * Maximum length of messages array.
178
+ */
179
+ #maximumLength = 0;
180
+
181
+ /**
182
+ * Length to which messages array is truncated when maximum is reached.
183
+ */
184
+ #truncateLength = 0;
185
+
186
+ /**
187
+ * Constructor.
188
+ *
189
+ * @param logger
190
+ * Logger.
191
+ *
192
+ * @param maximumLength
193
+ * Maximum length of messages array.
194
+ *
195
+ * @param truncateLength
196
+ * Length to which messages array is truncated when maximum is reached. Default is 50% of `maximumLength`, maximum
197
+ * is 80% of `maximumLength`.
198
+ */
199
+ constructor(logger: Logger<T>, maximumLength: number, truncateLength?: number) {
200
+ this.resize(maximumLength, truncateLength);
201
+
202
+ logger.attachTransport((logObject) => {
203
+ // Truncate logger messages if necessary.
204
+ if (this.#messages.length >= this.#maximumLength) {
205
+ this.#messages.splice(0, this.#maximumLength - this.#truncateLength);
206
+ }
207
+
208
+ const message = JSON.stringify(logObject);
209
+
210
+ this.#messages.push(message);
211
+
212
+ // Notify all registered callbacks.
213
+ for (const notificationCallback of this.#notificationCallbacksMap.values()) {
214
+ notificationCallback(message, this.#messages);
215
+ }
216
+ });
217
+ }
218
+
219
+ /**
220
+ * Get the messages.
221
+ */
222
+ get messages(): string[] {
223
+ return this.#messages;
224
+ }
225
+
226
+ /**
227
+ * Get the maximum length of messages array.
228
+ */
229
+ get maximumLength(): number {
230
+ return this.#maximumLength;
231
+ }
232
+
233
+ /**
234
+ * Get the length to which messages array is truncated when maximum is reached.
235
+ */
236
+ get truncateLength(): number {
237
+ return this.#truncateLength;
238
+ }
239
+
240
+ /**
241
+ * Add a notification callback. If one already exists under the current name, do nothing.
242
+ *
243
+ * @param name
244
+ * Callback name.
245
+ *
246
+ * @param notificationCallback
247
+ * Callback.
248
+ *
249
+ * @returns
250
+ * True if successfully added.
251
+ */
252
+ addNotificationCallback(name: string, notificationCallback: (message: string | undefined, messages: readonly string[]) => void): boolean {
253
+ const added = !this.#notificationCallbacksMap.has(name);
254
+
255
+ if (added) {
256
+ this.#notificationCallbacksMap.set(name, notificationCallback);
257
+
258
+ // Notify with existing messages.
259
+ notificationCallback(undefined, this.#messages);
260
+ }
261
+
262
+ return added;
263
+ }
264
+
265
+ /**
266
+ * Remove a notification callback.
267
+ *
268
+ * @param name
269
+ * Callback name.
270
+ */
271
+ removeNotificationCallback(name: string): void {
272
+ this.#notificationCallbacksMap.delete(name);
273
+ }
274
+
275
+ /**
276
+ * Resize the messages array.
277
+ *
278
+ * @param maximumLength
279
+ * Maximum length of messages array.
280
+ *
281
+ * @param truncateLength
282
+ * Length to which messages array is truncated when maximum is reached. Default is 50% of `maximumLength`, maximum
283
+ * is 80% of `maximumLength`.
284
+ */
285
+ resize(maximumLength: number, truncateLength?: number): void {
286
+ this.#maximumLength = maximumLength;
287
+ this.#truncateLength = truncateLength !== undefined ? Math.min(truncateLength, Math.floor(maximumLength * 0.8)) : Math.floor(maximumLength / 2);
288
+ }
52
289
  }
@@ -0,0 +1,38 @@
1
+ import { ReadOnlyAppDataStorage } from "./app-data-storage.js";
2
+ import { i18nextCore } from "./locale/i18n.js";
3
+
4
+ /**
5
+ * Remote application data storage using HTTP. The `store()` and `delete()` methods are not supported.
6
+ */
7
+ export class RemoteAppDataStorage extends ReadOnlyAppDataStorage<true> {
8
+ /**
9
+ * Constructor.
10
+ *
11
+ * @param baseURL
12
+ * Base URL. The URL must not end with a slash.
13
+ */
14
+ constructor(baseURL: string) {
15
+ super(true, baseURL);
16
+ }
17
+
18
+ /**
19
+ * @inheritDoc
20
+ */
21
+ protected override async doRead(key: string, asBinary: boolean | undefined): Promise<string | Uint8Array | undefined> {
22
+ return fetch(key).then(async (response) => {
23
+ let result: string | Uint8Array | undefined;
24
+
25
+ if (response.ok) {
26
+ result = asBinary === true ? new Uint8Array(await response.arrayBuffer()) : await response.text();
27
+ } else if (response.status === 404) {
28
+ result = undefined;
29
+ } else {
30
+ throw new RangeError(i18nextCore.t("RemoteAppDataStorage.httpError", {
31
+ status: response.status
32
+ }));
33
+ }
34
+
35
+ return result;
36
+ });
37
+ }
38
+ }
@@ -94,7 +94,7 @@ export function pick<T extends object, K extends keyof T>(o: T, ...keys: K[]): P
94
94
  */
95
95
  export function exclude<TWide extends TNarrow, TNarrow extends object, K extends keyof TNarrow>(wide: TWide, narrow: TNarrow): Omit<TWide, K> {
96
96
  // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Keys are valid.
97
- return omit(wide, ...(Object.keys(narrow) as K[]));
97
+ return omit(wide, ...Object.keys(narrow) as K[]);
98
98
  }
99
99
 
100
100
  /**
@@ -120,7 +120,7 @@ export function exclude<TWide extends TNarrow, TNarrow extends object, K extends
120
120
  */
121
121
  export function include<TWide extends TNarrow, TNarrow extends object, K extends keyof TNarrow>(wide: TWide, narrow: TNarrow): Pick<TWide, K> {
122
122
  // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Keys are valid.
123
- return pick(wide, ...(Object.keys(narrow) as K[]));
123
+ return pick(wide, ...Object.keys(narrow) as K[]);
124
124
  }
125
125
 
126
126
  /**
package/src/type.ts CHANGED
@@ -63,6 +63,12 @@ export type TypedConstructor<TConstructor extends abstract new (...args: Constru
63
63
  export type TypedAbstractConstructor<TConstructor extends abstract new (...args: ConstructorParameters<TConstructor>) => InstanceType<TConstructor>> =
64
64
  AbstractConstructor<ConstructorParameters<TConstructor>, InstanceType<TConstructor>>;
65
65
 
66
+ /**
67
+ * Promisable type. Extends a type by allowing a {@linkcode Promise} of the same type. Typically used in abstract method
68
+ * declarations where the implementing class may or may not implement the method as asynchronous.
69
+ */
70
+ export type Promisable<T> = T | Promise<T>;
71
+
66
72
  /**
67
73
  * Determine the fundamental promised type. This is stricter than `Awaited<Type>` in that it requires a {@linkcode
68
74
  * Promise}.