@nextera.one/tps-standard 0.4.3 → 0.5.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.
@@ -0,0 +1,295 @@
1
+ /**
2
+ * TPS-UID v1 Tests
3
+ * Tests for the TPSUID7RB (Binary Reversible) identifier format.
4
+ * @version 1.0
5
+ */
6
+ import { TPSUID7RB } from '../src/index';
7
+ console.log('=== TPS-UID v1 Test Suite ===\n');
8
+ let passed = 0;
9
+ let failed = 0;
10
+ function test(name, fn) {
11
+ try {
12
+ const result = fn();
13
+ if (result) {
14
+ console.log(`✅ ${name}`);
15
+ passed++;
16
+ }
17
+ else {
18
+ console.log(`❌ ${name} - assertion failed`);
19
+ failed++;
20
+ }
21
+ }
22
+ catch (error) {
23
+ console.log(`❌ ${name} - ${error.message}`);
24
+ failed++;
25
+ }
26
+ }
27
+ // ============================================================================
28
+ // PART 1: Basic Encode/Decode Roundtrip
29
+ // ============================================================================
30
+ console.log('--- Part 1: Basic Encode/Decode ---\n');
31
+ test('Base64url encode/decode roundtrip', () => {
32
+ const tps = 'tps://31.95,35.91,800m@T:greg.m3.c1.y26.M01.d09.h14.n30.s25';
33
+ const encoded = TPSUID7RB.encodeBinaryB64(tps);
34
+ const decoded = TPSUID7RB.decodeBinaryB64(encoded);
35
+ return decoded.tps === tps;
36
+ });
37
+ test('Binary encode/decode roundtrip', () => {
38
+ const tps = 'tps://32,35@T:greg.m3.c1.y26.M01.d09';
39
+ const bytes = TPSUID7RB.encodeBinary(tps);
40
+ const decoded = TPSUID7RB.decodeBinary(bytes);
41
+ return decoded.tps === tps;
42
+ });
43
+ test('Prefix is correct', () => {
44
+ const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
45
+ const encoded = TPSUID7RB.encodeBinaryB64(tps);
46
+ return encoded.startsWith('tpsuid7rb_');
47
+ });
48
+ test('Magic bytes are TPU7', () => {
49
+ const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
50
+ const bytes = TPSUID7RB.encodeBinary(tps);
51
+ const magic = String.fromCharCode(bytes[0], bytes[1], bytes[2], bytes[3]);
52
+ return magic === 'TPU7';
53
+ });
54
+ test('Version byte is 0x01', () => {
55
+ const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
56
+ const bytes = TPSUID7RB.encodeBinary(tps);
57
+ return bytes[4] === 0x01;
58
+ });
59
+ console.log();
60
+ // ============================================================================
61
+ // PART 2: Compression
62
+ // ============================================================================
63
+ console.log('--- Part 2: Compression ---\n');
64
+ test('Compressed encode/decode roundtrip', () => {
65
+ const tps = 'tps://31.95,35.91,800m@T:greg.m3.c1.y26.M01.d09.h14.n30.s25';
66
+ const encoded = TPSUID7RB.encodeBinaryB64(tps, { compress: true });
67
+ const decoded = TPSUID7RB.decodeBinaryB64(encoded);
68
+ return decoded.tps === tps && decoded.compressed === true;
69
+ });
70
+ test('Uncompressed flag is false', () => {
71
+ const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
72
+ const encoded = TPSUID7RB.encodeBinaryB64(tps, { compress: false });
73
+ const decoded = TPSUID7RB.decodeBinaryB64(encoded);
74
+ return decoded.compressed === false;
75
+ });
76
+ test('Compression flag bit is set correctly', () => {
77
+ const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
78
+ const compressed = TPSUID7RB.encodeBinary(tps, { compress: true });
79
+ const uncompressed = TPSUID7RB.encodeBinary(tps, { compress: false });
80
+ return (compressed[5] & 0x01) === 0x01 && (uncompressed[5] & 0x01) === 0x00;
81
+ });
82
+ console.log();
83
+ // ============================================================================
84
+ // PART 3: Epoch Time
85
+ // ============================================================================
86
+ console.log('--- Part 3: Epoch Time ---\n');
87
+ test('Epoch time is extracted correctly', () => {
88
+ const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09.h14.n30.s25';
89
+ const decoded = TPSUID7RB.decodeBinaryB64(TPSUID7RB.encodeBinaryB64(tps));
90
+ const expectedEpoch = Date.UTC(2026, 0, 9, 14, 30, 25);
91
+ return decoded.epochMs === expectedEpoch;
92
+ });
93
+ test('Custom epochMs override works', () => {
94
+ const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
95
+ const customEpoch = 1700000000000;
96
+ const encoded = TPSUID7RB.encodeBinaryB64(tps, { epochMs: customEpoch });
97
+ const decoded = TPSUID7RB.decodeBinaryB64(encoded);
98
+ return decoded.epochMs === customEpoch;
99
+ });
100
+ test('Epoch parsing from TPS with time-only format', () => {
101
+ const tps = 'tps://32,35@T:greg.m3.c1.y26.M06.d15.h10.n00.s00';
102
+ const encoded = TPSUID7RB.encodeBinaryB64(tps);
103
+ const decoded = TPSUID7RB.decodeBinaryB64(encoded);
104
+ const expected = Date.UTC(2026, 5, 15, 10, 0, 0);
105
+ return decoded.epochMs === expected;
106
+ });
107
+ console.log();
108
+ // ============================================================================
109
+ // PART 4: Validation
110
+ // ============================================================================
111
+ console.log('--- Part 4: Validation ---\n');
112
+ test('Valid TPS-UID passes validation', () => {
113
+ const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
114
+ const encoded = TPSUID7RB.encodeBinaryB64(tps);
115
+ return TPSUID7RB.validateBinaryB64(encoded);
116
+ });
117
+ test('Invalid prefix fails validation', () => {
118
+ return !TPSUID7RB.validateBinaryB64('invalid_abc123');
119
+ });
120
+ test('Empty string fails validation', () => {
121
+ return !TPSUID7RB.validateBinaryB64('');
122
+ });
123
+ test('Wrong prefix fails validation', () => {
124
+ return !TPSUID7RB.validateBinaryB64('tpsuid7r_abc123');
125
+ });
126
+ console.log();
127
+ // ============================================================================
128
+ // PART 5: Error Handling
129
+ // ============================================================================
130
+ console.log('--- Part 5: Error Handling ---\n');
131
+ test('Missing prefix throws error', () => {
132
+ try {
133
+ TPSUID7RB.decodeBinaryB64('abc123');
134
+ return false;
135
+ }
136
+ catch (e) {
137
+ return e.message.includes('missing prefix');
138
+ }
139
+ });
140
+ test('Bad magic throws error', () => {
141
+ try {
142
+ const badBytes = new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x01, 0x00]);
143
+ TPSUID7RB.decodeBinary(badBytes);
144
+ return false;
145
+ }
146
+ catch (e) {
147
+ return e.message.includes('bad magic');
148
+ }
149
+ });
150
+ test('Too short throws error', () => {
151
+ try {
152
+ const shortBytes = new Uint8Array([0x54, 0x50, 0x55, 0x37]);
153
+ TPSUID7RB.decodeBinary(shortBytes);
154
+ return false;
155
+ }
156
+ catch (e) {
157
+ return e.message.includes('too short');
158
+ }
159
+ });
160
+ test('Unsupported version throws error', () => {
161
+ try {
162
+ const badVersion = new Uint8Array(20);
163
+ badVersion.set([0x54, 0x50, 0x55, 0x37, 0x99], 0); // TPU7 with bad version
164
+ TPSUID7RB.decodeBinary(badVersion);
165
+ return false;
166
+ }
167
+ catch (e) {
168
+ return e.message.includes('unsupported version');
169
+ }
170
+ });
171
+ console.log();
172
+ // ============================================================================
173
+ // PART 6: Generate
174
+ // ============================================================================
175
+ console.log('--- Part 6: Generate ---\n');
176
+ test('Generate creates valid TPS-UID', () => {
177
+ const generated = TPSUID7RB.generate();
178
+ return TPSUID7RB.validateBinaryB64(generated);
179
+ });
180
+ test('Generate with location includes coordinates', () => {
181
+ const generated = TPSUID7RB.generate({ latitude: 32.5, longitude: 35.5 });
182
+ const decoded = TPSUID7RB.decodeBinaryB64(generated);
183
+ return decoded.tps.includes('32.5,35.5');
184
+ });
185
+ test('Generate with altitude includes altitude', () => {
186
+ const generated = TPSUID7RB.generate({
187
+ latitude: 32.5,
188
+ longitude: 35.5,
189
+ altitude: 100,
190
+ });
191
+ const decoded = TPSUID7RB.decodeBinaryB64(generated);
192
+ return decoded.tps.includes('100m');
193
+ });
194
+ test('Generate without location uses unknown', () => {
195
+ const generated = TPSUID7RB.generate();
196
+ const decoded = TPSUID7RB.decodeBinaryB64(generated);
197
+ return decoded.tps.includes('unknown@');
198
+ });
199
+ console.log();
200
+ // ============================================================================
201
+ // PART 7: Edge Cases
202
+ // ============================================================================
203
+ console.log('--- Part 7: Edge Cases ---\n');
204
+ test('Long TPS string roundtrip', () => {
205
+ const tps = 'tps://31.9523456,35.9123456,1234.56m@T:greg.m3.c1.y26.M12.d31.h23.n59.s59;extension1;extension2;extension3';
206
+ const encoded = TPSUID7RB.encodeBinaryB64(tps);
207
+ const decoded = TPSUID7RB.decodeBinaryB64(encoded);
208
+ return decoded.tps === tps;
209
+ });
210
+ test('Unicode in extensions survives roundtrip', () => {
211
+ const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d01;note=تجربة';
212
+ const encoded = TPSUID7RB.encodeBinaryB64(tps);
213
+ const decoded = TPSUID7RB.decodeBinaryB64(encoded);
214
+ return decoded.tps === tps;
215
+ });
216
+ test('Privacy location types preserved', () => {
217
+ const types = ['unknown', 'hidden', 'redacted'];
218
+ return types.every((type) => {
219
+ const tps = `tps://${type}@T:greg.m3.c1.y26.M01.d01`;
220
+ const decoded = TPSUID7RB.decodeBinaryB64(TPSUID7RB.encodeBinaryB64(tps));
221
+ return decoded.tps.includes(type);
222
+ });
223
+ });
224
+ console.log();
225
+ // ============================================================================
226
+ // PART 8: Cryptographic Sealing
227
+ // ============================================================================
228
+ console.log('--- Part 8: Cryptographic Sealing ---\n');
229
+ // Helper to generate keys for testing
230
+ const crypto = require('crypto');
231
+ const { privateKey, publicKey } = crypto.generateKeyPairSync('ed25519');
232
+ test('Seal creates valid sealed binary', () => {
233
+ const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
234
+ const sealed = TPSUID7RB.seal(tps, privateKey);
235
+ // Minimal length: Header(16) + Payload + SealType(1) + Sig(64)
236
+ // Payload 'tps://...' is ~40 chars. Total ~ 120 bytes.
237
+ return sealed.length > 80 && sealed[sealed.length - 65] === 0x01; // Seal Type check
238
+ });
239
+ test('Verify and decode works for sealed UID', () => {
240
+ const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09.h12.n00';
241
+ const sealed = TPSUID7RB.seal(tps, privateKey);
242
+ const result = TPSUID7RB.verifyAndDecode(sealed, publicKey);
243
+ return result.tps === tps;
244
+ });
245
+ test('Tampered sealed UID fails verification', () => {
246
+ const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
247
+ const sealed = TPSUID7RB.seal(tps, privateKey);
248
+ // Tamper with payload (change a byte in the middle)
249
+ sealed[20] ^= 0xFF; // Flip bits
250
+ try {
251
+ TPSUID7RB.verifyAndDecode(sealed, publicKey);
252
+ return false;
253
+ }
254
+ catch (e) {
255
+ return e.message.includes('verification failed');
256
+ }
257
+ });
258
+ test('Verify fails with wrong public key', () => {
259
+ const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
260
+ const sealed = TPSUID7RB.seal(tps, privateKey);
261
+ const { publicKey: otherKey } = crypto.generateKeyPairSync('ed25519');
262
+ try {
263
+ TPSUID7RB.verifyAndDecode(sealed, otherKey);
264
+ return false;
265
+ }
266
+ catch (e) {
267
+ return e.message.includes('verification failed'); // Or openssl error
268
+ }
269
+ });
270
+ test('Sealing with compression works', () => {
271
+ const tps = 'tps://31.95,35.91,800m@T:greg.m3.c1.y26.M01.d09.h14.n30.s25'; // Longer for compression
272
+ const sealed = TPSUID7RB.seal(tps, privateKey, { compress: true });
273
+ const result = TPSUID7RB.verifyAndDecode(sealed, publicKey);
274
+ // Check compression flag (bit 0 and bit 1 set = 0x03)
275
+ // Header index 5 is flags
276
+ // The decode result doesn't explicitly return flags byte, but 'compressed' boolean
277
+ return result.tps === tps && result.compressed === true;
278
+ });
279
+ console.log();
280
+ // ============================================================================
281
+ // Summary
282
+ // ============================================================================
283
+ console.log('=== Test Summary ===\n');
284
+ console.log(`Total: ${passed + failed}`);
285
+ console.log(`Passed: ${passed}`);
286
+ console.log(`Failed: ${failed}`);
287
+ console.log();
288
+ if (failed > 0) {
289
+ console.log('❌ Some tests failed!');
290
+ process.exit(1);
291
+ }
292
+ else {
293
+ console.log('✅ All tests passed!');
294
+ process.exit(0);
295
+ }
@@ -0,0 +1,240 @@
1
+ /**
2
+ * TPS-UID v1 Tests
3
+ * Tests for the TPSUID7RB (Binary Reversible) identifier format.
4
+ * @version 1.0
5
+ */
6
+ import { TPSUID7RB } from '../src/index';
7
+ console.log('=== TPS-UID v1 Test Suite ===\n');
8
+ let passed = 0;
9
+ let failed = 0;
10
+ function test(name, fn) {
11
+ try {
12
+ const result = fn();
13
+ if (result) {
14
+ console.log(`✅ ${name}`);
15
+ passed++;
16
+ }
17
+ else {
18
+ console.log(`❌ ${name} - assertion failed`);
19
+ failed++;
20
+ }
21
+ }
22
+ catch (error) {
23
+ console.log(`❌ ${name} - ${error.message}`);
24
+ failed++;
25
+ }
26
+ }
27
+ // ============================================================================
28
+ // PART 1: Basic Encode/Decode Roundtrip
29
+ // ============================================================================
30
+ console.log('--- Part 1: Basic Encode/Decode ---\n');
31
+ test('Base64url encode/decode roundtrip', () => {
32
+ const tps = 'tps://31.95,35.91,800m@T:greg.m3.c1.y26.M01.d09.h14.n30.s25';
33
+ const encoded = TPSUID7RB.encodeBinaryB64(tps);
34
+ const decoded = TPSUID7RB.decodeBinaryB64(encoded);
35
+ return decoded.tps === tps;
36
+ });
37
+ test('Binary encode/decode roundtrip', () => {
38
+ const tps = 'tps://32,35@T:greg.m3.c1.y26.M01.d09';
39
+ const bytes = TPSUID7RB.encodeBinary(tps);
40
+ const decoded = TPSUID7RB.decodeBinary(bytes);
41
+ return decoded.tps === tps;
42
+ });
43
+ test('Prefix is correct', () => {
44
+ const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
45
+ const encoded = TPSUID7RB.encodeBinaryB64(tps);
46
+ return encoded.startsWith('tpsuid7rb_');
47
+ });
48
+ test('Magic bytes are TPU7', () => {
49
+ const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
50
+ const bytes = TPSUID7RB.encodeBinary(tps);
51
+ const magic = String.fromCharCode(bytes[0], bytes[1], bytes[2], bytes[3]);
52
+ return magic === 'TPU7';
53
+ });
54
+ test('Version byte is 0x01', () => {
55
+ const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
56
+ const bytes = TPSUID7RB.encodeBinary(tps);
57
+ return bytes[4] === 0x01;
58
+ });
59
+ console.log();
60
+ // ============================================================================
61
+ // PART 2: Compression
62
+ // ============================================================================
63
+ console.log('--- Part 2: Compression ---\n');
64
+ test('Compressed encode/decode roundtrip', () => {
65
+ const tps = 'tps://31.95,35.91,800m@T:greg.m3.c1.y26.M01.d09.h14.n30.s25';
66
+ const encoded = TPSUID7RB.encodeBinaryB64(tps, { compress: true });
67
+ const decoded = TPSUID7RB.decodeBinaryB64(encoded);
68
+ return decoded.tps === tps && decoded.compressed === true;
69
+ });
70
+ test('Uncompressed flag is false', () => {
71
+ const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
72
+ const encoded = TPSUID7RB.encodeBinaryB64(tps, { compress: false });
73
+ const decoded = TPSUID7RB.decodeBinaryB64(encoded);
74
+ return decoded.compressed === false;
75
+ });
76
+ test('Compression flag bit is set correctly', () => {
77
+ const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
78
+ const compressed = TPSUID7RB.encodeBinary(tps, { compress: true });
79
+ const uncompressed = TPSUID7RB.encodeBinary(tps, { compress: false });
80
+ return (compressed[5] & 0x01) === 0x01 && (uncompressed[5] & 0x01) === 0x00;
81
+ });
82
+ console.log();
83
+ // ============================================================================
84
+ // PART 3: Epoch Time
85
+ // ============================================================================
86
+ console.log('--- Part 3: Epoch Time ---\n');
87
+ test('Epoch time is extracted correctly', () => {
88
+ const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09.h14.n30.s25';
89
+ const decoded = TPSUID7RB.decodeBinaryB64(TPSUID7RB.encodeBinaryB64(tps));
90
+ const expectedEpoch = Date.UTC(2026, 0, 9, 14, 30, 25);
91
+ return decoded.epochMs === expectedEpoch;
92
+ });
93
+ test('Custom epochMs override works', () => {
94
+ const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
95
+ const customEpoch = 1700000000000;
96
+ const encoded = TPSUID7RB.encodeBinaryB64(tps, { epochMs: customEpoch });
97
+ const decoded = TPSUID7RB.decodeBinaryB64(encoded);
98
+ return decoded.epochMs === customEpoch;
99
+ });
100
+ test('Epoch parsing from TPS with time-only format', () => {
101
+ const tps = 'tps://32,35@T:greg.m3.c1.y26.M06.d15.h10.n00.s00';
102
+ const encoded = TPSUID7RB.encodeBinaryB64(tps);
103
+ const decoded = TPSUID7RB.decodeBinaryB64(encoded);
104
+ const expected = Date.UTC(2026, 5, 15, 10, 0, 0);
105
+ return decoded.epochMs === expected;
106
+ });
107
+ console.log();
108
+ // ============================================================================
109
+ // PART 4: Validation
110
+ // ============================================================================
111
+ console.log('--- Part 4: Validation ---\n');
112
+ test('Valid TPS-UID passes validation', () => {
113
+ const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
114
+ const encoded = TPSUID7RB.encodeBinaryB64(tps);
115
+ return TPSUID7RB.validateBinaryB64(encoded);
116
+ });
117
+ test('Invalid prefix fails validation', () => {
118
+ return !TPSUID7RB.validateBinaryB64('invalid_abc123');
119
+ });
120
+ test('Empty string fails validation', () => {
121
+ return !TPSUID7RB.validateBinaryB64('');
122
+ });
123
+ test('Wrong prefix fails validation', () => {
124
+ return !TPSUID7RB.validateBinaryB64('tpsuid7r_abc123');
125
+ });
126
+ console.log();
127
+ // ============================================================================
128
+ // PART 5: Error Handling
129
+ // ============================================================================
130
+ console.log('--- Part 5: Error Handling ---\n');
131
+ test('Missing prefix throws error', () => {
132
+ try {
133
+ TPSUID7RB.decodeBinaryB64('abc123');
134
+ return false;
135
+ }
136
+ catch (e) {
137
+ return e.message.includes('missing prefix');
138
+ }
139
+ });
140
+ test('Bad magic throws error', () => {
141
+ try {
142
+ const badBytes = new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x01, 0x00]);
143
+ TPSUID7RB.decodeBinary(badBytes);
144
+ return false;
145
+ }
146
+ catch (e) {
147
+ return e.message.includes('bad magic');
148
+ }
149
+ });
150
+ test('Too short throws error', () => {
151
+ try {
152
+ const shortBytes = new Uint8Array([0x54, 0x50, 0x55, 0x37]);
153
+ TPSUID7RB.decodeBinary(shortBytes);
154
+ return false;
155
+ }
156
+ catch (e) {
157
+ return e.message.includes('too short');
158
+ }
159
+ });
160
+ test('Unsupported version throws error', () => {
161
+ try {
162
+ const badVersion = new Uint8Array(20);
163
+ badVersion.set([0x54, 0x50, 0x55, 0x37, 0x99], 0); // TPU7 with bad version
164
+ TPSUID7RB.decodeBinary(badVersion);
165
+ return false;
166
+ }
167
+ catch (e) {
168
+ return e.message.includes('unsupported version');
169
+ }
170
+ });
171
+ console.log();
172
+ // ============================================================================
173
+ // PART 6: Generate
174
+ // ============================================================================
175
+ console.log('--- Part 6: Generate ---\n');
176
+ test('Generate creates valid TPS-UID', () => {
177
+ const generated = TPSUID7RB.generate();
178
+ return TPSUID7RB.validateBinaryB64(generated);
179
+ });
180
+ test('Generate with location includes coordinates', () => {
181
+ const generated = TPSUID7RB.generate({ latitude: 32.5, longitude: 35.5 });
182
+ const decoded = TPSUID7RB.decodeBinaryB64(generated);
183
+ return decoded.tps.includes('32.5,35.5');
184
+ });
185
+ test('Generate with altitude includes altitude', () => {
186
+ const generated = TPSUID7RB.generate({
187
+ latitude: 32.5,
188
+ longitude: 35.5,
189
+ altitude: 100,
190
+ });
191
+ const decoded = TPSUID7RB.decodeBinaryB64(generated);
192
+ return decoded.tps.includes('100m');
193
+ });
194
+ test('Generate without location uses unknown', () => {
195
+ const generated = TPSUID7RB.generate();
196
+ const decoded = TPSUID7RB.decodeBinaryB64(generated);
197
+ return decoded.tps.includes('unknown@');
198
+ });
199
+ console.log();
200
+ // ============================================================================
201
+ // PART 7: Edge Cases
202
+ // ============================================================================
203
+ console.log('--- Part 7: Edge Cases ---\n');
204
+ test('Long TPS string roundtrip', () => {
205
+ const tps = 'tps://31.9523456,35.9123456,1234.56m@T:greg.m3.c1.y26.M12.d31.h23.n59.s59;extension1;extension2;extension3';
206
+ const encoded = TPSUID7RB.encodeBinaryB64(tps);
207
+ const decoded = TPSUID7RB.decodeBinaryB64(encoded);
208
+ return decoded.tps === tps;
209
+ });
210
+ test('Unicode in extensions survives roundtrip', () => {
211
+ const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d01;note=تجربة';
212
+ const encoded = TPSUID7RB.encodeBinaryB64(tps);
213
+ const decoded = TPSUID7RB.decodeBinaryB64(encoded);
214
+ return decoded.tps === tps;
215
+ });
216
+ test('Privacy location types preserved', () => {
217
+ const types = ['unknown', 'hidden', 'redacted'];
218
+ return types.every((type) => {
219
+ const tps = `tps://${type}@T:greg.m3.c1.y26.M01.d01`;
220
+ const decoded = TPSUID7RB.decodeBinaryB64(TPSUID7RB.encodeBinaryB64(tps));
221
+ return decoded.tps.includes(type);
222
+ });
223
+ });
224
+ console.log();
225
+ // ============================================================================
226
+ // Summary
227
+ // ============================================================================
228
+ console.log('=== Test Summary ===\n');
229
+ console.log(`Total: ${passed + failed}`);
230
+ console.log(`Passed: ${passed}`);
231
+ console.log(`Failed: ${failed}`);
232
+ console.log();
233
+ if (failed > 0) {
234
+ console.log('❌ Some tests failed!');
235
+ process.exit(1);
236
+ }
237
+ else {
238
+ console.log('✅ All tests passed!');
239
+ process.exit(0);
240
+ }
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@nextera.one/tps-standard",
3
- "version": "0.4.3",
4
- "description": "The Universal Protocol for Space-Time Coordinates. A standard URI scheme (tps://) combining WGS84 spatial data with hierarchical, multi-calendar temporal coordinates.",
3
+ "version": "0.5.0",
4
+ "description": "The Universal Protocol for Space-Time Coordinates. A standard URI scheme (tps://) combining WGS84 spatial data with hierarchical, multi-calendar temporal coordinates. Includes TPS-UID: time-first, reversible event identifiers.",
5
5
  "keywords": [
6
6
  "tps",
7
+ "tps-uid",
7
8
  "time-protocol",
8
9
  "space-time",
9
10
  "reality-address",