@wener/utils 1.1.5 → 1.1.7

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 (137) hide show
  1. package/README.md +5 -1
  2. package/dist/cjs/index.js +1 -1
  3. package/dist/cjs/index.js.map +1 -1
  4. package/dist/cjs/server.js +1 -1
  5. package/dist/cjs/server.js.map +1 -1
  6. package/dist/esm/index.js +1 -1
  7. package/dist/esm/index.js.map +1 -1
  8. package/dist/esm/server.js +1 -1
  9. package/dist/esm/server.js.map +1 -1
  10. package/dist/system/index.js +1 -1
  11. package/dist/system/index.js.map +1 -1
  12. package/dist/system/server.js +1 -1
  13. package/dist/system/server.js.map +1 -1
  14. package/lib/arrays/MaybeArray.js.map +1 -1
  15. package/lib/asyncs/createLazyPromise.js +55 -0
  16. package/lib/asyncs/createLazyPromise.js.map +1 -0
  17. package/lib/asyncs/timeout.js +1 -1
  18. package/lib/asyncs/timeout.js.map +1 -1
  19. package/lib/browsers/copy.js +2 -3
  20. package/lib/browsers/copy.js.map +1 -1
  21. package/lib/browsers/loaders.js +6 -11
  22. package/lib/browsers/loaders.js.map +1 -1
  23. package/lib/crypto/{hex.js → base.js} +1 -1
  24. package/lib/crypto/base.js.map +1 -0
  25. package/lib/crypto/hashing.js +4 -4
  26. package/lib/crypto/hashing.js.map +1 -1
  27. package/lib/crypto/randomUUID.js +3 -5
  28. package/lib/crypto/randomUUID.js.map +1 -1
  29. package/lib/i18n/createTranslate.js +37 -0
  30. package/lib/i18n/createTranslate.js.map +1 -0
  31. package/lib/index.js +12 -7
  32. package/lib/index.js.map +1 -1
  33. package/lib/io/ArrayBuffers.js +164 -0
  34. package/lib/io/ArrayBuffers.js.map +1 -0
  35. package/lib/io/isBuffer.js +1 -1
  36. package/lib/io/isBuffer.js.map +1 -1
  37. package/lib/io/isTransferable.js +23 -0
  38. package/lib/io/isTransferable.js.map +1 -0
  39. package/lib/isomorphics/structuredClone.js +70 -0
  40. package/lib/isomorphics/structuredClone.js.map +1 -0
  41. package/lib/langs/classOf.js +6 -0
  42. package/lib/langs/classOf.js.map +1 -0
  43. package/lib/{validations/dequal.js → langs/deepEqual.js} +7 -7
  44. package/lib/langs/deepEqual.js.map +1 -0
  45. package/lib/{validations/shallow.js → langs/shallowEqual.js} +3 -3
  46. package/lib/langs/shallowEqual.js.map +1 -0
  47. package/lib/logging/createNoopLogger.js.map +1 -1
  48. package/lib/modules/isModule.js.map +1 -1
  49. package/lib/modules/parseModuleId.js +7 -5
  50. package/lib/modules/parseModuleId.js.map +1 -1
  51. package/lib/objects/parseObjectPath.js.map +1 -1
  52. package/lib/objects/set.js.map +1 -1
  53. package/lib/server/polyfillBrowser.js +12 -0
  54. package/lib/server/polyfillBrowser.js.map +1 -0
  55. package/lib/server/polyfillCrypto.js +10 -0
  56. package/lib/server/polyfillCrypto.js.map +1 -0
  57. package/lib/server/polyfillFetch.js +31 -0
  58. package/lib/server/polyfillFetch.js.map +1 -0
  59. package/lib/server/polyfillJsDom.js +49 -0
  60. package/lib/server/polyfillJsDom.js.map +1 -0
  61. package/lib/server.js +4 -1
  62. package/lib/server.js.map +1 -1
  63. package/lib/{formats → strings}/formatBytes.js +1 -1
  64. package/lib/{formats → strings}/formatBytes.js.map +1 -1
  65. package/lib/strings/renderTemplate.js +4 -2
  66. package/lib/strings/renderTemplate.js.map +1 -1
  67. package/lib/validations/isUUID.js +6 -0
  68. package/lib/validations/isUUID.js.map +1 -0
  69. package/package.json +14 -2
  70. package/src/arrays/MaybeArray.ts +1 -1
  71. package/src/asyncs/createLazyPromise.test.ts +39 -0
  72. package/src/asyncs/createLazyPromise.ts +63 -0
  73. package/src/asyncs/generatorOfStream.ts +1 -0
  74. package/src/asyncs/timeout.ts +1 -1
  75. package/src/browsers/copy.ts +6 -5
  76. package/src/browsers/loaders.ts +6 -11
  77. package/src/crypto/{hex.ts → base.ts} +3 -0
  78. package/src/crypto/hashing.test.ts +12 -8
  79. package/src/crypto/hashing.ts +4 -4
  80. package/src/crypto/randomUUID.ts +6 -4
  81. package/src/i18n/createTranslate.test.ts +155 -0
  82. package/src/i18n/createTranslate.ts +52 -0
  83. package/src/index.ts +22 -9
  84. package/src/io/AbstractEncoding.ts +21 -0
  85. package/src/io/ArrayBuffer.test-d.ts +4 -0
  86. package/src/io/ArrayBuffers.base64.test.ts +61 -0
  87. package/src/io/ArrayBuffers.test.ts +23 -0
  88. package/src/io/ArrayBuffers.ts +272 -0
  89. package/src/io/Buffer.ts +16 -0
  90. package/src/io/isBuffer.test.ts +9 -0
  91. package/src/io/isBuffer.ts +3 -8
  92. package/src/io/isTransferable.test.ts +10 -0
  93. package/src/io/isTransferable.ts +50 -0
  94. package/src/isomorphics/structuredClone.test.ts +14 -0
  95. package/src/isomorphics/structuredClone.ts +88 -0
  96. package/src/langs/classOf.ts +3 -0
  97. package/src/{validations/dequal.test.ts → langs/deepEqual.test.ts} +2 -2
  98. package/src/{validations/dequal.ts → langs/deepEqual.ts} +6 -5
  99. package/src/langs/langs.test.ts +23 -0
  100. package/src/{validations/shallow.ts → langs/shallowEqual.ts} +2 -2
  101. package/src/logging/Logger.ts +6 -0
  102. package/src/logging/createNoopLogger.ts +1 -1
  103. package/src/logging/logger.test.ts +3 -3
  104. package/src/modules/isModule.ts +3 -0
  105. package/src/modules/parseModuleId.test.ts +7 -2
  106. package/src/modules/parseModuleId.ts +15 -9
  107. package/src/objects/get.test-d.ts +51 -0
  108. package/src/objects/get.test.ts +2 -55
  109. package/src/objects/parseObjectPath.ts +4 -3
  110. package/src/objects/set.test.ts +32 -31
  111. package/src/objects/set.ts +2 -2
  112. package/src/server/polyfillBrowser.test.ts +15 -0
  113. package/src/server/polyfillBrowser.ts +17 -0
  114. package/src/server/polyfillCrypto.ts +7 -0
  115. package/src/server/polyfillFetch.ts +28 -0
  116. package/src/server/polyfillJsDom.ts +85 -0
  117. package/src/server.ts +4 -1
  118. package/src/{formats → strings}/formatBytes.ts +1 -1
  119. package/src/strings/renderTemplate.test.ts +1 -0
  120. package/src/strings/renderTemplate.ts +12 -6
  121. package/src/typedoc.ts +2 -0
  122. package/src/validations/isUUID.ts +3 -0
  123. package/tsconfig.json +2 -1
  124. package/lib/asyncs/LazyPromise.js +0 -27
  125. package/lib/asyncs/LazyPromise.js.map +0 -1
  126. package/lib/crypto/hex.js.map +0 -1
  127. package/lib/server/polyfill.js +0 -8
  128. package/lib/server/polyfill.js.map +0 -1
  129. package/lib/shim/urljoin.js +0 -51
  130. package/lib/shim/urljoin.js.map +0 -1
  131. package/lib/validations/dequal.js.map +0 -1
  132. package/lib/validations/shallow.js.map +0 -1
  133. package/src/asyncs/LazyPromise.ts +0 -29
  134. package/src/server/polyfill.ts +0 -5
  135. package/src/shim/urljoin.test.ts +0 -6
  136. package/src/shim/urljoin.ts +0 -75
  137. package/src/types.d.ts +0 -7
