@strictly/base 0.0.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.
Files changed (114) hide show
  1. package/.eslintrc.cjs +26 -0
  2. package/.out/errors/not_implemented.d.ts +3 -0
  3. package/.out/errors/not_implemented.js +5 -0
  4. package/.out/errors/unexpected_implementation.d.ts +3 -0
  5. package/.out/errors/unexpected_implementation.js +5 -0
  6. package/.out/errors/unreachable.d.ts +3 -0
  7. package/.out/errors/unreachable.js +6 -0
  8. package/.out/index.d.ts +20 -0
  9. package/.out/index.js +20 -0
  10. package/.out/test/index.d.ts +1 -0
  11. package/.out/test/index.js +1 -0
  12. package/.out/test/vitest/expect.d.ts +4 -0
  13. package/.out/test/vitest/expect.js +13 -0
  14. package/.out/tsconfig.json +16 -0
  15. package/.out/tsconfig.tsbuildinfo +1 -0
  16. package/.out/tsup.config.d.ts +3 -0
  17. package/.out/tsup.config.js +12 -0
  18. package/.out/types/element_of_array.d.ts +1 -0
  19. package/.out/types/element_of_array.js +1 -0
  20. package/.out/types/extract_generics.d.ts +2 -0
  21. package/.out/types/extract_generics.js +1 -0
  22. package/.out/types/is_field_optional.d.ts +1 -0
  23. package/.out/types/is_field_optional.js +1 -0
  24. package/.out/types/is_field_readonly.d.ts +8 -0
  25. package/.out/types/is_field_readonly.js +2 -0
  26. package/.out/types/maybe.d.ts +3 -0
  27. package/.out/types/maybe.js +1 -0
  28. package/.out/types/printable_of.d.ts +1 -0
  29. package/.out/types/printable_of.js +1 -0
  30. package/.out/types/required_of_record.d.ts +3 -0
  31. package/.out/types/required_of_record.js +1 -0
  32. package/.out/types/specs/element_of_array.tests.d.ts +1 -0
  33. package/.out/types/specs/element_of_array.tests.js +6 -0
  34. package/.out/types/specs/is_field_readonly.tests.d.ts +1 -0
  35. package/.out/types/specs/is_field_readonly.tests.js +9 -0
  36. package/.out/types/specs/printable_of.tests.d.ts +1 -0
  37. package/.out/types/specs/printable_of.tests.js +6 -0
  38. package/.out/types/specs/required_of_record.tests.d.ts +1 -0
  39. package/.out/types/specs/required_of_record.tests.js +12 -0
  40. package/.out/types/specs/string_key_of.tests.d.ts +1 -0
  41. package/.out/types/specs/string_key_of.tests.js +10 -0
  42. package/.out/types/string_key_of.d.ts +1 -0
  43. package/.out/types/string_key_of.js +1 -0
  44. package/.out/util/array.d.ts +1 -0
  45. package/.out/util/array.js +8 -0
  46. package/.out/util/cache.d.ts +13 -0
  47. package/.out/util/cache.js +42 -0
  48. package/.out/util/delay.d.ts +12 -0
  49. package/.out/util/delay.js +32 -0
  50. package/.out/util/format.d.ts +2 -0
  51. package/.out/util/format.js +11 -0
  52. package/.out/util/json.d.ts +1 -0
  53. package/.out/util/json.js +11 -0
  54. package/.out/util/poll.d.ts +8 -0
  55. package/.out/util/poll.js +23 -0
  56. package/.out/util/preconditions.d.ts +11 -0
  57. package/.out/util/preconditions.js +43 -0
  58. package/.out/util/promises.d.ts +1 -0
  59. package/.out/util/promises.js +11 -0
  60. package/.out/util/record.d.ts +10 -0
  61. package/.out/util/record.js +64 -0
  62. package/.out/util/specs/cache.tests.d.ts +1 -0
  63. package/.out/util/specs/cache.tests.js +49 -0
  64. package/.out/util/specs/format.tests.d.ts +1 -0
  65. package/.out/util/specs/format.tests.js +67 -0
  66. package/.out/util/specs/poll.tests.d.ts +1 -0
  67. package/.out/util/specs/poll.tests.js +47 -0
  68. package/.out/vitest.workspace.d.ts +2 -0
  69. package/.out/vitest.workspace.js +7 -0
  70. package/.turbo/turbo-build.log +18 -0
  71. package/.turbo/turbo-check-types.log +3 -0
  72. package/.turbo/turbo-release$colon$exports.log +3 -0
  73. package/README.md +3 -0
  74. package/dist/index.cjs +387 -0
  75. package/dist/index.d.cts +109 -0
  76. package/dist/index.d.ts +109 -0
  77. package/dist/index.js +328 -0
  78. package/errors/not_implemented.ts +5 -0
  79. package/errors/unexpected_implementation.ts +5 -0
  80. package/errors/unreachable.ts +6 -0
  81. package/index.ts +20 -0
  82. package/package.exports.json +18 -0
  83. package/package.json +50 -0
  84. package/test/index.ts +1 -0
  85. package/test/vitest/expect.ts +16 -0
  86. package/tsconfig.build.json +11 -0
  87. package/tsconfig.json +16 -0
  88. package/tsup.config.ts +16 -0
  89. package/types/element_of_array.ts +1 -0
  90. package/types/extract_generics.ts +2 -0
  91. package/types/is_field_optional.ts +5 -0
  92. package/types/is_field_readonly.ts +8 -0
  93. package/types/maybe.ts +5 -0
  94. package/types/printable_of.ts +1 -0
  95. package/types/required_of_record.ts +4 -0
  96. package/types/specs/element_of_array.tests.ts +9 -0
  97. package/types/specs/is_field_readonly.tests.ts +15 -0
  98. package/types/specs/printable_of.tests.ts +8 -0
  99. package/types/specs/required_of_record.tests.ts +16 -0
  100. package/types/specs/string_key_of.tests.ts +16 -0
  101. package/types/string_key_of.ts +1 -0
  102. package/util/array.ts +12 -0
  103. package/util/cache.ts +52 -0
  104. package/util/delay.ts +36 -0
  105. package/util/format.ts +23 -0
  106. package/util/json.ts +10 -0
  107. package/util/poll.ts +40 -0
  108. package/util/preconditions.ts +83 -0
  109. package/util/promises.ts +11 -0
  110. package/util/record.ts +133 -0
  111. package/util/specs/cache.tests.ts +68 -0
  112. package/util/specs/format.tests.ts +70 -0
  113. package/util/specs/poll.tests.ts +75 -0
  114. package/vitest.workspace.ts +11 -0
