@atproto/common-web 0.2.2 → 0.2.4-next.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.
Files changed (67) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE.txt +7 -0
  3. package/README.md +6 -1
  4. package/dist/arrays.d.ts +2 -0
  5. package/dist/arrays.d.ts.map +1 -0
  6. package/dist/arrays.js +22 -0
  7. package/dist/arrays.js.map +1 -0
  8. package/dist/async.d.ts +7 -1
  9. package/dist/async.d.ts.map +1 -0
  10. package/dist/async.js +187 -0
  11. package/dist/async.js.map +1 -0
  12. package/dist/check.d.ts +1 -0
  13. package/dist/check.d.ts.map +1 -0
  14. package/dist/check.js +16 -0
  15. package/dist/check.js.map +1 -0
  16. package/dist/did-doc.d.ts +3 -1
  17. package/dist/did-doc.d.ts.map +1 -0
  18. package/dist/did-doc.js +138 -0
  19. package/dist/did-doc.js.map +1 -0
  20. package/dist/index.d.ts +2 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +39 -14188
  23. package/dist/index.js.map +1 -7
  24. package/dist/ipld.d.ts +3 -2
  25. package/dist/ipld.d.ts.map +1 -0
  26. package/dist/ipld.js +119 -0
  27. package/dist/ipld.js.map +1 -0
  28. package/dist/retry.d.ts +8 -0
  29. package/dist/retry.d.ts.map +1 -0
  30. package/dist/retry.js +46 -0
  31. package/dist/retry.js.map +1 -0
  32. package/dist/strings.d.ts +2 -1
  33. package/dist/strings.d.ts.map +1 -0
  34. package/dist/strings.js +74 -0
  35. package/dist/strings.js.map +1 -0
  36. package/dist/tid.d.ts +1 -0
  37. package/dist/tid.d.ts.map +1 -0
  38. package/dist/tid.js +100 -0
  39. package/dist/tid.js.map +1 -0
  40. package/dist/times.d.ts +2 -0
  41. package/dist/times.d.ts.map +1 -0
  42. package/dist/times.js +19 -0
  43. package/dist/times.js.map +1 -0
  44. package/dist/types.d.ts +3 -2
  45. package/dist/types.d.ts.map +1 -0
  46. package/dist/types.js +42 -0
  47. package/dist/types.js.map +1 -0
  48. package/dist/util.d.ts +3 -2
  49. package/dist/util.d.ts.map +1 -0
  50. package/dist/util.js +114 -0
  51. package/dist/util.js.map +1 -0
  52. package/jest.config.js +3 -3
  53. package/package.json +7 -6
  54. package/src/arrays.ts +7 -0
  55. package/src/async.ts +27 -0
  56. package/src/did-doc.ts +5 -0
  57. package/src/index.ts +1 -0
  58. package/src/retry.ts +52 -0
  59. package/src/times.ts +7 -0
  60. package/src/util.ts +2 -2
  61. package/tests/retry.test.ts +93 -0
  62. package/tsconfig.build.json +6 -2
  63. package/tsconfig.json +5 -7
  64. package/tsconfig.tests.json +7 -0
  65. package/LICENSE +0 -21
  66. package/babel.config.js +0 -1
  67. package/build.js +0 -15