@@ -0,0 +1,155 @@
1
+ import test from 'ava';
2
+ import { createTranslate } from './createTranslate';
3
+
4
+ test('exports', (t) => {
5
+ const out = createTranslate();
6
+ t.is(typeof out, 'object', 'returns an object');
7
+ t.is(typeof out.t, 'function', '~> has "t" function');
8
+ t.is(typeof out.dict, 'function', '~> has "set" function');
9
+ t.is(typeof out.locale, 'function', '~> has "locale" function');
10
+ });
11
+
12
+ test('usage', (t) => {
13
+ const ctx = createTranslate({
14
+ en: { hello: 'Hello, {{name}}!' },
15
+ es: { hello: 'Hola {{name}}!' },
16
+ pt: { foo: 'foo {{name}}~!' },
17
+ });
18
+
19
+ t.deepEqual(ctx.dict('en'), { hello: 'Hello, {{name}}!' });
20
+
21
+ t.true(ctx.dict('foobar') === undefined);
22
+
23
+ const foo = ctx.t('hello');
24
+ t.is(foo, '', '~> "" w/o locale');
25
+
26
+ t.is(ctx.locale('en'), 'en', '>>> ctx.locale()');
27
+
28
+ t.is(ctx.locale(), 'en');
29
+
30
+ const bar = ctx.t('hello');
31
+ t.not(bar, '', '(en) found "hello" key');
32
+ t.is(bar, 'Hello, !', '~> interpolations empty if missing param');
33
+
34
+ const baz = ctx.t('hello', { name: 'world' });
35
+ t.is(baz, 'Hello, world!', '~> interpolations successful');
36
+
37
+ const bat = ctx.t('hello', { name: 'world' }, 'es');
38
+ t.not(bat, '', '(es) found "hello" key');
39
+ t.is(bat, 'Hola world!', '~> success');
40
+
41
+ t.is(ctx.t('hello', { name: 'world' }, 'pt'), '', '(pt) did NOT find "hello" key');
42
+
43
+ t.is(ctx.dict('pt', { hello: 'Oí {{name}}!' }), undefined, '>>> ctx.dict()');
44
+
45
+ const quz = ctx.t('hello', { name: 'world' }, 'pt');
46
+ t.not(quz, '', '(pt) found "hello" key');
47
+ t.is(quz, 'Oí world!', '~> success');
48
+
49
+ const qut = ctx.t('foo', { name: 'bar' }, 'pt');
50
+ t.not(qut, '', '(pt) found "foo" key');
51
+ t.is(qut, 'foo bar~!', '~> success');
52
+
53
+ t.is(ctx.locale('es'), 'es', '>>> ctx.locale()');
54
+
55
+ t.is(ctx.locale(), 'es');
56
+ t.is(ctx.locale(''), 'es');
57
+ t.is(ctx.locale(false as any), 'es');
58
+ t.is(ctx.locale(null as any), 'es');
59
+ t.is(ctx.locale(0 as any), 'es');
60
+
61
+ const qux = ctx.t('hello', { name: 'default' });
62
+ t.not(qux, '', '(es) found "hello" key');
63
+ t.is(qux, 'Hola default!', '~> success');
64
+
65
+ t.is(ctx.t('hello', { name: 'world' }, 'de'), '', '(de) did NOT find "hello" key');
66
+
67
+ t.is(ctx.dict('de', { hello: 'Hallo {{name}}!' }), undefined, '>>> ctx.dict(de)');
68
+
69
+ const qar = ctx.t('hello', { name: 'world' }, 'de');
70
+ t.not(qar, '', '(de) found "hello" key');
71
+ t.is(qar, 'Hallo world!', '~> success');
72
+ });
73
+
74
+ test('functional', (t) => {
75
+ const ctx = createTranslate({
76
+ en: {
77
+ hello(value: any) {
78
+ return `hello ${value || 'stranger'}~!`;
79
+ },
80
+ },
81
+ });
82
+
83
+ ctx.locale('en');
84
+
85
+ const foo = ctx.t('hello');
86
+ t.is(foo, 'hello stranger~!', '~> called function w/o param');
87
+
88
+ const bar = ctx.t('hello', 'world' as any);
89
+ t.is(bar, 'hello world~!', '~> called function w/ param (string)');
90
+
91
+ const baz = ctx.t('hello', [1, 2, 3]);
92
+ t.is(baz, 'hello 1,2,3~!', '~> called function w/ param (array)');
93
+ });
94
+
95
+ test('nested', (t) => {
96
+ const ctx = createTranslate({
97
+ en: {
98
+ fruits: {
99
+ apple: 'apple',
100
+ orange: 'orange',
101
+ grape: 'grape',
102
+ },
103
+ },
104
+ es: {
105
+ fruits: {
106
+ apple: 'manzana',
107
+ orange: 'naranja',
108
+ grape: 'uva',
109
+ },
110
+ },
111
+ });
112
+
113
+ ctx.locale('en');
114
+ t.is(ctx.t('fruits.apple'), 'apple', '(en) fruits.apple');
115
+ t.is(ctx.t('fruits.orange'), 'orange', '(en) fruits.orange');
116
+ t.is(ctx.t(['fruits', 'grape']), 'grape', '(en) ["fruits","grape"]');
117
+ t.is(ctx.t('fruits.404'), '', '(en) fruits.404 ~> ""');
118
+ t.is(ctx.t('error.404'), '', '(en) error.404 ~> ""');
119
+
120
+ ctx.locale('es');
121
+ t.is(ctx.t('fruits.apple'), 'manzana', '(es) fruits.apple');
122
+ t.is(ctx.t('fruits.orange'), 'naranja', '(es) fruits.orange');
123
+ t.is(ctx.t(['fruits', 'grape']), 'uva', '(es) ["fruits","grape"]');
124
+ t.is(ctx.t('fruits.404'), '', '(es) fruits.404 ~> ""');
125
+ t.is(ctx.t('error.404'), '', '(es) error.404 ~> ""');
126
+ });
127
+
128
+ test('arrays', (t) => {
129
+ const ctx = createTranslate({
130
+ en: {
131
+ foo: '{{0}} + {{1}} = {{2}}',
132
+ bar: [
133
+ {
134
+ baz: 'roses are {{colors.0}}, violets are {{colors.1}}',
135
+ },
136
+ ],
137
+ },
138
+ });
139
+
140
+ ctx.locale('en');
141
+
142
+ t.is(ctx.t('foo', [1, 2, 3]), '1 + 2 = 3', '~> foo');
143
+
144
+ t.is(ctx.t('bar.0.baz', { colors: ['red', 'blue'] }), 'roses are red, violets are blue', '~> bar.0.baz');
145
+ });
146
+
147
+ test('invalid value', (t) => {
148
+ const ctx = createTranslate({
149
+ en: {
150
+ foo: ['bar'],
151
+ },
152
+ });
153
+
154
+ t.deepEqual(ctx.t('foo', null as any, 'en'), ['bar']);
155
+ });
@@ -0,0 +1,52 @@
1
+ import { get } from '../objects/get';
2
+ import { ObjectPathLike } from '../objects/parseObjectPath';
3
+ import { renderTemplate } from '../strings/renderTemplate';
4
+
5
+ export interface Translate<T extends object> {
6
+ /** Get/Set the language key */
7
+ locale(lang?: string): string;
8
+
9
+ /** Define the dict of translations for a language */
10
+ dict(lang: string, dict: T): void;
11
+
12
+ /** Get the dict of translations for a language */
13
+ dict(lang: string): T;
14
+
15
+ /** Retrieve a translation segment for the current language */
16
+ t<X extends Record<string, any> | any[]>(key: ObjectPathLike, params?: X, lang?: string): string;
17
+ }
18
+
19
+ export function createTranslate<T extends object>(obj?: Record<string, T>): Translate<T> {
20
+ let locale = '';
21
+ const tree = obj || {};
22
+
23
+ return {
24
+ locale(lang) {
25
+ return (locale = lang || locale);
26
+ },
27
+
28
+ dict: ((lang, dict?) => {
29
+ if (dict) {
30
+ tree[lang] = Object.assign(tree[lang] || {}, dict);
31
+ return;
32
+ }
33
+ return tree[lang];
34
+ }) as Translate<T>['dict'],
35
+
36
+ t(key, params, lang) {
37
+ const val = get(tree[lang || locale], key, '') as any;
38
+ if (process.env.NODE_ENV === 'development') {
39
+ if (val == null) {
40
+ return console.error(
41
+ `[Translate] Missing the "${[].concat(key as any).join('.')}" key within the "${
42
+ lang || locale
43
+ }" dictionary`,
44
+ );
45
+ }
46
+ }
47
+ if (typeof val === 'function') return val(params);
48
+ if (typeof val === 'string') return renderTemplate(val, params, 'common');
49
+ return val;
50
+ },
51
+ };
52
+ }
package/src/index.ts CHANGED
@@ -13,7 +13,7 @@ export { set } from './objects/set';
13
13
  export { parseObjectPath } from './objects/parseObjectPath';
