@nextera.one/tps-standard 0.4.2 → 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.
package/README.md CHANGED
@@ -7,7 +7,7 @@ TPS defines a deterministic way to represent **when** something happened, **wher
7
7
  ## 📦 Installation
8
8
 
9
9
  ```bash
10
- npm install tps-standard
10
+ npm i @nextera.one/tps-standard
11
11
  ```
12
12
 
13
13
  ## 🚀 Quick Start
@@ -15,27 +15,27 @@ npm install tps-standard
15
15
  ### Basic Example
16
16
 
17
17
  ```ts
18
- import { TPS } from 'tps-standard';
18
+ import { TPS } from "tps-standard";
19
19
 
20
20
  // Create a TPS time string from current date
21
- const nowTime = TPS.fromDate(new Date(), 'greg');
21
+ const nowTime = TPS.fromDate(new Date(), "greg");
22
22
  console.log(nowTime);
23
23
  // Output: "T:greg.m3.c1.y26.M01.d07.h13.n20.s45"
24
24
 
25
25
  // Parse a full TPS URI with location and extensions
26
- const uri = 'tps://31.95,35.91,800@T:greg.m3.c1.y26.M01.d07.h13.n20;f4;r7';
26
+ const uri = "tps://31.95,35.91,800@T:greg.m3.c1.y26.M01.d07.h13.n20;f4;r7";
27
27
  const parsed = TPS.parse(uri);
28
28
  console.log(parsed);
29
29
  // { latitude: 31.95, longitude: 35.91, altitude: 800, calendar: 'greg', year: 26, ... }
30
30
 
31
31
  // Convert back to URI
32
32
  const components = {
33
- calendar: 'greg',
33
+ calendar: "greg",
34
34
  year: 26,
35
35
  month: 1,
36
36
  day: 7,
37
37
  latitude: 31.95,
38
- longitude: 35.91
38
+ longitude: 35.91,
39
39
  };
40
40
  const uriString = TPS.toURI(components);
41
41
  console.log(uriString);
@@ -52,34 +52,35 @@ TPS represents time as a coordinate using this hierarchy:
52
52
  T:greg.m3.c1.y26.M01.d07.h13.n20.s45
53
53
  ```
54
54
 
55
- | Component | Meaning |
56
- |-----------|---------|
57
- | `greg` | Calendar code (gregorian) |
58
- | `m3` | Millennium 3 (2000-2999) |
59
- | `c1` | Century 1 (2000-2099) |
60
- | `y26` | Year 26 (2026) |
61
- | `M01` | Month 01 (January) |
62
- | `d07` | Day 07 |
63
- | `h13` | Hour 13 (1:00 PM) |
64
- | `n20` | Minute 20 |
65
- | `s45` | Second 45 |
55
+ | Component | Meaning |
56
+ | --------- | ------------------------- |
57
+ | `greg` | Calendar code (gregorian) |
58
+ | `m3` | Millennium 3 (2000-2999) |
59
+ | `c1` | Century 1 (2000-2099) |
60
+ | `y26` | Year 26 (2026) |
61
+ | `M01` | Month 01 (January) |
62
+ | `d07` | Day 07 |
63
+ | `h13` | Hour 13 (1:00 PM) |
64
+ | `n20` | Minute 20 |
65
+ | `s45` | Second 45 |
66
66
 
67
67
  Partial coordinates represent **time volumes** (entire year, century, etc.).
68
68
 
69
69
  ### TPS URI Format
70
70
 
71
71
  **Canonical form:**
72
+
72
73
  ```
73
74
  tps://[SPACE]@[TIME][;EXTENSIONS]
74
75
  ```
75
76
 
76
77
  #### Components
77
78
 
78
- | Component | Description |
79
- |-----------|-------------|
80
- | `SPACE` | `lat,lon[,alt]m` (WGS84) or `unknown`/`hidden`/`redacted` |
81
- | `TIME` | TPS Time format (`T:calendar.hierarchy`) |
82
- | `EXTENSIONS` | Optional context (key-value pairs separated by dots) |
79
+ | Component | Description |
80
+ | ------------ | --------------------------------------------------------- |
81
+ | `SPACE` | `lat,lon[,alt]m` (WGS84) or `unknown`/`hidden`/`redacted` |
82
+ | `TIME` | TPS Time format (`T:calendar.hierarchy`) |
83
+ | `EXTENSIONS` | Optional context (key-value pairs separated by dots) |
83
84
 
