@dxos/keys 0.8.4-main.28f8d3d → 0.8.4-main.2c6827d
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 +78 -24
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +78 -24
- 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 +21 -13
- 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 +48 -50
- 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.2c6827d",
|
|
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/invariant": "0.8.4-main.
|
|
32
|
-
"@dxos/node-std": "0.8.4-main.
|
|
30
|
+
"@dxos/debug": "0.8.4-main.2c6827d",
|
|
31
|
+
"@dxos/invariant": "0.8.4-main.2c6827d",
|
|
32
|
+
"@dxos/node-std": "0.8.4-main.2c6827d"
|
|
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';
|
|
@@ -29,6 +29,12 @@ export const QueueSubspaceTags = Object.freeze({
|
|
|
29
29
|
|
|
30
30
|
export type QueueSubspaceTag = (typeof QueueSubspaceTags)[keyof typeof QueueSubspaceTags];
|
|
31
31
|
|
|
32
|
+
// TODO(burdon): Refactor.
|
|
33
|
+
// Consider: https://github.com/multiformats/multiaddr
|
|
34
|
+
// dxn:echo:[<space-id>:[<queue-id>:]]<object-id>
|
|
35
|
+
// dxn:echo:[S/<space-id>:[Q/<queue-id>:]]<object-id>
|
|
36
|
+
// dxn:type:dxos.org/markdown/Contact
|
|
37
|
+
|
|
32
38
|
/**
|
|
33
39
|
* DXN unambiguously names a resource like an ECHO object, schema definition, plugin, etc.
|
|
34
40
|
* Each DXN starts with a dxn prefix, followed by a resource kind.
|
|
@@ -68,16 +74,16 @@ export class DXN {
|
|
|
68
74
|
*/
|
|
69
75
|
static kind = Object.freeze({
|
|
70
76
|
/**
|
|
71
|
-
* dxn:type:<
|
|
77
|
+
* dxn:type:<type_name>[:<version>]
|
|
72
78
|
*/
|
|
73
79
|
TYPE: 'type',
|
|
74
80
|
|
|
75
81
|
/**
|
|
76
|
-
* dxn:echo:<
|
|
77
|
-
* dxn:echo:@:<
|
|
82
|
+
* dxn:echo:<space_id>:<echo_id>
|
|
83
|
+
* dxn:echo:@:<echo_id>
|
|
78
84
|
*/
|
|
79
|
-
// TODO(burdon): Rename to OBJECT? (BREAKING CHANGE).
|
|
80
|
-
// TODO(burdon): Add separate Kind for space
|
|
85
|
+
// TODO(burdon): Rename to OBJECT? (BREAKING CHANGE to update "echo").
|
|
86
|
+
// TODO(burdon): Add separate Kind for space?
|
|
81
87
|
ECHO: 'echo',
|
|
82
88
|
|
|
83
89
|
/**
|
|
@@ -89,14 +95,19 @@ export class DXN {
|
|
|
89
95
|
QUEUE: 'queue',
|
|
90
96
|
});
|
|
91
97
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Exactly equals.
|
|
100
|
+
*/
|
|
96
101
|
static equals(a: DXN, b: DXN): boolean {
|
|
97
102
|
return a.kind === b.kind && a.parts.length === b.parts.length && a.parts.every((part, i) => part === b.parts[i]);
|
|
98
103
|
}
|
|
99
104
|
|
|
105
|
+
static equalsEchoId(a: DXN, b: DXN): boolean {
|
|
106
|
+
const a1 = a.asEchoDXN();
|
|
107
|
+
const b1 = b.asEchoDXN();
|
|
108
|
+
return !!a1 && !!b1 && a1.echoId === b1.echoId;
|
|
109
|
+
}
|
|
110
|
+
|
|
100
111
|
// TODO(burdon): Rename isValid.
|
|
101
112
|
static isDXNString(dxn: string): boolean {
|
|
102
113
|
return dxn.startsWith('dxn:');
|
|
@@ -129,14 +140,14 @@ export class DXN {
|
|
|
129
140
|
}
|
|
130
141
|
|
|
131
142
|
/**
|
|
132
|
-
* @example `dxn:type:example.com/type/
|
|
143
|
+
* @example `dxn:type:example.com/type/Person`
|
|
133
144
|
*/
|
|
134
145
|
static fromTypename(typename: string): DXN {
|
|
135
146
|
return new DXN(DXN.kind.TYPE, [typename]);
|
|
136
147
|
}
|
|
137
148
|
|
|
138
149
|
/**
|
|
139
|
-
* @example `dxn:type:example.com/type/
|
|
150
|
+
* @example `dxn:type:example.com/type/Person:0.1.0`
|
|
140
151
|
*/
|
|
141
152
|
// TODO(dmaretskyi): Consider using @ as the version separator.
|
|
142
153
|
static fromTypenameAndVersion(typename: string, version: string): DXN {
|
|
@@ -148,7 +159,7 @@ export class DXN {
|
|
|
148
159
|
*/
|
|
149
160
|
static fromSpaceAndObjectId(spaceId: SpaceId, objectId: ObjectId): DXN {
|
|
150
161
|
assertArgument(SpaceId.isValid(spaceId), `Invalid space ID: ${spaceId}`);
|
|
151
|
-
assertArgument(ObjectId.isValid(objectId), `Invalid object ID: ${objectId}`);
|
|
162
|
+
assertArgument(ObjectId.isValid(objectId), 'objectId', `Invalid object ID: ${objectId}`);
|
|
152
163
|
return new DXN(DXN.kind.ECHO, [spaceId, objectId]);
|
|
153
164
|
}
|
|
154
165
|
|
|
@@ -156,14 +167,14 @@ export class DXN {
|
|
|
156
167
|
* @example `dxn:echo:@:01J00J9B45YHYSGZQTQMSKMGJ6`
|
|
157
168
|
*/
|
|
158
169
|
static fromLocalObjectId(id: string): DXN {
|
|
159
|
-
assertArgument(ObjectId.isValid(id), `Invalid object ID: ${id}`);
|
|
170
|
+
assertArgument(ObjectId.isValid(id), 'id', `Invalid object ID: ${id}`);
|
|
160
171
|
return new DXN(DXN.kind.ECHO, [LOCAL_SPACE_TAG, id]);
|
|
161
172
|
}
|
|
162
173
|
|
|
163
174
|
static fromQueue(subspaceTag: QueueSubspaceTag, spaceId: SpaceId, queueId: ObjectId, objectId?: ObjectId) {
|
|
164
175
|
assertArgument(SpaceId.isValid(spaceId), `Invalid space ID: ${spaceId}`);
|
|
165
|
-
assertArgument(ObjectId.isValid(queueId), `Invalid queue ID: ${queueId}`);
|
|
166
|
-
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}`);
|
|
167
178
|
|
|
168
179
|
return new DXN(DXN.kind.QUEUE, [subspaceTag, spaceId, queueId, ...(objectId ? [objectId] : [])]);
|
|
169
180
|
}
|
|
@@ -172,9 +183,10 @@ export class DXN {
|
|
|
172
183
|
#parts: string[];
|
|
173
184
|
|
|
174
185
|
constructor(kind: string, parts: string[]) {
|
|
175
|
-
assertArgument(parts.length > 0, `Invalid DXN: ${parts}`);
|
|
186
|
+
assertArgument(parts.length > 0, 'parts', `Invalid DXN: ${parts}`);
|
|
176
187
|
assertArgument(
|
|
177
188
|
parts.every((part) => typeof part === 'string' && part.length > 0 && part.indexOf(':') === -1),
|
|
189
|
+
'parts',
|
|
178
190
|
`Invalid DXN: ${parts}`,
|
|
179
191
|
);
|
|
180
192
|
|
|
@@ -182,12 +194,12 @@ export class DXN {
|
|
|
182
194
|
switch (kind) {
|
|
183
195
|
case DXN.kind.TYPE:
|
|
184
196
|
if (parts.length > 2) {
|
|
185
|
-
throw new Error('Invalid
|
|
197
|
+
throw new Error('Invalid DXN.kind.TYPE');
|
|
186
198
|
}
|
|
187
199
|
break;
|
|
188
200
|
case DXN.kind.ECHO:
|
|
189
201
|
if (parts.length !== 2) {
|
|
190
|
-
throw new Error('Invalid
|
|
202
|
+
throw new Error('Invalid DXN.kind.ECHO');
|
|
191
203
|
}
|
|
192
204
|
break;
|
|
193
205
|
}
|
|
@@ -225,6 +237,10 @@ export class DXN {
|
|
|
225
237
|
};
|
|
226
238
|
}
|
|
227
239
|
|
|
240
|
+
get kind() {
|
|
241
|
+
return this.#kind;
|
|
242
|
+
}
|
|
243
|
+
|
|
228
244
|
get parts() {
|
|
229
245
|
return this.#parts;
|
|
230
246
|
}
|
|
@@ -235,6 +251,10 @@ export class DXN {
|
|
|
235
251
|
return this.#parts[0];
|
|
236
252
|
}
|
|
237
253
|
|
|
254
|
+
equals(other: DXN): boolean {
|
|
255
|
+
return DXN.equals(this, other);
|
|
256
|
+
}
|
|
257
|
+
|
|
238
258
|
hasTypenameOf(typename: string): boolean {
|
|
239
259
|
return this.#kind === DXN.kind.TYPE && this.#parts.length === 1 && this.#parts[0] === typename;
|
|
240
260
|
}
|
|
@@ -264,6 +284,7 @@ export class DXN {
|
|
|
264
284
|
const [spaceId, echoId] = this.#parts;
|
|
265
285
|
return {
|
|
266
286
|
spaceId: spaceId === LOCAL_SPACE_TAG ? undefined : (spaceId as SpaceId | undefined),
|
|
287
|
+
// TODO(burdon): objectId.
|
|
267
288
|
echoId,
|
|
268
289
|
};
|
|
269
290
|
}
|
|
@@ -294,33 +315,18 @@ export class DXN {
|
|
|
294
315
|
}
|
|
295
316
|
}
|
|
296
317
|
|
|
297
|
-
// TODO(dmaretskyi): Fluent API:
|
|
298
|
-
/*
|
|
299
|
-
class DXN {
|
|
300
|
-
...
|
|
301
|
-
isEchoDXN(): this is EchoDXN {
|
|
302
|
-
return this.#kind === DXN.kind.ECHO;
|
|
303
|
-
}
|
|
304
|
-
...
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
interface EchoDXN extends DXN {
|
|
308
|
-
objectId: ObjectId;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
declare const dxn: DXN;
|
|
312
|
-
|
|
313
|
-
dxn.objectId
|
|
314
|
-
|
|
315
|
-
if(dxn.isEchoDXN()) {
|
|
316
|
-
dxn.objectId
|
|
317
|
-
}
|
|
318
|
-
```
|
|
319
|
-
|
|
320
318
|
/**
|
|
321
319
|
* API namespace.
|
|
322
320
|
*/
|
|
323
321
|
export declare namespace DXN {
|
|
322
|
+
/**
|
|
323
|
+
* DXN represented as a javascript string.
|
|
324
|
+
*/
|
|
325
|
+
// TODO(burdon): Use Effect branded string?
|
|
326
|
+
// export const String = S.String.pipe(S.brand('DXN'));
|
|
327
|
+
// export type String = S.To(typoeof String);
|
|
328
|
+
export type String = string & { __DXNString: never };
|
|
329
|
+
|
|
324
330
|
export type TypeDXN = {
|
|
325
331
|
type: string;
|
|
326
332
|
version?: string;
|
|
@@ -337,12 +343,4 @@ export declare namespace DXN {
|
|
|
337
343
|
queueId: string; // TODO(dmaretskyi): ObjectId.
|
|
338
344
|
objectId?: string; // TODO(dmaretskyi): ObjectId.
|
|
339
345
|
};
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* DXN represented as a javascript string.
|
|
343
|
-
*/
|
|
344
|
-
export type String = string & { __DXNString: never };
|
|
345
|
-
// TODO(burdon): Make brand.
|
|
346
|
-
// export const String = S.String.pipe(S.brand('DXN'));
|
|
347
|
-
// export type String = S.To(typoeof String);
|
|
348
346
|
}
|
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
|
+
};
|