@dxos/keys 0.8.4-main.5ea62a8 → 0.8.4-main.72ec0f3
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/dist/lib/browser/index.mjs +178 -220
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +178 -220
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/dxn.d.ts +3 -3
- package/dist/types/src/dxn.d.ts.map +1 -1
- package/dist/types/src/object-id.d.ts +50 -1
- package/dist/types/src/object-id.d.ts.map +1 -1
- package/dist/types/src/prng.d.ts +32 -0
- package/dist/types/src/prng.d.ts.map +1 -0
- package/dist/types/src/space-id.d.ts +1 -1
- package/dist/types/src/space-id.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/src/dxn.ts +9 -8
- package/src/object-id.ts +92 -3
- package/src/prng.ts +54 -0
- package/src/space-id.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/keys",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.72ec0f3",
|
|
4
4
|
"description": "Key utils and definitions.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -25,11 +25,11 @@
|
|
|
25
25
|
"src"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"effect": "3.
|
|
28
|
+
"effect": "3.18.3",
|
|
29
29
|
"ulidx": "^2.3.0",
|
|
30
|
-
"@dxos/debug": "0.8.4-main.
|
|
31
|
-
"@dxos/
|
|
32
|
-
"@dxos/
|
|
30
|
+
"@dxos/debug": "0.8.4-main.72ec0f3",
|
|
31
|
+
"@dxos/invariant": "0.8.4-main.72ec0f3",
|
|
32
|
+
"@dxos/node-std": "0.8.4-main.72ec0f3"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"base32-decode": "^1.0.0",
|
package/src/dxn.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import type { InspectOptionsStylized, inspect } from 'node:util';
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import * as Schema from 'effect/Schema';
|
|
8
8
|
|
|
9
9
|
import { type DevtoolsFormatter, devtoolsFormatter, inspectCustom } from '@dxos/debug';
|
|
10
10
|
import { assertArgument, invariant } from '@dxos/invariant';
|
|
@@ -140,14 +140,14 @@ export class DXN {
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
/**
|
|
143
|
-
* @example `dxn:type:example.com/type/
|
|
143
|
+
* @example `dxn:type:example.com/type/Person`
|
|
144
144
|
*/
|
|
145
145
|
static fromTypename(typename: string): DXN {
|
|
146
146
|
return new DXN(DXN.kind.TYPE, [typename]);
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
/**
|
|
150
|
-
* @example `dxn:type:example.com/type/
|
|
150
|
+
* @example `dxn:type:example.com/type/Person:0.1.0`
|
|
151
151
|
*/
|
|
152
152
|
// TODO(dmaretskyi): Consider using @ as the version separator.
|
|
153
153
|
static fromTypenameAndVersion(typename: string, version: string): DXN {
|
|
@@ -159,7 +159,7 @@ export class DXN {
|
|
|
159
159
|
*/
|
|
160
160
|
static fromSpaceAndObjectId(spaceId: SpaceId, objectId: ObjectId): DXN {
|
|
161
161
|
assertArgument(SpaceId.isValid(spaceId), `Invalid space ID: ${spaceId}`);
|
|
162
|
-
assertArgument(ObjectId.isValid(objectId), `Invalid object ID: ${objectId}`);
|
|
162
|
+
assertArgument(ObjectId.isValid(objectId), 'objectId', `Invalid object ID: ${objectId}`);
|
|
163
163
|
return new DXN(DXN.kind.ECHO, [spaceId, objectId]);
|
|
164
164
|
}
|
|
165
165
|
|
|
@@ -167,14 +167,14 @@ export class DXN {
|
|
|
167
167
|
* @example `dxn:echo:@:01J00J9B45YHYSGZQTQMSKMGJ6`
|
|
168
168
|
*/
|
|
169
169
|
static fromLocalObjectId(id: string): DXN {
|
|
170
|
-
assertArgument(ObjectId.isValid(id), `Invalid object ID: ${id}`);
|
|
170
|
+
assertArgument(ObjectId.isValid(id), 'id', `Invalid object ID: ${id}`);
|
|
171
171
|
return new DXN(DXN.kind.ECHO, [LOCAL_SPACE_TAG, id]);
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
static fromQueue(subspaceTag: QueueSubspaceTag, spaceId: SpaceId, queueId: ObjectId, objectId?: ObjectId) {
|
|
175
175
|
assertArgument(SpaceId.isValid(spaceId), `Invalid space ID: ${spaceId}`);
|
|
176
|
-
assertArgument(ObjectId.isValid(queueId), `Invalid queue ID: ${queueId}`);
|
|
177
|
-
assertArgument(!objectId || ObjectId.isValid(objectId), `Invalid object ID: ${objectId}`);
|
|
176
|
+
assertArgument(ObjectId.isValid(queueId), 'queueId', `Invalid queue ID: ${queueId}`);
|
|
177
|
+
assertArgument(!objectId || ObjectId.isValid(objectId), 'objectId', `Invalid object ID: ${objectId}`);
|
|
178
178
|
|
|
179
179
|
return new DXN(DXN.kind.QUEUE, [subspaceTag, spaceId, queueId, ...(objectId ? [objectId] : [])]);
|
|
180
180
|
}
|
|
@@ -183,9 +183,10 @@ export class DXN {
|
|
|
183
183
|
#parts: string[];
|
|
184
184
|
|
|
185
185
|
constructor(kind: string, parts: string[]) {
|
|
186
|
-
assertArgument(parts.length > 0, `Invalid DXN: ${parts}`);
|
|
186
|
+
assertArgument(parts.length > 0, 'parts', `Invalid DXN: ${parts}`);
|
|
187
187
|
assertArgument(
|
|
188
188
|
parts.every((part) => typeof part === 'string' && part.length > 0 && part.indexOf(':') === -1),
|
|
189
|
+
'parts',
|
|
189
190
|
`Invalid DXN: ${parts}`,
|
|
190
191
|
);
|
|
191
192
|
|
package/src/object-id.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
5
|
+
import * as Schema from 'effect/Schema';
|
|
6
|
+
import { type PRNG, type ULIDFactory, monotonicFactory } from 'ulidx';
|
|
7
7
|
|
|
8
8
|
// TODO(dmaretskyi): Make brand.
|
|
9
9
|
// export const ObjectIdBrand: unique symbol = Symbol('@dxos/echo/ObjectId');
|
|
@@ -16,9 +16,39 @@ const ObjectIdSchema = Schema.String.pipe(Schema.pattern(/^[0-7][0-9A-HJKMNP-TV-
|
|
|
16
16
|
export type ObjectId = typeof ObjectIdSchema.Type;
|
|
17
17
|
|
|
18
18
|
export interface ObjectIdClass extends Schema.SchemaClass<ObjectId, string> {
|
|
19
|
+
/**
|
|
20
|
+
* @returns true if the string is a valid ObjectId.
|
|
21
|
+
*/
|
|
19
22
|
isValid(id: string): id is ObjectId;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Creates an ObjectId from a string validating the format.
|
|
26
|
+
*/
|
|
20
27
|
make(id: string): ObjectId;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Generates a random ObjectId.
|
|
31
|
+
*/
|
|
21
32
|
random(): ObjectId;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* WARNING: To be used only within tests.
|
|
36
|
+
*
|
|
37
|
+
* Disables randomness in ObjectId generation, causing the same sequence of IDs to be generated.
|
|
38
|
+
* Do not use in production code as this will cause data collisions.
|
|
39
|
+
* Place this at the top of the test file to ensure that the same sequence of IDs is generated.
|
|
40
|
+
*
|
|
41
|
+
* ```ts
|
|
42
|
+
* ObjectId.dangerouslyDisableRandomness();
|
|
43
|
+
*
|
|
44
|
+
* describe('suite', () => {
|
|
45
|
+
* // ...
|
|
46
|
+
* });
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* NOTE: The generated IDs depend on the order of ObjectId.random() calls, which might be affected by test order, scheduling, etc.
|
|
50
|
+
*/
|
|
51
|
+
dangerouslyDisableRandomness(): void;
|
|
22
52
|
}
|
|
23
53
|
|
|
24
54
|
/**
|
|
@@ -27,6 +57,9 @@ export interface ObjectIdClass extends Schema.SchemaClass<ObjectId, string> {
|
|
|
27
57
|
* Follows ULID spec.
|
|
28
58
|
*/
|
|
29
59
|
export const ObjectId: ObjectIdClass = class extends ObjectIdSchema {
|
|
60
|
+
static #factory: ULIDFactory = monotonicFactory();
|
|
61
|
+
static #seedTime: number | undefined = undefined;
|
|
62
|
+
|
|
30
63
|
static isValid(id: string): id is ObjectId {
|
|
31
64
|
try {
|
|
32
65
|
Schema.decodeSync(ObjectId)(id);
|
|
@@ -37,6 +70,62 @@ export const ObjectId: ObjectIdClass = class extends ObjectIdSchema {
|
|
|
37
70
|
}
|
|
38
71
|
|
|
39
72
|
static random(): ObjectId {
|
|
40
|
-
return
|
|
73
|
+
return this.#factory(this.#seedTime) as ObjectId;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
static dangerouslyDisableRandomness() {
|
|
77
|
+
this.#factory = monotonicFactory(makeTestPRNG());
|
|
78
|
+
this.#seedTime = new Date('2025-01-01').getTime();
|
|
41
79
|
}
|
|
42
80
|
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Test PRNG that always starts with the same seed and produces the same sequence.
|
|
84
|
+
*/
|
|
85
|
+
const makeTestPRNG = (): PRNG => {
|
|
86
|
+
const rng = new SimplePRNG();
|
|
87
|
+
return () => {
|
|
88
|
+
return rng.next();
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Simple Linear Congruential Generator (LCG) for pseudo-random number generation.
|
|
94
|
+
* Returns numbers in the range [0, 1) (0 inclusive, 1 exclusive).
|
|
95
|
+
*/
|
|
96
|
+
export class SimplePRNG {
|
|
97
|
+
#seed: number;
|
|
98
|
+
|
|
99
|
+
// LCG parameters (from Numerical Recipes)
|
|
100
|
+
static readonly #a = 1664525;
|
|
101
|
+
static readonly #c = 1013904223;
|
|
102
|
+
static readonly #m = Math.pow(2, 32);
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Creates a new PRNG instance.
|
|
106
|
+
* @param seed - Initial seed value. If not provided, uses 0.
|
|
107
|
+
*/
|
|
108
|
+
constructor(seed: number = 0) {
|
|
109
|
+
this.#seed = seed;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Generates the next pseudo-random number in the range [0, 1).
|
|
114
|
+
* @returns A pseudo-random number between 0 (inclusive) and 1 (exclusive).
|
|
115
|
+
*/
|
|
116
|
+
next(): number {
|
|
117
|
+
// Update seed using LCG formula: (a * seed + c) mod m
|
|
118
|
+
this.#seed = (SimplePRNG.#a * this.#seed + SimplePRNG.#c) % SimplePRNG.#m;
|
|
119
|
+
|
|
120
|
+
// Normalize to [0, 1) range
|
|
121
|
+
return this.#seed / SimplePRNG.#m;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Resets the generator with a new seed.
|
|
126
|
+
* @param seed - New seed value.
|
|
127
|
+
*/
|
|
128
|
+
reset(seed: number): void {
|
|
129
|
+
this.#seed = seed;
|
|
130
|
+
}
|
|
131
|
+
}
|
package/src/prng.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Simple Linear Congruential Generator (LCG) for pseudo-random number generation.
|
|
7
|
+
* Returns numbers in the range [0, 1) (0 inclusive, 1 exclusive).
|
|
8
|
+
*/
|
|
9
|
+
export class SimplePRNG {
|
|
10
|
+
private _seed: number;
|
|
11
|
+
|
|
12
|
+
// LCG parameters (from Numerical Recipes)
|
|
13
|
+
private static readonly _a = 1664525;
|
|
14
|
+
private static readonly _c = 1013904223;
|
|
15
|
+
private static readonly _m = Math.pow(2, 32);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Creates a new PRNG instance.
|
|
19
|
+
* @param seed - Initial seed value. If not provided, uses current timestamp.
|
|
20
|
+
*/
|
|
21
|
+
constructor(seed?: number) {
|
|
22
|
+
this._seed = seed ?? Date.now();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Generates the next pseudo-random number in the range [0, 1).
|
|
27
|
+
* @returns A pseudo-random number between 0 (inclusive) and 1 (exclusive).
|
|
28
|
+
*/
|
|
29
|
+
next(): number {
|
|
30
|
+
// Update seed using LCG formula: (a * seed + c) mod m
|
|
31
|
+
this._seed = (SimplePRNG._a * this._seed + SimplePRNG._c) % SimplePRNG._m;
|
|
32
|
+
|
|
33
|
+
// Normalize to [0, 1) range
|
|
34
|
+
return this._seed / SimplePRNG._m;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Resets the generator with a new seed.
|
|
39
|
+
* @param seed - New seed value.
|
|
40
|
+
*/
|
|
41
|
+
reset(seed: number): void {
|
|
42
|
+
this._seed = seed;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Creates a simple PRNG function with optional seed.
|
|
48
|
+
* @param seed - Optional seed value.
|
|
49
|
+
* @returns A function that returns pseudo-random numbers in [0, 1).
|
|
50
|
+
*/
|
|
51
|
+
export const createPRNG = (seed?: number): (() => number) => {
|
|
52
|
+
const prng = new SimplePRNG(seed);
|
|
53
|
+
return () => prng.next();
|
|
54
|
+
};
|