package/dist/index.js ADDED
@@ -0,0 +1,328 @@
1
+ // errors/not_implemented.ts
2
+ var NotImplementedError = class extends Error {
3
+ constructor(name) {
4
+ super(`${name} not implemented`);
5
+ }
6
+ };
7
+
8
+ // errors/unexpected_implementation.ts
9
+ var UnexpectedImplementationError = class extends Error {
10
+ constructor(impl) {
11
+ super(impl);
12
+ }
13
+ };
14
+
15
+ // errors/unreachable.ts
16
+ var UnreachableError = class extends Error {
17
+ constructor(v) {
18
+ super(`Unreachable value received: ${v}`);
19
+ }
20
+ };
21
+
22
+ // test/vitest/expect.ts
23
+ function expectEquals(v1, v2) {
24
+ expect(v1).toEqual(v2);
25
+ }
26
+ function expectTruthy(b) {
27
+ expect(b).toBeTruthy();
28
+ }
29
+ function expectDefined(v) {
30
+ expect(v).toBeDefined();
31
+ }
32
+ function expectDefinedAndReturn(v) {
33
+ expectDefined(v);
34
+ return v;
35
+ }
36
+
37
+ // util/array.ts
38
+ async function asyncReduce(arr, reducer, initial) {
39
+ let acc = initial;
40
+ for (let i = 0; i < arr.length; i++) {
41
+ const v = arr[i];
42
+ acc = await reducer(acc, v, i);
43
+ }
44
+ return acc;
45
+ }
46
+
47
+ // util/cache.ts
48
+ var Cache = class {
49
+ constructor(valueFactory) {
50
+ this.valueFactory = valueFactory;
51
+ }
52
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
53
+ cache = /* @__PURE__ */ new Map();
54
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
55
+ retrieveFinalMap(...args) {
56
+ return args.slice(0, -1).reduce(function(cache, key) {
57
+ let map2 = cache.get(key);
58
+ if (map2 == null) {
59
+ map2 = /* @__PURE__ */ new Map();
60
+ cache.set(key, map2);
61
+ }
62
+ return map2;
63
+ }, this.cache);
64
+ }
65
+ retrieve(...args) {
66
+ const finalKey = args[args.length - 1];
67
+ const finalMap = this.retrieveFinalMap(...args);
68
+ if (!finalMap.has(finalKey)) {
69
+ return null;
70
+ }
71
+ return [finalMap.get(finalKey)];
72
+ }
73
+ retrieveOrCreate(...args) {
74
+ const finalKey = args[args.length - 1];
75
+ const finalMap = this.retrieveFinalMap(...args);
76
+ if (finalMap == null || !finalMap.has(finalKey)) {
77
+ const value = this.valueFactory(...args);
78
+ finalMap.set(finalKey, value);
79
+ }
80
+ return finalMap.get(finalKey);
81
+ }
82
+ clear(...args) {
83
+ const finalKey = args[args.length - 1];
84
+ const finalMap = this.retrieveFinalMap(...args);
85
+ finalMap.delete(finalKey);
86
+ }
87
+ };
88
+
89
+ // util/delay.ts
90
+ function createDelay(millis) {
91
+ return function() {
92
+ return delay(millis);
93
+ };
94
+ }
95
+ function createWarmupDelay(coldMillis, warmMillis) {
96
+ let warmup;
97
+ return function() {
98
+ if (warmup != null) {
99
+ return warmup.then(function() {
100
+ return delay(warmMillis);
101
+ });
102
+ } else {
103
+ warmup = delay(coldMillis);
104
+ return warmup;
105
+ }
106
+ };
107
+ }
108
+ function delay(millis) {
109
+ return new Promise(function(resolve) {
110
+ setTimeout(resolve, millis);
111
+ });
112
+ }
113
+ var secondDelay = createDelay(1e3);
114
+
115
+ // util/format.ts
116
+ function format(message, ...args) {
117
+ let index = 0;
118
+ return message.replaceAll(/{(\d*)}/g, function(_substring, indexString) {
119
+ let argIndex = parseInt(indexString);
120
+ if (Number.isNaN(argIndex)) {
121
+ argIndex = index;
122
+ index++;
123
+ }
124
+ return JSON.stringify(args[argIndex]);
125
+ });
126
+ }
127
+
128
+ // util/json.ts
129
+ function errorHandlingJsonParse(json, errorHandler) {
130
+ try {
131
+ return JSON.parse(json);
132
+ } catch (e) {
133
+ errorHandler?.(e);
134
+ return null;
135
+ }
136
+ }
137
+
138
+ // util/poll.ts
139
+ function constantPollInterval(delay2) {
140
+ return function() {
141
+ return delay2;
142
+ };
143
+ }
144
+ async function poll(f, {
145
+ pollInterval = constantPollInterval(200),
146
+ retries = 3
147
+ } = {}) {
148
+ let retriesRemaining = retries;
149
+ while (retriesRemaining > 0) {
150
+ await delay(pollInterval(retriesRemaining));
151
+ retriesRemaining--;
152
+ const v = await f();
153
+ if (v != null) {
154
+ return v;
155
+ }
156
+ }
157
+ return null;
158
+ }
159
+
160
+ // util/preconditions.ts
161
+ var PreconditionFailedError = class extends Error {
162
+ constructor(message, ...args) {
163
+ super(format(message, ...args));
164
+ this.name = "PreconditionFailedError";
165
+ }
166
+ };
167
+ function assertExistsAndReturn(t, message, ...args) {
168
+ assertExists(t, message, ...args);
169
+ return t;
170
+ }
171
+ function assertExists(v, message, ...args) {
172
+ if (v == null) {
173
+ throw new PreconditionFailedError(message, ...args);
174
+ }
175
+ }
176
+ function assertEqual(a, b, message = "{} != {}", arg1 = a, arg2 = b, ...args) {
177
+ if (a !== b) {
178
+ throw new PreconditionFailedError(
179
+ message,
180
+ arg1,
181
+ arg2,
182
+ ...args
183
+ );
184
+ }
185
+ }
186
+ function assertState(condition, message, ...args) {
187
+ if (!condition) {
188
+ throw new PreconditionFailedError(message, ...args);
189
+ }
190
+ }
191
+ function assertIs(v, condition, message, ...args) {
192
+ if (!condition(v)) {
193
+ throw new PreconditionFailedError(message, ...args);
194
+ }
195
+ }
196
+ function checkUnary(t, message, ...args) {
197
+ if (t.length !== 1) {
198
+ throw new PreconditionFailedError(message, ...args);
199
+ }
200
+ return t[0];
201
+ }
202
+ function checkValidNumber(n, message, ...args) {
203
+ if (isNaN(n) || !isFinite(n)) {
204
+ throw new PreconditionFailedError(message, ...args);
205
+ }
206
+ return n;
207
+ }
208
+
209
+ // util/promises.ts
210
+ function callAsPromise(f) {
211
+ return new Promise(function(resolve, reject) {
212
+ f(function(e) {
213
+ if (e == null) {
214
+ resolve();
215
+ }
216
+ reject(e);
217
+ });
218
+ });
219
+ }
220
+
221
+ // util/record.ts
222
+ function reverse(obj) {
223
+ return Object.keys(obj).reduce((acc, stringKey) => {
224
+ const key = stringKey;
225
+ const value = obj[key];
226
+ acc[value] = key;
227
+ return acc;
228
+ }, {});
229
+ }
230
+ function rollup(...records) {
231
+ return records.slice(1).reduce((acc, record) => {
232
+ Object.keys(record).forEach((key) => {
233
+ const k = key;
234
+ acc[k] = acc[k] ?? record[k];
235
+ });
236
+ return acc;
237
+ }, records[0]);
238
+ }
239
+ function union(r1, r2) {
240
+ return {
241
+ ...r1,
242
+ ...r2
243
+ };
244
+ }
245
+ function map(r, f) {
246
+ return Object.entries(r).reduce(
247
+ function(acc, [
248
+ k,
249
+ v
250
+ ]) {
251
+ const typedKey = k;
252
+ acc[typedKey] = f(typedKey, v);
253
+ return acc;
254
+ },
255
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
256
+ {}
257
+ );
258
+ }
259
+ function reduce(r, f, a) {
260
+ return Object.entries(r).reduce(
261
+ function(acc, [
262
+ k,
263
+ v
264
+ ]) {
265
+ const typedKey = k;
266
+ return f(acc, typedKey, v);
267
+ },
268
+ a
269
+ );
270
+ }
271
+ function forEach(r, f) {
272
+ return Object.entries(r).forEach(
273
+ function([
274
+ k,
275
+ v
276
+ ]) {
277
+ return f(k, v);
278
+ }
279
+ );
280
+ }
281
+ function toArray(r) {
282
+ return reduce(
283
+ r,
284
+ function(acc, k, v) {
285
+ acc.push([
286
+ k,
287
+ v
288
+ ]);
289
+ return acc;
290
+ },
291
+ []
292
+ );
293
+ }
294
+ export {
295
+ Cache,
296
+ NotImplementedError,
297
+ PreconditionFailedError,
298
+ UnexpectedImplementationError,
299
+ UnreachableError,
300
+ assertEqual,
301
+ assertExists,
302
+ assertExistsAndReturn,
303
+ assertIs,
304
+ assertState,
305
+ asyncReduce,
306
+ callAsPromise,
307
+ checkUnary,
308
+ checkValidNumber,
309
+ constantPollInterval,
310
+ createDelay,
311
+ createWarmupDelay,
312
+ delay,
313
+ errorHandlingJsonParse,
314
+ expectDefined,
315
+ expectDefinedAndReturn,
316
+ expectEquals,
317
+ expectTruthy,
318
+ forEach,
319
+ format,
320
+ map,
321
+ poll,
322
+ reduce,
323
+ reverse,
324
+ rollup,
325
+ secondDelay,
326
+ toArray,
327
+ union
328
+ };
@@ -0,0 +1,5 @@
1
+ export class NotImplementedError extends Error {
2
+ constructor(name: string) {
3
+ super(`${name} not implemented`)
4
+ }
5
+ }
@@ -0,0 +1,5 @@
1
+ export class UnexpectedImplementationError extends Error {
2
+ constructor(impl: string) {
3
+ super(impl)
4
+ }
5
+ }
@@ -0,0 +1,6 @@
1
+ export class UnreachableError extends Error {
2
+ constructor(v: never) {
3
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
4
+ super(`Unreachable value received: ${v}`)
5
+ }
6
+ }
package/index.ts ADDED
@@ -0,0 +1,20 @@
1
+ export * from './errors/not_implemented'
2
+ export * from './errors/unexpected_implementation'
3
+ export * from './errors/unreachable'
4
+ export * from './test'
5
+ export * from './types/element_of_array'
6
+ export * from './types/extract_generics'
7
+ export * from './types/is_field_readonly'
8
+ export * from './types/maybe'
9
+ export * from './types/printable_of'
10
+ export * from './types/required_of_record'
11
+ export * from './types/string_key_of'
12
+ export * from './util/array'
13
+ export * from './util/cache'
14
+ export * from './util/delay'
15
+ export * from './util/format'
16
+ export * from './util/json'
17
+ export * from './util/poll'
18
+ export * from './util/preconditions'
19
+ export * from './util/promises'
20
+ export * from './util/record'
@@ -0,0 +1,18 @@
1
+ {
2
+ "exports": {
3
+ ".": {
4
+ "import": {
5
+ "types": "./dist/index.d.ts",
6
+
7
+ "default": "./dist/index.js"
8
+ },
9
+ "require": {
10
+ "types": "./dist/index.d.cts",
11
+
12
+ "default": "./dist/index.cjs"
13
+ }
14
+ }
15
+ },
16
+ "main": "./dist/index.js",
17
+ "types": "./dist/index.d.ts"
18
+ }
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "author": "Chris <chris.glover@gmail.com> (@madmaw)",
3
+ "devDependencies": {
4
+ "@strictly/support-vite": "*",
5
+ "json": "^11.0.0"
6
+ },
7
+ "homepage": "https://madmaw.github.io/de/base",
8
+ "keywords": [
9
+ "react",
10
+ "state management",
11
+ "types"
12
+ ],
13
+ "license": "MIT",
14
+ "name": "@strictly/base",
15
+ "packageManager": "yarn@1.22.22",
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "repository": {
20
+ "directory": "packages/base",
21
+ "type": "git",
22
+ "url": "git+https://github.com/madmaw/de.git"
23
+ },
24
+ "scripts": {
25
+ "build": "tsup",
26
+ "check-types": "tsc",
27
+ "clean": "del-cli dist",
28
+ "lint": "cross-env ESLINT_USE_FLAT_CONFIG=false eslint . --max-warnings=0",
29
+ "lint:fix": "cross-env ESLINT_USE_FLAT_CONFIG=false eslint . --fix",
30
+ "release:exports": "json -f package.json -f package.exports.json --merge > package.release.json",
31
+ "test": "vitest run",
32
+ "test:watch": "vitest"
33
+ },
34
+ "type": "module",
35
+ "version": "0.0.1",
36
+ "exports": {
37
+ ".": {
38
+ "import": {
39
+ "types": "./dist/index.d.ts",
40
+ "default": "./dist/index.js"
41
+ },
42
+ "require": {
43
+ "types": "./dist/index.d.cts",
44
+ "default": "./dist/index.cjs"
45
+ }
46
+ }
47
+ },
48
+ "main": "./dist/index.js",
49
+ "types": "./dist/index.d.ts"
50
+ }
package/test/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './vitest/expect'
@@ -0,0 +1,16 @@
1
+ export function expectEquals<V1, V2 extends V1>(v1: V1, v2: V2): asserts v1 is V2 {
2
+ expect(v1).toEqual(v2)
3
+ }
4
+
5
+ export function expectTruthy(b: boolean): asserts b is true {
6
+ expect(b).toBeTruthy()
7
+ }
8
+
9
+ export function expectDefined<V>(v: V): asserts v is NonNullable<V> {
10
+ expect(v).toBeDefined()
11
+ }
12
+
13
+ export function expectDefinedAndReturn<V>(v: V): NonNullable<V> {
14
+ expectDefined(v)
15
+ return v
16
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "compilerOptions": {
3
+ "baseUrl": "."
4
+ },
5
+ "extends": "../../tsconfig.build.json",
6
+ "include": [
7
+ ".eslintrc.cjs",
8
+ "tsup.config.ts",
9
+ "vitest.workspace.ts"
10
+ ]
11
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "baseUrl": ".",
4
+ "outDir": "./.out"
5
+ },
6
+ "extends": "../../tsconfig.json",
7
+ "include": [
8
+ "**/*.ts",
9
+ "tsconfig.json"
10
+ ],
11
+ "references": [
12
+ {
13
+ "path": "../../support/vite"
14
+ }
15
+ ]
16
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,16 @@
1
+ import {
2
+ defineConfig,
3
+ type Options,
4
+ } from 'tsup'
5
+
6
+ export default defineConfig((options: Options) => ({
7
+ entry: ['index.ts'],
8
+ tsconfig: './tsconfig.build.json',
9
+ clean: false,
10
+ dts: true,
11
+ format: [
12
+ 'cjs',
13
+ 'esm',
14
+ ],
15
+ ...options,
16
+ }))
@@ -0,0 +1 @@
1
+ export type ElementOfArray<A> = A extends readonly (infer T)[] ? T : never
@@ -0,0 +1,2 @@
1
+ export type ExtractPromiseType<T> = T extends Promise<infer V> ? V : never
2
+ export type ExtractArrayType<T> = T extends Array<infer V> ? V : never
@@ -0,0 +1,5 @@
1
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2
+ export type IsFieldOptional<R extends Record<string, any>, K extends keyof R> = undefined extends R[K]
3
+ // yes, we can't use the `extends` as true/false directly
4
+ ? true
5
+ : false
@@ -0,0 +1,8 @@
1
+ import { type IsEqual } from 'type-fest'
2
+
3
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4
+ export type IsFieldReadonly<R extends Record<string, any>, K extends keyof R> = {
5
+ [P in keyof R]: IsEqual<{ [Q in P]: R[P] }, { readonly [Q in P]: R[P] }>
6
+ }[K]
7
+
8
+ // extends never ? false : true
package/types/maybe.ts ADDED
@@ -0,0 +1,5 @@
1
+ export type Just<T> = [T]
2
+
3
+ export type Nothing = null
4
+
5
+ export type Maybe<T> = Just<T> | Nothing
@@ -0,0 +1 @@
1
+ export type PrintableOf<T> = Extract<T, string | number>
@@ -0,0 +1,4 @@
1
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2
+ export type RequiredOfRecord<R extends Readonly<Record<string | number | symbol, any>>> = {
3
+ [K in keyof R as undefined extends R[K] ? never : K]-?: NonNullable<R[K]>
4
+ }
@@ -0,0 +1,9 @@
1
+ import { type ElementOfArray } from 'types/element_of_array'
2
+
3
+ describe('ElementOfArray', function () {
4
+ type A = readonly number[]
5
+
6
+ it('extracts the element type', function () {
7
+ expectTypeOf<ElementOfArray<A>>().toEqualTypeOf<number>()
8
+ })
9
+ })
@@ -0,0 +1,15 @@
1
+ import { type IsFieldReadonly } from 'types/is_field_readonly'
2
+
3
+ describe('IsFieldReadonly', function () {
4
+ it('detects readonly', function () {
5
+ type T = IsFieldReadonly<{ readonly a: 1 }, 'a'>
6
+
7
+ expectTypeOf<true>().toEqualTypeOf<T>()
8
+ })
9
+
10
+ it('detects mutable', function () {
11
+ type T = IsFieldReadonly<{ a: 1 }, 'a'>
12
+
13
+ expectTypeOf<false>().toEqualTypeOf<T>()
14
+ })
15
+ })
@@ -0,0 +1,8 @@
1
+ import { type PrintableOf } from 'types/printable_of'
2
+
3
+ describe('PrintableOf', function () {
4
+ it('filters out the non-printable types', function () {
5
+ type T = PrintableOf<string | number | boolean>
6
+ expectTypeOf<T>().toEqualTypeOf<string | number>()
7
+ })
8
+ })
@@ -0,0 +1,16 @@
1
+ import { type RequiredOfRecord } from 'types/required_of_record'
2
+
3
+ describe('RequiredOfRecord', function () {
4
+ it('works on empty record', function () {
5
+ expectTypeOf<RequiredOfRecord<{}>>().toEqualTypeOf<{}>()
6
+ })
7
+
8
+ it('removes all optional types', function () {
9
+ expectTypeOf<RequiredOfRecord<{ a?: 1, b?: true, c?: 'a' }>>().toEqualTypeOf<{}>()
10
+ })
11
+
12
+ it('leaves all mandatory types alone', function () {
13
+ type T = { a: 1, b: true, c: 'a' }
14
+ expectTypeOf<RequiredOfRecord<T>>().toEqualTypeOf<T>()
15
+ })
16
+ })
@@ -0,0 +1,16 @@
1
+ import { type StringKeyOf } from 'types/string_key_of'
2
+
3
+ const s = Symbol()
4
+ type S = typeof s
5
+
6
+ describe('PrintableOf', function () {
7
+ it('filters out the non-string keys', function () {
8
+ type T = StringKeyOf<Record<string | number | symbol, unknown>>
9
+ expectTypeOf<T>().toEqualTypeOf<string>()
10
+ })
11
+
12
+ it('filters out the non-string literal keys', function () {
13
+ type T = StringKeyOf<Record<'a' | 'b' | 1 | 2 | S, unknown>>
14
+ expectTypeOf<T>().toEqualTypeOf<'a' | 'b'>()
15
+ })
16
+ })
@@ -0,0 +1 @@
1
+ export type StringKeyOf<T> = Exclude<keyof T, number | symbol>
package/util/array.ts ADDED
@@ -0,0 +1,12 @@
1
+ export async function asyncReduce<T, Acc>(
2
+ arr: readonly T[],
3
+ reducer: (acc: Acc, v: T, index: number) => Promise<Acc>,
4
+ initial: Acc,
5
+ ): Promise<Acc> {
6
+ let acc = initial
7
+ for (let i = 0; i < arr.length; i++) {
8
+ const v = arr[i]
9
+ acc = await reducer(acc, v, i)
10
+ }
11
+ return acc
12
+ }
package/util/cache.ts ADDED
@@ -0,0 +1,52 @@
1
+ import { type Maybe } from 'types/maybe'
2
+
3
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4
+ export type CacheValueFactory<A extends any[], V> = {
5
+ (...args: A): V,
6
+ }
7
+
8
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
+ export class Cache<A extends any[], V> {
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
+ private readonly cache: Map<any, V> = new Map()
12
+
13
+ constructor(private readonly valueFactory: CacheValueFactory<A, V>) {
14
+ }
15
+
16
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
+ private retrieveFinalMap(...args: A): Map<any, V> {
18
+ return args.slice(0, -1).reduce(function (cache, key) {
19
+ let map = cache.get(key)
20
+ if (map == null) {
21
+ map = new Map()
22
+ cache.set(key, map)
23
+ }
24
+ return map
25
+ }, this.cache)
26
+ }
27
+
28
+ retrieve(...args: A): Maybe<V> {
29
+ const finalKey = args[args.length - 1]
30
+ const finalMap = this.retrieveFinalMap(...args)
31
+ if (!finalMap.has(finalKey)) {
32
+ return null
33
+ }
34
+ return [finalMap.get(finalKey)!]
35
+ }
36
+
37
+ retrieveOrCreate(...args: A): V {
38
+ const finalKey = args[args.length - 1]
39
+ const finalMap = this.retrieveFinalMap(...args)
40
+ if (finalMap == null || !finalMap.has(finalKey)) {
41
+ const value = this.valueFactory(...args)
42
+ finalMap.set(finalKey, value)
43
+ }
44
+ return finalMap.get(finalKey)!
45
+ }
46
+
47
+ clear(...args: A) {
48
+ const finalKey = args[args.length - 1]
49
+ const finalMap = this.retrieveFinalMap(...args)
50
+ finalMap.delete(finalKey)
51
+ }
52
+ }