14
14
 
15
15
  // async
16
- export { createLazyPromise, type LazyPromise } from './asyncs/LazyPromise';
16
+ export { createLazyPromise, type LazyPromise } from './asyncs/createLazyPromise';
17
17
  export { setAsyncInterval, clearAsyncInterval } from './asyncs/AsyncInterval';
18
18
  export { type MaybePromise } from './asyncs/MaybePromise';
19
19
 
@@ -26,12 +26,15 @@ export { isPromise } from './asyncs/isPromise';
26
26
  export { isClass } from './validations/isClass';
27
27
  export { isDefined } from './validations/isDefined';
28
28
  export { isEmptyObject } from './validations/isEmptyObject';
29
- export { shallow } from './validations/shallow';
30
- export { dequal } from './validations/dequal';
29
+ export { shallowEqual } from './langs/shallowEqual';
30
+ export { deepEqual } from './langs/deepEqual';
31
+ export { isUUID } from './validations/isUUID';
32
+
33
+ export { classOf } from './langs/classOf';
31
34
 
32
35
  // modules
33
36
  export { parseModuleId, type ParsedModuleId } from './modules/parseModuleId';
34
- export { isModule } from './modules/isModule';
37
+ export { isModule, type Module } from './modules/isModule';
35
38
 
36
39
  // logging
