@dxos/keys 0.8.4-main.dedc0f3 → 0.8.4-main.dfabb4ec29

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/package.json CHANGED
@@ -1,9 +1,13 @@
1
1
  {
2
2
  "name": "@dxos/keys",
3
- "version": "0.8.4-main.dedc0f3",
3
+ "version": "0.8.4-main.dfabb4ec29",
4
4
  "description": "Key utils and definitions.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/dxos/dxos"
10
+ },
7
11
  "license": "MIT",
8
12
  "author": "DXOS.org",
9
13
  "sideEffects": false,
@@ -17,23 +21,23 @@
17
21
  }
18
22
  },
19
23
  "types": "dist/types/src/index.d.ts",
20
- "typesVersions": {
21
- "*": {}
22
- },
23
24
  "files": [
24
25
  "dist",
25
26
  "src"
26
27
  ],
27
28
  "dependencies": {
28
- "effect": "3.17.7",
29
29
  "ulidx": "^2.3.0",
30
- "@dxos/debug": "0.8.4-main.dedc0f3",
31
- "@dxos/invariant": "0.8.4-main.dedc0f3",
32
- "@dxos/node-std": "0.8.4-main.dedc0f3"
30
+ "@dxos/debug": "0.8.4-main.dfabb4ec29",
31
+ "@dxos/invariant": "0.8.4-main.dfabb4ec29",
32
+ "@dxos/node-std": "0.8.4-main.dfabb4ec29"
33
33
  },
34
34
  "devDependencies": {
35
35
  "base32-decode": "^1.0.0",
36
- "base32-encode": "^2.0.0"
36
+ "base32-encode": "^2.0.0",
37
+ "effect": "3.20.0"
38
+ },
39
+ "peerDependencies": {
40
+ "effect": "3.20.0"
37
41
  },