84
85
  #### Location Privacy
85
86
 
@@ -92,57 +93,156 @@ tps://[SPACE]@[TIME][;EXTENSIONS]
92
93
 
93
94
  - `greg` — Gregorian calendar (default)
94
95
  - `unix` — Unix epoch seconds
95
- - `hij` — Hijri (Islamic) — *requires driver*
96
- - `jul` — Julian — *requires driver*
97
- - `holo` — Holocene — *requires driver*
96
+ - `hij` — Hijri (Islamic) — _requires driver_
97
+ - `jul` — Julian — _requires driver_
98
+ - `holo` — Holocene — _requires driver_
98
99
 
99
100
  ## 🔌 Plugin Architecture
100
101
 
101
- TPS supports custom calendar drivers for non-Gregorian systems.
102
+ TPS supports custom calendar drivers for non-Gregorian systems. Drivers can wrap external date libraries (like `moment-hijri`, `@js-joda/extra`, etc.).
102
103
 
103
104
  ### CalendarDriver Interface
104
105
 
105
106
  ```ts
106
107
  export interface CalendarDriver {
107
108
  readonly code: CalendarCode;
109
+ readonly name?: string; // Optional human-readable name
110
+
111
+ // Required methods
108
112
  fromGregorian(date: Date): Partial<TPSComponents>;
109
113
  toGregorian(components: Partial<TPSComponents>): Date;
110
114
  fromDate(date: Date): string;
115
+
116
+ // Optional enhanced methods
117
+ parseDate?(input: string, format?: string): Partial<TPSComponents>;
118
+ format?(components: Partial<TPSComponents>, format?: string): string;
119
+ validate?(input: string | Partial<TPSComponents>): boolean;
120
+ getMetadata?(): CalendarMetadata;
111
121
  }
112
122
  ```
113
123
 
114
124
  ### Register a Custom Driver
115
125
 
116
126
  ```ts
117
- import { TPS, CalendarDriver, TPSComponents } from 'tps-standard';
127
+ import { TPS, CalendarDriver, TPSComponents } from "@nextera.one/tps-standard";
118
128
 
119
129
  class HijriDriver implements CalendarDriver {
120
- readonly code = 'hij';
130
+ readonly code = "hij";
131
+ readonly name = "Hijri (Islamic)";
132
+
133
+ // Parse a Hijri date string like '1447-07-21'
134
+ parseDate(input: string): Partial<TPSComponents> {
135
+ const [year, month, day] = input.split("-").map(Number);
136
+ return { calendar: "hij", year, month, day };
137
+ }
138
+
139
+ // Format components to Hijri date string
140
+ format(comp: Partial<TPSComponents>): string {
141
+ return `${comp.year}-${String(comp.month).padStart(2, "0")}-${String(
142
+ comp.day
143
+ ).padStart(2, "0")}`;
144
+ }
121
145
 
122
146
  fromGregorian(date: Date): Partial<TPSComponents> {
123
- // Convert Gregorian date to Hijri components
124
- // ... conversion logic ...
125
- return { year: 1445, month: 7, day: 15 };
147
+ // Use external library for accurate conversion
148
+ // Example with moment-hijri:
149
+ // const m = moment(date);
150
+ // return { year: m.iYear(), month: m.iMonth() + 1, day: m.iDate() };
151
+ return { year: 1447, month: 7, day: 21 };
126
152
  }
127
153
 
128
154
  toGregorian(components: Partial<TPSComponents>): Date {
129
- // Convert Hijri components to Gregorian Date
130
- // ... conversion logic ...
155
+ // Reverse conversion using external library
131
156
  return new Date();
132
157
  }
133
158
 
134
159
  fromDate(date: Date): string {
135
160
  const comp = this.fromGregorian(date);
136
- return `T:hij.y${comp.year}.M${comp.month}.d${comp.day}`;
161
+ const pad = (n?: number) => String(n || 0).padStart(2, "0");
162
+ return `T:hij.y${comp.year}.M${pad(comp.month)}.d${pad(comp.day)}`;
137
163
  }
138
164
  }
139
165
 
140
166
  // Register the driver
141
167
  TPS.registerDriver(new HijriDriver());
168
+ ```
169
+
170
+ ### Using Calendar Drivers
171
+
172
+ ```ts
173
+ // Parse a Hijri date string directly
174
+ const components = TPS.parseCalendarDate("hij", "1447-07-21");
175
+ // { calendar: 'hij', year: 1447, month: 7, day: 21 }
176
+
177
+ // Convert to TPS URI with location
178
+ const uri = TPS.fromCalendarDate("hij", "1447-07-21", {
179
+ latitude: 31.95,
180
+ longitude: 35.91,
181
+ });
182
+ // "tps://31.95,35.91@T:hij.y1447.M07.d21"
183
+
184
+ // Format TPS components back to calendar-native string
185
+ const parsed = TPS.parse("tps://unknown@T:hij.y1447.M07.d21");
186
+ const formatted = TPS.formatCalendarDate("hij", parsed);
187
+ // "1447-07-21"
188
+
189
+ // Using the driver directly
190
+ const driver = TPS.getDriver("hij");
191
+ if (driver?.parseDate) {
192
+ const comp = driver.parseDate("1447-07-21");
193
+ const gregDate = driver.toGregorian(comp);
194
+ }
195
+ ```
196
+
197
+ ### Wrapping External Libraries
198
+
199
+ Example with `moment-hijri`:
142
200
 
