@wemush/wols 0.1.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.
Files changed (87) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +294 -0
  3. package/dist/cjs/compact-url.js +272 -0
  4. package/dist/cjs/compact-url.js.map +1 -0
  5. package/dist/cjs/crypto/decrypt.js +125 -0
  6. package/dist/cjs/crypto/decrypt.js.map +1 -0
  7. package/dist/cjs/crypto/encrypt.js +151 -0
  8. package/dist/cjs/crypto/encrypt.js.map +1 -0
  9. package/dist/cjs/crypto/index.js +16 -0
  10. package/dist/cjs/crypto/index.js.map +1 -0
  11. package/dist/cjs/errors.js +188 -0
  12. package/dist/cjs/errors.js.map +1 -0
  13. package/dist/cjs/index.js +81 -0
  14. package/dist/cjs/index.js.map +1 -0
  15. package/dist/cjs/parser.js +148 -0
  16. package/dist/cjs/parser.js.map +1 -0
  17. package/dist/cjs/qr/generate.js +275 -0
  18. package/dist/cjs/qr/generate.js.map +1 -0
  19. package/dist/cjs/qr/index.js +18 -0
  20. package/dist/cjs/qr/index.js.map +1 -0
  21. package/dist/cjs/specimen.js +163 -0
  22. package/dist/cjs/specimen.js.map +1 -0
  23. package/dist/cjs/types.js +78 -0
  24. package/dist/cjs/types.js.map +1 -0
  25. package/dist/cjs/utils/cuid.js +150 -0
  26. package/dist/cjs/utils/cuid.js.map +1 -0
  27. package/dist/cjs/utils/iso8601.js +131 -0
  28. package/dist/cjs/utils/iso8601.js.map +1 -0
  29. package/dist/cjs/validator.js +307 -0
  30. package/dist/cjs/validator.js.map +1 -0
  31. package/dist/esm/compact-url.js +264 -0
  32. package/dist/esm/compact-url.js.map +1 -0
  33. package/dist/esm/crypto/decrypt.js +122 -0
  34. package/dist/esm/crypto/decrypt.js.map +1 -0
  35. package/dist/esm/crypto/encrypt.js +147 -0
  36. package/dist/esm/crypto/encrypt.js.map +1 -0
  37. package/dist/esm/crypto/index.js +10 -0
  38. package/dist/esm/crypto/index.js.map +1 -0
  39. package/dist/esm/errors.js +178 -0
  40. package/dist/esm/errors.js.map +1 -0
  41. package/dist/esm/index.js +47 -0
  42. package/dist/esm/index.js.map +1 -0
  43. package/dist/esm/parser.js +145 -0
  44. package/dist/esm/parser.js.map +1 -0
  45. package/dist/esm/qr/generate.js +235 -0
  46. package/dist/esm/qr/generate.js.map +1 -0
  47. package/dist/esm/qr/index.js +10 -0
  48. package/dist/esm/qr/index.js.map +1 -0
  49. package/dist/esm/specimen.js +159 -0
  50. package/dist/esm/specimen.js.map +1 -0
  51. package/dist/esm/types.js +71 -0
  52. package/dist/esm/types.js.map +1 -0
  53. package/dist/esm/utils/cuid.js +145 -0
  54. package/dist/esm/utils/cuid.js.map +1 -0
  55. package/dist/esm/utils/iso8601.js +122 -0
  56. package/dist/esm/utils/iso8601.js.map +1 -0
  57. package/dist/esm/validator.js +304 -0
  58. package/dist/esm/validator.js.map +1 -0
  59. package/dist/types/compact-url.d.ts +104 -0
  60. package/dist/types/compact-url.d.ts.map +1 -0
  61. package/dist/types/crypto/decrypt.d.ts +33 -0
  62. package/dist/types/crypto/decrypt.d.ts.map +1 -0
  63. package/dist/types/crypto/encrypt.d.ts +32 -0
  64. package/dist/types/crypto/encrypt.d.ts.map +1 -0
  65. package/dist/types/crypto/index.d.ts +10 -0
  66. package/dist/types/crypto/index.d.ts.map +1 -0
  67. package/dist/types/errors.d.ts +103 -0
  68. package/dist/types/errors.d.ts.map +1 -0
  69. package/dist/types/index.d.ts +21 -0
  70. package/dist/types/index.d.ts.map +1 -0
  71. package/dist/types/parser.d.ts +30 -0
  72. package/dist/types/parser.d.ts.map +1 -0
  73. package/dist/types/qr/generate.d.ts +62 -0
  74. package/dist/types/qr/generate.d.ts.map +1 -0
  75. package/dist/types/qr/index.d.ts +10 -0
  76. package/dist/types/qr/index.d.ts.map +1 -0
  77. package/dist/types/specimen.d.ts +71 -0
  78. package/dist/types/specimen.d.ts.map +1 -0
  79. package/dist/types/types.d.ts +269 -0
  80. package/dist/types/types.d.ts.map +1 -0
  81. package/dist/types/utils/cuid.d.ts +44 -0
  82. package/dist/types/utils/cuid.d.ts.map +1 -0
  83. package/dist/types/utils/iso8601.d.ts +79 -0
  84. package/dist/types/utils/iso8601.d.ts.map +1 -0
  85. package/dist/types/validator.d.ts +25 -0
  86. package/dist/types/validator.d.ts.map +1 -0
  87. package/package.json +95 -0
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Specimen Creation and Serialization
3
+ *
4
+ * This module provides functions to create and serialize
5
+ * WOLS v1.1.0 compliant specimen objects.
6
+ *
7
+ * @module specimen
8
+ */
9
+ import { WOLS_CONTEXT, WOLS_VERSION, asSpecimenId, isStrain } from './types.js';
10
+ import { createSpecimenId } from './utils/cuid.js';
11
+ /**
12
+ * Expand a strain input to a full Strain object.
13
+ * String inputs are expanded to { name: string }.
14
+ *
15
+ * @param strain - String or Strain object input
16
+ * @returns Strain object or undefined
17
+ */
18
+ function expandStrain(strain) {
19
+ if (strain === undefined) {
20
+ return undefined;
21
+ }
22
+ if (typeof strain === 'string') {
23
+ return { name: strain };
24
+ }
25
+ if (isStrain(strain)) {
26
+ return strain;
27
+ }
28
+ return undefined;
29
+ }
30
+ /**
31
+ * Create a new specimen with auto-generated ID and JSON-LD fields.
32
+ *
33
+ * This function creates a fully-formed WOLS v1.1.0 compliant Specimen
34
+ * with automatic ID generation, version assignment, and JSON-LD context.
35
+ *
36
+ * @param input - Specimen creation parameters
37
+ * @returns Fully-formed Specimen with JSON-LD fields
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * // Minimal specimen
42
+ * const culture = createSpecimen({
43
+ * type: 'CULTURE',
44
+ * species: 'Pleurotus ostreatus',
45
+ * });
46
+ *
47
+ * // Full specimen with strain info
48
+ * const substrate = createSpecimen({
49
+ * type: 'SUBSTRATE',
50
+ * species: 'Hericium erinaceus',
51
+ * strain: {
52
+ * name: "Lion's Mane Premium",
53
+ * generation: 'F2',
54
+ * clonalGeneration: 3,
55
+ * },
56
+ * stage: 'COLONIZATION',
57
+ * batch: 'batch_2025_12_001',
58
+ * organization: 'org_mushoh',
59
+ * });
60
+ *
61
+ * // String strain shorthand
62
+ * const spawn = createSpecimen({
63
+ * type: 'SPAWN',
64
+ * species: 'Ganoderma lucidum',
65
+ * strain: 'Reishi Red', // Auto-expands to { name: 'Reishi Red' }
66
+ * });
67
+ * ```
68
+ */
69
+ export function createSpecimen(input) {
70
+ const id = asSpecimenId(createSpecimenId());
71
+ const strain = expandStrain(input.strain);
72
+ // Build specimen with required fields
73
+ const specimen = {
74
+ '@context': WOLS_CONTEXT,
75
+ '@type': 'Specimen',
76
+ id,
77
+ version: WOLS_VERSION,
78
+ type: input.type,
79
+ species: input.species,
80
+ };
81
+ // Add optional fields only if defined
82
+ if (strain !== undefined) {
83
+ specimen.strain = strain;
84
+ }
85
+ if (input.stage !== undefined) {
86
+ specimen.stage = input.stage;
87
+ }
88
+ if (input.batch !== undefined) {
89
+ specimen.batch = input.batch;
90
+ }
91
+ if (input.organization !== undefined) {
92
+ specimen.organization = input.organization;
93
+ }
94
+ if (input.creator !== undefined) {
95
+ specimen.creator = input.creator;
96
+ }
97
+ if (input.custom !== undefined) {
98
+ specimen.custom = input.custom;
99
+ }
100
+ return specimen;
101
+ }
102
+ /**
103
+ * Serialize a specimen to JSON-LD string.
104
+ *
105
+ * The output is formatted with JSON-LD fields (@context, @type) first,
106
+ * followed by id, version, and remaining fields. Undefined fields are omitted.
107
+ *
108
+ * @param specimen - Specimen to serialize
109
+ * @returns JSON string suitable for QR encoding
110
+ *
111
+ * @example
112
+ * ```typescript
113
+ * const specimen = createSpecimen({
114
+ * type: 'CULTURE',
115
+ * species: 'Pleurotus ostreatus',
116
+ * });
117
+ *
118
+ * const json = serializeSpecimen(specimen);
119
+ * // Returns: '{"@context":"https://wemush.com/wols/v1","@type":"Specimen",...}'
120
+ * ```
121
+ */
122
+ export function serializeSpecimen(specimen) {
123
+ // Build ordered object with JSON-LD fields first
124
+ const ordered = {
125
+ '@context': specimen['@context'],
126
+ '@type': specimen['@type'],
127
+ id: specimen.id,
128
+ version: specimen.version,
129
+ type: specimen.type,
130
+ species: specimen.species,
131
+ };
132
+ // Add optional fields in order, only if defined
133
+ if (specimen.strain !== undefined) {
134
+ ordered['strain'] = specimen.strain;
135
+ }
136
+ if (specimen.stage !== undefined) {
137
+ ordered['stage'] = specimen.stage;
138
+ }
139
+ if (specimen.created !== undefined) {
140
+ ordered['created'] = specimen.created;
141
+ }
142
+ if (specimen.batch !== undefined) {
143
+ ordered['batch'] = specimen.batch;
144
+ }
145
+ if (specimen.organization !== undefined) {
146
+ ordered['organization'] = specimen.organization;
147
+ }
148
+ if (specimen.creator !== undefined) {
149
+ ordered['creator'] = specimen.creator;
150
+ }
151
+ if (specimen.custom !== undefined) {
152
+ ordered['custom'] = specimen.custom;
153
+ }
154
+ if (specimen.signature !== undefined) {
155
+ ordered['signature'] = specimen.signature;
156
+ }
157
+ return JSON.stringify(ordered);
158
+ }
159
+ //# sourceMappingURL=specimen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"specimen.js","sourceRoot":"","sources":["../../src/specimen.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * WOLS TypeScript Library - Core Types
3
+ *
4
+ * This module defines all the core types, interfaces, and enums
5
+ * for the WeMush Open Labeling Standard (WOLS) v1.1.0.
6
+ *
7
+ * @packageDocumentation
8
+ * @module @wemush/wols
9
+ */
10
+ // =============================================================================
11
+ // CONSTANTS
12
+ // =============================================================================
13
+ /**
14
+ * WOLS specification version implemented by this library.
15
+ */
16
+ export const WOLS_VERSION = '1.1.0';
17
+ /**
18
+ * JSON-LD context URL for WOLS specimens.
19
+ */
20
+ export const WOLS_CONTEXT = 'https://wemush.com/wols/v1';
21
+ /**
22
+ * Create a SpecimenId from a string.
23
+ * Does not validate the format - use isValidSpecimenId for validation.
24
+ */
25
+ export function asSpecimenId(id) {
26
+ return id;
27
+ }
28
+ /**
29
+ * Array of valid specimen types for validation.
30
+ */
31
+ export const SPECIMEN_TYPES = [
32
+ 'CULTURE',
33
+ 'SPAWN',
34
+ 'SUBSTRATE',
35
+ 'FRUITING',
36
+ 'HARVEST',
37
+ ];
38
+ /**
39
+ * Array of valid growth stages for validation.
40
+ */
41
+ export const GROWTH_STAGES = [
42
+ 'INOCULATION',
43
+ 'COLONIZATION',
44
+ 'FRUITING',
45
+ 'HARVEST',
46
+ ];
47
+ // =============================================================================
48
+ // TYPE GUARDS
49
+ // =============================================================================
50
+ /**
51
+ * Type guard to check if a value is a valid SpecimenType.
52
+ */
53
+ export function isSpecimenType(value) {
54
+ return typeof value === 'string' && SPECIMEN_TYPES.includes(value);
55
+ }
56
+ /**
57
+ * Type guard to check if a value is a valid GrowthStage.
58
+ */
59
+ export function isGrowthStage(value) {
60
+ return typeof value === 'string' && GROWTH_STAGES.includes(value);
61
+ }
62
+ /**
63
+ * Type guard to check if a value is a Strain object.
64
+ */
65
+ export function isStrain(value) {
66
+ return (typeof value === 'object' &&
67
+ value !== null &&
68
+ 'name' in value &&
69
+ typeof value.name === 'string');
70
+ }
71
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,gFAAgF;AAChF,YAAY;AACZ,gFAAgF;AAEhF;;GAEG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,OAAO,CAAC;AAEpC;;GAEG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,4BAA4B,CAAC;AAkBzD;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,EAAU;IACrC,OAAO,EAAgB,CAAC;AAC1B,CAAC;AAWD;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAA4B;IACrD,SAAS;IACT,OAAO;IACP,WAAW;IACX,UAAU;IACV,SAAS;CACD,CAAC;AAOX;;GAEG;AACH,MAAM,CAAC,MAAM,aAAa,GAA2B;IACnD,aAAa;IACb,cAAc;IACd,UAAU;IACV,SAAS;CACD,CAAC;AAgPX,gFAAgF;AAChF,cAAc;AACd,gFAAgF;AAEhF;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,cAAc,CAAC,QAAQ,CAAC,KAAqB,CAAC,CAAC;AACrF,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,aAAa,CAAC,QAAQ,CAAC,KAAoB,CAAC,CAAC;AACnF,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,MAAM,IAAI,KAAK;QACf,OAAQ,KAAgB,CAAC,IAAI,KAAK,QAAQ,CAC3C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,145 @@
1
+ /**
2
+ * CUID2 Generator - Zero-dependency implementation
3
+ *
4
+ * Generates collision-resistant unique identifiers following the CUID2 algorithm.
5
+ * This is a minimal implementation focused on the needs of WOLS specimen IDs.
6
+ *
7
+ * @module utils/cuid
8
+ */
9
+ // Character set for base36 encoding (0-9, a-z)
10
+ const ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz';
11
+ // Default length for generated IDs
12
+ const DEFAULT_LENGTH = 24;
13
+ // Counter for additional entropy
14
+ let counter = 0;
15
+ /**
16
+ * Get cryptographically secure random bytes.
17
+ * Uses Web Crypto API (works in Node.js 18+ and modern browsers).
18
+ */
19
+ function getRandomBytes(length) {
20
+ const bytes = new Uint8Array(length);
21
+ // Use globalThis.crypto for cross-platform compatibility
22
+ const cryptoObj = typeof globalThis !== 'undefined'
23
+ ? globalThis.crypto
24
+ : undefined;
25
+ if (cryptoObj?.getRandomValues) {
26
+ cryptoObj.getRandomValues(bytes);
27
+ }
28
+ else {
29
+ // Fallback for older environments (should not happen with Node 18+)
30
+ for (let i = 0; i < length; i++) {
31
+ bytes[i] = Math.floor(Math.random() * 256);
32
+ }
33
+ }
34
+ return bytes;
35
+ }
36
+ /**
37
+ * Convert a byte array to a base36 string.
38
+ */
39
+ function toBase36(bytes) {
40
+ let result = '';
41
+ for (const byte of bytes) {
42
+ result += ALPHABET[byte % 36];
43
+ }
44
+ return result;
45
+ }
46
+ /**
47
+ * Get a timestamp component for the ID.
48
+ * Uses high-resolution time when available.
49
+ */
50
+ function getTimestamp() {
51
+ const now = Date.now();
52
+ return now.toString(36);
53
+ }
54
+ /**
55
+ * Get counter component to ensure uniqueness within same millisecond.
56
+ */
57
+ function getCounter() {
58
+ counter = (counter + 1) % 1679616; // Max 4 base36 chars
59
+ return counter.toString(36).padStart(4, '0');
60
+ }
61
+ /**
62
+ * Generate a fingerprint based on environment.
63
+ * In browsers, uses navigator info. In Node.js, uses process info.
64
+ */
65
+ function getFingerprint() {
66
+ const parts = [];
67
+ // Node.js environment - use globalThis for cross-platform compatibility
68
+ const processObj = typeof globalThis !== 'undefined'
69
+ ? globalThis.process
70
+ : undefined;
71
+ const navigatorObj = typeof globalThis !== 'undefined'
72
+ ? globalThis.navigator
73
+ : undefined;
74
+ if (processObj?.pid) {
75
+ parts.push(processObj.pid.toString(36));
76
+ const hrtime = processObj.hrtime?.();
77
+ if (hrtime) {
78
+ parts.push((hrtime[1] ?? 0).toString(36));
79
+ }
80
+ }
81
+ // Browser environment
82
+ if (navigatorObj) {
83
+ parts.push((navigatorObj.userAgent?.length ?? 0).toString(36));
84
+ parts.push((navigatorObj.language?.length ?? 0).toString(36));
85
+ }
86
+ // Fallback random
87
+ if (parts.length === 0) {
88
+ const random = getRandomBytes(4);
89
+ parts.push(toBase36(random));
90
+ }
91
+ return parts.join('').slice(0, 4);
92
+ }
93
+ /**
94
+ * Generate a CUID2-style identifier.
95
+ *
96
+ * The ID format is: [random][timestamp][counter][fingerprint][random]
97
+ * All components are base36 encoded for URL safety.
98
+ *
99
+ * @param length - Length of the generated ID (default: 24)
100
+ * @returns A unique, URL-safe identifier
101
+ *
102
+ * @example
103
+ * ```typescript
104
+ * const id = createId();
105
+ * // Returns something like: "clx1a2b3c4d5e6f7g8h9i0j1"
106
+ * ```
107
+ */
108
+ export function createId(length = DEFAULT_LENGTH) {
109
+ const timestamp = getTimestamp();
110
+ const count = getCounter();
111
+ const fingerprint = getFingerprint();
112
+ // Calculate random bytes needed
113
+ const fixedLength = timestamp.length + count.length + fingerprint.length;
114
+ const randomLength = Math.max(0, length - fixedLength);
115
+ // Generate random component
116
+ const randomBytes = getRandomBytes(randomLength);
117
+ const random = toBase36(randomBytes);
118
+ // Combine components
119
+ const id = (random + timestamp + count + fingerprint).slice(0, length);
120
+ return id;
121
+ }
122
+ /**
123
+ * Generate a specimen ID with the WOLS prefix.
124
+ *
125
+ * @returns A specimen ID in format "wemush:{cuid}"
126
+ *
127
+ * @example
128
+ * ```typescript
129
+ * const specimenId = createSpecimenId();
130
+ * // Returns: "wemush:clx1a2b3c4d5e6f7g8h9i0j1"
131
+ * ```
132
+ */
133
+ export function createSpecimenId() {
134
+ return `wemush:${createId()}`;
135
+ }
136
+ /**
137
+ * Validate a specimen ID format.
138
+ *
139
+ * @param id - The ID to validate
140
+ * @returns true if the ID matches the expected format
141
+ */
142
+ export function isValidSpecimenId(id) {
143
+ return /^wemush:[a-z0-9]+$/.test(id);
144
+ }
145
+ //# sourceMappingURL=cuid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cuid.js","sourceRoot":"","sources":["../../../src/utils/cuid.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,+CAA+C;AAC/C,MAAM,QAAQ,GAAG,sCAAsC,CAAC;AAExD,mCAAmC;AACnC,MAAM,cAAc,GAAG,EAAE,CAAC;AAE1B,iCAAiC;AACjC,IAAI,OAAO,GAAG,CAAC,CAAC;AAEhB;;;GAGG;AACH,SAAS,cAAc,CAAC,MAAc;IACpC,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;IACrC,yDAAyD;IACzD,MAAM,SAAS,GACb,OAAO,UAAU,KAAK,WAAW;QAC/B,CAAC,CAAE,UAA6C,CAAC,MAAM;QACvD,CAAC,CAAC,SAAS,CAAC;IAEhB,IAAI,SAAS,EAAE,eAAe,EAAE,CAAC;QAC/B,SAAS,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC;SAAM,CAAC;QACN,oEAAoE;QACpE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAChC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,SAAS,QAAQ,CAAC,KAAiB;IACjC,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,QAAQ,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,SAAS,YAAY;IACnB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,OAAO,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;AAC1B,CAAC;AAED;;GAEG;AACH,SAAS,UAAU;IACjB,OAAO,GAAG,CAAC,OAAO,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,qBAAqB;IACxD,OAAO,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAC/C,CAAC;AAED;;;GAGG;AACH,SAAS,cAAc;IACrB,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,wEAAwE;IACxE,MAAM,UAAU,GACd,OAAO,UAAU,KAAK,WAAW;QAC/B,CAAC,CAAE,UAAsD,CAAC,OAAO;QACjE,CAAC,CAAC,SAAS,CAAC;IAEhB,MAAM,YAAY,GAChB,OAAO,UAAU,KAAK,WAAW;QAC/B,CAAC,CAAE,UAAmD,CAAC,SAAS;QAChE,CAAC,CAAC,SAAS,CAAC;IAEhB,IAAI,UAAU,EAAE,GAAG,EAAE,CAAC;QACpB,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;QACxC,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC;QACrC,IAAI,MAAM,EAAE,CAAC;YACX,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IAED,sBAAsB;IACtB,IAAI,YAAY,EAAE,CAAC;QACjB,KAAK,CAAC,IAAI,CAAC,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;QAC/D,KAAK,CAAC,IAAI,CAAC,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;IAChE,CAAC;IAED,kBAAkB;IAClB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,MAAM,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;QACjC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IAC/B,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACpC,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,QAAQ,CAAC,SAAiB,cAAc;IACtD,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;IACjC,MAAM,KAAK,GAAG,UAAU,EAAE,CAAC;IAC3B,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;IAErC,gCAAgC;IAChC,MAAM,WAAW,GAAG,SAAS,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC;IACzE,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC;IAEvD,4BAA4B;IAC5B,MAAM,WAAW,GAAG,cAAc,CAAC,YAAY,CAAC,CAAC;IACjD,MAAM,MAAM,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC;IAErC,qBAAqB;IACrB,MAAM,EAAE,GAAG,CAAC,MAAM,GAAG,SAAS,GAAG,KAAK,GAAG,WAAW,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAEvE,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO,UAAU,QAAQ,EAAE,EAAE,CAAC;AAChC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,EAAU;IAC1C,OAAO,oBAAoB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACvC,CAAC"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * ISO 8601 Date Utilities
3
+ *
4
+ * Provides parsing and formatting for ISO 8601 date strings
5
+ * as used in WOLS specimens.
6
+ *
7
+ * @module utils/iso8601
8
+ */
9
+ /**
10
+ * Regular expression for validating ISO 8601 date strings.
11
+ * Matches formats like:
12
+ * - 2025-12-16T10:30:00Z
13
+ * - 2025-12-16T10:30:00.000Z
14
+ * - 2025-12-16T10:30:00+00:00
15
+ * - 2025-12-16T10:30:00-05:00
16
+ */
17
+ const ISO8601_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:\d{2})$/;
18
+ /**
19
+ * Simple date-only regex for partial dates
20
+ */
21
+ const DATE_ONLY_REGEX = /^\d{4}-\d{2}-\d{2}$/;
22
+ /**
23
+ * Check if a string is a valid ISO 8601 date.
24
+ *
25
+ * @param value - The string to validate
26
+ * @returns true if valid ISO 8601 format
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * isValidISO8601('2025-12-16T10:30:00Z'); // true
31
+ * isValidISO8601('2025-12-16'); // false (date-only)
32
+ * isValidISO8601('invalid'); // false
33
+ * ```
34
+ */
35
+ export function isValidISO8601(value) {
36
+ if (!ISO8601_REGEX.test(value)) {
37
+ return false;
38
+ }
39
+ // Also verify it's a parseable date
40
+ const date = new Date(value);
41
+ return !isNaN(date.getTime());
42
+ }
43
+ /**
44
+ * Check if a string is a valid date-only format (YYYY-MM-DD).
45
+ *
46
+ * @param value - The string to validate
47
+ * @returns true if valid date-only format
48
+ */
49
+ export function isValidDateOnly(value) {
50
+ if (!DATE_ONLY_REGEX.test(value)) {
51
+ return false;
52
+ }
53
+ const date = new Date(value + 'T00:00:00Z');
54
+ return !isNaN(date.getTime());
55
+ }
56
+ /**
57
+ * Get the current timestamp in ISO 8601 format.
58
+ *
59
+ * @returns Current time as ISO 8601 string
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * const now = getCurrentISO8601();
64
+ * // Returns: "2025-12-16T10:30:00.000Z"
65
+ * ```
66
+ */
67
+ export function getCurrentISO8601() {
68
+ return new Date().toISOString();
69
+ }
70
+ /**
71
+ * Parse an ISO 8601 date string to a Date object.
72
+ *
73
+ * @param value - The ISO 8601 string to parse
74
+ * @returns The parsed Date, or null if invalid
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * const date = parseISO8601('2025-12-16T10:30:00Z');
79
+ * // Returns: Date object
80
+ *
81
+ * const invalid = parseISO8601('invalid');
82
+ * // Returns: null
83
+ * ```
84
+ */
85
+ export function parseISO8601(value) {
86
+ if (!isValidISO8601(value)) {
87
+ return null;
88
+ }
89
+ return new Date(value);
90
+ }
91
+ /**
92
+ * Format a Date object to ISO 8601 string.
93
+ *
94
+ * @param date - The Date to format
95
+ * @returns ISO 8601 string
96
+ */
97
+ export function formatISO8601(date) {
98
+ return date.toISOString();
99
+ }
100
+ /**
101
+ * Get Unix timestamp (seconds since epoch) from ISO 8601 string.
102
+ *
103
+ * @param value - The ISO 8601 string
104
+ * @returns Unix timestamp in seconds, or null if invalid
105
+ */
106
+ export function toUnixTimestamp(value) {
107
+ const date = parseISO8601(value);
108
+ if (!date) {
109
+ return null;
110
+ }
111
+ return Math.floor(date.getTime() / 1000);
112
+ }
113
+ /**
114
+ * Convert Unix timestamp to ISO 8601 string.
115
+ *
116
+ * @param timestamp - Unix timestamp in seconds
117
+ * @returns ISO 8601 string
118
+ */
119
+ export function fromUnixTimestamp(timestamp) {
120
+ return new Date(timestamp * 1000).toISOString();
121
+ }
122
+ //# sourceMappingURL=iso8601.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"iso8601.js","sourceRoot":"","sources":["../../../src/utils/iso8601.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;;;;GAOG;AACH,MAAM,aAAa,GAAG,0EAA0E,CAAC;AAEjG;;GAEG;AACH,MAAM,eAAe,GAAG,qBAAqB,CAAC;AAE9C;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,oCAAoC;IACpC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7B,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;AAChC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACjC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,KAAK,GAAG,YAAY,CAAC,CAAC;IAC5C,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;AAChC,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AAClC,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC;AACzB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,IAAU;IACtC,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;AAC5B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;IACjC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC;AAC3C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAAiB;IACjD,OAAO,IAAI,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;AAClD,CAAC"}