38
42
  "publishConfig": {
39
43
  "access": "public"
package/src/dxn.ts CHANGED
@@ -2,10 +2,9 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ import * as Schema from 'effect/Schema';
5
6
  import type { InspectOptionsStylized, inspect } from 'node:util';
6
7
 
7
- import { Schema } from 'effect';
8
-
9
8
  import { type DevtoolsFormatter, devtoolsFormatter, inspectCustom } from '@dxos/debug';
10
9
  import { assertArgument, invariant } from '@dxos/invariant';
11
10
 
@@ -33,7 +32,7 @@ export type QueueSubspaceTag = (typeof QueueSubspaceTags)[keyof typeof QueueSubs
33
32
  // Consider: https://github.com/multiformats/multiaddr
34
33
  // dxn:echo:[<space-id>:[<queue-id>:]]<object-id>
35
34
  // dxn:echo:[S/<space-id>:[Q/<queue-id>:]]<object-id>
36
- // dxn:type:dxos.org/markdown/Contact
35
+ // dxn:type:org.dxos.markdown.contact
37
36
 
38
37
  /**
39
38
  * DXN unambiguously names a resource like an ECHO object, schema definition, plugin, etc.
@@ -47,8 +46,8 @@ export type QueueSubspaceTag = (typeof QueueSubspaceTags)[keyof typeof QueueSubs
47
46
  * dxn:echo:<space key>:<echo id>
48
47
  * dxn:echo:BA25QRC2FEWCSAMRP4RZL65LWJ7352CKE:01J00J9B45YHYSGZQTQMSKMGJ6
49
48
  * dxn:echo:@:01J00J9B45YHYSGZQTQMSKMGJ6
50
- * dxn:type:dxos.org/type/Calendar
51
- * dxn:plugin:dxos.org/agent/plugin/functions
49
+ * dxn:type:org.dxos.type.calendar
50
+ * dxn:plugin:org.dxos.agent.plugin.functions
52
51
  * ```
53
52
  */
54
53
  export class DXN {
@@ -57,11 +56,11 @@ export class DXN {
57
56
  static Schema = Schema.NonEmptyString.pipe(
58
57
  Schema.pattern(/^dxn:([^:]+):(?:[^:]+:?)+[^:]$/),
59
58
  // TODO(dmaretskyi): To set the format we need to move the annotation IDs out of the echo-schema package.
60
- // FormatAnnotation.set(FormatEnum.DXN),
59
+ // FormatAnnotation.set(TypeFormat.DXN),
61
60
  Schema.annotations({
62
61
  title: 'DXN',
63
62
  description: 'DXN URI',
64
- examples: ['dxn:type:example.com/type/MyType', 'dxn:echo:@:01J00J9B45YHYSGZQTQMSKMGJ6'],
63
+ examples: ['dxn:type:com.example.type.my-type', 'dxn:echo:@:01J00J9B45YHYSGZQTQMSKMGJ6'],
65
64
  }),
66
65
  );
67
66
 
@@ -140,14 +139,14 @@ export class DXN {
140
139
  }
141
140
 
142
141
  /**
143
- * @example `dxn:type:example.com/type/Contact`
142
+ * @example `dxn:type:com.example.type.person`
144
143
  */
145
144
  static fromTypename(typename: string): DXN {
146
145
  return new DXN(DXN.kind.TYPE, [typename]);
147
146
  }
148
147
 
149
148
  /**
150
- * @example `dxn:type:example.com/type/Contact:0.1.0`
149
+ * @example `dxn:type:com.example.type.person:0.1.0`
151
150
  */
152
151
  // TODO(dmaretskyi): Consider using @ as the version separator.
153
152
  static fromTypenameAndVersion(typename: string, version: string): DXN {
@@ -300,7 +299,7 @@ export class DXN {
300
299
  }
301
300
 
302
301
  return {
303
- subspaceTag,
302
+ subspaceTag: subspaceTag as QueueSubspaceTag,
304
303
  spaceId: spaceId as SpaceId,
305
304
  queueId,
306
305
  objectId: objectId as string | undefined,
@@ -338,7 +337,7 @@ export declare namespace DXN {
338
337
  };
339
338
 
340
339
  export type QueueDXN = {
341
- subspaceTag: string;
340
+ subspaceTag: QueueSubspaceTag;
342
341
  spaceId: SpaceId;
343
342
  queueId: string; // TODO(dmaretskyi): ObjectId.
344
343
  objectId?: string; // TODO(dmaretskyi): ObjectId.
package/src/object-id.ts CHANGED
@@ -2,8 +2,8 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Schema } from 'effect';
6
- import { ulid } from 'ulidx';
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 ulid() as ObjectId;
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
+ };
package/src/public-key.ts CHANGED
@@ -2,10 +2,9 @@
2
2
  // Copyright 2020 DXOS.org
3
3
  //
4
4
 
5
- import { type InspectOptionsStylized, type inspect } from 'node:util';
6
-
7
5
  import base32Decode from 'base32-decode';
8
6
  import base32Encode from 'base32-encode';
7
+ import { type InspectOptionsStylized, type inspect } from 'node:util';
9
8
 
10
9
  import {
11
10
  type DevtoolsFormatter,
@@ -217,7 +216,7 @@ export class PublicKey implements Equatable {
217
216
  return 'B' + base32Encode(this._value, 'RFC4648');
218
217
  }
219
218
 
220
- truncate(length = undefined): string {
219
+ truncate(length?: number): string {
221
220
  return truncateKey(this, length);
222
221
  }
223
222
 
package/src/space-id.ts CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  import base32Decode from 'base32-decode';
6
6
  import base32Encode from 'base32-encode';
7
- import { Schema } from 'effect';
7
+ import * as Schema from 'effect/Schema';
8
8
 
9
9
  import { invariant } from '@dxos/invariant';
10
10
 
@@ -17,7 +17,7 @@ const MULTIBASE_PREFIX = 'B';
17
17
 
18
18
  const ENCODED_LENGTH = 33;
19
19
 
20
- const isValid = (value: string): value is SpaceId => {
20
+ const isValid = (value: unknown): value is SpaceId => {
21
21
  return typeof value === 'string' && value.startsWith(MULTIBASE_PREFIX) && value.length === ENCODED_LENGTH;
22
22
  };
23
23
 
@@ -33,7 +33,7 @@ export const SpaceId: Schema.Schema<SpaceId, string> & {
33
33
  byteLength: number;
34
34
  encode: (value: Uint8Array) => SpaceId;
35
35
  decode: (value: SpaceId) => Uint8Array;
36
- isValid: (value: string) => value is SpaceId;
36
+ isValid: (value: unknown) => value is SpaceId;
37
37
  make: (value: string) => SpaceId;
38
38
  random: () => SpaceId;
39
39
  } = class extends Schema.String.pipe(Schema.filter(isValid)) {