37
40
  export { type Logger, type LogLevel } from './logging/Logger';
@@ -42,21 +45,31 @@ export { createChildLogger } from './logging/createChildLogger';
42
45
  // strings
43
46
  export { pascalCase, camelCase } from './strings/camelCase';
44
47
  export { renderTemplate } from './strings/renderTemplate';
48
+ export { formatBytes } from './strings/formatBytes';
45
49
 
46
- export { createRandom } from './maths/random';
50
+ // i18n
51
+ export { createTranslate } from './i18n/createTranslate';
52
+
53
+ // io
47
54
  export { isBuffer } from './io/isBuffer';
55
+ export { isTransferable } from './io/isTransferable';
56
+ export { ArrayBuffers } from './io/ArrayBuffers';
57
+ export type { AbstractEncoding } from './io/AbstractEncoding';
48
58
 
59
+ // browser
49
60
  export { copy } from './browsers/copy';
50
61
  export { download } from './browsers/download';
51
62
  export { loadScripts, loadStyles } from './browsers/loaders';
52
63
  export { getFileFromDataTransfer } from './browsers/getFileFromDataTransfer';
53
64
 
65
+ // polyfills
54
66
  export { getGlobalThis } from './isomorphics/getGlobalThis';
55
-
56
- export { formatBytes } from './formats/formatBytes';
57
- export { urljoin } from './shim/urljoin';
67
+ export { structuredClone } from './isomorphics/structuredClone';
58
68
 