143
- // Now use it
144
- const hijriTime = TPS.fromDate(new Date(), 'hij');
145
- console.log(hijriTime);
201
+ ```ts
202
+ import moment from "moment-hijri";
203
+
204
+ class MomentHijriDriver implements CalendarDriver {
205
+ readonly code = "hij";
206
+
207
+ parseDate(input: string, format = "iYYYY-iMM-iDD"): Partial<TPSComponents> {
208
+ const m = moment(input, format);
209
+ return {
210
+ calendar: "hij",
211
+ year: m.iYear(),
212
+ month: m.iMonth() + 1,
213
+ day: m.iDate(),
214
+ hour: m.hour(),
215
+ minute: m.minute(),
216
+ second: m.second(),
217
+ };
218
+ }
219
+
220
+ fromGregorian(date: Date): Partial<TPSComponents> {
221
+ const m = moment(date);
222
+ return {
223
+ calendar: "hij",
224
+ year: m.iYear(),
225
+ month: m.iMonth() + 1,
226
+ day: m.iDate(),
227
+ hour: m.hour(),
228
+ minute: m.minute(),
229
+ second: m.second(),
230
+ };
231
+ }
232
+
233
+ toGregorian(comp: Partial<TPSComponents>): Date {
234
+ const m = moment(`${comp.year}-${comp.month}-${comp.day}`, "iYYYY-iM-iD");
235
+ return m.toDate();
236
+ }
237
+
238
+ fromDate(date: Date): string {
239
+ const c = this.fromGregorian(date);
240
+ const p = (n?: number) => String(n || 0).padStart(2, "0");
241
+ return `T:hij.y${c.year}.M${p(c.month)}.d${p(c.day)}.h${p(c.hour)}.n${p(
242
+ c.minute
243
+ )}.s${p(c.second)}`;
244
+ }
245
+ }
146
246
  ```
147
247
 
148
248
  ## 📚 API Reference
@@ -152,8 +252,8 @@ console.log(hijriTime);
152
252
  Validates whether a string is properly formatted TPS.
153
253
 
154
254
  ```ts
155
- TPS.validate('tps://31.95,35.91@T:greg.m3.c1.y26'); // true
156
- TPS.validate('invalid'); // false
255
+ TPS.validate("tps://31.95,35.91@T:greg.m3.c1.y26"); // true
256
+ TPS.validate("invalid"); // false
157
257
  ```
158
258
 
159
259
  ### `TPS.parse(input: string): TPSComponents | null`
@@ -161,7 +261,9 @@ TPS.validate('invalid'); // false
161
261
  Parses a TPS string into components. Returns `null` if invalid.
162
262
 
