@atproto/syntax 0.1.4 → 0.2.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/CHANGELOG.md +12 -0
- package/LICENSE.txt +1 -1
- package/dist/datetime.d.ts +6 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +148 -5
- package/dist/index.js.map +3 -3
- package/dist/recordkey.d.ts +4 -0
- package/dist/tid.d.ts +4 -0
- package/jest.config.js +1 -1
- package/package.json +1 -1
- package/src/aturi_validation.ts +5 -5
- package/src/datetime.ts +112 -0
- package/src/index.ts +3 -0
- package/src/recordkey.ts +26 -0
- package/src/tid.ts +24 -0
- package/tests/datetime.test.ts +118 -0
- package/tests/recordkey.test.ts +42 -0
- package/tests/tid.test.ts +42 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @atproto/syntax
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#2223](https://github.com/bluesky-social/atproto/pull/2223) [`0c815b964`](https://github.com/bluesky-social/atproto/commit/0c815b964c030aa0f277c40bf9786f130dc320f4) Thanks [@bnewbold](https://github.com/bnewbold)! - allow colon character in record-key syntax
|
|
8
|
+
|
|
9
|
+
## 0.1.5
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#1908](https://github.com/bluesky-social/atproto/pull/1908) [`3c0ef382`](https://github.com/bluesky-social/atproto/commit/3c0ef382c12a413cc971ae47ffb341236c545f60) Thanks [@gaearon](https://github.com/gaearon)! - prevent unnecessary throw/catch on uri syntax
|
|
14
|
+
|
|
3
15
|
## 0.1.4
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/LICENSE.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Dual MIT/Apache-2.0 License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2022-
|
|
3
|
+
Copyright (c) 2022-2024 Bluesky PBC, and Contributors
|
|
4
4
|
|
|
5
5
|
Except as otherwise noted in individual files, this software is licensed under the MIT license (<http://opensource.org/licenses/MIT>), or the Apache License, Version 2.0 (<http://www.apache.org/licenses/LICENSE-2.0>).
|
|
6
6
|
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const ensureValidDatetime: (dtStr: string) => void;
|
|
2
|
+
export declare const isValidDatetime: (dtStr: string) => boolean;
|
|
3
|
+
export declare const normalizeDatetime: (dtStr: string) => string;
|
|
4
|
+
export declare const normalizeDatetimeAlways: (dtStr: string) => string;
|
|
5
|
+
export declare class InvalidDatetimeError extends Error {
|
|
6
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -25,23 +25,34 @@ __export(src_exports, {
|
|
|
25
25
|
DISALLOWED_TLDS: () => DISALLOWED_TLDS,
|
|
26
26
|
DisallowedDomainError: () => DisallowedDomainError,
|
|
27
27
|
INVALID_HANDLE: () => INVALID_HANDLE,
|
|
28
|
+
InvalidDatetimeError: () => InvalidDatetimeError,
|
|
28
29
|
InvalidDidError: () => InvalidDidError,
|
|
29
30
|
InvalidHandleError: () => InvalidHandleError,
|
|
30
31
|
InvalidNsidError: () => InvalidNsidError,
|
|
32
|
+
InvalidRecordKeyError: () => InvalidRecordKeyError,
|
|
33
|
+
InvalidTidError: () => InvalidTidError,
|
|
31
34
|
NSID: () => NSID,
|
|
32
35
|
ReservedHandleError: () => ReservedHandleError,
|
|
33
36
|
UnsupportedDomainError: () => UnsupportedDomainError,
|
|
34
37
|
ensureValidAtUri: () => ensureValidAtUri,
|
|
35
38
|
ensureValidAtUriRegex: () => ensureValidAtUriRegex,
|
|
39
|
+
ensureValidDatetime: () => ensureValidDatetime,
|
|
36
40
|
ensureValidDid: () => ensureValidDid,
|
|
37
41
|
ensureValidDidRegex: () => ensureValidDidRegex,
|
|
38
42
|
ensureValidHandle: () => ensureValidHandle,
|
|
39
43
|
ensureValidHandleRegex: () => ensureValidHandleRegex,
|
|
40
44
|
ensureValidNsid: () => ensureValidNsid,
|
|
41
45
|
ensureValidNsidRegex: () => ensureValidNsidRegex,
|
|
46
|
+
ensureValidRecordKey: () => ensureValidRecordKey,
|
|
47
|
+
ensureValidTid: () => ensureValidTid,
|
|
48
|
+
isValidDatetime: () => isValidDatetime,
|
|
42
49
|
isValidHandle: () => isValidHandle,
|
|
50
|
+
isValidRecordKey: () => isValidRecordKey,
|
|
51
|
+
isValidTid: () => isValidTid,
|
|
43
52
|
isValidTld: () => isValidTld,
|
|
44
53
|
normalizeAndEnsureValidHandle: () => normalizeAndEnsureValidHandle,
|
|
54
|
+
normalizeDatetime: () => normalizeDatetime,
|
|
55
|
+
normalizeDatetimeAlways: () => normalizeDatetimeAlways,
|
|
45
56
|
normalizeHandle: () => normalizeHandle
|
|
46
57
|
});
|
|
47
58
|
module.exports = __toCommonJS(src_exports);
|
|
@@ -250,13 +261,13 @@ var ensureValidAtUri = (uri) => {
|
|
|
250
261
|
throw new Error("ATURI requires at least method and authority sections");
|
|
251
262
|
}
|
|
252
263
|
try {
|
|
253
|
-
|
|
254
|
-
} catch {
|
|
255
|
-
try {
|
|
264
|
+
if (parts[2].startsWith("did:")) {
|
|
256
265
|
ensureValidDid(parts[2]);
|
|
257
|
-
}
|
|
258
|
-
|
|
266
|
+
} else {
|
|
267
|
+
ensureValidHandle(parts[2]);
|
|
259
268
|
}
|
|
269
|
+
} catch {
|
|
270
|
+
throw new Error("ATURI authority must be a valid handle or DID");
|
|
260
271
|
}
|
|
261
272
|
if (parts.length >= 4) {
|
|
262
273
|
if (parts[3].length == 0) {
|
|
@@ -432,6 +443,127 @@ function parseRelative(str) {
|
|
|
432
443
|
}
|
|
433
444
|
return void 0;
|
|
434
445
|
}
|
|
446
|
+
|
|
447
|
+
// src/tid.ts
|
|
448
|
+
var ensureValidTid = (tid) => {
|
|
449
|
+
if (tid.length != 13) {
|
|
450
|
+
throw new InvalidTidError("TID must be 13 characters");
|
|
451
|
+
}
|
|
452
|
+
if (!/^[234567abcdefghij][234567abcdefghijklmnopqrstuvwxyz]{12}$/.test(tid)) {
|
|
453
|
+
throw new InvalidTidError("TID syntax not valid (regex)");
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
var isValidTid = (tid) => {
|
|
457
|
+
try {
|
|
458
|
+
ensureValidTid(tid);
|
|
459
|
+
} catch (err) {
|
|
460
|
+
if (err instanceof InvalidTidError) {
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
throw err;
|
|
464
|
+
}
|
|
465
|
+
return true;
|
|
466
|
+
};
|
|
467
|
+
var InvalidTidError = class extends Error {
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
// src/recordkey.ts
|
|
471
|
+
var ensureValidRecordKey = (rkey) => {
|
|
472
|
+
if (rkey.length > 512 || rkey.length < 1) {
|
|
473
|
+
throw new InvalidRecordKeyError("record key must be 1 to 512 characters");
|
|
474
|
+
}
|
|
475
|
+
if (!/^[a-zA-Z0-9_~.:-]{1,512}$/.test(rkey)) {
|
|
476
|
+
throw new InvalidRecordKeyError("record key syntax not valid (regex)");
|
|
477
|
+
}
|
|
478
|
+
if (rkey == "." || rkey == "..")
|
|
479
|
+
throw new InvalidRecordKeyError('record key can not be "." or ".."');
|
|
480
|
+
};
|
|
481
|
+
var isValidRecordKey = (rkey) => {
|
|
482
|
+
try {
|
|
483
|
+
ensureValidRecordKey(rkey);
|
|
484
|
+
} catch (err) {
|
|
485
|
+
if (err instanceof InvalidRecordKeyError) {
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
throw err;
|
|
489
|
+
}
|
|
490
|
+
return true;
|
|
491
|
+
};
|
|
492
|
+
var InvalidRecordKeyError = class extends Error {
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
// src/datetime.ts
|
|
496
|
+
var ensureValidDatetime = (dtStr) => {
|
|
497
|
+
const date = new Date(dtStr);
|
|
498
|
+
if (isNaN(date.getTime())) {
|
|
499
|
+
throw new InvalidDatetimeError("datetime did not parse as ISO 8601");
|
|
500
|
+
}
|
|
501
|
+
if (date.toISOString().startsWith("-")) {
|
|
502
|
+
throw new InvalidDatetimeError("datetime normalized to a negative time");
|
|
503
|
+
}
|
|
504
|
+
if (!/^[0-9]{4}-[01][0-9]-[0-3][0-9]T[0-2][0-9]:[0-6][0-9]:[0-6][0-9](.[0-9]{1,20})?(Z|([+-][0-2][0-9]:[0-5][0-9]))$/.test(dtStr)) {
|
|
505
|
+
throw new InvalidDatetimeError("datetime didn't validate via regex");
|
|
506
|
+
}
|
|
507
|
+
if (dtStr.length > 64) {
|
|
508
|
+
throw new InvalidDatetimeError("datetime is too long (64 chars max)");
|
|
509
|
+
}
|
|
510
|
+
if (dtStr.endsWith("-00:00")) {
|
|
511
|
+
throw new InvalidDatetimeError('datetime can not use "-00:00" for UTC timezone');
|
|
512
|
+
}
|
|
513
|
+
if (dtStr.startsWith("000")) {
|
|
514
|
+
throw new InvalidDatetimeError("datetime so close to year zero not allowed");
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
var isValidDatetime = (dtStr) => {
|
|
518
|
+
try {
|
|
519
|
+
ensureValidDatetime(dtStr);
|
|
520
|
+
} catch (err) {
|
|
521
|
+
if (err instanceof InvalidDatetimeError) {
|
|
522
|
+
return false;
|
|
523
|
+
}
|
|
524
|
+
throw err;
|
|
525
|
+
}
|
|
526
|
+
return true;
|
|
527
|
+
};
|
|
528
|
+
var normalizeDatetime = (dtStr) => {
|
|
529
|
+
if (isValidDatetime(dtStr)) {
|
|
530
|
+
const outStr = new Date(dtStr).toISOString();
|
|
531
|
+
if (isValidDatetime(outStr)) {
|
|
532
|
+
return outStr;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
if (!/.*(([+-]\d\d:?\d\d)|[a-zA-Z])$/.test(dtStr)) {
|
|
536
|
+
const date2 = new Date(dtStr + "Z");
|
|
537
|
+
if (!isNaN(date2.getTime())) {
|
|
538
|
+
const tzStr = date2.toISOString();
|
|
539
|
+
if (isValidDatetime(tzStr)) {
|
|
540
|
+
return tzStr;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
const date = new Date(dtStr);
|
|
545
|
+
if (isNaN(date.getTime())) {
|
|
546
|
+
throw new InvalidDatetimeError("datetime did not parse as any timestamp format");
|
|
547
|
+
}
|
|
548
|
+
const isoStr = date.toISOString();
|
|
549
|
+
if (isValidDatetime(isoStr)) {
|
|
550
|
+
return isoStr;
|
|
551
|
+
} else {
|
|
552
|
+
throw new InvalidDatetimeError("datetime normalized to invalid timestamp string");
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
var normalizeDatetimeAlways = (dtStr) => {
|
|
556
|
+
try {
|
|
557
|
+
return normalizeDatetime(dtStr);
|
|
558
|
+
} catch (err) {
|
|
559
|
+
if (err instanceof InvalidDatetimeError) {
|
|
560
|
+
return new Date(0).toISOString();
|
|
561
|
+
}
|
|
562
|
+
throw err;
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
var InvalidDatetimeError = class extends Error {
|
|
566
|
+
};
|
|
435
567
|
// Annotate the CommonJS export names for ESM import in node:
|
|
436
568
|
0 && (module.exports = {
|
|
437
569
|
ATP_URI_REGEX,
|
|
@@ -439,23 +571,34 @@ function parseRelative(str) {
|
|
|
439
571
|
DISALLOWED_TLDS,
|
|
440
572
|
DisallowedDomainError,
|
|
441
573
|
INVALID_HANDLE,
|
|
574
|
+
InvalidDatetimeError,
|
|
442
575
|
InvalidDidError,
|
|
443
576
|
InvalidHandleError,
|
|
444
577
|
InvalidNsidError,
|
|
578
|
+
InvalidRecordKeyError,
|
|
579
|
+
InvalidTidError,
|
|
445
580
|
NSID,
|
|
446
581
|
ReservedHandleError,
|
|
447
582
|
UnsupportedDomainError,
|
|
448
583
|
ensureValidAtUri,
|
|
449
584
|
ensureValidAtUriRegex,
|
|
585
|
+
ensureValidDatetime,
|
|
450
586
|
ensureValidDid,
|
|
451
587
|
ensureValidDidRegex,
|
|
452
588
|
ensureValidHandle,
|
|
453
589
|
ensureValidHandleRegex,
|
|
454
590
|
ensureValidNsid,
|
|
455
591
|
ensureValidNsidRegex,
|
|
592
|
+
ensureValidRecordKey,
|
|
593
|
+
ensureValidTid,
|
|
594
|
+
isValidDatetime,
|
|
456
595
|
isValidHandle,
|
|
596
|
+
isValidRecordKey,
|
|
597
|
+
isValidTid,
|
|
457
598
|
isValidTld,
|
|
458
599
|
normalizeAndEnsureValidHandle,
|
|
600
|
+
normalizeDatetime,
|
|
601
|
+
normalizeDatetimeAlways,
|
|
459
602
|
normalizeHandle
|
|
460
603
|
});
|
|
461
604
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/index.ts", "../src/handle.ts", "../src/did.ts", "../src/nsid.ts", "../src/aturi_validation.ts", "../src/aturi.ts"],
|
|
4
|
-
"sourcesContent": ["export * from './handle'\nexport * from './did'\nexport * from './nsid'\nexport * from './aturi'\n", "export const INVALID_HANDLE = 'handle.invalid'\n\n// Currently these are registration-time restrictions, not protocol-level\n// restrictions. We have a couple accounts in the wild that we need to clean up\n// before hard-disallow.\n// See also: https://en.wikipedia.org/wiki/Top-level_domain#Reserved_domains\nexport const DISALLOWED_TLDS = [\n '.local',\n '.arpa',\n '.invalid',\n '.localhost',\n '.internal',\n '.example',\n '.alt',\n // policy could concievably change on \".onion\" some day\n '.onion',\n // NOTE: .test is allowed in testing and devopment. In practical terms\n // \"should\" \"never\" actually resolve and get registered in production\n]\n\n// Handle constraints, in English:\n// - must be a possible domain name\n// - RFC-1035 is commonly referenced, but has been updated. eg, RFC-3696,\n// section 2. and RFC-3986, section 3. can now have leading numbers (eg,\n// 4chan.org)\n// - \"labels\" (sub-names) are made of ASCII letters, digits, hyphens\n// - can not start or end with a hyphen\n// - TLD (last component) should not start with a digit\n// - can't end with a hyphen (can end with digit)\n// - each segment must be between 1 and 63 characters (not including any periods)\n// - overall length can't be more than 253 characters\n// - separated by (ASCII) periods; does not start or end with period\n// - case insensitive\n// - domains (handles) are equal if they are the same lower-case\n// - punycode allowed for internationalization\n// - no whitespace, null bytes, joining chars, etc\n// - does not validate whether domain or TLD exists, or is a reserved or\n// special TLD (eg, .onion or .local)\n// - does not validate punycode\nexport const ensureValidHandle = (handle: string): void => {\n // check that all chars are boring ASCII\n if (!/^[a-zA-Z0-9.-]*$/.test(handle)) {\n throw new InvalidHandleError(\n 'Disallowed characters in handle (ASCII letters, digits, dashes, periods only)',\n )\n }\n\n if (handle.length > 253) {\n throw new InvalidHandleError('Handle is too long (253 chars max)')\n }\n const labels = handle.split('.')\n if (labels.length < 2) {\n throw new InvalidHandleError('Handle domain needs at least two parts')\n }\n for (let i = 0; i < labels.length; i++) {\n const l = labels[i]\n if (l.length < 1) {\n throw new InvalidHandleError('Handle parts can not be empty')\n }\n if (l.length > 63) {\n throw new InvalidHandleError('Handle part too long (max 63 chars)')\n }\n if (l.endsWith('-') || l.startsWith('-')) {\n throw new InvalidHandleError(\n 'Handle parts can not start or end with hyphens',\n )\n }\n if (i + 1 == labels.length && !/^[a-zA-Z]/.test(l)) {\n throw new InvalidHandleError(\n 'Handle final component (TLD) must start with ASCII letter',\n )\n }\n }\n}\n\n// simple regex translation of above constraints\nexport const ensureValidHandleRegex = (handle: string): void => {\n if (\n !/^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/.test(\n handle,\n )\n ) {\n throw new InvalidHandleError(\"Handle didn't validate via regex\")\n }\n if (handle.length > 253) {\n throw new InvalidHandleError('Handle is too long (253 chars max)')\n }\n}\n\nexport const normalizeHandle = (handle: string): string => {\n return handle.toLowerCase()\n}\n\nexport const normalizeAndEnsureValidHandle = (handle: string): string => {\n const normalized = normalizeHandle(handle)\n ensureValidHandle(normalized)\n return normalized\n}\n\nexport const isValidHandle = (handle: string): boolean => {\n try {\n ensureValidHandle(handle)\n } catch (err) {\n if (err instanceof InvalidHandleError) {\n return false\n }\n throw err\n }\n\n return true\n}\n\nexport const isValidTld = (handle: string): boolean => {\n return !DISALLOWED_TLDS.some((domain) => handle.endsWith(domain))\n}\n\nexport class InvalidHandleError extends Error {}\nexport class ReservedHandleError extends Error {}\nexport class UnsupportedDomainError extends Error {}\nexport class DisallowedDomainError extends Error {}\n", "// Human-readable constraints:\n// - valid W3C DID (https://www.w3.org/TR/did-core/#did-syntax)\n// - entire URI is ASCII: [a-zA-Z0-9._:%-]\n// - always starts \"did:\" (lower-case)\n// - method name is one or more lower-case letters, followed by \":\"\n// - remaining identifier can have any of the above chars, but can not end in \":\"\n// - it seems that a bunch of \":\" can be included, and don't need spaces between\n// - \"%\" is used only for \"percent encoding\" and must be followed by two hex characters (and thus can't end in \"%\")\n// - query (\"?\") and fragment (\"#\") stuff is defined for \"DID URIs\", but not as part of identifier itself\n// - \"The current specification does not take a position on the maximum length of a DID\"\n// - in current atproto, only allowing did:plc and did:web. But not *forcing* this at lexicon layer\n// - hard length limit of 8KBytes\n// - not going to validate \"percent encoding\" here\nexport const ensureValidDid = (did: string): void => {\n // check that all chars are boring ASCII\n if (!/^[a-zA-Z0-9._:%-]*$/.test(did)) {\n throw new InvalidDidError(\n 'Disallowed characters in DID (ASCII letters, digits, and a couple other characters only)',\n )\n }\n\n const parts = did.split(':')\n if (parts.length < 3) {\n throw new InvalidDidError(\n 'DID requires prefix, method, and method-specific content',\n )\n }\n\n if (parts[0] != 'did') {\n throw new InvalidDidError('DID requires \"did:\" prefix')\n }\n\n if (!/^[a-z]+$/.test(parts[1])) {\n throw new InvalidDidError('DID method must be lower-case letters')\n }\n\n if (did.endsWith(':') || did.endsWith('%')) {\n throw new InvalidDidError('DID can not end with \":\" or \"%\"')\n }\n\n if (did.length > 2 * 1024) {\n throw new InvalidDidError('DID is too long (2048 chars max)')\n }\n}\n\nexport const ensureValidDidRegex = (did: string): void => {\n // simple regex to enforce most constraints via just regex and length.\n // hand wrote this regex based on above constraints\n if (!/^did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]$/.test(did)) {\n throw new InvalidDidError(\"DID didn't validate via regex\")\n }\n\n if (did.length > 2 * 1024) {\n throw new InvalidDidError('DID is too long (2048 chars max)')\n }\n}\n\nexport class InvalidDidError extends Error {}\n", "/*\nGrammar:\n\nalpha = \"a\" / \"b\" / \"c\" / \"d\" / \"e\" / \"f\" / \"g\" / \"h\" / \"i\" / \"j\" / \"k\" / \"l\" / \"m\" / \"n\" / \"o\" / \"p\" / \"q\" / \"r\" / \"s\" / \"t\" / \"u\" / \"v\" / \"w\" / \"x\" / \"y\" / \"z\" / \"A\" / \"B\" / \"C\" / \"D\" / \"E\" / \"F\" / \"G\" / \"H\" / \"I\" / \"J\" / \"K\" / \"L\" / \"M\" / \"N\" / \"O\" / \"P\" / \"Q\" / \"R\" / \"S\" / \"T\" / \"U\" / \"V\" / \"W\" / \"X\" / \"Y\" / \"Z\"\nnumber = \"1\" / \"2\" / \"3\" / \"4\" / \"5\" / \"6\" / \"7\" / \"8\" / \"9\" / \"0\"\ndelim = \".\"\nsegment = alpha *( alpha / number / \"-\" )\nauthority = segment *( delim segment )\nname = alpha *( alpha )\nnsid = authority delim name\n\n*/\n\nexport class NSID {\n segments: string[] = []\n\n static parse(nsid: string): NSID {\n return new NSID(nsid)\n }\n\n static create(authority: string, name: string): NSID {\n const segments = [...authority.split('.').reverse(), name].join('.')\n return new NSID(segments)\n }\n\n static isValid(nsid: string): boolean {\n try {\n NSID.parse(nsid)\n return true\n } catch (e) {\n return false\n }\n }\n\n constructor(nsid: string) {\n ensureValidNsid(nsid)\n this.segments = nsid.split('.')\n }\n\n get authority() {\n return this.segments\n .slice(0, this.segments.length - 1)\n .reverse()\n .join('.')\n }\n\n get name() {\n return this.segments.at(this.segments.length - 1)\n }\n\n toString() {\n return this.segments.join('.')\n }\n}\n\n// Human readable constraints on NSID:\n// - a valid domain in reversed notation\n// - followed by an additional period-separated name, which is camel-case letters\nexport const ensureValidNsid = (nsid: string): void => {\n const toCheck = nsid\n\n // check that all chars are boring ASCII\n if (!/^[a-zA-Z0-9.-]*$/.test(toCheck)) {\n throw new InvalidNsidError(\n 'Disallowed characters in NSID (ASCII letters, digits, dashes, periods only)',\n )\n }\n\n if (toCheck.length > 253 + 1 + 63) {\n throw new InvalidNsidError('NSID is too long (317 chars max)')\n }\n const labels = toCheck.split('.')\n if (labels.length < 3) {\n throw new InvalidNsidError('NSID needs at least three parts')\n }\n for (let i = 0; i < labels.length; i++) {\n const l = labels[i]\n if (l.length < 1) {\n throw new InvalidNsidError('NSID parts can not be empty')\n }\n if (l.length > 63) {\n throw new InvalidNsidError('NSID part too long (max 63 chars)')\n }\n if (l.endsWith('-') || l.startsWith('-')) {\n throw new InvalidNsidError('NSID parts can not start or end with hyphen')\n }\n if (/^[0-9]/.test(l) && i == 0) {\n throw new InvalidNsidError('NSID first part may not start with a digit')\n }\n if (!/^[a-zA-Z]+$/.test(l) && i + 1 == labels.length) {\n throw new InvalidNsidError('NSID name part must be only letters')\n }\n }\n}\n\nexport const ensureValidNsidRegex = (nsid: string): void => {\n // simple regex to enforce most constraints via just regex and length.\n // hand wrote this regex based on above constraints\n if (\n !/^[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(\\.[a-zA-Z]([a-zA-Z]{0,61}[a-zA-Z])?)$/.test(\n nsid,\n )\n ) {\n throw new InvalidNsidError(\"NSID didn't validate via regex\")\n }\n if (nsid.length > 253 + 1 + 63) {\n throw new InvalidNsidError('NSID is too long (317 chars max)')\n }\n}\n\nexport class InvalidNsidError extends Error {}\n", "import { ensureValidHandle, ensureValidHandleRegex } from './handle'\nimport { ensureValidDid, ensureValidDidRegex } from './did'\nimport { ensureValidNsid, ensureValidNsidRegex } from './nsid'\n\n// Human-readable constraints on ATURI:\n// - following regular URLs, a 8KByte hard total length limit\n// - follows ATURI docs on website\n// - all ASCII characters, no whitespace. non-ASCII could be URL-encoded\n// - starts \"at://\"\n// - \"authority\" is a valid DID or a valid handle\n// - optionally, follow \"authority\" with \"/\" and valid NSID as start of path\n// - optionally, if NSID given, follow that with \"/\" and rkey\n// - rkey path component can include URL-encoded (\"percent encoded\"), or:\n// ALPHA / DIGIT / \"-\" / \".\" / \"_\" / \"~\" / \":\" / \"@\" / \"!\" / \"$\" / \"&\" / \"'\" / \"(\" / \")\" / \"*\" / \"+\" / \",\" / \";\" / \"=\"\n// [a-zA-Z0-9._~:@!$&'\\(\\)*+,;=-]\n// - rkey must have at least one char\n// - regardless of path component, a fragment can follow as \"#\" and then a JSON pointer (RFC-6901)\nexport const ensureValidAtUri = (uri: string) => {\n // JSON pointer is pretty different from rest of URI, so split that out first\n const uriParts = uri.split('#')\n if (uriParts.length > 2) {\n throw new Error('ATURI can have at most one \"#\", separating fragment out')\n }\n const fragmentPart = uriParts[1] || null\n uri = uriParts[0]\n\n // check that all chars are boring ASCII\n if (!/^[a-zA-Z0-9._~:@!$&')(*+,;=%/-]*$/.test(uri)) {\n throw new Error('Disallowed characters in ATURI (ASCII)')\n }\n\n const parts = uri.split('/')\n if (parts.length >= 3 && (parts[0] != 'at:' || parts[1].length != 0)) {\n throw new Error('ATURI must start with \"at://\"')\n }\n if (parts.length < 3) {\n throw new Error('ATURI requires at least method and authority sections')\n }\n\n try {\n ensureValidHandle(parts[2])\n } catch {\n try {\n ensureValidDid(parts[2])\n } catch {\n throw new Error('ATURI authority must be a valid handle or DID')\n }\n }\n\n if (parts.length >= 4) {\n if (parts[3].length == 0) {\n throw new Error(\n 'ATURI can not have a slash after authority without a path segment',\n )\n }\n try {\n ensureValidNsid(parts[3])\n } catch {\n throw new Error(\n 'ATURI requires first path segment (if supplied) to be valid NSID',\n )\n }\n }\n\n if (parts.length >= 5) {\n if (parts[4].length == 0) {\n throw new Error(\n 'ATURI can not have a slash after collection, unless record key is provided',\n )\n }\n // would validate rkey here, but there are basically no constraints!\n }\n\n if (parts.length >= 6) {\n throw new Error(\n 'ATURI path can have at most two parts, and no trailing slash',\n )\n }\n\n if (uriParts.length >= 2 && fragmentPart == null) {\n throw new Error('ATURI fragment must be non-empty and start with slash')\n }\n\n if (fragmentPart != null) {\n if (fragmentPart.length == 0 || fragmentPart[0] != '/') {\n throw new Error('ATURI fragment must be non-empty and start with slash')\n }\n // NOTE: enforcing *some* checks here for sanity. Eg, at least no whitespace\n if (!/^\\/[a-zA-Z0-9._~:@!$&')(*+,;=%[\\]/-]*$/.test(fragmentPart)) {\n throw new Error('Disallowed characters in ATURI fragment (ASCII)')\n }\n }\n\n if (uri.length > 8 * 1024) {\n throw new Error('ATURI is far too long')\n }\n}\n\nexport const ensureValidAtUriRegex = (uri: string): void => {\n // simple regex to enforce most constraints via just regex and length.\n // hand wrote this regex based on above constraints. whew!\n const aturiRegex =\n /^at:\\/\\/(?<authority>[a-zA-Z0-9._:%-]+)(\\/(?<collection>[a-zA-Z0-9-.]+)(\\/(?<rkey>[a-zA-Z0-9._~:@!$&%')(*+,;=-]+))?)?(#(?<fragment>\\/[a-zA-Z0-9._~:@!$&%')(*+,;=\\-[\\]/\\\\]*))?$/\n const rm = uri.match(aturiRegex)\n if (!rm || !rm.groups) {\n throw new Error(\"ATURI didn't validate via regex\")\n }\n const groups = rm.groups\n\n try {\n ensureValidHandleRegex(groups.authority)\n } catch {\n try {\n ensureValidDidRegex(groups.authority)\n } catch {\n throw new Error('ATURI authority must be a valid handle or DID')\n }\n }\n\n if (groups.collection) {\n try {\n ensureValidNsidRegex(groups.collection)\n } catch {\n throw new Error('ATURI collection path segment must be a valid NSID')\n }\n }\n\n if (uri.length > 8 * 1024) {\n throw new Error('ATURI is far too long')\n }\n}\n", "export * from './aturi_validation'\n\nexport const ATP_URI_REGEX =\n // proto- --did-------------- --name---------------- --path---- --query-- --hash--\n /^(at:\\/\\/)?((?:did:[a-z0-9:%-]+)|(?:[a-z0-9][a-z0-9.:-]*))(\\/[^?#\\s]*)?(\\?[^#\\s]+)?(#[^\\s]+)?$/i\n// --path----- --query-- --hash--\nconst RELATIVE_REGEX = /^(\\/[^?#\\s]*)?(\\?[^#\\s]+)?(#[^\\s]+)?$/i\n\nexport class AtUri {\n hash: string\n host: string\n pathname: string\n searchParams: URLSearchParams\n\n constructor(uri: string, base?: string) {\n let parsed\n if (base) {\n parsed = parse(base)\n if (!parsed) {\n throw new Error(`Invalid at uri: ${base}`)\n }\n const relativep = parseRelative(uri)\n if (!relativep) {\n throw new Error(`Invalid path: ${uri}`)\n }\n Object.assign(parsed, relativep)\n } else {\n parsed = parse(uri)\n if (!parsed) {\n throw new Error(`Invalid at uri: ${uri}`)\n }\n }\n\n this.hash = parsed.hash\n this.host = parsed.host\n this.pathname = parsed.pathname\n this.searchParams = parsed.searchParams\n }\n\n static make(handleOrDid: string, collection?: string, rkey?: string) {\n let str = handleOrDid\n if (collection) str += '/' + collection\n if (rkey) str += '/' + rkey\n return new AtUri(str)\n }\n\n get protocol() {\n return 'at:'\n }\n\n get origin() {\n return `at://${this.host}`\n }\n\n get hostname() {\n return this.host\n }\n\n set hostname(v: string) {\n this.host = v\n }\n\n get search() {\n return this.searchParams.toString()\n }\n\n set search(v: string) {\n this.searchParams = new URLSearchParams(v)\n }\n\n get collection() {\n return this.pathname.split('/').filter(Boolean)[0] || ''\n }\n\n set collection(v: string) {\n const parts = this.pathname.split('/').filter(Boolean)\n parts[0] = v\n this.pathname = parts.join('/')\n }\n\n get rkey() {\n return this.pathname.split('/').filter(Boolean)[1] || ''\n }\n\n set rkey(v: string) {\n const parts = this.pathname.split('/').filter(Boolean)\n if (!parts[0]) parts[0] = 'undefined'\n parts[1] = v\n this.pathname = parts.join('/')\n }\n\n get href() {\n return this.toString()\n }\n\n toString() {\n let path = this.pathname || '/'\n if (!path.startsWith('/')) {\n path = `/${path}`\n }\n let qs = this.searchParams.toString()\n if (qs && !qs.startsWith('?')) {\n qs = `?${qs}`\n }\n let hash = this.hash\n if (hash && !hash.startsWith('#')) {\n hash = `#${hash}`\n }\n return `at://${this.host}${path}${qs}${hash}`\n }\n}\n\nfunction parse(str: string) {\n const match = ATP_URI_REGEX.exec(str)\n if (match) {\n return {\n hash: match[5] || '',\n host: match[2] || '',\n pathname: match[3] || '',\n searchParams: new URLSearchParams(match[4] || ''),\n }\n }\n return undefined\n}\n\nfunction parseRelative(str: string) {\n const match = RELATIVE_REGEX.exec(str)\n if (match) {\n return {\n hash: match[3] || '',\n pathname: match[1] || '',\n searchParams: new URLSearchParams(match[2] || ''),\n }\n }\n return undefined\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
3
|
+
"sources": ["../src/index.ts", "../src/handle.ts", "../src/did.ts", "../src/nsid.ts", "../src/aturi_validation.ts", "../src/aturi.ts", "../src/tid.ts", "../src/recordkey.ts", "../src/datetime.ts"],
|
|
4
|
+
"sourcesContent": ["export * from './handle'\nexport * from './did'\nexport * from './nsid'\nexport * from './aturi'\nexport * from './tid'\nexport * from './recordkey'\nexport * from './datetime'\n", "export const INVALID_HANDLE = 'handle.invalid'\n\n// Currently these are registration-time restrictions, not protocol-level\n// restrictions. We have a couple accounts in the wild that we need to clean up\n// before hard-disallow.\n// See also: https://en.wikipedia.org/wiki/Top-level_domain#Reserved_domains\nexport const DISALLOWED_TLDS = [\n '.local',\n '.arpa',\n '.invalid',\n '.localhost',\n '.internal',\n '.example',\n '.alt',\n // policy could concievably change on \".onion\" some day\n '.onion',\n // NOTE: .test is allowed in testing and devopment. In practical terms\n // \"should\" \"never\" actually resolve and get registered in production\n]\n\n// Handle constraints, in English:\n// - must be a possible domain name\n// - RFC-1035 is commonly referenced, but has been updated. eg, RFC-3696,\n// section 2. and RFC-3986, section 3. can now have leading numbers (eg,\n// 4chan.org)\n// - \"labels\" (sub-names) are made of ASCII letters, digits, hyphens\n// - can not start or end with a hyphen\n// - TLD (last component) should not start with a digit\n// - can't end with a hyphen (can end with digit)\n// - each segment must be between 1 and 63 characters (not including any periods)\n// - overall length can't be more than 253 characters\n// - separated by (ASCII) periods; does not start or end with period\n// - case insensitive\n// - domains (handles) are equal if they are the same lower-case\n// - punycode allowed for internationalization\n// - no whitespace, null bytes, joining chars, etc\n// - does not validate whether domain or TLD exists, or is a reserved or\n// special TLD (eg, .onion or .local)\n// - does not validate punycode\nexport const ensureValidHandle = (handle: string): void => {\n // check that all chars are boring ASCII\n if (!/^[a-zA-Z0-9.-]*$/.test(handle)) {\n throw new InvalidHandleError(\n 'Disallowed characters in handle (ASCII letters, digits, dashes, periods only)',\n )\n }\n\n if (handle.length > 253) {\n throw new InvalidHandleError('Handle is too long (253 chars max)')\n }\n const labels = handle.split('.')\n if (labels.length < 2) {\n throw new InvalidHandleError('Handle domain needs at least two parts')\n }\n for (let i = 0; i < labels.length; i++) {\n const l = labels[i]\n if (l.length < 1) {\n throw new InvalidHandleError('Handle parts can not be empty')\n }\n if (l.length > 63) {\n throw new InvalidHandleError('Handle part too long (max 63 chars)')\n }\n if (l.endsWith('-') || l.startsWith('-')) {\n throw new InvalidHandleError(\n 'Handle parts can not start or end with hyphens',\n )\n }\n if (i + 1 == labels.length && !/^[a-zA-Z]/.test(l)) {\n throw new InvalidHandleError(\n 'Handle final component (TLD) must start with ASCII letter',\n )\n }\n }\n}\n\n// simple regex translation of above constraints\nexport const ensureValidHandleRegex = (handle: string): void => {\n if (\n !/^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/.test(\n handle,\n )\n ) {\n throw new InvalidHandleError(\"Handle didn't validate via regex\")\n }\n if (handle.length > 253) {\n throw new InvalidHandleError('Handle is too long (253 chars max)')\n }\n}\n\nexport const normalizeHandle = (handle: string): string => {\n return handle.toLowerCase()\n}\n\nexport const normalizeAndEnsureValidHandle = (handle: string): string => {\n const normalized = normalizeHandle(handle)\n ensureValidHandle(normalized)\n return normalized\n}\n\nexport const isValidHandle = (handle: string): boolean => {\n try {\n ensureValidHandle(handle)\n } catch (err) {\n if (err instanceof InvalidHandleError) {\n return false\n }\n throw err\n }\n\n return true\n}\n\nexport const isValidTld = (handle: string): boolean => {\n return !DISALLOWED_TLDS.some((domain) => handle.endsWith(domain))\n}\n\nexport class InvalidHandleError extends Error {}\nexport class ReservedHandleError extends Error {}\nexport class UnsupportedDomainError extends Error {}\nexport class DisallowedDomainError extends Error {}\n", "// Human-readable constraints:\n// - valid W3C DID (https://www.w3.org/TR/did-core/#did-syntax)\n// - entire URI is ASCII: [a-zA-Z0-9._:%-]\n// - always starts \"did:\" (lower-case)\n// - method name is one or more lower-case letters, followed by \":\"\n// - remaining identifier can have any of the above chars, but can not end in \":\"\n// - it seems that a bunch of \":\" can be included, and don't need spaces between\n// - \"%\" is used only for \"percent encoding\" and must be followed by two hex characters (and thus can't end in \"%\")\n// - query (\"?\") and fragment (\"#\") stuff is defined for \"DID URIs\", but not as part of identifier itself\n// - \"The current specification does not take a position on the maximum length of a DID\"\n// - in current atproto, only allowing did:plc and did:web. But not *forcing* this at lexicon layer\n// - hard length limit of 8KBytes\n// - not going to validate \"percent encoding\" here\nexport const ensureValidDid = (did: string): void => {\n // check that all chars are boring ASCII\n if (!/^[a-zA-Z0-9._:%-]*$/.test(did)) {\n throw new InvalidDidError(\n 'Disallowed characters in DID (ASCII letters, digits, and a couple other characters only)',\n )\n }\n\n const parts = did.split(':')\n if (parts.length < 3) {\n throw new InvalidDidError(\n 'DID requires prefix, method, and method-specific content',\n )\n }\n\n if (parts[0] != 'did') {\n throw new InvalidDidError('DID requires \"did:\" prefix')\n }\n\n if (!/^[a-z]+$/.test(parts[1])) {\n throw new InvalidDidError('DID method must be lower-case letters')\n }\n\n if (did.endsWith(':') || did.endsWith('%')) {\n throw new InvalidDidError('DID can not end with \":\" or \"%\"')\n }\n\n if (did.length > 2 * 1024) {\n throw new InvalidDidError('DID is too long (2048 chars max)')\n }\n}\n\nexport const ensureValidDidRegex = (did: string): void => {\n // simple regex to enforce most constraints via just regex and length.\n // hand wrote this regex based on above constraints\n if (!/^did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]$/.test(did)) {\n throw new InvalidDidError(\"DID didn't validate via regex\")\n }\n\n if (did.length > 2 * 1024) {\n throw new InvalidDidError('DID is too long (2048 chars max)')\n }\n}\n\nexport class InvalidDidError extends Error {}\n", "/*\nGrammar:\n\nalpha = \"a\" / \"b\" / \"c\" / \"d\" / \"e\" / \"f\" / \"g\" / \"h\" / \"i\" / \"j\" / \"k\" / \"l\" / \"m\" / \"n\" / \"o\" / \"p\" / \"q\" / \"r\" / \"s\" / \"t\" / \"u\" / \"v\" / \"w\" / \"x\" / \"y\" / \"z\" / \"A\" / \"B\" / \"C\" / \"D\" / \"E\" / \"F\" / \"G\" / \"H\" / \"I\" / \"J\" / \"K\" / \"L\" / \"M\" / \"N\" / \"O\" / \"P\" / \"Q\" / \"R\" / \"S\" / \"T\" / \"U\" / \"V\" / \"W\" / \"X\" / \"Y\" / \"Z\"\nnumber = \"1\" / \"2\" / \"3\" / \"4\" / \"5\" / \"6\" / \"7\" / \"8\" / \"9\" / \"0\"\ndelim = \".\"\nsegment = alpha *( alpha / number / \"-\" )\nauthority = segment *( delim segment )\nname = alpha *( alpha )\nnsid = authority delim name\n\n*/\n\nexport class NSID {\n segments: string[] = []\n\n static parse(nsid: string): NSID {\n return new NSID(nsid)\n }\n\n static create(authority: string, name: string): NSID {\n const segments = [...authority.split('.').reverse(), name].join('.')\n return new NSID(segments)\n }\n\n static isValid(nsid: string): boolean {\n try {\n NSID.parse(nsid)\n return true\n } catch (e) {\n return false\n }\n }\n\n constructor(nsid: string) {\n ensureValidNsid(nsid)\n this.segments = nsid.split('.')\n }\n\n get authority() {\n return this.segments\n .slice(0, this.segments.length - 1)\n .reverse()\n .join('.')\n }\n\n get name() {\n return this.segments.at(this.segments.length - 1)\n }\n\n toString() {\n return this.segments.join('.')\n }\n}\n\n// Human readable constraints on NSID:\n// - a valid domain in reversed notation\n// - followed by an additional period-separated name, which is camel-case letters\nexport const ensureValidNsid = (nsid: string): void => {\n const toCheck = nsid\n\n // check that all chars are boring ASCII\n if (!/^[a-zA-Z0-9.-]*$/.test(toCheck)) {\n throw new InvalidNsidError(\n 'Disallowed characters in NSID (ASCII letters, digits, dashes, periods only)',\n )\n }\n\n if (toCheck.length > 253 + 1 + 63) {\n throw new InvalidNsidError('NSID is too long (317 chars max)')\n }\n const labels = toCheck.split('.')\n if (labels.length < 3) {\n throw new InvalidNsidError('NSID needs at least three parts')\n }\n for (let i = 0; i < labels.length; i++) {\n const l = labels[i]\n if (l.length < 1) {\n throw new InvalidNsidError('NSID parts can not be empty')\n }\n if (l.length > 63) {\n throw new InvalidNsidError('NSID part too long (max 63 chars)')\n }\n if (l.endsWith('-') || l.startsWith('-')) {\n throw new InvalidNsidError('NSID parts can not start or end with hyphen')\n }\n if (/^[0-9]/.test(l) && i == 0) {\n throw new InvalidNsidError('NSID first part may not start with a digit')\n }\n if (!/^[a-zA-Z]+$/.test(l) && i + 1 == labels.length) {\n throw new InvalidNsidError('NSID name part must be only letters')\n }\n }\n}\n\nexport const ensureValidNsidRegex = (nsid: string): void => {\n // simple regex to enforce most constraints via just regex and length.\n // hand wrote this regex based on above constraints\n if (\n !/^[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(\\.[a-zA-Z]([a-zA-Z]{0,61}[a-zA-Z])?)$/.test(\n nsid,\n )\n ) {\n throw new InvalidNsidError(\"NSID didn't validate via regex\")\n }\n if (nsid.length > 253 + 1 + 63) {\n throw new InvalidNsidError('NSID is too long (317 chars max)')\n }\n}\n\nexport class InvalidNsidError extends Error {}\n", "import { ensureValidHandle, ensureValidHandleRegex } from './handle'\nimport { ensureValidDid, ensureValidDidRegex } from './did'\nimport { ensureValidNsid, ensureValidNsidRegex } from './nsid'\n\n// Human-readable constraints on ATURI:\n// - following regular URLs, a 8KByte hard total length limit\n// - follows ATURI docs on website\n// - all ASCII characters, no whitespace. non-ASCII could be URL-encoded\n// - starts \"at://\"\n// - \"authority\" is a valid DID or a valid handle\n// - optionally, follow \"authority\" with \"/\" and valid NSID as start of path\n// - optionally, if NSID given, follow that with \"/\" and rkey\n// - rkey path component can include URL-encoded (\"percent encoded\"), or:\n// ALPHA / DIGIT / \"-\" / \".\" / \"_\" / \"~\" / \":\" / \"@\" / \"!\" / \"$\" / \"&\" / \"'\" / \"(\" / \")\" / \"*\" / \"+\" / \",\" / \";\" / \"=\"\n// [a-zA-Z0-9._~:@!$&'\\(\\)*+,;=-]\n// - rkey must have at least one char\n// - regardless of path component, a fragment can follow as \"#\" and then a JSON pointer (RFC-6901)\nexport const ensureValidAtUri = (uri: string) => {\n // JSON pointer is pretty different from rest of URI, so split that out first\n const uriParts = uri.split('#')\n if (uriParts.length > 2) {\n throw new Error('ATURI can have at most one \"#\", separating fragment out')\n }\n const fragmentPart = uriParts[1] || null\n uri = uriParts[0]\n\n // check that all chars are boring ASCII\n if (!/^[a-zA-Z0-9._~:@!$&')(*+,;=%/-]*$/.test(uri)) {\n throw new Error('Disallowed characters in ATURI (ASCII)')\n }\n\n const parts = uri.split('/')\n if (parts.length >= 3 && (parts[0] != 'at:' || parts[1].length != 0)) {\n throw new Error('ATURI must start with \"at://\"')\n }\n if (parts.length < 3) {\n throw new Error('ATURI requires at least method and authority sections')\n }\n\n try {\n if (parts[2].startsWith('did:')) {\n ensureValidDid(parts[2])\n } else {\n ensureValidHandle(parts[2])\n }\n } catch {\n throw new Error('ATURI authority must be a valid handle or DID')\n }\n\n if (parts.length >= 4) {\n if (parts[3].length == 0) {\n throw new Error(\n 'ATURI can not have a slash after authority without a path segment',\n )\n }\n try {\n ensureValidNsid(parts[3])\n } catch {\n throw new Error(\n 'ATURI requires first path segment (if supplied) to be valid NSID',\n )\n }\n }\n\n if (parts.length >= 5) {\n if (parts[4].length == 0) {\n throw new Error(\n 'ATURI can not have a slash after collection, unless record key is provided',\n )\n }\n // would validate rkey here, but there are basically no constraints!\n }\n\n if (parts.length >= 6) {\n throw new Error(\n 'ATURI path can have at most two parts, and no trailing slash',\n )\n }\n\n if (uriParts.length >= 2 && fragmentPart == null) {\n throw new Error('ATURI fragment must be non-empty and start with slash')\n }\n\n if (fragmentPart != null) {\n if (fragmentPart.length == 0 || fragmentPart[0] != '/') {\n throw new Error('ATURI fragment must be non-empty and start with slash')\n }\n // NOTE: enforcing *some* checks here for sanity. Eg, at least no whitespace\n if (!/^\\/[a-zA-Z0-9._~:@!$&')(*+,;=%[\\]/-]*$/.test(fragmentPart)) {\n throw new Error('Disallowed characters in ATURI fragment (ASCII)')\n }\n }\n\n if (uri.length > 8 * 1024) {\n throw new Error('ATURI is far too long')\n }\n}\n\nexport const ensureValidAtUriRegex = (uri: string): void => {\n // simple regex to enforce most constraints via just regex and length.\n // hand wrote this regex based on above constraints. whew!\n const aturiRegex =\n /^at:\\/\\/(?<authority>[a-zA-Z0-9._:%-]+)(\\/(?<collection>[a-zA-Z0-9-.]+)(\\/(?<rkey>[a-zA-Z0-9._~:@!$&%')(*+,;=-]+))?)?(#(?<fragment>\\/[a-zA-Z0-9._~:@!$&%')(*+,;=\\-[\\]/\\\\]*))?$/\n const rm = uri.match(aturiRegex)\n if (!rm || !rm.groups) {\n throw new Error(\"ATURI didn't validate via regex\")\n }\n const groups = rm.groups\n\n try {\n ensureValidHandleRegex(groups.authority)\n } catch {\n try {\n ensureValidDidRegex(groups.authority)\n } catch {\n throw new Error('ATURI authority must be a valid handle or DID')\n }\n }\n\n if (groups.collection) {\n try {\n ensureValidNsidRegex(groups.collection)\n } catch {\n throw new Error('ATURI collection path segment must be a valid NSID')\n }\n }\n\n if (uri.length > 8 * 1024) {\n throw new Error('ATURI is far too long')\n }\n}\n", "export * from './aturi_validation'\n\nexport const ATP_URI_REGEX =\n // proto- --did-------------- --name---------------- --path---- --query-- --hash--\n /^(at:\\/\\/)?((?:did:[a-z0-9:%-]+)|(?:[a-z0-9][a-z0-9.:-]*))(\\/[^?#\\s]*)?(\\?[^#\\s]+)?(#[^\\s]+)?$/i\n// --path----- --query-- --hash--\nconst RELATIVE_REGEX = /^(\\/[^?#\\s]*)?(\\?[^#\\s]+)?(#[^\\s]+)?$/i\n\nexport class AtUri {\n hash: string\n host: string\n pathname: string\n searchParams: URLSearchParams\n\n constructor(uri: string, base?: string) {\n let parsed\n if (base) {\n parsed = parse(base)\n if (!parsed) {\n throw new Error(`Invalid at uri: ${base}`)\n }\n const relativep = parseRelative(uri)\n if (!relativep) {\n throw new Error(`Invalid path: ${uri}`)\n }\n Object.assign(parsed, relativep)\n } else {\n parsed = parse(uri)\n if (!parsed) {\n throw new Error(`Invalid at uri: ${uri}`)\n }\n }\n\n this.hash = parsed.hash\n this.host = parsed.host\n this.pathname = parsed.pathname\n this.searchParams = parsed.searchParams\n }\n\n static make(handleOrDid: string, collection?: string, rkey?: string) {\n let str = handleOrDid\n if (collection) str += '/' + collection\n if (rkey) str += '/' + rkey\n return new AtUri(str)\n }\n\n get protocol() {\n return 'at:'\n }\n\n get origin() {\n return `at://${this.host}`\n }\n\n get hostname() {\n return this.host\n }\n\n set hostname(v: string) {\n this.host = v\n }\n\n get search() {\n return this.searchParams.toString()\n }\n\n set search(v: string) {\n this.searchParams = new URLSearchParams(v)\n }\n\n get collection() {\n return this.pathname.split('/').filter(Boolean)[0] || ''\n }\n\n set collection(v: string) {\n const parts = this.pathname.split('/').filter(Boolean)\n parts[0] = v\n this.pathname = parts.join('/')\n }\n\n get rkey() {\n return this.pathname.split('/').filter(Boolean)[1] || ''\n }\n\n set rkey(v: string) {\n const parts = this.pathname.split('/').filter(Boolean)\n if (!parts[0]) parts[0] = 'undefined'\n parts[1] = v\n this.pathname = parts.join('/')\n }\n\n get href() {\n return this.toString()\n }\n\n toString() {\n let path = this.pathname || '/'\n if (!path.startsWith('/')) {\n path = `/${path}`\n }\n let qs = this.searchParams.toString()\n if (qs && !qs.startsWith('?')) {\n qs = `?${qs}`\n }\n let hash = this.hash\n if (hash && !hash.startsWith('#')) {\n hash = `#${hash}`\n }\n return `at://${this.host}${path}${qs}${hash}`\n }\n}\n\nfunction parse(str: string) {\n const match = ATP_URI_REGEX.exec(str)\n if (match) {\n return {\n hash: match[5] || '',\n host: match[2] || '',\n pathname: match[3] || '',\n searchParams: new URLSearchParams(match[4] || ''),\n }\n }\n return undefined\n}\n\nfunction parseRelative(str: string) {\n const match = RELATIVE_REGEX.exec(str)\n if (match) {\n return {\n hash: match[3] || '',\n pathname: match[1] || '',\n searchParams: new URLSearchParams(match[2] || ''),\n }\n }\n return undefined\n}\n", "export const ensureValidTid = (tid: string): void => {\n if (tid.length != 13) {\n throw new InvalidTidError('TID must be 13 characters')\n }\n // simple regex to enforce most constraints via just regex and length.\n if (!/^[234567abcdefghij][234567abcdefghijklmnopqrstuvwxyz]{12}$/.test(tid)) {\n throw new InvalidTidError('TID syntax not valid (regex)')\n }\n}\n\nexport const isValidTid = (tid: string): boolean => {\n try {\n ensureValidTid(tid)\n } catch (err) {\n if (err instanceof InvalidTidError) {\n return false\n }\n throw err\n }\n\n return true\n}\n\nexport class InvalidTidError extends Error {}\n", "export const ensureValidRecordKey = (rkey: string): void => {\n if (rkey.length > 512 || rkey.length < 1) {\n throw new InvalidRecordKeyError('record key must be 1 to 512 characters')\n }\n // simple regex to enforce most constraints via just regex and length.\n if (!/^[a-zA-Z0-9_~.:-]{1,512}$/.test(rkey)) {\n throw new InvalidRecordKeyError('record key syntax not valid (regex)')\n }\n if (rkey == '.' || rkey == '..')\n throw new InvalidRecordKeyError('record key can not be \".\" or \"..\"')\n}\n\nexport const isValidRecordKey = (rkey: string): boolean => {\n try {\n ensureValidRecordKey(rkey)\n } catch (err) {\n if (err instanceof InvalidRecordKeyError) {\n return false\n }\n throw err\n }\n\n return true\n}\n\nexport class InvalidRecordKeyError extends Error {}\n", "/* Validates datetime string against atproto Lexicon 'datetime' format.\n * Syntax is described at: https://atproto.com/specs/lexicon#datetime\n */\nexport const ensureValidDatetime = (dtStr: string): void => {\n const date = new Date(dtStr)\n // must parse as ISO 8601; this also verifies semantics like month is not 13 or 00\n if (isNaN(date.getTime())) {\n throw new InvalidDatetimeError('datetime did not parse as ISO 8601')\n }\n if (date.toISOString().startsWith('-')) {\n throw new InvalidDatetimeError('datetime normalized to a negative time')\n }\n // regex and other checks for RFC-3339\n if (\n !/^[0-9]{4}-[01][0-9]-[0-3][0-9]T[0-2][0-9]:[0-6][0-9]:[0-6][0-9](.[0-9]{1,20})?(Z|([+-][0-2][0-9]:[0-5][0-9]))$/.test(\n dtStr,\n )\n ) {\n throw new InvalidDatetimeError(\"datetime didn't validate via regex\")\n }\n if (dtStr.length > 64) {\n throw new InvalidDatetimeError('datetime is too long (64 chars max)')\n }\n if (dtStr.endsWith('-00:00')) {\n throw new InvalidDatetimeError(\n 'datetime can not use \"-00:00\" for UTC timezone',\n )\n }\n if (dtStr.startsWith('000')) {\n throw new InvalidDatetimeError('datetime so close to year zero not allowed')\n }\n}\n\n/* Same logic as ensureValidDatetime(), but returns a boolean instead of throwing an exception.\n */\nexport const isValidDatetime = (dtStr: string): boolean => {\n try {\n ensureValidDatetime(dtStr)\n } catch (err) {\n if (err instanceof InvalidDatetimeError) {\n return false\n }\n throw err\n }\n\n return true\n}\n\n/* Takes a flexible datetime string and normalizes representation.\n *\n * This function will work with any valid atproto datetime (eg, anything which isValidDatetime() is true for). It *additionally* is more flexible about accepting datetimes that don't comply to RFC 3339, or are missing timezone information, and normalizing them to a valid datetime.\n *\n * One use-case is a consistent, sortable string. Another is to work with older invalid createdAt datetimes.\n *\n * Successful output will be a valid atproto datetime with millisecond precision (3 sub-second digits) and UTC timezone with trailing 'Z' syntax. Throws `InvalidDatetimeError` if the input string could not be parsed as a datetime, even with permissive parsing.\n *\n * Expected output format: YYYY-MM-DDTHH:mm:ss.sssZ\n */\nexport const normalizeDatetime = (dtStr: string): string => {\n if (isValidDatetime(dtStr)) {\n const outStr = new Date(dtStr).toISOString()\n if (isValidDatetime(outStr)) {\n return outStr\n }\n }\n\n // check if this permissive datetime is missing a timezone\n if (!/.*(([+-]\\d\\d:?\\d\\d)|[a-zA-Z])$/.test(dtStr)) {\n const date = new Date(dtStr + 'Z')\n if (!isNaN(date.getTime())) {\n const tzStr = date.toISOString()\n if (isValidDatetime(tzStr)) {\n return tzStr\n }\n }\n }\n\n // finally try parsing as simple datetime\n const date = new Date(dtStr)\n if (isNaN(date.getTime())) {\n throw new InvalidDatetimeError(\n 'datetime did not parse as any timestamp format',\n )\n }\n const isoStr = date.toISOString()\n if (isValidDatetime(isoStr)) {\n return isoStr\n } else {\n throw new InvalidDatetimeError(\n 'datetime normalized to invalid timestamp string',\n )\n }\n}\n\n/* Variant of normalizeDatetime() which always returns a valid datetime strings.\n *\n * If a InvalidDatetimeError is encountered, returns the UNIX epoch time as a UTC datetime (1970-01-01T00:00:00.000Z).\n */\nexport const normalizeDatetimeAlways = (dtStr: string): string => {\n try {\n return normalizeDatetime(dtStr)\n } catch (err) {\n if (err instanceof InvalidDatetimeError) {\n return new Date(0).toISOString()\n }\n throw err\n }\n}\n\n/* Indicates a datetime string did not pass full atproto Lexicon datetime string format checks.\n */\nexport class InvalidDatetimeError extends Error {}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/tid.d.ts
ADDED
package/jest.config.js
CHANGED
package/package.json
CHANGED
package/src/aturi_validation.ts
CHANGED
|
@@ -38,13 +38,13 @@ export const ensureValidAtUri = (uri: string) => {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
try {
|
|
41
|
-
|
|
42
|
-
} catch {
|
|
43
|
-
try {
|
|
41
|
+
if (parts[2].startsWith('did:')) {
|
|
44
42
|
ensureValidDid(parts[2])
|
|
45
|
-
}
|
|
46
|
-
|
|
43
|
+
} else {
|
|
44
|
+
ensureValidHandle(parts[2])
|
|
47
45
|
}
|
|
46
|
+
} catch {
|
|
47
|
+
throw new Error('ATURI authority must be a valid handle or DID')
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
if (parts.length >= 4) {
|
package/src/datetime.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/* Validates datetime string against atproto Lexicon 'datetime' format.
|
|
2
|
+
* Syntax is described at: https://atproto.com/specs/lexicon#datetime
|
|
3
|
+
*/
|
|
4
|
+
export const ensureValidDatetime = (dtStr: string): void => {
|
|
5
|
+
const date = new Date(dtStr)
|
|
6
|
+
// must parse as ISO 8601; this also verifies semantics like month is not 13 or 00
|
|
7
|
+
if (isNaN(date.getTime())) {
|
|
8
|
+
throw new InvalidDatetimeError('datetime did not parse as ISO 8601')
|
|
9
|
+
}
|
|
10
|
+
if (date.toISOString().startsWith('-')) {
|
|
11
|
+
throw new InvalidDatetimeError('datetime normalized to a negative time')
|
|
12
|
+
}
|
|
13
|
+
// regex and other checks for RFC-3339
|
|
14
|
+
if (
|
|
15
|
+
!/^[0-9]{4}-[01][0-9]-[0-3][0-9]T[0-2][0-9]:[0-6][0-9]:[0-6][0-9](.[0-9]{1,20})?(Z|([+-][0-2][0-9]:[0-5][0-9]))$/.test(
|
|
16
|
+
dtStr,
|
|
17
|
+
)
|
|
18
|
+
) {
|
|
19
|
+
throw new InvalidDatetimeError("datetime didn't validate via regex")
|
|
20
|
+
}
|
|
21
|
+
if (dtStr.length > 64) {
|
|
22
|
+
throw new InvalidDatetimeError('datetime is too long (64 chars max)')
|
|
23
|
+
}
|
|
24
|
+
if (dtStr.endsWith('-00:00')) {
|
|
25
|
+
throw new InvalidDatetimeError(
|
|
26
|
+
'datetime can not use "-00:00" for UTC timezone',
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
if (dtStr.startsWith('000')) {
|
|
30
|
+
throw new InvalidDatetimeError('datetime so close to year zero not allowed')
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Same logic as ensureValidDatetime(), but returns a boolean instead of throwing an exception.
|
|
35
|
+
*/
|
|
36
|
+
export const isValidDatetime = (dtStr: string): boolean => {
|
|
37
|
+
try {
|
|
38
|
+
ensureValidDatetime(dtStr)
|
|
39
|
+
} catch (err) {
|
|
40
|
+
if (err instanceof InvalidDatetimeError) {
|
|
41
|
+
return false
|
|
42
|
+
}
|
|
43
|
+
throw err
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return true
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Takes a flexible datetime string and normalizes representation.
|
|
50
|
+
*
|
|
51
|
+
* This function will work with any valid atproto datetime (eg, anything which isValidDatetime() is true for). It *additionally* is more flexible about accepting datetimes that don't comply to RFC 3339, or are missing timezone information, and normalizing them to a valid datetime.
|
|
52
|
+
*
|
|
53
|
+
* One use-case is a consistent, sortable string. Another is to work with older invalid createdAt datetimes.
|
|
54
|
+
*
|
|
55
|
+
* Successful output will be a valid atproto datetime with millisecond precision (3 sub-second digits) and UTC timezone with trailing 'Z' syntax. Throws `InvalidDatetimeError` if the input string could not be parsed as a datetime, even with permissive parsing.
|
|
56
|
+
*
|
|
57
|
+
* Expected output format: YYYY-MM-DDTHH:mm:ss.sssZ
|
|
58
|
+
*/
|
|
59
|
+
export const normalizeDatetime = (dtStr: string): string => {
|
|
60
|
+
if (isValidDatetime(dtStr)) {
|
|
61
|
+
const outStr = new Date(dtStr).toISOString()
|
|
62
|
+
if (isValidDatetime(outStr)) {
|
|
63
|
+
return outStr
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// check if this permissive datetime is missing a timezone
|
|
68
|
+
if (!/.*(([+-]\d\d:?\d\d)|[a-zA-Z])$/.test(dtStr)) {
|
|
69
|
+
const date = new Date(dtStr + 'Z')
|
|
70
|
+
if (!isNaN(date.getTime())) {
|
|
71
|
+
const tzStr = date.toISOString()
|
|
72
|
+
if (isValidDatetime(tzStr)) {
|
|
73
|
+
return tzStr
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// finally try parsing as simple datetime
|
|
79
|
+
const date = new Date(dtStr)
|
|
80
|
+
if (isNaN(date.getTime())) {
|
|
81
|
+
throw new InvalidDatetimeError(
|
|
82
|
+
'datetime did not parse as any timestamp format',
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
const isoStr = date.toISOString()
|
|
86
|
+
if (isValidDatetime(isoStr)) {
|
|
87
|
+
return isoStr
|
|
88
|
+
} else {
|
|
89
|
+
throw new InvalidDatetimeError(
|
|
90
|
+
'datetime normalized to invalid timestamp string',
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* Variant of normalizeDatetime() which always returns a valid datetime strings.
|
|
96
|
+
*
|
|
97
|
+
* If a InvalidDatetimeError is encountered, returns the UNIX epoch time as a UTC datetime (1970-01-01T00:00:00.000Z).
|
|
98
|
+
*/
|
|
99
|
+
export const normalizeDatetimeAlways = (dtStr: string): string => {
|
|
100
|
+
try {
|
|
101
|
+
return normalizeDatetime(dtStr)
|
|
102
|
+
} catch (err) {
|
|
103
|
+
if (err instanceof InvalidDatetimeError) {
|
|
104
|
+
return new Date(0).toISOString()
|
|
105
|
+
}
|
|
106
|
+
throw err
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* Indicates a datetime string did not pass full atproto Lexicon datetime string format checks.
|
|
111
|
+
*/
|
|
112
|
+
export class InvalidDatetimeError extends Error {}
|
package/src/index.ts
CHANGED
package/src/recordkey.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const ensureValidRecordKey = (rkey: string): void => {
|
|
2
|
+
if (rkey.length > 512 || rkey.length < 1) {
|
|
3
|
+
throw new InvalidRecordKeyError('record key must be 1 to 512 characters')
|
|
4
|
+
}
|
|
5
|
+
// simple regex to enforce most constraints via just regex and length.
|
|
6
|
+
if (!/^[a-zA-Z0-9_~.:-]{1,512}$/.test(rkey)) {
|
|
7
|
+
throw new InvalidRecordKeyError('record key syntax not valid (regex)')
|
|
8
|
+
}
|
|
9
|
+
if (rkey == '.' || rkey == '..')
|
|
10
|
+
throw new InvalidRecordKeyError('record key can not be "." or ".."')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const isValidRecordKey = (rkey: string): boolean => {
|
|
14
|
+
try {
|
|
15
|
+
ensureValidRecordKey(rkey)
|
|
16
|
+
} catch (err) {
|
|
17
|
+
if (err instanceof InvalidRecordKeyError) {
|
|
18
|
+
return false
|
|
19
|
+
}
|
|
20
|
+
throw err
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return true
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class InvalidRecordKeyError extends Error {}
|
package/src/tid.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const ensureValidTid = (tid: string): void => {
|
|
2
|
+
if (tid.length != 13) {
|
|
3
|
+
throw new InvalidTidError('TID must be 13 characters')
|
|
4
|
+
}
|
|
5
|
+
// simple regex to enforce most constraints via just regex and length.
|
|
6
|
+
if (!/^[234567abcdefghij][234567abcdefghijklmnopqrstuvwxyz]{12}$/.test(tid)) {
|
|
7
|
+
throw new InvalidTidError('TID syntax not valid (regex)')
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const isValidTid = (tid: string): boolean => {
|
|
12
|
+
try {
|
|
13
|
+
ensureValidTid(tid)
|
|
14
|
+
} catch (err) {
|
|
15
|
+
if (err instanceof InvalidTidError) {
|
|
16
|
+
return false
|
|
17
|
+
}
|
|
18
|
+
throw err
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return true
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class InvalidTidError extends Error {}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isValidDatetime,
|
|
3
|
+
ensureValidDatetime,
|
|
4
|
+
normalizeDatetime,
|
|
5
|
+
normalizeDatetimeAlways,
|
|
6
|
+
InvalidDatetimeError,
|
|
7
|
+
} from '../src'
|
|
8
|
+
import * as readline from 'readline'
|
|
9
|
+
import * as fs from 'fs'
|
|
10
|
+
|
|
11
|
+
describe('datetime validation', () => {
|
|
12
|
+
const expectValid = (h: string) => {
|
|
13
|
+
ensureValidDatetime(h)
|
|
14
|
+
normalizeDatetime(h)
|
|
15
|
+
normalizeDatetimeAlways(h)
|
|
16
|
+
}
|
|
17
|
+
const expectInvalid = (h: string) => {
|
|
18
|
+
expect(() => ensureValidDatetime(h)).toThrow(InvalidDatetimeError)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
it('conforms to interop valid datetimes', () => {
|
|
22
|
+
const lineReader = readline.createInterface({
|
|
23
|
+
input: fs.createReadStream(
|
|
24
|
+
`${__dirname}/interop-files/datetime_syntax_valid.txt`,
|
|
25
|
+
),
|
|
26
|
+
terminal: false,
|
|
27
|
+
})
|
|
28
|
+
lineReader.on('line', (line) => {
|
|
29
|
+
if (line.startsWith('#') || line.length == 0) {
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
if (!isValidDatetime(line)) {
|
|
33
|
+
console.log(line)
|
|
34
|
+
}
|
|
35
|
+
expectValid(line)
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('conforms to interop invalid datetimes', () => {
|
|
40
|
+
const lineReader = readline.createInterface({
|
|
41
|
+
input: fs.createReadStream(
|
|
42
|
+
`${__dirname}/interop-files/datetime_syntax_invalid.txt`,
|
|
43
|
+
),
|
|
44
|
+
terminal: false,
|
|
45
|
+
})
|
|
46
|
+
lineReader.on('line', (line) => {
|
|
47
|
+
if (line.startsWith('#') || line.length == 0) {
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
expectInvalid(line)
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('conforms to interop invalid parse (semantics) datetimes', () => {
|
|
55
|
+
const lineReader = readline.createInterface({
|
|
56
|
+
input: fs.createReadStream(
|
|
57
|
+
`${__dirname}/interop-files/datetime_parse_invalid.txt`,
|
|
58
|
+
),
|
|
59
|
+
terminal: false,
|
|
60
|
+
})
|
|
61
|
+
lineReader.on('line', (line) => {
|
|
62
|
+
if (line.startsWith('#') || line.length == 0) {
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
expectInvalid(line)
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
describe('normalization', () => {
|
|
71
|
+
it('normalizes datetimes', () => {
|
|
72
|
+
expect(normalizeDatetime('1234-04-12T23:20:50Z')).toEqual(
|
|
73
|
+
'1234-04-12T23:20:50.000Z',
|
|
74
|
+
)
|
|
75
|
+
expect(normalizeDatetime('1985-04-12T23:20:50Z')).toEqual(
|
|
76
|
+
'1985-04-12T23:20:50.000Z',
|
|
77
|
+
)
|
|
78
|
+
expect(normalizeDatetime('1985-04-12T23:20:50.123')).toEqual(
|
|
79
|
+
'1985-04-12T23:20:50.123Z',
|
|
80
|
+
)
|
|
81
|
+
expect(normalizeDatetime('1985-04-12 23:20:50.123')).toEqual(
|
|
82
|
+
'1985-04-12T23:20:50.123Z',
|
|
83
|
+
)
|
|
84
|
+
expect(normalizeDatetime('1985-04-12T10:20:50.1+01:00')).toEqual(
|
|
85
|
+
'1985-04-12T09:20:50.100Z',
|
|
86
|
+
)
|
|
87
|
+
expect(normalizeDatetime('Fri, 02 Jan 1999 12:34:56 GMT')).toEqual(
|
|
88
|
+
'1999-01-02T12:34:56.000Z',
|
|
89
|
+
)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('throws on invalid normalized datetimes', () => {
|
|
93
|
+
expect(() => normalizeDatetime('')).toThrow(InvalidDatetimeError)
|
|
94
|
+
expect(() => normalizeDatetime('blah')).toThrow(InvalidDatetimeError)
|
|
95
|
+
expect(() => normalizeDatetime('1999-19-39T23:20:50.123Z')).toThrow(
|
|
96
|
+
InvalidDatetimeError,
|
|
97
|
+
)
|
|
98
|
+
expect(() => normalizeDatetime('-000001-12-31T23:00:00.000Z')).toThrow(
|
|
99
|
+
InvalidDatetimeError,
|
|
100
|
+
)
|
|
101
|
+
expect(() => normalizeDatetime('0000-01-01T00:00:00+01:00')).toThrow(
|
|
102
|
+
InvalidDatetimeError,
|
|
103
|
+
)
|
|
104
|
+
expect(() => normalizeDatetime('0001-01-01T00:00:00+01:00')).toThrow(
|
|
105
|
+
InvalidDatetimeError,
|
|
106
|
+
)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('normalizes datetimes always', () => {
|
|
110
|
+
expect(normalizeDatetimeAlways('1985-04-12T23:20:50Z')).toEqual(
|
|
111
|
+
'1985-04-12T23:20:50.000Z',
|
|
112
|
+
)
|
|
113
|
+
expect(normalizeDatetimeAlways('blah')).toEqual('1970-01-01T00:00:00.000Z')
|
|
114
|
+
expect(normalizeDatetimeAlways('0000-01-01T00:00:00+01:00')).toEqual(
|
|
115
|
+
'1970-01-01T00:00:00.000Z',
|
|
116
|
+
)
|
|
117
|
+
})
|
|
118
|
+
})
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ensureValidRecordKey, InvalidRecordKeyError } from '../src'
|
|
2
|
+
import * as readline from 'readline'
|
|
3
|
+
import * as fs from 'fs'
|
|
4
|
+
|
|
5
|
+
describe('recordkey validation', () => {
|
|
6
|
+
const expectValid = (r: string) => {
|
|
7
|
+
ensureValidRecordKey(r)
|
|
8
|
+
}
|
|
9
|
+
const expectInvalid = (r: string) => {
|
|
10
|
+
expect(() => ensureValidRecordKey(r)).toThrow(InvalidRecordKeyError)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
it('conforms to interop valid recordkey', () => {
|
|
14
|
+
const lineReader = readline.createInterface({
|
|
15
|
+
input: fs.createReadStream(
|
|
16
|
+
`${__dirname}/interop-files/recordkey_syntax_valid.txt`,
|
|
17
|
+
),
|
|
18
|
+
terminal: false,
|
|
19
|
+
})
|
|
20
|
+
lineReader.on('line', (line) => {
|
|
21
|
+
if (line.startsWith('#') || line.length == 0) {
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
expectValid(line)
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('conforms to interop invalid recordkeys', () => {
|
|
29
|
+
const lineReader = readline.createInterface({
|
|
30
|
+
input: fs.createReadStream(
|
|
31
|
+
`${__dirname}/interop-files/recordkey_syntax_invalid.txt`,
|
|
32
|
+
),
|
|
33
|
+
terminal: false,
|
|
34
|
+
})
|
|
35
|
+
lineReader.on('line', (line) => {
|
|
36
|
+
if (line.startsWith('#') || line.length == 0) {
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
expectInvalid(line)
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
})
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ensureValidTid, InvalidTidError } from '../src'
|
|
2
|
+
import * as readline from 'readline'
|
|
3
|
+
import * as fs from 'fs'
|
|
4
|
+
|
|
5
|
+
describe('tid validation', () => {
|
|
6
|
+
const expectValid = (t: string) => {
|
|
7
|
+
ensureValidTid(t)
|
|
8
|
+
}
|
|
9
|
+
const expectInvalid = (t: string) => {
|
|
10
|
+
expect(() => ensureValidTid(t)).toThrow(InvalidTidError)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
it('conforms to interop valid tid', () => {
|
|
14
|
+
const lineReader = readline.createInterface({
|
|
15
|
+
input: fs.createReadStream(
|
|
16
|
+
`${__dirname}/interop-files/tid_syntax_valid.txt`,
|
|
17
|
+
),
|
|
18
|
+
terminal: false,
|
|
19
|
+
})
|
|
20
|
+
lineReader.on('line', (line) => {
|
|
21
|
+
if (line.startsWith('#') || line.length == 0) {
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
expectValid(line)
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('conforms to interop invalid tids', () => {
|
|
29
|
+
const lineReader = readline.createInterface({
|
|
30
|
+
input: fs.createReadStream(
|
|
31
|
+
`${__dirname}/interop-files/tid_syntax_invalid.txt`,
|
|
32
|
+
),
|
|
33
|
+
terminal: false,
|
|
34
|
+
})
|
|
35
|
+
lineReader.on('line', (line) => {
|
|
36
|
+
if (line.startsWith('#') || line.length == 0) {
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
expectInvalid(line)
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
})
|