@tozd/identifier 0.7.0 → 0.8.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.
package/README.md CHANGED
@@ -12,7 +12,7 @@ Features:
12
12
 
13
13
  - Identifiers have 128 bits of entropy, making them suitable as global identifiers.
14
14
  - By default identifiers are random, but you can convert existing
15
- [UUIDs](https://en.wikipedia.org/wiki/Universally_unique_identifier).
15
+ [UUIDs](https://en.wikipedia.org/wiki/Universally_unique_identifier) or (lists of) strings.
16
16
  - They are encoded into readable base 58 strings always of 22 characters in length.
17
17
 
18
18
  ## Installation
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tozd/identifier",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "description": "Readable global identifiers.",
6
6
  "license": "Apache-2.0",
@@ -33,18 +33,18 @@
33
33
  "uuid": "^13.0.0"
34
34
  },
35
35
  "devDependencies": {
36
- "@eslint/compat": "^1.4.0",
37
- "@eslint/js": "^9.37.0",
38
- "@vitest/coverage-v8": "^3.2.4",
39
- "eslint": "^9.37.0",
36
+ "@eslint/compat": "^2.0.0",
37
+ "@eslint/js": "^9.39.1",
38
+ "@vitest/coverage-v8": "^4.0.9",
39
+ "eslint": "^9.39.1",
40
40
  "eslint-config-prettier": "^10.1.8",
41
- "globals": "^16.4.0",
42
- "npm-check-updates": "^19.0.0",
41
+ "globals": "^16.5.0",
42
+ "npm-check-updates": "^19.1.2",
43
43
  "prettier": "^3.6.2",
44
44
  "prettier-plugin-organize-imports": "^4.3.0",
45
45
  "typescript": "^5.9.3",
46
- "typescript-eslint": "^8.46.0",
47
- "vitest": "^3.2.4"
46
+ "typescript-eslint": "^8.46.4",
47
+ "vitest": "^4.0.9"
48
48
  },
49
49
  "publishConfig": {
50
50
  "access": "public"
package/src/index.ts CHANGED
@@ -81,4 +81,37 @@ export class Identifier {
81
81
  return false
82
82
  }
83
83
  }
84
+
85
+ // from generates a deterministic identifier from one or more string values using iterative SHA-256 hashing.
86
+ //
87
+ // Each value is normalized using Unicode NFC normalization before hashing. The function computes
88
+ // hash = SHA256(normalize(values[0])), then hash = SHA256(hash + normalize(values[1])), and so on.
89
+ // The final identifier is derived from the first 128 bits of the resulting hash.
90
+ //
91
+ // Different values or different orderings produce different identifiers.
92
+ public static async from(...values: string[]): Promise<Identifier> {
93
+ const encoder = new TextEncoder()
94
+ let hash: Uint8Array | undefined
95
+ for (const value of values) {
96
+ // Normalize the string using NFC.
97
+ const normalized = value.normalize("NFC")
98
+ const normalizedBytes = encoder.encode(normalized)
99
+
100
+ if (hash === undefined) {
101
+ // First iteration: hash just the normalized value.
102
+ const hashBuffer = await crypto.subtle.digest("SHA-256", normalizedBytes)
103
+ hash = new Uint8Array(hashBuffer)
104
+ } else {
105
+ // Subsequent iterations: hash = SHA256(hash + normalized).
106
+ const combined = new Uint8Array(hash.length + normalizedBytes.length)
107
+ combined.set(hash)
108
+ combined.set(normalizedBytes, hash.length)
109
+ const hashBuffer = await crypto.subtle.digest("SHA-256", combined)
110
+ hash = new Uint8Array(hashBuffer)
111
+ }
112
+ }
113
+
114
+ // Take first 128 bits (16 bytes) of the final hash.
115
+ return new Identifier(hash!.slice(0, 16))
116
+ }
84
117
  }