@nextera.one/tps-standard 0.4.2

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 ADDED
@@ -0,0 +1,309 @@
1
+ # TPS — Temporal Positioning Standard
2
+
3
+ > **A universal coordinate system for Space, Time, and Context.**
4
+
5
+ TPS defines a deterministic way to represent **when** something happened, **where** it happened, and **under which calendar**, using a single, machine-readable identifier.
6
+
7
+ ## 📦 Installation
8
+
9
+ ```bash
10
+ npm install tps-standard
11
+ ```
12
+
13
+ ## 🚀 Quick Start
14
+
15
+ ### Basic Example
16
+
17
+ ```ts
18
+ import { TPS } from 'tps-standard';
19
+
20
+ // Create a TPS time string from current date
21
+ const nowTime = TPS.fromDate(new Date(), 'greg');
22
+ console.log(nowTime);
23
+ // Output: "T:greg.m3.c1.y26.M01.d07.h13.n20.s45"
24
+
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';
27
+ const parsed = TPS.parse(uri);
28
+ console.log(parsed);
29
+ // { latitude: 31.95, longitude: 35.91, altitude: 800, calendar: 'greg', year: 26, ... }
30
+
31
+ // Convert back to URI
32
+ const components = {
33
+ calendar: 'greg',
34
+ year: 26,
35
+ month: 1,
36
+ day: 7,
37
+ latitude: 31.95,
38
+ longitude: 35.91
39
+ };
40
+ const uriString = TPS.toURI(components);
41
+ console.log(uriString);
42
+ // Output: "tps://31.95,35.91@T:greg.y26.M01.d07"
43
+ ```
44
+
45
+ ## 📖 Core Concepts
46
+
47
+ ### Time Hierarchy
48
+
49
+ TPS represents time as a coordinate using this hierarchy:
50
+
51
+ ```
52
+ T:greg.m3.c1.y26.M01.d07.h13.n20.s45
53
+ ```
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 |
66
+
67
+ Partial coordinates represent **time volumes** (entire year, century, etc.).
68
+
69
+ ### TPS URI Format
70
+
71
+ **Canonical form:**
72
+ ```
73
+ tps://[SPACE]@[TIME][;EXTENSIONS]
74
+ ```
75
+
76
+ #### Components
77
+
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) |
83
+
84
+ #### Location Privacy
85
+
86
+ - **Coordinates:** `31.95,35.91,800m` (latitude, longitude, altitude in meters)
87
+ - **Unknown:** `unknown` (technical failure, no GPS)
88
+ - **Hidden:** `hidden` (user masked their location)
89
+ - **Redacted:** `redacted` (censored for legal/security)
90
+
91
+ ### Supported Calendars
92
+
93
+ - `greg` — Gregorian calendar (default)
94
+ - `unix` — Unix epoch seconds
95
+ - `hij` — Hijri (Islamic) — *requires driver*
96
+ - `jul` — Julian — *requires driver*
97
+ - `holo` — Holocene — *requires driver*
98
+
99
+ ## 🔌 Plugin Architecture
100
+
101
+ TPS supports custom calendar drivers for non-Gregorian systems.
102
+
103
+ ### CalendarDriver Interface
104
+
105
+ ```ts
106
+ export interface CalendarDriver {
107
+ readonly code: CalendarCode;
108
+ fromGregorian(date: Date): Partial<TPSComponents>;
109
+ toGregorian(components: Partial<TPSComponents>): Date;
110
+ fromDate(date: Date): string;
111
+ }
112
+ ```
113
+
114
+ ### Register a Custom Driver
115
+
116
+ ```ts
117
+ import { TPS, CalendarDriver, TPSComponents } from 'tps-standard';
118
+
119
+ class HijriDriver implements CalendarDriver {
120
+ readonly code = 'hij';
121
+
122
+ fromGregorian(date: Date): Partial<TPSComponents> {
123
+ // Convert Gregorian date to Hijri components
124
+ // ... conversion logic ...
125
+ return { year: 1445, month: 7, day: 15 };
126
+ }
127
+
128
+ toGregorian(components: Partial<TPSComponents>): Date {
129
+ // Convert Hijri components to Gregorian Date
130
+ // ... conversion logic ...
131
+ return new Date();
132
+ }
133
+
134
+ fromDate(date: Date): string {
135
+ const comp = this.fromGregorian(date);
136
+ return `T:hij.y${comp.year}.M${comp.month}.d${comp.day}`;
137
+ }
138
+ }
139
+
140
+ // Register the driver
141
+ TPS.registerDriver(new HijriDriver());
142
+
143
+ // Now use it
144
+ const hijriTime = TPS.fromDate(new Date(), 'hij');
145
+ console.log(hijriTime);
146
+ ```
147
+
148
+ ## 📚 API Reference
149
+
150
+ ### `TPS.validate(input: string): boolean`
151
+
152
+ Validates whether a string is properly formatted TPS.
153
+
154
+ ```ts
155
+ TPS.validate('tps://31.95,35.91@T:greg.m3.c1.y26'); // true
156
+ TPS.validate('invalid'); // false
157
+ ```
158
+
159
+ ### `TPS.parse(input: string): TPSComponents | null`
160
+
161
+ Parses a TPS string into components. Returns `null` if invalid.
162
+
163
+ ```ts
164
+ const parsed = TPS.parse('tps://31.95,35.91,800@T:greg.m3.c1.y26.M01.d07.h13.n20;f4;r7');
165
+ // {
166
+ // latitude: 31.95,
167
+ // longitude: 35.91,
168
+ // altitude: 800,
169
+ // calendar: 'greg',
170
+ // millennium: 3,
171
+ // century: 1,
172
+ // year: 26,
173
+ // month: 1,
174
+ // day: 7,
175
+ // hour: 13,
176
+ // minute: 20,
177
+ // extensions: { f: "4", r: "7" }
178
+ // }
179
+ ```
180
+
181
+ ### `TPS.toURI(components: TPSComponents): string`
182
+
183
+ Converts a components object into a canonical TPS URI string.
184
+
185
+ ```ts
186
+ const components = {
187
+ calendar: 'greg',
188
+ year: 26,
189
+ month: 1,
190
+ day: 7,
191
+ latitude: 31.95,
192
+ longitude: 35.91,
193
+ altitude: 800,
194
+ extensions: { f: "4", r: "7" }
195
+ };
196
+ const uri = TPS.toURI(components);
197
+ // "tps://31.95,35.91,800m@T:greg.y26.M01.d07;f4.r7"
198
+ ```
199
+
200
+ ### `TPS.fromDate(date: Date, calendar: CalendarCode): string`
201
+
202
+ Generates a TPS time string from a JavaScript Date. Supports registered drivers.
203
+
204
+ ```ts
205
+ const timeString = TPS.fromDate(new Date(), 'greg');
206
+ // "T:greg.m3.c1.y26.M01.d07.h13.n20.s45"
207
+
208
+ const unixTime = TPS.fromDate(new Date(), 'unix');
209
+ // "T:unix.s1704729645.123"
210
+ ```
211
+
212
+ ### `TPS.toDate(tpsString: string): Date | null`
213
+
214
+ Converts a TPS string back to a JavaScript Date object.
215
+
216
+ ```ts
217
+ const date = TPS.toDate('T:greg.m3.c1.y26.M01.d07.h13.n20.s45');
218
+ console.log(date); // Date object for 2026-01-07 13:20:45 UTC
219
+ ```
220
+
221
+ ### `TPS.to(targetCalendar: CalendarCode, tpsString: string): string | null`
222
+
223
+ Converts a TPS string from one calendar to another using registered drivers.
224
+
225
+ ```ts
226
+ const gregTime = 'T:greg.m3.c1.y26.M01.d07';
227
+ const hijriTime = TPS.to('hij', gregTime);
228
+ // Requires registered Hijri driver
229
+ ```
230
+
231
+ ### `TPS.registerDriver(driver: CalendarDriver): void`
232
+
233
+ Registers a custom calendar driver plugin.
234
+
235
+ ```ts
236
+ const hijriDriver = new HijriDriver();
237
+ TPS.registerDriver(hijriDriver);
238
+ ```
239
+
240
+ ### `TPS.getDriver(code: CalendarCode): CalendarDriver | undefined`
241
+
242
+ Retrieves a registered calendar driver.
243
+
244
+ ```ts
245
+ const driver = TPS.getDriver('hij');
246
+ if (driver) {
247
+ const components = driver.fromGregorian(new Date());
248
+ }
249
+ ```
250
+
251
+ ## 📋 Types Reference
252
+
253
+ ### `TPSComponents`
254
+
255
+ ```ts
256
+ interface TPSComponents {
257
+ // Temporal
258
+ calendar: CalendarCode;
259
+ millennium?: number;
260
+ century?: number;
261
+ year?: number;
262
+ month?: number;
263
+ day?: number;
264
+ hour?: number;
265
+ minute?: number;
266
+ second?: number;
267
+ unixSeconds?: number;
268
+
269
+ // Spatial
270
+ latitude?: number;
271
+ longitude?: number;
272
+ altitude?: number;
273
+
274
+ // Location Privacy Flags
275
+ isUnknownLocation?: boolean;
276
+ isRedactedLocation?: boolean;
277
+ isHiddenLocation?: boolean;
278
+
279
+ // Context
280
+ extensions?: Record<string, string>;
281
+ }
282
+ ```
283
+
284
+ ### `CalendarCode`
285
+
286
+ ```ts
287
+ type CalendarCode = 'greg' | 'hij' | 'jul' | 'holo' | 'unix';
288
+ ```
289
+
290
+ ## 🎯 Use Cases
291
+
292
+ - **Audit Trails:** Immutable timestamp logs with explicit calendar
293
+ - **Distributed Systems:** Coordinate events across timezones
294
+ - **Security & Forensics:** Evidence timestamps with location binding
295
+ - **AI Agents:** Machine-readable event metadata
296
+ - **Long-term Archives:** Deterministic time encoding for historical records
297
+
298
+ ## 🏗️ Design Principles
299
+
300
+ - **No Assumptions:** Explicit calendar, timezone, precision
301
+ - **Deterministic Parsing:** Same input always produces same output
302
+ - **Human Auditable:** Readable hierarchical structure
303
+ - **Machine Native:** Easy regex and parsing
304
+ - **Backward Compatible:** Partial coordinates are valid
305
+ - **Privacy First:** Built-in location masking
306
+
307
+ ## 📄 License
308
+
309
+ MIT © 2026 TPS Standards Working Group
@@ -0,0 +1,106 @@
1
+ /**
2
+ * TPS: Temporal Positioning System
3
+ * The Universal Protocol for Space-Time Coordinates.
4
+ * @packageDocumentation
5
+ * @version 0.4.2
6
+ * @license MIT
7
+ * @copyright 2026 TPS Standards Working Group
8
+ */
9
+ export type CalendarCode = 'greg' | 'hij' | 'jul' | 'holo' | 'unix';
10
+ export interface TPSComponents {
11
+ calendar: CalendarCode;
12
+ millennium?: number;
13
+ century?: number;
14
+ year?: number;
15
+ month?: number;
16
+ day?: number;
17
+ hour?: number;
18
+ minute?: number;
19
+ second?: number;
20
+ unixSeconds?: number;
21
+ latitude?: number;
22
+ longitude?: number;
23
+ altitude?: number;
24
+ /** Technical missing data (e.g. server log without GPS) */
25
+ isUnknownLocation?: boolean;
26
+ /** Removed for legal/security reasons (e.g. GDPR) */
27
+ isRedactedLocation?: boolean;
28
+ /** Masked by user preference (e.g. "Don't show my location") */
29
+ isHiddenLocation?: boolean;
30
+ extensions?: Record<string, string>;
31
+ }
32
+ /**
33
+ * Interface for Calendar Driver plugins.
34
+ * Implementations must provide conversion logic to/from Gregorian.
35
+ */
36
+ export interface CalendarDriver {
37
+ /** The calendar code this driver handles (e.g., 'hij', 'jul'). */
38
+ readonly code: CalendarCode;
39
+ /**
40
+ * Converts a Gregorian Date to this calendar's components.
41
+ * @param date - The Gregorian Date object.
42
+ * @returns Partial TPS components for year, month, day, etc.
43
+ */
44
+ fromGregorian(date: Date): Partial<TPSComponents>;
45
+ /**
46
+ * Converts this calendar's components to a Gregorian Date.
47
+ * @param components - Partial TPS components (year, month, day, etc.).
48
+ * @returns A JavaScript Date object.
49
+ */
50
+ toGregorian(components: Partial<TPSComponents>): Date;
51
+ /**
52
+ * Generates a TPS time string for this calendar from a Date.
53
+ * @param date - The Gregorian Date object.
54
+ * @returns A TPS time string (e.g., "T:hij.m2.c5.y47...").
55
+ */
56
+ fromDate(date: Date): string;
57
+ }
58
+ export declare class TPS {
59
+ private static readonly drivers;
60
+ /**
61
+ * Registers a calendar driver plugin.
62
+ * @param driver - The driver instance to register.
63
+ */
64
+ static registerDriver(driver: CalendarDriver): void;
65
+ /**
66
+ * Gets a registered calendar driver.
67
+ * @param code - The calendar code.
68
+ * @returns The driver or undefined.
69
+ */
70
+ static getDriver(code: CalendarCode): CalendarDriver | undefined;
71
+ private static readonly REGEX_URI;
72
+ private static readonly REGEX_TIME;
73
+ static validate(input: string): boolean;
74
+ static parse(input: string): TPSComponents | null;
75
+ /**
76
+ * SERIALIZER: Converts a components object into a full TPS URI.
77
+ * @param comp - The TPS components.
78
+ * @returns Full URI string (e.g. "tps://...").
79
+ */
80
+ static toURI(comp: TPSComponents): string;
81
+ /**
82
+ * CONVERTER: Creates a TPS Time Object string from a JavaScript Date.
83
+ * Supports plugin drivers for non-Gregorian calendars.
84
+ * @param date - The JS Date object (defaults to Now).
85
+ * @param calendar - The target calendar driver (default 'greg').
86
+ * @returns Canonical string (e.g., "T:greg.m3.c1.y26...").
87
+ */
88
+ static fromDate(date?: Date, calendar?: CalendarCode): string;
89
+ /**
90
+ * CONVERTER: Converts a TPS string to a Date in a target calendar format.
91
+ * Uses plugin drivers for cross-calendar conversion.
92
+ * @param tpsString - The source TPS string (any calendar).
93
+ * @param targetCalendar - The target calendar code (e.g., 'hij').
94
+ * @returns A TPS string in the target calendar, or null if invalid.
95
+ */
96
+ static to(targetCalendar: CalendarCode, tpsString: string): string | null;
97
+ /**
98
+ * CONVERTER: Reconstructs a JavaScript Date object from a TPS string.
99
+ * Supports plugin drivers for non-Gregorian calendars.
100
+ * @param tpsString - The TPS string.
101
+ * @returns JS Date object or `null` if invalid.
102
+ */
103
+ static toDate(tpsString: string): Date | null;
104
+ private static _mapGroupsToComponents;
105
+ private static pad;
106
+ }
package/dist/index.js ADDED
@@ -0,0 +1,242 @@
1
+ "use strict";
2
+ /**
3
+ * TPS: Temporal Positioning System
4
+ * The Universal Protocol for Space-Time Coordinates.
5
+ * @packageDocumentation
6
+ * @version 0.4.2
7
+ * @license MIT
8
+ * @copyright 2026 TPS Standards Working Group
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.TPS = void 0;
12
+ class TPS {
13
+ /**
14
+ * Registers a calendar driver plugin.
15
+ * @param driver - The driver instance to register.
16
+ */
17
+ static registerDriver(driver) {
18
+ this.drivers.set(driver.code, driver);
19
+ }
20
+ /**
21
+ * Gets a registered calendar driver.
22
+ * @param code - The calendar code.
23
+ * @returns The driver or undefined.
24
+ */
25
+ static getDriver(code) {
26
+ return this.drivers.get(code);
27
+ }
28
+ // --- CORE METHODS ---
29
+ static validate(input) {
30
+ if (input.startsWith('tps://'))
31
+ return this.REGEX_URI.test(input);
32
+ return this.REGEX_TIME.test(input);
33
+ }
34
+ static parse(input) {
35
+ if (input.startsWith('tps://')) {
36
+ const match = this.REGEX_URI.exec(input);
37
+ if (!match || !match.groups)
38
+ return null;
39
+ return this._mapGroupsToComponents(match.groups);
40
+ }
41
+ const match = this.REGEX_TIME.exec(input);
42
+ if (!match || !match.groups)
43
+ return null;
44
+ return this._mapGroupsToComponents(match.groups);
45
+ }
46
+ /**
47
+ * SERIALIZER: Converts a components object into a full TPS URI.
48
+ * @param comp - The TPS components.
49
+ * @returns Full URI string (e.g. "tps://...").
50
+ */
51
+ static toURI(comp) {
52
+ // 1. Build Space Part
53
+ let spacePart = 'unknown'; // Default safe fallback
54
+ if (comp.isHiddenLocation) {
55
+ spacePart = 'hidden';
56
+ }
57
+ else if (comp.isRedactedLocation) {
58
+ spacePart = 'redacted';
59
+ }
60
+ else if (comp.isUnknownLocation) {
61
+ spacePart = 'unknown';
62
+ }
63
+ else if (comp.latitude !== undefined && comp.longitude !== undefined) {
64
+ spacePart = `${comp.latitude},${comp.longitude}`;
65
+ if (comp.altitude !== undefined) {
66
+ spacePart += `,${comp.altitude}m`;
67
+ }
68
+ }
69
+ // 2. Build Time Part
70
+ let timePart = `T:${comp.calendar}`;
71
+ if (comp.calendar === 'unix' && comp.unixSeconds !== undefined) {
72
+ timePart += `.s${comp.unixSeconds}`;
73
+ }
74
+ else {
75
+ if (comp.millennium !== undefined)
76
+ timePart += `.m${comp.millennium}`;
77
+ if (comp.century !== undefined)
78
+ timePart += `.c${comp.century}`;
79
+ if (comp.year !== undefined)
80
+ timePart += `.y${comp.year}`;
81
+ if (comp.month !== undefined)
82
+ timePart += `.M${this.pad(comp.month)}`;
83
+ if (comp.day !== undefined)
84
+ timePart += `.d${this.pad(comp.day)}`;
85
+ if (comp.hour !== undefined)
86
+ timePart += `.h${this.pad(comp.hour)}`;
87
+ if (comp.minute !== undefined)
88
+ timePart += `.n${this.pad(comp.minute)}`;
89
+ if (comp.second !== undefined)
90
+ timePart += `.s${this.pad(comp.second)}`;
91
+ }
92
+ // 3. Build Extensions
93
+ let extPart = '';
94
+ if (comp.extensions && Object.keys(comp.extensions).length > 0) {
95
+ const extStrings = Object.entries(comp.extensions).map(([k, v]) => `${k}${v}`);
96
+ extPart = `;${extStrings.join('.')}`;
97
+ }
98
+ return `tps://${spacePart}@${timePart}${extPart}`;
99
+ }
100
+ /**
101
+ * CONVERTER: Creates a TPS Time Object string from a JavaScript Date.
102
+ * Supports plugin drivers for non-Gregorian calendars.
103
+ * @param date - The JS Date object (defaults to Now).
104
+ * @param calendar - The target calendar driver (default 'greg').
105
+ * @returns Canonical string (e.g., "T:greg.m3.c1.y26...").
106
+ */
107
+ static fromDate(date = new Date(), calendar = 'greg') {
108
+ // Check for registered driver first
109
+ const driver = this.drivers.get(calendar);
110
+ if (driver) {
111
+ return driver.fromDate(date);
112
+ }
113
+ // Built-in handlers
114
+ if (calendar === 'unix') {
115
+ const s = (date.getTime() / 1000).toFixed(3);
116
+ return `T:unix.s${s}`;
117
+ }
118
+ if (calendar === 'greg') {
119
+ const fullYear = date.getUTCFullYear();
120
+ const m = Math.floor(fullYear / 1000) + 1;
121
+ const c = Math.floor((fullYear % 1000) / 100) + 1;
122
+ const y = fullYear % 100;
123
+ const M = date.getUTCMonth() + 1;
124
+ const d = date.getUTCDate();
125
+ const h = date.getUTCHours();
126
+ const n = date.getUTCMinutes();
127
+ const s = date.getUTCSeconds();
128
+ return `T:greg.m${m}.c${c}.y${y}.M${this.pad(M)}.d${this.pad(d)}.h${this.pad(h)}.n${this.pad(n)}.s${this.pad(s)}`;
129
+ }
130
+ throw new Error(`Calendar driver '${calendar}' not implemented. Register a driver.`);
131
+ }
132
+ /**
133
+ * CONVERTER: Converts a TPS string to a Date in a target calendar format.
134
+ * Uses plugin drivers for cross-calendar conversion.
135
+ * @param tpsString - The source TPS string (any calendar).
136
+ * @param targetCalendar - The target calendar code (e.g., 'hij').
137
+ * @returns A TPS string in the target calendar, or null if invalid.
138
+ */
139
+ static to(targetCalendar, tpsString) {
140
+ // 1. Parse to components and convert to Gregorian Date
141
+ const gregDate = this.toDate(tpsString);
142
+ if (!gregDate)
143
+ return null;
144
+ // 2. Convert Gregorian to target calendar using driver
145
+ return this.fromDate(gregDate, targetCalendar);
146
+ }
147
+ /**
148
+ * CONVERTER: Reconstructs a JavaScript Date object from a TPS string.
149
+ * Supports plugin drivers for non-Gregorian calendars.
150
+ * @param tpsString - The TPS string.
151
+ * @returns JS Date object or `null` if invalid.
152
+ */
153
+ static toDate(tpsString) {
154
+ const p = this.parse(tpsString);
155
+ if (!p)
156
+ return null;
157
+ // Check for registered driver first
158
+ const driver = this.drivers.get(p.calendar);
159
+ if (driver) {
160
+ return driver.toGregorian(p);
161
+ }
162
+ // Built-in handlers
163
+ if (p.calendar === 'unix' && p.unixSeconds !== undefined) {
164
+ return new Date(p.unixSeconds * 1000);
165
+ }
166
+ if (p.calendar === 'greg') {
167
+ const m = p.millennium || 0;
168
+ const c = p.century || 1;
169
+ const y = p.year || 0;
170
+ const fullYear = (m - 1) * 1000 + (c - 1) * 100 + y;
171
+ return new Date(Date.UTC(fullYear, (p.month || 1) - 1, p.day || 1, p.hour || 0, p.minute || 0, Math.floor(p.second || 0)));
172
+ }
173
+ return null;
174
+ }
175
+ // --- INTERNAL HELPERS ---
176
+ static _mapGroupsToComponents(g) {
177
+ const components = {};
178
+ components.calendar = g.calendar;
179
+ // Time Mapping
180
+ if (components.calendar === 'unix' && g.unix) {
181
+ components.unixSeconds = parseFloat(g.unix.substring(1));
182
+ }
183
+ else {
184
+ if (g.millennium)
185
+ components.millennium = parseInt(g.millennium, 10);
186
+ if (g.century)
187
+ components.century = parseInt(g.century, 10);
188
+ if (g.year)
189
+ components.year = parseInt(g.year, 10);
190
+ if (g.month)
191
+ components.month = parseInt(g.month, 10);
192
+ if (g.day)
193
+ components.day = parseInt(g.day, 10);
194
+ if (g.hour)
195
+ components.hour = parseInt(g.hour, 10);
196
+ if (g.minute)
197
+ components.minute = parseInt(g.minute, 10);
198
+ if (g.second)
199
+ components.second = parseFloat(g.second);
200
+ }
201
+ // Space Mapping
202
+ if (g.space) {
203
+ if (g.space === 'unknown')
204
+ components.isUnknownLocation = true;
205
+ else if (g.space === 'redacted')
206
+ components.isRedactedLocation = true;
207
+ else if (g.space === 'hidden')
208
+ components.isHiddenLocation = true;
209
+ else {
210
+ if (g.lat)
211
+ components.latitude = parseFloat(g.lat);
212
+ if (g.lon)
213
+ components.longitude = parseFloat(g.lon);
214
+ if (g.alt)
215
+ components.altitude = parseFloat(g.alt);
216
+ }
217
+ }
218
+ // Extensions Mapping
219
+ if (g.extensions) {
220
+ const extObj = {};
221
+ const parts = g.extensions.split('.');
222
+ parts.forEach((p) => {
223
+ const key = p.charAt(0);
224
+ const val = p.substring(1);
225
+ if (key && val)
226
+ extObj[key] = val;
227
+ });
228
+ components.extensions = extObj;
229
+ }
230
+ return components;
231
+ }
232
+ static pad(n) {
233
+ const s = n.toString();
234
+ return s.length < 2 ? '0' + s : s;
235
+ }
236
+ }
237
+ exports.TPS = TPS;
238
+ // --- PLUGIN REGISTRY ---
239
+ TPS.drivers = new Map();
240
+ // --- REGEX ---
241
+ TPS.REGEX_URI = new RegExp('^tps://(?<space>unknown|redacted|hidden|(?<lat>-?\\d+(?:\\.\\d+)?),(?<lon>-?\\d+(?:\\.\\d+)?)(?:,(?<alt>-?\\d+(?:\\.\\d+)?)m?)?)@T:(?<calendar>[a-z]{3,4})\\.(?:(?<unix>s\\d+(?:\\.\\d+)?)|m(?<millennium>-?\\d+)(?:\\.c(?<century>\\d+)(?:\\.y(?<year>\\d+)(?:\\.M(?<month>\\d{1,2})(?:\\.d(?<day>\\d{1,2})(?:\\.h(?<hour>\\d{1,2})(?:\\.n(?<minute>\\d{1,2})(?:\\.s(?<second>\\d{1,2}(?:\\.\\d+)?))?)?)?)?)?)?)?)?(?:;(?<extensions>[a-z0-9\\.\\-\\_]+))?$');
242
+ TPS.REGEX_TIME = new RegExp('^T:(?<calendar>[a-z]{3,4})\\.(?:(?<unix>s\\d+(?:\\.\\d+)?)|m(?<millennium>-?\\d+)(?:\\.c(?<century>\\d+)(?:\\.y(?<year>\\d+)(?:\\.M(?<month>\\d{1,2})(?:\\.d(?<day>\\d{1,2})(?:\\.h(?<hour>\\d{1,2})(?:\\.n(?<minute>\\d{1,2})(?:\\.s(?<second>\\d{1,2}(?:\\.\\d+)?))?)?)?)?)?)?)?)?$');
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@nextera.one/tps-standard",
3
+ "version": "0.4.2",
4
+ "description": "The Universal Protocol for Space-Time Coordinates. A standard URI scheme (tps://) combining WGS84 spatial data with hierarchical, multi-calendar temporal coordinates.",
5
+ "keywords": [
6
+ "tps",
7
+ "time-protocol",
8
+ "space-time",
9
+ "reality-address",
10
+ "stuid",
11
+ "coordinates",
12
+ "calendar",
13
+ "gregorian",
14
+ "hijri",
15
+ "unix",
16
+ "geolocation"
17
+ ],
18
+ "homepage": "https://tps-standard.org",
19
+ "bugs": {
20
+ "url": "https://github.com/nextera-one/tps/issues"
21
+ },
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/nextera-one/tps.git"
25
+ },
26
+ "license": "MIT",
27
+ "author": "Mohammed Ayesh",
28
+ "types": "dist/index.d.ts",
29
+ "main": "dist/index.js",
30
+ "scripts": {
31
+ "build": "tsc",
32
+ "prepublishOnly": "npm run build",
33
+ "test": "node dist/index.js"
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "src"
38
+ ],
39
+ "devDependencies": {
40
+ "typescript": "^5.3.3",
41
+ "@types/node": "^20.10.6"
42
+ }
43
+ }
package/src/index.ts ADDED
@@ -0,0 +1,324 @@
1
+ /**
2
+ * TPS: Temporal Positioning System
3
+ * The Universal Protocol for Space-Time Coordinates.
4
+ * @packageDocumentation
5
+ * @version 0.4.2
6
+ * @license MIT
7
+ * @copyright 2026 TPS Standards Working Group
8
+ */
9
+
10
+ export type CalendarCode = 'greg' | 'hij' | 'jul' | 'holo' | 'unix';
11
+
12
+ export interface TPSComponents {
13
+ // --- TEMPORAL ---
14
+ calendar: CalendarCode;
15
+ millennium?: number;
16
+ century?: number;
17
+ year?: number;
18
+ month?: number;
19
+ day?: number;
20
+ hour?: number;
21
+ minute?: number;
22
+ second?: number;
23
+ unixSeconds?: number;
24
+
25
+ // --- SPATIAL ---
26
+ latitude?: number;
27
+ longitude?: number;
28
+ altitude?: number;
29
+
30
+ /** Technical missing data (e.g. server log without GPS) */
31
+ isUnknownLocation?: boolean;
32
+ /** Removed for legal/security reasons (e.g. GDPR) */
33
+ isRedactedLocation?: boolean;
34
+ /** Masked by user preference (e.g. "Don't show my location") */
35
+ isHiddenLocation?: boolean;
36
+
37
+ // --- CONTEXT ---
38
+ extensions?: Record<string, string>;
39
+ }
40
+
41
+ // --- PLUGIN ARCHITECTURE ---
42
+
43
+ /**
44
+ * Interface for Calendar Driver plugins.
45
+ * Implementations must provide conversion logic to/from Gregorian.
46
+ */
47
+ export interface CalendarDriver {
48
+ /** The calendar code this driver handles (e.g., 'hij', 'jul'). */
49
+ readonly code: CalendarCode;
50
+
51
+ /**
52
+ * Converts a Gregorian Date to this calendar's components.
53
+ * @param date - The Gregorian Date object.
54
+ * @returns Partial TPS components for year, month, day, etc.
55
+ */
56
+ fromGregorian(date: Date): Partial<TPSComponents>;
57
+
58
+ /**
59
+ * Converts this calendar's components to a Gregorian Date.
60
+ * @param components - Partial TPS components (year, month, day, etc.).
61
+ * @returns A JavaScript Date object.
62
+ */
63
+ toGregorian(components: Partial<TPSComponents>): Date;
64
+
65
+ /**
66
+ * Generates a TPS time string for this calendar from a Date.
67
+ * @param date - The Gregorian Date object.
68
+ * @returns A TPS time string (e.g., "T:hij.m2.c5.y47...").
69
+ */
70
+ fromDate(date: Date): string;
71
+ }
72
+
73
+ export class TPS {
74
+ // --- PLUGIN REGISTRY ---
75
+ private static readonly drivers: Map<CalendarCode, CalendarDriver> =
76
+ new Map();
77
+
78
+ /**
79
+ * Registers a calendar driver plugin.
80
+ * @param driver - The driver instance to register.
81
+ */
82
+ static registerDriver(driver: CalendarDriver): void {
83
+ this.drivers.set(driver.code, driver);
84
+ }
85
+
86
+ /**
87
+ * Gets a registered calendar driver.
88
+ * @param code - The calendar code.
89
+ * @returns The driver or undefined.
90
+ */
91
+ static getDriver(code: CalendarCode): CalendarDriver | undefined {
92
+ return this.drivers.get(code);
93
+ }
94
+
95
+ // --- REGEX ---
96
+ private static readonly REGEX_URI = new RegExp(
97
+ '^tps://(?<space>unknown|redacted|hidden|(?<lat>-?\\d+(?:\\.\\d+)?),(?<lon>-?\\d+(?:\\.\\d+)?)(?:,(?<alt>-?\\d+(?:\\.\\d+)?)m?)?)@T:(?<calendar>[a-z]{3,4})\\.(?:(?<unix>s\\d+(?:\\.\\d+)?)|m(?<millennium>-?\\d+)(?:\\.c(?<century>\\d+)(?:\\.y(?<year>\\d+)(?:\\.M(?<month>\\d{1,2})(?:\\.d(?<day>\\d{1,2})(?:\\.h(?<hour>\\d{1,2})(?:\\.n(?<minute>\\d{1,2})(?:\\.s(?<second>\\d{1,2}(?:\\.\\d+)?))?)?)?)?)?)?)?)?(?:;(?<extensions>[a-z0-9\\.\\-\\_]+))?$',
98
+ );
99
+
100
+ private static readonly REGEX_TIME = new RegExp(
101
+ '^T:(?<calendar>[a-z]{3,4})\\.(?:(?<unix>s\\d+(?:\\.\\d+)?)|m(?<millennium>-?\\d+)(?:\\.c(?<century>\\d+)(?:\\.y(?<year>\\d+)(?:\\.M(?<month>\\d{1,2})(?:\\.d(?<day>\\d{1,2})(?:\\.h(?<hour>\\d{1,2})(?:\\.n(?<minute>\\d{1,2})(?:\\.s(?<second>\\d{1,2}(?:\\.\\d+)?))?)?)?)?)?)?)?)?$',
102
+ );
103
+
104
+ // --- CORE METHODS ---
105
+
106
+ static validate(input: string): boolean {
107
+ if (input.startsWith('tps://')) return this.REGEX_URI.test(input);
108
+ return this.REGEX_TIME.test(input);
109
+ }
110
+
111
+ static parse(input: string): TPSComponents | null {
112
+ if (input.startsWith('tps://')) {
113
+ const match = this.REGEX_URI.exec(input);
114
+ if (!match || !match.groups) return null;
115
+ return this._mapGroupsToComponents(match.groups);
116
+ }
117
+ const match = this.REGEX_TIME.exec(input);
118
+ if (!match || !match.groups) return null;
119
+ return this._mapGroupsToComponents(match.groups);
120
+ }
121
+
122
+ /**
123
+ * SERIALIZER: Converts a components object into a full TPS URI.
124
+ * @param comp - The TPS components.
125
+ * @returns Full URI string (e.g. "tps://...").
126
+ */
127
+ static toURI(comp: TPSComponents): string {
128
+ // 1. Build Space Part
129
+ let spacePart = 'unknown'; // Default safe fallback
130
+
131
+ if (comp.isHiddenLocation) {
132
+ spacePart = 'hidden';
133
+ } else if (comp.isRedactedLocation) {
134
+ spacePart = 'redacted';
135
+ } else if (comp.isUnknownLocation) {
136
+ spacePart = 'unknown';
137
+ } else if (comp.latitude !== undefined && comp.longitude !== undefined) {
138
+ spacePart = `${comp.latitude},${comp.longitude}`;
139
+ if (comp.altitude !== undefined) {
140
+ spacePart += `,${comp.altitude}m`;
141
+ }
142
+ }
143
+
144
+ // 2. Build Time Part
145
+ let timePart = `T:${comp.calendar}`;
146
+
147
+ if (comp.calendar === 'unix' && comp.unixSeconds !== undefined) {
148
+ timePart += `.s${comp.unixSeconds}`;
149
+ } else {
150
+ if (comp.millennium !== undefined) timePart += `.m${comp.millennium}`;
151
+ if (comp.century !== undefined) timePart += `.c${comp.century}`;
152
+ if (comp.year !== undefined) timePart += `.y${comp.year}`;
153
+ if (comp.month !== undefined) timePart += `.M${this.pad(comp.month)}`;
154
+ if (comp.day !== undefined) timePart += `.d${this.pad(comp.day)}`;
155
+ if (comp.hour !== undefined) timePart += `.h${this.pad(comp.hour)}`;
156
+ if (comp.minute !== undefined) timePart += `.n${this.pad(comp.minute)}`;
157
+ if (comp.second !== undefined) timePart += `.s${this.pad(comp.second)}`;
158
+ }
159
+
160
+ // 3. Build Extensions
161
+ let extPart = '';
162
+ if (comp.extensions && Object.keys(comp.extensions).length > 0) {
163
+ const extStrings = Object.entries(comp.extensions).map(
164
+ ([k, v]) => `${k}${v}`,
165
+ );
166
+ extPart = `;${extStrings.join('.')}`;
167
+ }
168
+
169
+ return `tps://${spacePart}@${timePart}${extPart}`;
170
+ }
171
+
172
+ /**
173
+ * CONVERTER: Creates a TPS Time Object string from a JavaScript Date.
174
+ * Supports plugin drivers for non-Gregorian calendars.
175
+ * @param date - The JS Date object (defaults to Now).
176
+ * @param calendar - The target calendar driver (default 'greg').
177
+ * @returns Canonical string (e.g., "T:greg.m3.c1.y26...").
178
+ */
179
+ static fromDate(
180
+ date: Date = new Date(),
181
+ calendar: CalendarCode = 'greg',
182
+ ): string {
183
+ // Check for registered driver first
184
+ const driver = this.drivers.get(calendar);
185
+ if (driver) {
186
+ return driver.fromDate(date);
187
+ }
188
+
189
+ // Built-in handlers
190
+ if (calendar === 'unix') {
191
+ const s = (date.getTime() / 1000).toFixed(3);
192
+ return `T:unix.s${s}`;
193
+ }
194
+
195
+ if (calendar === 'greg') {
196
+ const fullYear = date.getUTCFullYear();
197
+ const m = Math.floor(fullYear / 1000) + 1;
198
+ const c = Math.floor((fullYear % 1000) / 100) + 1;
199
+ const y = fullYear % 100;
200
+ const M = date.getUTCMonth() + 1;
201
+ const d = date.getUTCDate();
202
+ const h = date.getUTCHours();
203
+ const n = date.getUTCMinutes();
204
+ const s = date.getUTCSeconds();
205
+
206
+ return `T:greg.m${m}.c${c}.y${y}.M${this.pad(M)}.d${this.pad(d)}.h${this.pad(h)}.n${this.pad(n)}.s${this.pad(s)}`;
207
+ }
208
+
209
+ throw new Error(
210
+ `Calendar driver '${calendar}' not implemented. Register a driver.`,
211
+ );
212
+ }
213
+
214
+ /**
215
+ * CONVERTER: Converts a TPS string to a Date in a target calendar format.
216
+ * Uses plugin drivers for cross-calendar conversion.
217
+ * @param tpsString - The source TPS string (any calendar).
218
+ * @param targetCalendar - The target calendar code (e.g., 'hij').
219
+ * @returns A TPS string in the target calendar, or null if invalid.
220
+ */
221
+ static to(targetCalendar: CalendarCode, tpsString: string): string | null {
222
+ // 1. Parse to components and convert to Gregorian Date
223
+ const gregDate = this.toDate(tpsString);
224
+ if (!gregDate) return null;
225
+
226
+ // 2. Convert Gregorian to target calendar using driver
227
+ return this.fromDate(gregDate, targetCalendar);
228
+ }
229
+
230
+ /**
231
+ * CONVERTER: Reconstructs a JavaScript Date object from a TPS string.
232
+ * Supports plugin drivers for non-Gregorian calendars.
233
+ * @param tpsString - The TPS string.
234
+ * @returns JS Date object or `null` if invalid.
235
+ */
236
+ static toDate(tpsString: string): Date | null {
237
+ const p = this.parse(tpsString);
238
+ if (!p) return null;
239
+
240
+ // Check for registered driver first
241
+ const driver = this.drivers.get(p.calendar);
242
+ if (driver) {
243
+ return driver.toGregorian(p);
244
+ }
245
+
246
+ // Built-in handlers
247
+ if (p.calendar === 'unix' && p.unixSeconds !== undefined) {
248
+ return new Date(p.unixSeconds * 1000);
249
+ }
250
+
251
+ if (p.calendar === 'greg') {
252
+ const m = p.millennium || 0;
253
+ const c = p.century || 1;
254
+ const y = p.year || 0;
255
+ const fullYear = (m - 1) * 1000 + (c - 1) * 100 + y;
256
+
257
+ return new Date(
258
+ Date.UTC(
259
+ fullYear,
260
+ (p.month || 1) - 1,
261
+ p.day || 1,
262
+ p.hour || 0,
263
+ p.minute || 0,
264
+ Math.floor(p.second || 0),
265
+ ),
266
+ );
267
+ }
268
+ return null;
269
+ }
270
+
271
+ // --- INTERNAL HELPERS ---
272
+
273
+ private static _mapGroupsToComponents(
274
+ g: Record<string, string>,
275
+ ): TPSComponents {
276
+ const components: any = {};
277
+ components.calendar = g.calendar as CalendarCode;
278
+
279
+ // Time Mapping
280
+ if (components.calendar === 'unix' && g.unix) {
281
+ components.unixSeconds = parseFloat(g.unix.substring(1));
282
+ } else {
283
+ if (g.millennium) components.millennium = parseInt(g.millennium, 10);
284
+ if (g.century) components.century = parseInt(g.century, 10);
285
+ if (g.year) components.year = parseInt(g.year, 10);
286
+ if (g.month) components.month = parseInt(g.month, 10);
287
+ if (g.day) components.day = parseInt(g.day, 10);
288
+ if (g.hour) components.hour = parseInt(g.hour, 10);
289
+ if (g.minute) components.minute = parseInt(g.minute, 10);
290
+ if (g.second) components.second = parseFloat(g.second);
291
+ }
292
+
293
+ // Space Mapping
294
+ if (g.space) {
295
+ if (g.space === 'unknown') components.isUnknownLocation = true;
296
+ else if (g.space === 'redacted') components.isRedactedLocation = true;
297
+ else if (g.space === 'hidden') components.isHiddenLocation = true;
298
+ else {
299
+ if (g.lat) components.latitude = parseFloat(g.lat);
300
+ if (g.lon) components.longitude = parseFloat(g.lon);
301
+ if (g.alt) components.altitude = parseFloat(g.alt);
302
+ }
303
+ }
304
+
305
+ // Extensions Mapping
306
+ if (g.extensions) {
307
+ const extObj: any = {};
308
+ const parts = g.extensions.split('.');
309
+ parts.forEach((p: string) => {
310
+ const key = p.charAt(0);
311
+ const val = p.substring(1);
312
+ if (key && val) extObj[key] = val;
313
+ });
314
+ components.extensions = extObj;
315
+ }
316
+
317
+ return components as TPSComponents;
318
+ }
319
+
320
+ private static pad(n: number): string {
321
+ const s = n.toString();
322
+ return s.length < 2 ? '0' + s : s;
323
+ }
324
+ }