package/dist/util.js ADDED
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseIntWithFallback = exports.dedupeStrs = exports.range = exports.chunkArray = exports.errHasMsg = exports.isErrnoException = exports.asyncFilter = exports.s32decode = exports.s32encode = exports.streamToBuffer = exports.flattenUint8Arrays = exports.bailableWait = exports.wait = exports.jitter = exports.noUndefinedVals = void 0;
4
+ const noUndefinedVals = (obj) => {
5
+ Object.keys(obj).forEach((k) => {
6
+ if (obj[k] === undefined) {
7
+ delete obj[k];
8
+ }
9
+ });
10
+ return obj;
11
+ };
12
+ exports.noUndefinedVals = noUndefinedVals;
13
+ const jitter = (maxMs) => {
14
+ return Math.round((Math.random() - 0.5) * maxMs * 2);
15
+ };
16
+ exports.jitter = jitter;
17
+ const wait = (ms) => {
18
+ return new Promise((res) => setTimeout(res, ms));
19
+ };
20
+ exports.wait = wait;
21
+ const bailableWait = (ms) => {
22
+ let bail;
23
+ const waitPromise = new Promise((res) => {
24
+ const timeout = setTimeout(res, ms);
25
+ bail = () => {
26
+ clearTimeout(timeout);
27
+ res();
28
+ };
29
+ });
30
+ return { bail, wait: () => waitPromise };
31
+ };
32
+ exports.bailableWait = bailableWait;
33
+ const flattenUint8Arrays = (arrs) => {
34
+ const length = arrs.reduce((acc, cur) => {
35
+ return acc + cur.length;
36
+ }, 0);
37
+ const flattened = new Uint8Array(length);
38
+ let offset = 0;
39
+ arrs.forEach((arr) => {
40
+ flattened.set(arr, offset);
41
+ offset += arr.length;
42
+ });
43
+ return flattened;
44
+ };
45
+ exports.flattenUint8Arrays = flattenUint8Arrays;
46
+ const streamToBuffer = async (stream) => {
47
+ const arrays = [];
48
+ for await (const chunk of stream) {
49
+ arrays.push(chunk);
50
+ }
51
+ return (0, exports.flattenUint8Arrays)(arrays);
52
+ };
53
+ exports.streamToBuffer = streamToBuffer;
54
+ const S32_CHAR = '234567abcdefghijklmnopqrstuvwxyz';
55
+ const s32encode = (i) => {
56
+ let s = '';
57
+ while (i) {
58
+ const c = i % 32;
59
+ i = Math.floor(i / 32);
60
+ s = S32_CHAR.charAt(c) + s;
61
+ }
62
+ return s;
63
+ };
64
+ exports.s32encode = s32encode;
65
+ const s32decode = (s) => {
66
+ let i = 0;
67
+ for (const c of s) {
68
+ i = i * 32 + S32_CHAR.indexOf(c);
69
+ }
70
+ return i;
71
+ };
72
+ exports.s32decode = s32decode;
73
+ const asyncFilter = async (arr, fn) => {
74
+ const results = await Promise.all(arr.map((t) => fn(t)));
75
+ return arr.filter((_, i) => results[i]);
76
+ };
77
+ exports.asyncFilter = asyncFilter;
78
+ const isErrnoException = (err) => {
79
+ return !!err && err['code'];
80
+ };
81
+ exports.isErrnoException = isErrnoException;
82
+ const errHasMsg = (err, msg) => {
83
+ return !!err && typeof err === 'object' && err['message'] === msg;
84
+ };
85
+ exports.errHasMsg = errHasMsg;
86
+ const chunkArray = (arr, chunkSize) => {
87
+ return arr.reduce((acc, cur, i) => {
88
+ const chunkI = Math.floor(i / chunkSize);
89
+ if (!acc[chunkI]) {
90
+ acc[chunkI] = [];
91
+ }
92
+ acc[chunkI].push(cur);
93
+ return acc;
94
+ }, []);
95
+ };
96
+ exports.chunkArray = chunkArray;
97
+ const range = (num) => {
98
+ const nums = [];
99
+ for (let i = 0; i < num; i++) {
100
+ nums.push(i);
101
+ }
102
+ return nums;
103
+ };
104
+ exports.range = range;
105
+ const dedupeStrs = (strs) => {
106
+ return [...new Set(strs)];
107
+ };
108
+ exports.dedupeStrs = dedupeStrs;
109
+ const parseIntWithFallback = (value, fallback) => {
110
+ const parsed = parseInt(value || '', 10);
111
+ return isNaN(parsed) ? fallback : parsed;
112
+ };
113
+ exports.parseIntWithFallback = parseIntWithFallback;
114
+ //# sourceMappingURL=util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"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"}
package/jest.config.js CHANGED
@@ -1,6 +1,6 @@
1
- const base = require('../../jest.config.base.js')
2
-
1
+ /** @type {import('jest').Config} */
3
2
  module.exports = {
4
- ...base,
5
3
  displayName: 'Common Web',
4
+ transform: { '^.+\\.(t|j)s$': '@swc/jest' },
5
+ setupFiles: ['<rootDir>/../../jest.setup.ts'],
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/common-web",
3
- "version": "0.2.2",
3
+ "version": "0.2.4-next.0",
4
4
  "license": "MIT",
5
5
  "description": "Shared web-platform-friendly code for atproto libraries",
6
6
  "keywords": [
@@ -13,17 +13,18 @@
13
13
  "directory": "packages/common-web"
14
14
  },
15
15
  "main": "dist/index.js",
16
+ "types": "dist/index.d.ts",
16
17
  "dependencies": {
17
18
  "graphemer": "^1.4.0",
18
19
  "multiformats": "^9.9.0",
19
20
  "uint8arrays": "3.0.0",
20
21
  "zod": "^3.21.4"
21
22
  },
23
+ "devDependencies": {
24
+ "jest": "^28.1.2"
25
+ },
22
26
  "scripts": {
23
27
  "test": "jest",
24
- "build": "node ./build.js",
25
- "postbuild": "tsc --build tsconfig.build.json",
26
- "update-main-to-dist": "node ../../update-main-to-dist.js packages/common-web"
27
- },
28
- "types": "dist/index.d.ts"
28
+ "build": "tsc --build tsconfig.build.json"
29
+ }
29
30
  }