163
263
  ```ts
164
- const parsed = TPS.parse('tps://31.95,35.91,800@T:greg.m3.c1.y26.M01.d07.h13.n20;f4;r7');
264
+ const parsed = TPS.parse(
265
+ "tps://31.95,35.91,800@T:greg.m3.c1.y26.M01.d07.h13.n20;f4;r7"
266
+ );
165
267
  // {
166
268
  // latitude: 31.95,
167
269
  // longitude: 35.91,
@@ -184,14 +286,14 @@ Converts a components object into a canonical TPS URI string.
184
286
 
185
287
  ```ts
186
288
  const components = {
187
- calendar: 'greg',
289
+ calendar: "greg",
188
290
  year: 26,
189
291
  month: 1,
190
292
  day: 7,
191
293
  latitude: 31.95,
192
294
  longitude: 35.91,
193
295
  altitude: 800,
194
- extensions: { f: "4", r: "7" }
296
+ extensions: { f: "4", r: "7" },
195
297
  };
196
298
  const uri = TPS.toURI(components);
197
299
  // "tps://31.95,35.91,800m@T:greg.y26.M01.d07;f4.r7"
@@ -202,10 +304,10 @@ const uri = TPS.toURI(components);
202
304
  Generates a TPS time string from a JavaScript Date. Supports registered drivers.
203
305
 
204
306
  ```ts
205
- const timeString = TPS.fromDate(new Date(), 'greg');
307
+ const timeString = TPS.fromDate(new Date(), "greg");
206
308
  // "T:greg.m3.c1.y26.M01.d07.h13.n20.s45"
207
309
 
208
- const unixTime = TPS.fromDate(new Date(), 'unix');
310
+ const unixTime = TPS.fromDate(new Date(), "unix");
209
311
  // "T:unix.s1704729645.123"
210
312
  ```
211
313
 
@@ -214,7 +316,7 @@ const unixTime = TPS.fromDate(new Date(), 'unix');
214
316
  Converts a TPS string back to a JavaScript Date object.
215
317
 
216
318
  ```ts
217
- const date = TPS.toDate('T:greg.m3.c1.y26.M01.d07.h13.n20.s45');
319
+ const date = TPS.toDate("T:greg.m3.c1.y26.M01.d07.h13.n20.s45");
218
320
  console.log(date); // Date object for 2026-01-07 13:20:45 UTC
219
321
  ```
220
322
 
@@ -223,8 +325,8 @@ console.log(date); // Date object for 2026-01-07 13:20:45 UTC
223
325
  Converts a TPS string from one calendar to another using registered drivers.
224
326
 
225
327
  ```ts
226
- const gregTime = 'T:greg.m3.c1.y26.M01.d07';
227
- const hijriTime = TPS.to('hij', gregTime);
328
+ const gregTime = "T:greg.m3.c1.y26.M01.d07";
329
+ const hijriTime = TPS.to("hij", gregTime);
228
330
  // Requires registered Hijri driver
229
331
  ```
230
332
 
@@ -242,7 +344,7 @@ TPS.registerDriver(hijriDriver);
242
344
  Retrieves a registered calendar driver.
243
345
 
244
346
  ```ts
245
- const driver = TPS.getDriver('hij');
347
+ const driver = TPS.getDriver("hij");
246
348
  if (driver) {
247
349
  const components = driver.fromGregorian(new Date());
248
350
  }
@@ -284,7 +386,124 @@ interface TPSComponents {
284
386
  ### `CalendarCode`
285
387
 
286
388
  ```ts
