tagged-urn 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +158 -0
- package/RULES.md +92 -0
- package/package.json +32 -0
- package/tagged-urn.js +783 -0
- package/tagged-urn.test.js +748 -0
package/README.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Tagged URN - JavaScript Implementation
|
|
2
|
+
|
|
3
|
+
Production-ready JavaScript implementation of Tagged URN with strict validation and matching rules.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Strict Rule Enforcement** - Follows exact same rules as Rust, Go, and Objective-C implementations
|
|
8
|
+
- **Case Insensitive** - All input normalized to lowercase
|
|
9
|
+
- **Tag Order Independent** - Canonical alphabetical sorting
|
|
10
|
+
- **Wildcard Support** - `*` matching in values only
|
|
11
|
+
- **Value-less Tags** - Tags without values (`tag`) are wildcards (`tag=*`)
|
|
12
|
+
- **Extended Characters** - Support for `/` and `:` in tag components
|
|
13
|
+
- **Production Ready** - No fallbacks, fails hard on invalid input
|
|
14
|
+
- **Comprehensive Tests** - Full test suite verifying all rules
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install tagged-urn
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
const { TaggedUrn, TaggedUrnBuilder, UrnMatcher } = require('tagged-urn');
|
|
26
|
+
|
|
27
|
+
// Create from string
|
|
28
|
+
const urn = TaggedUrn.fromString('cap:op=generate;ext=pdf');
|
|
29
|
+
console.log(urn.toString()); // "cap:ext=pdf;op=generate"
|
|
30
|
+
|
|
31
|
+
// Use builder pattern
|
|
32
|
+
const built = new TaggedUrnBuilder()
|
|
33
|
+
.tag('op', 'extract')
|
|
34
|
+
.tag('target', 'metadata')
|
|
35
|
+
.build();
|
|
36
|
+
|
|
37
|
+
// Matching
|
|
38
|
+
const request = TaggedUrn.fromString('cap:op=generate');
|
|
39
|
+
console.log(urn.matches(request)); // true
|
|
40
|
+
|
|
41
|
+
// Find best match
|
|
42
|
+
const urns = [
|
|
43
|
+
TaggedUrn.fromString('cap:op=*'),
|
|
44
|
+
TaggedUrn.fromString('cap:op=generate'),
|
|
45
|
+
TaggedUrn.fromString('cap:op=generate;ext=pdf')
|
|
46
|
+
];
|
|
47
|
+
const best = UrnMatcher.findBestMatch(urns, request);
|
|
48
|
+
console.log(best.toString()); // "cap:ext=pdf;op=generate" (most specific)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## API Reference
|
|
52
|
+
|
|
53
|
+
### TaggedUrn Class
|
|
54
|
+
|
|
55
|
+
#### Static Methods
|
|
56
|
+
- `TaggedUrn.fromString(s)` - Parse Tagged URN from string
|
|
57
|
+
- Throws `TaggedUrnError` on invalid format
|
|
58
|
+
|
|
59
|
+
#### Instance Methods
|
|
60
|
+
- `toString()` - Get canonical string representation
|
|
61
|
+
- `getTag(key)` - Get tag value (case-insensitive)
|
|
62
|
+
- `hasTag(key, value)` - Check if tag exists with value
|
|
63
|
+
- `withTag(key, value)` - Add/update tag (returns new instance)
|
|
64
|
+
- `withoutTag(key)` - Remove tag (returns new instance)
|
|
65
|
+
- `matches(other)` - Check if this URN matches another
|
|
66
|
+
- `canHandle(request)` - Check if this URN can handle a request
|
|
67
|
+
- `specificity()` - Get specificity score for matching
|
|
68
|
+
- `isMoreSpecificThan(other)` - Compare specificity
|
|
69
|
+
- `isCompatibleWith(other)` - Check compatibility
|
|
70
|
+
- `equals(other)` - Check equality
|
|
71
|
+
|
|
72
|
+
### TaggedUrnBuilder Class
|
|
73
|
+
|
|
74
|
+
Fluent builder for constructing Tagged URNs:
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
const urn = new TaggedUrnBuilder()
|
|
78
|
+
.tag('op', 'generate')
|
|
79
|
+
.tag('format', 'json')
|
|
80
|
+
.build();
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### UrnMatcher Class
|
|
84
|
+
|
|
85
|
+
Utility for matching sets of Tagged URNs:
|
|
86
|
+
|
|
87
|
+
- `UrnMatcher.findBestMatch(urns, request)` - Find most specific match
|
|
88
|
+
- `UrnMatcher.findAllMatches(urns, request)` - Find all matches (sorted by specificity)
|
|
89
|
+
- `UrnMatcher.areCompatible(urns1, urns2)` - Check if URN sets are compatible
|
|
90
|
+
|
|
91
|
+
### Error Handling
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
const { TaggedUrnError, ErrorCodes } = require('tagged-urn');
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const urn = TaggedUrn.fromString('invalid:format');
|
|
98
|
+
} catch (error) {
|
|
99
|
+
if (error instanceof TaggedUrnError) {
|
|
100
|
+
console.log(`Error code: ${error.code}`);
|
|
101
|
+
console.log(`Message: ${error.message}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Error codes:
|
|
107
|
+
- `ErrorCodes.INVALID_FORMAT` - General format error
|
|
108
|
+
- `ErrorCodes.MISSING_CAP_PREFIX` - Missing "cap:" prefix
|
|
109
|
+
- `ErrorCodes.INVALID_CHARACTER` - Invalid characters in tags
|
|
110
|
+
- `ErrorCodes.DUPLICATE_KEY` - Duplicate tag keys
|
|
111
|
+
- `ErrorCodes.NUMERIC_KEY` - Pure numeric tag keys
|
|
112
|
+
- `ErrorCodes.EMPTY_TAG` - Empty tag components
|
|
113
|
+
|
|
114
|
+
## Rules
|
|
115
|
+
|
|
116
|
+
This implementation strictly follows the 21 Tagged URN rules. See `RULES.md` for complete specification.
|
|
117
|
+
|
|
118
|
+
### Key Rules Summary:
|
|
119
|
+
|
|
120
|
+
1. **Case Insensitive** - `cap:OP=Generate` == `cap:op=generate`
|
|
121
|
+
2. **Order Independent** - `cap:a=1;b=2` == `cap:b=2;a=1`
|
|
122
|
+
3. **Prefix Required** - Must start with `cap:`
|
|
123
|
+
4. **Semicolon Separated** - Tags separated by `;`
|
|
124
|
+
5. **Optional Trailing `;`** - `cap:a=1;` == `cap:a=1`
|
|
125
|
+
6. **Canonical Form** - Lowercase, alphabetically sorted, no trailing `;`
|
|
126
|
+
7. **Wildcard Values** - `*` allowed in values only, not keys
|
|
127
|
+
8. **Extended Characters** - `/` and `:` allowed in tag components
|
|
128
|
+
9. **No Duplicate Keys** - Fails hard on duplicates
|
|
129
|
+
10. **No Numeric Keys** - Pure numeric keys forbidden
|
|
130
|
+
|
|
131
|
+
## Testing
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
npm test
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Runs comprehensive test suite covering all rules and edge cases.
|
|
138
|
+
|
|
139
|
+
## Browser Support
|
|
140
|
+
|
|
141
|
+
Works in both Node.js and browsers:
|
|
142
|
+
|
|
143
|
+
```html
|
|
144
|
+
<script src="tagged-urn.js"></script>
|
|
145
|
+
<script>
|
|
146
|
+
const urn = TaggedUrn.fromString('cap:op=generate');
|
|
147
|
+
console.log(urn.toString());
|
|
148
|
+
</script>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Cross-Language Compatibility
|
|
152
|
+
|
|
153
|
+
This JavaScript implementation produces identical results to:
|
|
154
|
+
- [Rust implementation](../tagged-urn-rs/)
|
|
155
|
+
- [Go implementation](../tagged-urn-go/)
|
|
156
|
+
- [Objective-C implementation](../tagged-urn-objc/)
|
|
157
|
+
|
|
158
|
+
All implementations pass the same test cases and follow identical rules.
|
package/RULES.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Tagged URN Rules
|
|
2
|
+
|
|
3
|
+
## Definitive specification for Tagged URN format and behavior
|
|
4
|
+
|
|
5
|
+
### 1. Case Insensitivity
|
|
6
|
+
Tagged URNs are case insensitive. All input is normalized to lowercase for storage and comparison.
|
|
7
|
+
|
|
8
|
+
### 2. Tag Order Independence
|
|
9
|
+
The order of tags in the URN string does not matter. Tags are always sorted alphabetically by key in canonical form.
|
|
10
|
+
|
|
11
|
+
### 3. Mandatory Prefix
|
|
12
|
+
Tagged URNs must always be preceded by `cap:` which is the signifier of a tagged URN.
|
|
13
|
+
|
|
14
|
+
### 4. Tag Separator
|
|
15
|
+
Tags are separated by semicolons (`;`).
|
|
16
|
+
|
|
17
|
+
### 5. Trailing Semicolon Optional
|
|
18
|
+
Presence or absence of the final trailing semicolon does not matter. Both `cap:key=value` and `cap:key=value;` are equivalent.
|
|
19
|
+
|
|
20
|
+
### 6. Character Restrictions
|
|
21
|
+
- No spaces in tagged URNs
|
|
22
|
+
- Allowed characters in tag keys and values: alphanumeric, dashes (`-`), underscores (`_`), slashes (`/`), colons (`:`), dots (`.`), and asterisk (`*` in values only)
|
|
23
|
+
- Quote marks (`"`, `'`), hash (`#`), and other special characters are not accepted
|
|
24
|
+
|
|
25
|
+
### 7. Tag Structure
|
|
26
|
+
- Tag separator within a tag: `=` separates tag key from tag value
|
|
27
|
+
- Tag separator between tags: `;` separates key-value pairs
|
|
28
|
+
- After the initial `cap:` prefix, colons (`:`) are treated as normal characters, not separators
|
|
29
|
+
|
|
30
|
+
### 8. No Special Tags
|
|
31
|
+
No reserved tag names - anything goes for tag keys.
|
|
32
|
+
|
|
33
|
+
### 9. Canonical Form
|
|
34
|
+
The canonical form of Tagged URNs is all lowercase, with tags sorted by keys alphabetically, and no final trailing semicolon.
|
|
35
|
+
|
|
36
|
+
### 10. Wildcard Support
|
|
37
|
+
- Wildcard `*` is accepted only as tag value, not as tag key
|
|
38
|
+
- When used as a tag value, `*` matches any value for that tag key
|
|
39
|
+
|
|
40
|
+
### 11. Value-less Tags (Existence Assertion)
|
|
41
|
+
- Tags may be specified without a value: `cap:key1=value1;optimize;key2=value2`
|
|
42
|
+
- A value-less tag like `optimize` is equivalent to `optimize=*` (wildcard)
|
|
43
|
+
- This asserts that the tag exists but matches any value
|
|
44
|
+
- Value-less tags are useful as flags or for checking tag existence
|
|
45
|
+
- **Parsing:** `tag` (no `=`) is parsed as `tag=*`
|
|
46
|
+
- **Serialization:** `tag=*` is serialized as just `tag` (no `=*`)
|
|
47
|
+
- **Note:** `tag=` (explicit `=` with no value) is still an error - this is different from a value-less tag
|
|
48
|
+
|
|
49
|
+
### 12. Matching Specificity
|
|
50
|
+
As more tags are specified, URNs become more specific:
|
|
51
|
+
- `cap:` matches any URN
|
|
52
|
+
- `cap:prop=*` matches any URN that has a `prop` tag with any value
|
|
53
|
+
- `cap:prop=1` matches only URNs that have `prop=1`, regardless of other tags
|
|
54
|
+
|
|
55
|
+
### 13. Exact Tag Matching
|
|
56
|
+
`cap:prop=1` matches only URNs that have `prop=1` irrespective of other tags present.
|
|
57
|
+
|
|
58
|
+
### 14. Subset Matching
|
|
59
|
+
Only the tags specified in the criteria affect matching. URNs with extra tags not mentioned in the criteria still match if they satisfy all specified criteria.
|
|
60
|
+
|
|
61
|
+
### 15. Duplicate Keys
|
|
62
|
+
Duplicate keys in the same URN result in an error - last occurrence does not win.
|
|
63
|
+
|
|
64
|
+
### 16. UTF-8 Support
|
|
65
|
+
Full UTF-8 character support within the allowed character set restrictions.
|
|
66
|
+
|
|
67
|
+
### 17. Numeric Values
|
|
68
|
+
- Tag keys cannot be pure numeric
|
|
69
|
+
- Tag values can be pure numeric
|
|
70
|
+
|
|
71
|
+
### 18. Empty Tagged URN
|
|
72
|
+
`cap:` with no tags is valid and means "matches all URNs" (universal matcher).
|
|
73
|
+
|
|
74
|
+
### 19. Length Restrictions
|
|
75
|
+
No explicit length restrictions, though practical limits exist based on URL and system constraints (typically ~2000 characters).
|
|
76
|
+
|
|
77
|
+
### 20. Wildcard Restrictions
|
|
78
|
+
Asterisk (`*`) in tag keys is not valid. Asterisk is only valid in tag values to signify wildcard matching.
|
|
79
|
+
|
|
80
|
+
### 21. Colon Treatment
|
|
81
|
+
Forward slashes (`/`) and colons (`:`) are valid anywhere in tag components and treated as normal characters, except for the mandatory `cap:` prefix which is not part of the tag structure.
|
|
82
|
+
|
|
83
|
+
## Implementation Notes
|
|
84
|
+
|
|
85
|
+
- All implementations must normalize input to lowercase
|
|
86
|
+
- All implementations must sort tags alphabetically in canonical output
|
|
87
|
+
- All implementations must handle trailing semicolons consistently
|
|
88
|
+
- All implementations must validate character restrictions identically
|
|
89
|
+
- All implementations must implement matching logic identically
|
|
90
|
+
- All implementations must reject duplicate keys with appropriate error messages
|
|
91
|
+
- All implementations must parse value-less tags as wildcard (`tag` → `tag=*`)
|
|
92
|
+
- All implementations must serialize wildcard tags as value-less (`tag=*` → `tag`)
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tagged-urn",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "JavaScript implementation of Tagged URN with strict validation and matching",
|
|
5
|
+
"main": "tagged-urn.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "node tagged-urn.test.js"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"tagged-urn",
|
|
11
|
+
"urn",
|
|
12
|
+
"namespace",
|
|
13
|
+
"validation",
|
|
14
|
+
"matching"
|
|
15
|
+
],
|
|
16
|
+
"author": "Bahram Joharshamshiri",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/jowharshamshiri/tagged-urn.git",
|
|
21
|
+
"directory": "tagged-urn-js"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=14.0.0"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"tagged-urn.js",
|
|
28
|
+
"tagged-urn.test.js",
|
|
29
|
+
"RULES.md",
|
|
30
|
+
"README.md"
|
|
31
|
+
]
|
|
32
|
+
}
|