59
69
  // crypto
60
70
  export { randomUUID } from './crypto/randomUUID';
61
71
  export { sha1, sha256, sha384, sha512 } from './crypto/hashing';
62
- export { hex } from './crypto/hex';
72
+ export { hex } from './crypto/base';
73
+
74
+ // misc
75
+ export { createRandom } from './maths/random';
@@ -0,0 +1,21 @@
1
+ /**
2
+ * AbstractEncoding contract
3
+ *
4
+ * @see https://github.com/mafintosh/abstract-encoding
5
+ */
6
+ export interface AbstractEncoding<T> {
7
+ /**
8
+ * encode a value to a buffer
9
+ */
10
+ encode(data: T, buffer?: ArrayBuffer, offset?: number): BufferSource;
11
+
12
+ /**
13
+ * decode data from buffer
14
+ */
15
+ decode(buffer: BufferSource, start?: number, end?: number): T;
16
+
17
+ /**
18
+ * byteLength of data
19
+ */
20
+ byteLength?: (data: T) => number;
21
+ }
@@ -0,0 +1,4 @@
1
+ import { expectType } from 'tsd';
2
+ import { ArrayBuffers } from './ArrayBuffers';
3
+
4
+ expectType<Buffer>(ArrayBuffers.asView(Buffer, new Uint8Array()));
@@ -0,0 +1,61 @@
1
+ import test from 'ava';
2
+ import { ArrayBuffers } from './ArrayBuffers';
3
+
4
+ test.before(() => {
5
+ ArrayBuffers._allowedBuffer = false;
6
+ });
7
+
8
+ test('base64: ignore whitespace', function (t) {
9
+ const text = '\n YW9ldQ== ';
10
+ const buf = ArrayBuffers.from(text, 'base64');
11
+ t.is(ArrayBuffers.toString(buf), 'aoeu');
12
+ });
13
+
14
+ test('base64: strings without padding', function (t) {
15
+ t.is(ArrayBuffers.toString(ArrayBuffers.from('YW9ldQ', 'base64')), 'aoeu');
16
+ });
17
+
18
+ test('base64: newline in utf8 -- should not be an issue', function (t) {
19
+ t.is(
20
+ ArrayBuffers.toString(
21
+ ArrayBuffers.from('LS0tCnRpdGxlOiBUaHJlZSBkYXNoZXMgbWFya3MgdGhlIHNwb3QKdGFnczoK', 'base64'),
22
+ 'utf8',
23
+ ),
24
+ '---\ntitle: Three dashes marks the spot\ntags:\n',
25
+ );
26
+ });
27
+
28
+ test('base64: newline in base64 -- should get stripped', function (t) {
29
+ t.is(
30
+ ArrayBuffers.toString(
31
+ ArrayBuffers.from(
32
+ 'LS0tCnRpdGxlOiBUaHJlZSBkYXNoZXMgbWFya3MgdGhlIHNwb3QKdGFnczoK\nICAtIHlhbWwKICAtIGZyb250LW1hdHRlcgogIC0gZGFzaGVzCmV4cGFuZWQt',
33
+ 'base64',
34
+ ),
35
+ 'utf8',
36
+ ),
37
+ '---\ntitle: Three dashes marks the spot\ntags:\n - yaml\n - front-matter\n - dashes\nexpaned-',
38
+ );
39
+ });
40
+
41
+ test('base64: tab characters in base64 - should get stripped', function (t) {
42
+ t.is(
43
+ ArrayBuffers.toString(
44
+ ArrayBuffers.from(
45
+ 'LS0tCnRpdGxlOiBUaHJlZSBkYXNoZXMgbWFya3MgdGhlIHNwb3QKdGFnczoK\t\t\t\tICAtIHlhbWwKICAtIGZyb250LW1hdHRlcgogIC0gZGFzaGVzCmV4cGFuZWQt',
46
+ 'base64',
47
+ ),
48
+ 'utf8',
49
+ ),
50
+ '---\ntitle: Three dashes marks the spot\ntags:\n - yaml\n - front-matter\n - dashes\nexpaned-',
51
+ );
52
+ });
53
+
54
+ test('base64: invalid non-alphanumeric characters -- should be stripped', function (t) {
55
+ t.is(ArrayBuffers.toString(ArrayBuffers.from('!"#$%&\'()*,.:;<=>?@[\\]^`{|}~', 'base64'), 'utf8'), '');
56
+ });
57
+
58
+ test('base64: high byte', function (t) {
59
+ const highByte = ArrayBuffers.from([128]);
60
+ t.deepEqual(ArrayBuffers.alloc(1, ArrayBuffers.toString(highByte, 'base64'), 'base64'), highByte);
61
+ });
@@ -0,0 +1,23 @@
1
+ import test from 'ava';
2
+ import { ArrayBuffers } from './ArrayBuffers';
3
+
4
+ test('concat', (t) => {
5
+ const check = (Type: any = Uint8Array, va = [1, 2, 3], vb = [4, 5, 6]) => {
6
+ const a = new Type(va);
7
+ const b = new Type(vb);
8
+ t.deepEqual(ArrayBuffers.concat([a, b]), new Type([...va, ...vb]).buffer, `should concat ${Type}`);
9
+ };
10
+ check(Uint8Array);
11
+ check(Uint16Array, [0xff, 0xffff]);
12
+ check(Uint32Array, [0xfffffff]);
13
+ });
14
+
15
+ test('asView for Buffer', (t) => {
16
+ const buf = Buffer.from([1, 2, 3, 4, 5]);
17
+ t.is(ArrayBuffers.asView(Buffer, buf), buf);
18
+ const uint8 = ArrayBuffers.asView(Uint8Array, buf);
19
+ t.is(ArrayBuffers.asView(Buffer, uint8).buffer, buf.buffer);
20
+
21
+ t.deepEqual(uint8, buf);
22
+ t.deepEqual(ArrayBuffers.asView(Buffer, uint8), buf);
23
+ });
@@ -0,0 +1,272 @@
1
+ import { classOf } from '../langs/classOf';
2
+ import { isBuffer } from './isBuffer';
3
+
4
+ /**
5
+ * Various utils to work with {@link ArrayBuffer}
6
+ */
7
+ export interface ArrayBuffers {
8
+ /**
9
+ * isArrayBuffer check if the given value is an {@link ArrayBuffer}
10
+ */
11
+ isArrayBuffer(v: any): v is ArrayBuffer;
12
+
13
+ /**
14
+ * slice the given view with the given offset and length, will handle the {@link Buffer} as well
15
+ *
16
+ * @see {@link https://nodejs.org/api/buffer.html#bufslicestart-end Buffer.slice}
17
+ */
18
+ slice<T extends ArrayBufferView>(o: T, start?: number, end?: number): T;
19
+
20
+ /**
21
+ * asView convert the given value to given {@link TypedArray} view
22
+ *
23
+ * TypedArray can be {@link Buffer}, will avoid copy
24
+ */
25
+ asView<C extends ArrayBufferViewConstructor<unknown>>(
26
+ TypedArray: C,
27
+ v: BufferSource,
28
+ byteOffset?: number,
29
+ byteLength?: number,
30
+ ): InstanceType<C>;
31
+
32
+ /**
33
+ * toString convert the given {@link BufferSource} to string
34
+ */
35
+ toString(v: BufferSource | string, encoding?: ToStringEncoding): string;
36
+
37
+ /**
38
+ * Returns true if encoding is the name of a supported character encoding, or false otherwise.
39
+ */
40
+ isEncoding(v?: string): v is ToStringEncoding;
41
+
42
+ toJSON<T = any>(v: BufferSource | string, reviver?: (this: any, key: string, value: any) => any): T;
43
+
44
+ /**
45
+ * from convert the given value to {@link ArrayBuffer}
46
+ */
47
+ from(v: string | BufferSource, encoding?: ToStringEncoding): ArrayBuffer;
48
+
49
+ /**
50
+ * concat the given {@link BufferSource} to a new {@link ArrayBuffer}
51
+ */
52
+ concat(buffers: Array<BufferSource>, result?: ArrayBuffer, offset?: number): ArrayBuffer;
53
+ }
54
+
55
+ type ToStringEncoding =
56
+ | 'ascii'
57
+ | 'utf16le'
58
+ // | 'utf-16le'
59
+ | 'ucs2'
60
+ | 'ucs-2'
61
+ | 'base64'
62
+ | 'base64url'
63
+ | 'latin1'
64
+ | 'binary'
65
+ | 'utf8'
66
+ | 'utf-8'
67
+ | 'hex';
68
+
69
+ // eslint-disable-next-line @typescript-eslint/no-redeclare
70
+ export const ArrayBuffers = {
71
+ _allowedBuffer: true,
72
+ isArrayBuffer: (v: any): v is ArrayBuffer => {
73
+ return v instanceof ArrayBuffer;
74
+ },
75
+ slice: (o: TypedArray, start?: number, end?: number) => {
76
+ // NodeJS Buffer slice is not the same as UInt8Array slice
77
+ // https://nodejs.org/api/buffer.html#bufslicestart-end
78
+ if (isBuffer(o)) {
79
+ return Uint8Array.prototype.slice.call(o, start, end);
80
+ }
81
+ return o.slice(start, end);
82
+ },
83
+ asView: <C extends ArrayBufferViewConstructor<unknown>, I extends InstanceType<C>>(
84
+ TypedArray: C,
85
+ v: BufferSource,
86
+ byteOffset?: number,
87
+ byteLength?: number,
88
+ ): I => {
89
+ if (v instanceof TypedArray && (byteOffset ?? 0) === 0 && byteLength === undefined) {
90
+ return v as I;
91
+ }
92
+ if (ArrayBuffer.isView(v) || isBuffer(v)) {
93
+ if (ArrayBuffers._allowedBuffer && typeof Buffer !== 'undefined' && (TypedArray as any) === Buffer) {
94
+ // new Buffer() is deprecated
95
+ return Buffer.from(v.buffer, byteOffset, byteLength) as I;
96
+ }
97
+ return new TypedArray(v.buffer, v.byteOffset + (byteOffset ?? 0), byteLength ?? v.byteLength) as I;
98
+ }
99
+ return new TypedArray(v, byteOffset, byteLength) as I;
100
+ },
101
+ toString: (buf: BufferSource | string, encoding: ToStringEncoding = 'utf8') => {
102
+ // 'ascii' 'utf16le' | 'ucs2' | 'ucs-2' | 'base64' | 'base64url' | 'latin1' | 'binary' | 'hex'
103
+ if (typeof buf === 'string') {
104
+ return buf;
105
+ }
106
+ if (typeof Buffer !== 'undefined' && ArrayBuffers._allowedBuffer) {
107
+ return Buffer.from(ArrayBuffers.asView(Uint8Array, buf)).toString(encoding);
108
+ }
109
+ // reference
110
+ // https://github.com/feross/buffer/blob/master/index.js
111
+ switch (encoding) {
112
+ case 'hex': {
113
+ const view: Uint8Array = ArrayBuffers.asView(Uint8Array, buf);
114
+ return [...view].map((b) => hexLookupTable[b]).join('');
115
+ }
116
+ case 'base64': {
117
+ const view: Uint8Array = ArrayBuffers.asView(Uint8Array, buf);
118
+ return btoa(String.fromCharCode(...view));
119
+ }
120
+ case 'utf8':
121
+ // falls through
122
+ case 'utf-8':
123
+ return new TextDecoder().decode(buf as any);
124
+ case 'ascii': {
125
+ const view: Uint8Array = ArrayBuffers.asView(Uint8Array, buf);
126
+ return String.fromCharCode(...view.map((v) => v & 0x7f));
127
+ }
128
+ case 'latin1':
129
+ // falls through
130
+ case 'binary': {
131
+ const view: Uint8Array = ArrayBuffers.asView(Uint8Array, buf);
132
+ return String.fromCharCode(...view);
133
+ }
134
+ case 'ucs2':
135
+ // falls through
136
+ case 'ucs-2':
137
+ // case 'utf-16le':
138
+ // falls through
139
+ case 'utf16le': {
140
+ const view: Uint8Array = ArrayBuffers.asView(Uint8Array, buf);
141
+ let res = '';
142
+ // If length is odd, the last 8 bits must be ignored (same as node.js)
143
+ for (let i = 0; i < view.length - 1; i += 2) {
144
+ res += String.fromCharCode(view[i] + view[i + 1] * 256);
145
+ }
146
+ return res;
147
+ }
148
+ default:
149
+ throw new Error(`[ArrayBuffers.toString] Unknown encoding: ${encoding}`);
150
+ }
151
+ },
152
+ toJSON: (v: BufferSource | string, reviver?: (this: any, key: string, value: any) => any) => {
153
+ return JSON.parse(ArrayBuffers.toString(v), reviver);
154
+ },
155
+ alloc: (size: number, fill?: string | number, encoding?: ToStringEncoding) => {
156
+ if (fill !== undefined) {
157
+ if (typeof fill === 'number') {
158
+ return new Uint8Array(size).fill(fill);
159
+ }
160
+ // as cast
161
+ // https://stackoverflow.com/questions/73994091
162
+ return ArrayBuffers.asView(Uint8Array, ArrayBuffers.from(fill, encoding)).slice(0, size);
163
+ }
164
+ return new ArrayBuffer(size);
165
+ },
166
+ from: (
167
+ v: string | BufferSource | ArrayLike<number> | Iterable<number>,
168
+ encoding: ToStringEncoding = 'utf8',
169
+ ): BufferSource => {
170
+ if (!v) {
171
+ return new ArrayBuffer(0);
172
+ }
173
+ if (typeof v === 'string') {
174
+ if (typeof Buffer !== 'undefined' && ArrayBuffers._allowedBuffer) {
175
+ return Buffer.from(v, encoding);
176
+ }
177
+
178
+ switch (encoding) {
179
+ case 'utf-8':
180
+ // falls through
181
+ case 'utf8':
182
+ return new TextEncoder().encode(v).buffer;
183
+ case 'base64':
184
+ // replaceAll
185
+ return Uint8Array.from(atob(v.replace(/[^0-9a-zA-Z=+/_ \r\n]/g, '')), (c) => c.charCodeAt(0));
186
+ default:
187
+ throw new Error(`[ArrayBuffers.from] Unknown encoding: ${encoding}`);
188
+ }
189
+ }
190
+ if (v instanceof ArrayBuffer) {
191
+ return v;
192
+ }
193
+ // lost length
194
+ if (ArrayBuffer.isView(v) || isBuffer(v)) {
195
+ if (v.byteOffset !== 0) {
196
+ // return v.buffer.slice(v.byteOffset, v.byteOffset + v.byteLength)
197
+ throw new Error('ArrayBuffers.from do not support view with offset');
198
+ }
199
+ return v.buffer;
200
+ }
201
+ if (Array.isArray(v)) {
202
+ return new Uint8Array(v);
203
+ }
204
+ const type = classOf(v);
205
+ throw new TypeError(`ArrayBuffers.from unsupported type ${type}`);
206
+ },
207
+ isEncoding: (encoding?: string) => {
208
+ switch (String(encoding).toLowerCase()) {
209
+ case 'hex':
210
+ case 'utf8':
211
+ case 'utf-8':
212
+ case 'ascii':
213
+ case 'latin1':
214
+ case 'binary':
215
+ case 'base64':
216
+ case 'ucs2':
217
+ case 'ucs-2':
218
+ case 'utf16le':
219
+ // case 'utf-16le':
220
+ return true;
221
+ default:
222
+ return false;
223
+ }
224
+ },
225
+ concat: (buffers: Array<BufferSource>, result?: ArrayBuffer, offset = 0) => {
226
+ // https://stackoverflow.com/questions/10786128/appending-arraybuffers
227
+
228
+ const length = buffers.reduce((a, b) => a + b.byteLength, 0);
229
+ const r = result ? new Uint8Array(result) : new Uint8Array(length);
230
+ for (const buffer of buffers) {
231
+ if (!buffer || !buffer.byteLength) continue;
232
+ let n: Uint8Array;
233
+ if (buffer instanceof ArrayBuffer) {
234
+ n = new Uint8Array(buffer);
235
+ } else if (ArrayBuffer.isView(buffer)) {
236
+ n = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
237
+ } else {
238
+ throw new Error(`ArrayBuffers.concat unsupported type ${classOf(buffer)}`);
239
+ }
240
+ r.set(n, offset);
241
+ offset += buffer.byteLength;
242
+ }
243
+ return r.buffer;
244
+ },
245
+ };
246
+
247
+ export type TypedArray =
248
+ | Uint8Array
249
+ | Uint8ClampedArray
250
+ | Uint16Array
251
+ | Uint32Array
252
+ | Int8Array
253
+ | Int16Array
254
+ | Int32Array
255
+ | BigUint64Array
256
+ | BigInt64Array
257
+ | Float32Array
258
+ | Float64Array;
259
+
260
+ type ArrayBufferViewConstructor<T> = new (buffer: ArrayBufferLike, byteOffset?: number, byteLength?: number) => T;
261
+
262
+ const hexLookupTable = (function () {
263
+ const alphabet = '0123456789abcdef';
264
+ const table = new Array(256);
265
+ for (let i = 0; i < 16; ++i) {
266
+ const i16 = i * 16;
267
+ for (let j = 0; j < 16; ++j) {
268
+ table[i16 + j] = alphabet[i] + alphabet[j];
269
+ }
270
+ }
271
+ return table;
272
+ })();
@@ -0,0 +1,16 @@
1
+ import { ArrayBuffers } from './ArrayBuffers';
2
+
3
+ export class Buffer extends Uint8Array {
4
+ // constructor(buffer: ArrayBufferLike, byteOffset?: number, length?: number) {
5
+ // super(buffer, byteOffset, length);
6
+ // }
7
+
8
+ static from(array: string | BufferSource | ArrayLike<number> | Iterable<number>, arg2?: any): Buffer {
9
+ // todo mapfn
10
+ return new Buffer(ArrayBuffers.from(array, arg2) as ArrayBuffer);
11
+ }
12
+
13
+ toString(encoding?: string): string {
14
+ return ArrayBuffers.toString(this, encoding as any);
15
+ }
16
+ }
@@ -0,0 +1,9 @@
1
+ import test from 'ava';
2
+ import { Buffer } from 'node:buffer';
3
+ import { classOf } from '../langs/classOf';
4
+ import { isBuffer } from './isBuffer';
5
+
6
+ test('isBuffer', (t) => {
7
+ t.true(isBuffer(Buffer.from('')));
8
+ t.is(classOf(Buffer), 'Function');
9
+ });