package/src/arrays.ts CHANGED
@@ -1,3 +1,10 @@
1
+ export const keyBy = <T>(arr: T[], key: string): Record<string, T> => {
2
+ return arr.reduce((acc, cur) => {
3
+ acc[cur[key]] = cur
4
+ return acc
5
+ }, {} as Record<string, T>)
6
+ }
7
+
1
8
  export const mapDefined = <T, S>(
2
9
  arr: T[],
3
10
  fn: (obj: T) => S | undefined,
package/src/async.ts CHANGED
@@ -72,6 +72,8 @@ export class AsyncBuffer<T> {
72
72
  private buffer: T[] = []
73
73
  private promise: Promise<void>
74
74
  private resolve: () => void
75
+ private closed = false
76
+ private toThrow: unknown | undefined
75
77
 
76
78
  constructor(public maxSize?: number) {
77
79
  // Initializing to satisfy types/build, immediately reset by resetPromise()
@@ -88,6 +90,10 @@ export class AsyncBuffer<T> {
88
90
  return this.buffer.length
89
91
  }
90
92
 
93
+ get isClosed(): boolean {
94
+ return this.closed
95
+ }
96
+
91
97
  resetPromise() {
92
98
  this.promise = new Promise<void>((r) => (this.resolve = r))
93
99
  }
@@ -104,7 +110,17 @@ export class AsyncBuffer<T> {
104
110
 
105
111
  async *events(): AsyncGenerator<T> {
106
112
  while (true) {
113
+ if (this.closed && this.buffer.length === 0) {
114
+ if (this.toThrow) {
115
+ throw this.toThrow
116
+ } else {
117
+ return
118
+ }
119
+ }
107
120
  await this.promise
121
+ if (this.toThrow) {
122
+ throw this.toThrow
123
+ }
108
124
  if (this.maxSize && this.size > this.maxSize) {
109
125
  throw new AsyncBufferFullError(this.maxSize)
110
126
  }
@@ -117,6 +133,17 @@ export class AsyncBuffer<T> {
117
133
  }
118
134
  }
119
135
  }
136
+
137
+ throw(err: unknown) {
138
+ this.toThrow = err
139
+ this.closed = true
140
+ this.resolve()
141
+ }
142
+
143
+ close() {
144
+ this.closed = true
145
+ this.resolve()
146
+ }
120
147
  }
121
148
 
122
149
  export class AsyncBufferFullError extends Error {
package/src/did-doc.ts CHANGED
@@ -44,6 +44,11 @@ export const getSigningKey = (
44
44
  publicKeyMultibase: found.publicKeyMultibase,
45
45
  }
46
46
  }
47
+ export const getSigningDidKey = (doc: DidDocument): string | undefined => {
48
+ const parsed = getSigningKey(doc)
49
+ if (!parsed) return
50
+ return `did:key:${parsed.publicKeyMultibase}`
51
+ }
47
52
 
48
53
  export const getPdsEndpoint = (doc: DidDocument): string | undefined => {
49
54
  return getServiceEndpoint(doc, {
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@ export * from './async'
6
6
  export * from './util'
7
7
  export * from './tid'
8
8
  export * from './ipld'
9
+ export * from './retry'
9
10
  export * from './types'
10
11
  export * from './times'
11
12
  export * from './strings'
package/src/retry.ts ADDED
@@ -0,0 +1,52 @@
1
+ import { wait } from './util'
2
+
3
+ export type RetryOptions = {
4
+ maxRetries?: number
5
+ getWaitMs?: (n: number) => number | null
6
+ retryable?: (err: unknown) => boolean
7
+ }
8
+
9
+ export async function retry<T>(
10
+ fn: () => Promise<T>,
11
+ opts: RetryOptions = {},
12
+ ): Promise<T> {
13
+ const { maxRetries = 3, retryable = () => true, getWaitMs = backoffMs } = opts
14
+ let retries = 0
15
+ let doneError: unknown
16
+ while (!doneError) {
17
+ try {
18
+ return await fn()
19
+ } catch (err) {
20
+ const waitMs = getWaitMs(retries)
21
+ const willRetry =
22
+ retries < maxRetries && waitMs !== null && retryable(err)
23
+ if (willRetry) {
24
+ retries += 1
25
+ if (waitMs !== 0) {
26
+ await wait(waitMs)
27
+ }
28
+ } else {
29
+ doneError = err
30
+ }
31
+ }
32
+ }
33
+ throw doneError
34
+ }
35
+
36
+ // Waits exponential backoff with max and jitter: ~100, ~200, ~400, ~800, ~1000, ~1000, ...
37
+ export function backoffMs(n: number, multiplier = 100, max = 1000) {
38
+ const exponentialMs = Math.pow(2, n) * multiplier
39
+ const ms = Math.min(exponentialMs, max)
40
+ return jitter(ms)
41
+ }
42
+
43
+ // Adds randomness +/-15% of value
44
+ function jitter(value: number) {
45
+ const delta = value * 0.15
46
+ return value + randomRange(-delta, delta)
47
+ }
48
+
49
+ function randomRange(from: number, to: number) {
50
+ const rand = Math.random() * (to - from)
51
+ return rand + from
52
+ }
package/src/times.ts CHANGED
@@ -6,3 +6,10 @@ export const DAY = HOUR * 24
6
6
  export const lessThanAgoMs = (time: Date, range: number) => {
7
7
  return Date.now() < time.getTime() + range
8
8
  }
9
+
10
+ export const addHoursToDate = (hours: number, startingDate?: Date): Date => {
11
+ // When date is passed, clone before calling `setHours()` so that we are not mutating the original date
12
+ const currentDate = startingDate ? new Date(startingDate) : new Date()
13
+ currentDate.setHours(currentDate.getHours() + hours)
14
+ return currentDate
15
+ }
package/src/util.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  export const noUndefinedVals = <T>(
2
- obj: Record<string, T>,
2
+ obj: Record<string, T | undefined>,
3
3
  ): Record<string, T> => {
4
4
  Object.keys(obj).forEach((k) => {
5
5
  if (obj[k] === undefined) {
6
6
  delete obj[k]
7
7
  }
8
8
  })
9
- return obj
9
+ return obj as Record<string, T>
10
10
  }
11
11
 
12
12
  export const jitter = (maxMs: number) => {
@@ -0,0 +1,93 @@
1
+ import { retry } from '../src/index'
2
+
3
+ describe('retry', () => {
4
+ describe('retry()', () => {
5
+ it('retries until max retries', async () => {
6
+ let fnCalls = 0
7
+ let waitMsCalls = 0
8
+ const fn = async () => {
9
+ fnCalls++
10
+ throw new Error(`Oops ${fnCalls}!`)
11
+ }
12
+ const getWaitMs = (retries) => {
13
+ waitMsCalls++
14
+ expect(retries).toEqual(waitMsCalls - 1)
15
+ return 0
16
+ }
17
+ await expect(retry(fn, { maxRetries: 13, getWaitMs })).rejects.toThrow(
18
+ 'Oops 14!',
19
+ )
20
+ expect(fnCalls).toEqual(14)
21
+ expect(waitMsCalls).toEqual(14)
22
+ })
23
+
24
+ it('retries until max wait', async () => {
25
+ let fnCalls = 0
26
+ let waitMsCalls = 0
27
+ const fn = async () => {
28
+ fnCalls++
29
+ throw new Error(`Oops ${fnCalls}!`)
30
+ }
31
+ const getWaitMs = (retries) => {
32
+ waitMsCalls++
33
+ expect(retries).toEqual(waitMsCalls - 1)
34
+ if (retries === 13) {
35
+ return null
36
+ }
37
+ return 0
38
+ }
39
+ await expect(
40
+ retry(fn, { maxRetries: Infinity, getWaitMs }),
41
+ ).rejects.toThrow('Oops 14!')
42
+ expect(fnCalls).toEqual(14)
43
+ expect(waitMsCalls).toEqual(14)
44
+ })
45
+
46
+ it('retries until non-retryable error', async () => {
47
+ let fnCalls = 0
48
+ let waitMsCalls = 0
49
+ const fn = async () => {
50
+ fnCalls++
51
+ throw new Error(`Oops ${fnCalls}!`)
52
+ }
53
+ const getWaitMs = (retries) => {
54
+ waitMsCalls++
55
+ expect(retries).toEqual(waitMsCalls - 1)
56
+ return 0
57
+ }
58
+ const retryable = (err: unknown) => err?.['message'] !== 'Oops 14!'
59
+ await expect(
60
+ retry(fn, { maxRetries: Infinity, getWaitMs, retryable }),
61
+ ).rejects.toThrow('Oops 14!')
62
+ expect(fnCalls).toEqual(14)
63
+ expect(waitMsCalls).toEqual(14)
64
+ })
65
+
66
+ it('returns latest result after retries', async () => {
67
+ let fnCalls = 0
68
+ const fn = async () => {
69
+ fnCalls++
70
+ if (fnCalls < 14) {
71
+ throw new Error(`Oops ${fnCalls}!`)
72
+ }
73
+ return 'ok'
74
+ }
75
+ const getWaitMs = () => 0
76
+ const result = await retry(fn, { maxRetries: Infinity, getWaitMs })
77
+ expect(result).toBe('ok')
78
+ expect(fnCalls).toBe(14)
79
+ })
80
+
81
+ it('returns result immediately on success', async () => {
82
+ let fnCalls = 0
83
+ const fn = async () => {
84
+ fnCalls++
85
+ return 'ok'
86
+ }
87
+ const getWaitMs = () => 0
88
+ const result = await retry(fn, { maxRetries: Infinity, getWaitMs })
89
+ expect(result).toBe('ok')
90
+ expect(fnCalls).toBe(1)
91
+ })
92
+ })
93
+ })
@@ -1,4 +1,8 @@
1
1
  {
2
- "extends": "./tsconfig.json",
3
- "exclude": ["**/*.spec.ts", "**/*.test.ts"]
2
+ "extends": "../../tsconfig/isomorphic.json",
3
+ "compilerOptions": {
4
+ "rootDir": "./src",
5
+ "outDir": "./dist"
6
+ },
7
+ "include": ["./src"]
4
8
  }
package/tsconfig.json CHANGED
@@ -1,9 +1,7 @@
1
1
  {
2
- "extends": "../../tsconfig.json",
3
- "compilerOptions": {
4
- "rootDir": "./src",
5
- "outDir": "./dist", // Your outDir,
6
- "emitDeclarationOnly": true
7
- },
8
- "include": ["./src", "__tests__/**/**.ts"]
2
+ "include": [],
3
+ "references": [
4
+ { "path": "./tsconfig.build.json" },
5
+ { "path": "./tsconfig.tests.json" }
6
+ ]
9
7
  }
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "../../tsconfig/tests.json",
3
+ "compilerOptions": {
4
+ "rootDir": "."
5
+ },
6
+ "include": ["./tests"]
7
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2022-2023 Bluesky PBC
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
package/babel.config.js DELETED
@@ -1 +0,0 @@
1
- module.exports = require('../../babel.config.js')
package/build.js DELETED
@@ -1,15 +0,0 @@
1
- const { nodeExternalsPlugin } = require('esbuild-node-externals')
2
-
3
- const buildShallow =
4
- process.argv.includes('--shallow') || process.env.ATP_BUILD_SHALLOW === 'true'
5
-
6
- require('esbuild').build({
7
- logLevel: 'info',
8
- entryPoints: ['src/index.ts'],
9
- bundle: true,
10
- sourcemap: true,
11
- outdir: 'dist',
12
- platform: 'browser',
13
- format: 'cjs',
14
- plugins: buildShallow ? [nodeExternalsPlugin()] : [],
15
- })