287
- type CalendarCode = 'greg' | 'hij' | 'jul' | 'holo' | 'unix';
389
+ type CalendarCode = "greg" | "hij" | "jul" | "holo" | "unix";
390
+ ```
391
+
392
+ ## 🆔 TPS-UID — Temporal Positioning Identifier
393
+
394
+ TPS-UID is a **time-first, reversible identifier** format that binds an event to a TPS coordinate. Unlike UUIDs which identify records, TPS-UID identifies **events in spacetime** and allows exact reconstruction of the original TPS string.
395
+
396
+ ### Why TPS-UID?
397
+
398
+ | Feature | UUID v4/v7 | TPS-UID |
399
+ | ----------------- | ---------------- | --------------- |
400
+ | **Purpose** | Identify objects | Identify events |
401
+ | **Time** | Optional/weak | Mandatory |
402
+ | **Reversible** | ❌ No | ✅ Yes |
403
+ | **Time-sortable** | v7 only | ✅ Always |
404
+ | **Audit-grade** | ❌ No | ✅ Yes |
405
+
406
+ ### Binary Schema
407
+
408
+ ```
409
+ MAGIC 4 bytes "TPU7"
410
+ VER 1 byte 0x01
411
+ FLAGS 1 byte bit0 = compression
412
+ TIME 6 bytes 48-bit epoch ms
413
+ NONCE 4 bytes collision guard
414
+ LEN varint payload length
415
+ TPS bytes UTF-8 TPS string
416
+ ```
417
+
418
+ ### Quick Start
419
+
420
+ ```ts
421
+ import { TPSUID7RB } from "@nextera.one/tps-standard";
422
+
423
+ // Create TPS-UID from a TPS string
424
+ const tps = "tps://31.95,35.91@T:greg.m3.c1.y26.M01.d09.h14.n30.s25";
425
+ const id = TPSUID7RB.encodeBinaryB64(tps);
426
+ // → "tpsuid7rb_VFBVNwEAAZujKmvo..."
427
+
428
+ // Decode back to original TPS (exact reconstruction)
429
+ const decoded = TPSUID7RB.decodeBinaryB64(id);
430
+ console.log(decoded.tps); // exact original TPS
431
+ console.log(decoded.epochMs); // 1767969025000
432
+
433
+ // Generate from current time
434
+ const generated = TPSUID7RB.generate({
435
+ latitude: 32.0,
436
+ longitude: 35.0,
437
+ });
438
+ ```
439
+
440
+ ### API Reference
441
+
442
+ #### `TPSUID7RB.encodeBinary(tps, opts?): Uint8Array`
443
+
444
+ Encode TPS string to binary bytes (canonical form).
445
+
446
+ ```ts
447
+ const bytes = TPSUID7RB.encodeBinary(tps, { compress: true });
448
+ ```
449
+
450
+ #### `TPSUID7RB.decodeBinary(bytes): TPSUID7RBDecodeResult`
451
+
452
+ Decode binary bytes back to original TPS.
453
+
454
+ ```ts
455
+ const decoded = TPSUID7RB.decodeBinary(bytes);
456
+ // { version: 'tpsuid7rb', epochMs, compressed, nonce, tps }
457
+ ```
458
+
459
+ #### `TPSUID7RB.encodeBinaryB64(tps, opts?): string`
460
+
461
+ Encode to base64url string with prefix (transport form).
462
+
463
+ ```ts
464
+ const id = TPSUID7RB.encodeBinaryB64(tps, { compress: true });
465
+ // "tpsuid7rb_..."
466
+ ```
467
+
468
+ #### `TPSUID7RB.decodeBinaryB64(id): TPSUID7RBDecodeResult`
469
+
470
+ Decode base64url string back to original TPS.
471
+
472
+ ```ts
473
+ const decoded = TPSUID7RB.decodeBinaryB64(id);
474
+ console.log(decoded.tps); // exact original
475
+ ```
476
+
477
+ #### `TPSUID7RB.validateBinaryB64(id): boolean`
478
+
479
+ Validate base64url encoded TPS-UID format.
480
+
481
+ ```ts
482
+ TPSUID7RB.validateBinaryB64("tpsuid7rb_VFB..."); // true
483
+ TPSUID7RB.validateBinaryB64("invalid"); // false
484
+ ```
485
+
486
+ #### `TPSUID7RB.generate(opts?): string`
487
+
488
+ Generate TPS-UID from current time.
489
+
490
+ ```ts
491
+ const id = TPSUID7RB.generate({
492
+ latitude: 32.0,
493
+ longitude: 35.0,
494
+ compress: true,
495
+ });
496
+ ```
497
+
498
+ ### Database Schema (Recommended)
499
+
500
+ ```sql
501
+ CREATE TABLE events (
502
+ epoch_ms BIGINT NOT NULL,
503
+ tps_uid VARBINARY(96) NOT NULL,
504
+ tps TEXT NOT NULL,
505
+ PRIMARY KEY (epoch_ms, tps_uid)
506
+ );
288
507
  ```
289
508
 
290
509
  ## 🎯 Use Cases