capns 0.78.18772 → 0.84.18923
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 +0 -1
- package/capns.js +56 -65
- package/capns.test.js +398 -12
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -65,7 +65,6 @@ console.log(best.toString()); // Most specific match
|
|
|
65
65
|
- `withTag(key, value)` - Add/update tag (returns new instance)
|
|
66
66
|
- `withoutTag(key)` - Remove tag (returns new instance)
|
|
67
67
|
- `accepts(request)` - Check if this cap (as pattern) accepts a request
|
|
68
|
-
- `canHandle(request)` - Check if this cap can handle a request
|
|
69
68
|
- `specificity()` - Get specificity score for matching
|
|
70
69
|
- `isMoreSpecificThan(other)` - Compare specificity
|
|
71
70
|
- `equals(other)` - Check equality
|
package/capns.js
CHANGED
|
@@ -349,8 +349,10 @@ class CapUrn {
|
|
|
349
349
|
}
|
|
350
350
|
|
|
351
351
|
// Direction specs: TaggedUrn semantic matching via MediaUrn
|
|
352
|
-
// Check in_urn: cap's input spec (pattern) accepts request's input (instance)
|
|
353
|
-
|
|
352
|
+
// Check in_urn: cap's input spec (pattern) accepts request's input (instance).
|
|
353
|
+
// "media:" on the PATTERN side (this.inSpec) means "I accept any input" — skip check.
|
|
354
|
+
// "*" is also treated as wildcard. "media:" on the instance side still participates.
|
|
355
|
+
if (this.inSpec !== '*' && this.inSpec !== 'media:' && request.inSpec !== '*') {
|
|
354
356
|
const capIn = TaggedUrn.fromString(this.inSpec);
|
|
355
357
|
const requestIn = TaggedUrn.fromString(request.inSpec);
|
|
356
358
|
if (!capIn.accepts(requestIn)) {
|
|
@@ -358,8 +360,10 @@ class CapUrn {
|
|
|
358
360
|
}
|
|
359
361
|
}
|
|
360
362
|
|
|
361
|
-
// Check out_urn: cap's output (instance) conforms to request's output (pattern)
|
|
362
|
-
|
|
363
|
+
// Check out_urn: cap's output (instance) conforms to request's output (pattern).
|
|
364
|
+
// "media:" on the PATTERN side (this.outSpec) means "I accept any output" — skip check.
|
|
365
|
+
// "*" is also treated as wildcard. "media:" on the instance side still participates.
|
|
366
|
+
if (this.outSpec !== '*' && this.outSpec !== 'media:' && request.outSpec !== '*') {
|
|
363
367
|
const capOut = TaggedUrn.fromString(this.outSpec);
|
|
364
368
|
const requestOut = TaggedUrn.fromString(request.outSpec);
|
|
365
369
|
if (!capOut.conformsTo(requestOut)) {
|
|
@@ -444,69 +448,9 @@ class CapUrn {
|
|
|
444
448
|
return true;
|
|
445
449
|
}
|
|
446
450
|
|
|
447
|
-
// First check if they're compatible
|
|
448
|
-
if (!this.isCompatibleWith(other)) {
|
|
449
|
-
return false;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
451
|
return this.specificity() > other.specificity();
|
|
453
452
|
}
|
|
454
453
|
|
|
455
|
-
/**
|
|
456
|
-
* Check if this cap is compatible with another
|
|
457
|
-
*
|
|
458
|
-
* Two caps are compatible if they can potentially match
|
|
459
|
-
* the same types of requests (considering wildcards and missing tags as wildcards)
|
|
460
|
-
* Direction specs are compatible if either is a subtype of the other via TaggedUrn matching
|
|
461
|
-
*
|
|
462
|
-
* @param {CapUrn} other - The other cap to check compatibility with
|
|
463
|
-
* @returns {boolean} Whether the caps are compatible
|
|
464
|
-
*/
|
|
465
|
-
isCompatibleWith(other) {
|
|
466
|
-
if (!other) {
|
|
467
|
-
return true;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
// Check in_urn compatibility: either direction of conformsTo succeeds
|
|
471
|
-
if (this.inSpec !== '*' && other.inSpec !== '*') {
|
|
472
|
-
const selfIn = TaggedUrn.fromString(this.inSpec);
|
|
473
|
-
const otherIn = TaggedUrn.fromString(other.inSpec);
|
|
474
|
-
const fwd = selfIn.conformsTo(otherIn);
|
|
475
|
-
const rev = otherIn.conformsTo(selfIn);
|
|
476
|
-
if (!fwd && !rev) {
|
|
477
|
-
return false;
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
// Check out_urn compatibility
|
|
481
|
-
if (this.outSpec !== '*' && other.outSpec !== '*') {
|
|
482
|
-
const selfOut = TaggedUrn.fromString(this.outSpec);
|
|
483
|
-
const otherOut = TaggedUrn.fromString(other.outSpec);
|
|
484
|
-
const fwd = selfOut.conformsTo(otherOut);
|
|
485
|
-
const rev = otherOut.conformsTo(selfOut);
|
|
486
|
-
if (!fwd && !rev) {
|
|
487
|
-
return false;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
// Get all unique tag keys from both caps
|
|
492
|
-
const allKeys = new Set([...Object.keys(this.tags), ...Object.keys(other.tags)]);
|
|
493
|
-
|
|
494
|
-
for (const key of allKeys) {
|
|
495
|
-
const v1 = this.tags[key];
|
|
496
|
-
const v2 = other.tags[key];
|
|
497
|
-
|
|
498
|
-
if (v1 !== undefined && v2 !== undefined) {
|
|
499
|
-
// Both have the tag - they must match or one must be wildcard
|
|
500
|
-
if (v1 !== '*' && v2 !== '*' && v1 !== v2) {
|
|
501
|
-
return false;
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
// If only one has the tag, it's compatible (missing tag is wildcard)
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
return true;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
454
|
/**
|
|
511
455
|
* Create a new cap with a specific tag set to wildcard
|
|
512
456
|
* Handles "in" and "out" specially
|
|
@@ -741,7 +685,7 @@ class CapMatcher {
|
|
|
741
685
|
static areCompatible(caps1, caps2) {
|
|
742
686
|
for (const c1 of caps1) {
|
|
743
687
|
for (const c2 of caps2) {
|
|
744
|
-
if (c1.
|
|
688
|
+
if (c1.accepts(c2) || c2.accepts(c1)) {
|
|
745
689
|
return true;
|
|
746
690
|
}
|
|
747
691
|
}
|
|
@@ -827,6 +771,20 @@ const MEDIA_PATH_OUTPUT = 'media:model-path;textable;form=map';
|
|
|
827
771
|
// Semantic output types - inference
|
|
828
772
|
const MEDIA_EMBEDDING_VECTOR = 'media:embedding-vector;textable;form=map';
|
|
829
773
|
const MEDIA_LLM_INFERENCE_OUTPUT = 'media:generated-text;textable;form=map';
|
|
774
|
+
// File path types
|
|
775
|
+
const MEDIA_FILE_PATH = 'media:file-path;textable;form=scalar';
|
|
776
|
+
const MEDIA_FILE_PATH_ARRAY = 'media:file-path;textable;form=list';
|
|
777
|
+
// Collection types
|
|
778
|
+
const MEDIA_COLLECTION = 'media:collection;form=map';
|
|
779
|
+
const MEDIA_COLLECTION_LIST = 'media:collection;form=list';
|
|
780
|
+
|
|
781
|
+
// =============================================================================
|
|
782
|
+
// STANDARD CAP URN CONSTANTS
|
|
783
|
+
// =============================================================================
|
|
784
|
+
|
|
785
|
+
// Standard echo capability URN
|
|
786
|
+
// Accepts any media type as input and outputs any media type
|
|
787
|
+
const CAP_IDENTITY = 'cap:in=media:;out=media:';
|
|
830
788
|
|
|
831
789
|
// =============================================================================
|
|
832
790
|
// MEDIA URN CLASS
|
|
@@ -900,6 +858,33 @@ class MediaUrn {
|
|
|
900
858
|
/** @returns {boolean} True if the "void" marker tag is present */
|
|
901
859
|
isVoid() { return this._urn.getTag('void') !== undefined; }
|
|
902
860
|
|
|
861
|
+
/** @returns {boolean} True if the "image" marker tag is present */
|
|
862
|
+
isImage() { return this._urn.getTag('image') !== undefined; }
|
|
863
|
+
|
|
864
|
+
/** @returns {boolean} True if the "audio" marker tag is present */
|
|
865
|
+
isAudio() { return this._urn.getTag('audio') !== undefined; }
|
|
866
|
+
|
|
867
|
+
/** @returns {boolean} True if the "video" marker tag is present */
|
|
868
|
+
isVideo() { return this._urn.getTag('video') !== undefined; }
|
|
869
|
+
|
|
870
|
+
/** @returns {boolean} True if the "numeric" marker tag is present */
|
|
871
|
+
isNumeric() { return this._urn.getTag('numeric') !== undefined; }
|
|
872
|
+
|
|
873
|
+
/** @returns {boolean} True if the "bool" marker tag is present */
|
|
874
|
+
isBool() { return this._urn.getTag('bool') !== undefined; }
|
|
875
|
+
|
|
876
|
+
/** @returns {boolean} True if "file-path" marker tag is present AND NOT form=list */
|
|
877
|
+
isFilePath() { return this._urn.getTag('file-path') !== undefined && !this.isList(); }
|
|
878
|
+
|
|
879
|
+
/** @returns {boolean} True if "file-path" marker tag is present AND form=list */
|
|
880
|
+
isFilePathArray() { return this._urn.getTag('file-path') !== undefined && this.isList(); }
|
|
881
|
+
|
|
882
|
+
/** @returns {boolean} True if "file-path" marker tag is present (single or array) */
|
|
883
|
+
isAnyFilePath() { return this.isFilePath() || this.isFilePathArray(); }
|
|
884
|
+
|
|
885
|
+
/** @returns {boolean} True if the "collection" marker tag is present */
|
|
886
|
+
isCollection() { return this._urn.getTag('collection') !== undefined; }
|
|
887
|
+
|
|
903
888
|
/**
|
|
904
889
|
* Check if this media URN conforms to another (pattern).
|
|
905
890
|
* @param {MediaUrn} pattern
|
|
@@ -4175,6 +4160,12 @@ module.exports = {
|
|
|
4175
4160
|
// Semantic output types - inference
|
|
4176
4161
|
MEDIA_EMBEDDING_VECTOR,
|
|
4177
4162
|
MEDIA_LLM_INFERENCE_OUTPUT,
|
|
4163
|
+
// File path types
|
|
4164
|
+
MEDIA_FILE_PATH,
|
|
4165
|
+
MEDIA_FILE_PATH_ARRAY,
|
|
4166
|
+
// Collection types
|
|
4167
|
+
MEDIA_COLLECTION,
|
|
4168
|
+
MEDIA_COLLECTION_LIST,
|
|
4178
4169
|
// Unified argument type
|
|
4179
4170
|
CapArgumentValue,
|
|
4180
4171
|
// Standard cap URN builders
|
package/capns.test.js
CHANGED
|
@@ -21,7 +21,11 @@ const {
|
|
|
21
21
|
MEDIA_PDF, MEDIA_EPUB, MEDIA_MD, MEDIA_TXT, MEDIA_RST, MEDIA_LOG,
|
|
22
22
|
MEDIA_HTML, MEDIA_XML, MEDIA_JSON, MEDIA_YAML, MEDIA_JSON_SCHEMA,
|
|
23
23
|
MEDIA_MODEL_SPEC, MEDIA_AVAILABILITY_OUTPUT, MEDIA_PATH_OUTPUT,
|
|
24
|
-
MEDIA_LLM_INFERENCE_OUTPUT
|
|
24
|
+
MEDIA_LLM_INFERENCE_OUTPUT,
|
|
25
|
+
MEDIA_FILE_PATH, MEDIA_FILE_PATH_ARRAY,
|
|
26
|
+
MEDIA_COLLECTION, MEDIA_COLLECTION_LIST,
|
|
27
|
+
MEDIA_DECISION, MEDIA_DECISION_ARRAY,
|
|
28
|
+
MEDIA_AUDIO_SPEECH, MEDIA_IMAGE_THUMBNAIL
|
|
25
29
|
} = require('./capns.js');
|
|
26
30
|
|
|
27
31
|
// ============================================================================
|
|
@@ -401,22 +405,24 @@ function test023_builderPreservesCase() {
|
|
|
401
405
|
assertEqual(cap.getTag('MyKey'), 'MyValue', 'getTag should be case-insensitive for keys');
|
|
402
406
|
}
|
|
403
407
|
|
|
404
|
-
// TEST024:
|
|
408
|
+
// TEST024: Directional accepts checks
|
|
405
409
|
function test024_compatibility() {
|
|
410
|
+
// General cap accepts specific request (missing tags = wildcards)
|
|
411
|
+
const general = CapUrn.fromString(testUrn('op=generate'));
|
|
412
|
+
const specific = CapUrn.fromString(testUrn('op=generate;ext=pdf'));
|
|
413
|
+
assert(general.accepts(specific), 'General cap should accept specific request');
|
|
414
|
+
// Specific cap also accepts general request (cap has extra tag, not blocking)
|
|
415
|
+
assert(specific.accepts(general), 'Specific cap accepts general request (extra tags ok)');
|
|
416
|
+
|
|
417
|
+
// Different op values: neither accepts the other
|
|
406
418
|
const cap1 = CapUrn.fromString(testUrn('op=generate;ext=pdf'));
|
|
407
|
-
const cap2 = CapUrn.fromString(testUrn('op=generate;format=*'));
|
|
408
419
|
const cap3 = CapUrn.fromString(testUrn('type=image;op=extract'));
|
|
420
|
+
assert(!cap1.accepts(cap3), 'Different op should not accept');
|
|
421
|
+
assert(!cap3.accepts(cap1), 'Different op should not accept (reverse)');
|
|
409
422
|
|
|
410
|
-
|
|
411
|
-
assert(!cap1.isCompatibleWith(cap3), 'Different op should not be compatible');
|
|
412
|
-
|
|
413
|
-
// Missing tags treated as wildcards
|
|
414
|
-
const cap4 = CapUrn.fromString(testUrn('op=generate'));
|
|
415
|
-
assert(cap1.isCompatibleWith(cap4), 'Missing ext in cap4 should be wildcard');
|
|
416
|
-
|
|
417
|
-
// Different in/out should not be compatible
|
|
423
|
+
// Different in/out should not accept
|
|
418
424
|
const cap5 = CapUrn.fromString('cap:in="media:textable;form=scalar";out="media:object";op=generate');
|
|
419
|
-
assert(!cap1.
|
|
425
|
+
assert(!cap1.accepts(cap5), 'Different inSpec should not accept');
|
|
420
426
|
}
|
|
421
427
|
|
|
422
428
|
// TEST025: CapMatcher.findBestMatch returns most specific
|
|
@@ -2259,6 +2265,344 @@ function test335_pluginRepoServerClientIntegration() {
|
|
|
2259
2265
|
assert(searchResults[0].id === plugin.id, 'Search and client should agree');
|
|
2260
2266
|
}
|
|
2261
2267
|
|
|
2268
|
+
// ============================================================================
|
|
2269
|
+
// media_urn.rs: TEST546-TEST558 (MediaUrn predicates)
|
|
2270
|
+
// ============================================================================
|
|
2271
|
+
|
|
2272
|
+
// TEST546: isImage returns true only when image marker tag is present
|
|
2273
|
+
function test546_isImage() {
|
|
2274
|
+
assert(MediaUrn.fromString(MEDIA_PNG).isImage(), 'MEDIA_PNG should be image');
|
|
2275
|
+
assert(MediaUrn.fromString(MEDIA_IMAGE_THUMBNAIL).isImage(), 'MEDIA_IMAGE_THUMBNAIL should be image');
|
|
2276
|
+
assert(MediaUrn.fromString('media:image;jpg;bytes').isImage(), 'media:image;jpg;bytes should be image');
|
|
2277
|
+
// Non-image types
|
|
2278
|
+
assert(!MediaUrn.fromString(MEDIA_PDF).isImage(), 'MEDIA_PDF should not be image');
|
|
2279
|
+
assert(!MediaUrn.fromString(MEDIA_STRING).isImage(), 'MEDIA_STRING should not be image');
|
|
2280
|
+
assert(!MediaUrn.fromString(MEDIA_AUDIO).isImage(), 'MEDIA_AUDIO should not be image');
|
|
2281
|
+
assert(!MediaUrn.fromString(MEDIA_VIDEO).isImage(), 'MEDIA_VIDEO should not be image');
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
// TEST547: isAudio returns true only when audio marker tag is present
|
|
2285
|
+
function test547_isAudio() {
|
|
2286
|
+
assert(MediaUrn.fromString(MEDIA_AUDIO).isAudio(), 'MEDIA_AUDIO should be audio');
|
|
2287
|
+
assert(MediaUrn.fromString(MEDIA_AUDIO_SPEECH).isAudio(), 'MEDIA_AUDIO_SPEECH should be audio');
|
|
2288
|
+
assert(MediaUrn.fromString('media:audio;mp3;bytes').isAudio(), 'media:audio;mp3;bytes should be audio');
|
|
2289
|
+
// Non-audio types
|
|
2290
|
+
assert(!MediaUrn.fromString(MEDIA_VIDEO).isAudio(), 'MEDIA_VIDEO should not be audio');
|
|
2291
|
+
assert(!MediaUrn.fromString(MEDIA_PNG).isAudio(), 'MEDIA_PNG should not be audio');
|
|
2292
|
+
assert(!MediaUrn.fromString(MEDIA_STRING).isAudio(), 'MEDIA_STRING should not be audio');
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
// TEST548: isVideo returns true only when video marker tag is present
|
|
2296
|
+
function test548_isVideo() {
|
|
2297
|
+
assert(MediaUrn.fromString(MEDIA_VIDEO).isVideo(), 'MEDIA_VIDEO should be video');
|
|
2298
|
+
assert(MediaUrn.fromString('media:video;mp4;bytes').isVideo(), 'media:video;mp4;bytes should be video');
|
|
2299
|
+
// Non-video types
|
|
2300
|
+
assert(!MediaUrn.fromString(MEDIA_AUDIO).isVideo(), 'MEDIA_AUDIO should not be video');
|
|
2301
|
+
assert(!MediaUrn.fromString(MEDIA_PNG).isVideo(), 'MEDIA_PNG should not be video');
|
|
2302
|
+
assert(!MediaUrn.fromString(MEDIA_STRING).isVideo(), 'MEDIA_STRING should not be video');
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
// TEST549: isNumeric returns true only when numeric marker tag is present
|
|
2306
|
+
function test549_isNumeric() {
|
|
2307
|
+
assert(MediaUrn.fromString(MEDIA_INTEGER).isNumeric(), 'MEDIA_INTEGER should be numeric');
|
|
2308
|
+
assert(MediaUrn.fromString(MEDIA_NUMBER).isNumeric(), 'MEDIA_NUMBER should be numeric');
|
|
2309
|
+
assert(MediaUrn.fromString(MEDIA_INTEGER_ARRAY).isNumeric(), 'MEDIA_INTEGER_ARRAY should be numeric');
|
|
2310
|
+
assert(MediaUrn.fromString(MEDIA_NUMBER_ARRAY).isNumeric(), 'MEDIA_NUMBER_ARRAY should be numeric');
|
|
2311
|
+
// Non-numeric types
|
|
2312
|
+
assert(!MediaUrn.fromString(MEDIA_STRING).isNumeric(), 'MEDIA_STRING should not be numeric');
|
|
2313
|
+
assert(!MediaUrn.fromString(MEDIA_BOOLEAN).isNumeric(), 'MEDIA_BOOLEAN should not be numeric');
|
|
2314
|
+
assert(!MediaUrn.fromString(MEDIA_BINARY).isNumeric(), 'MEDIA_BINARY should not be numeric');
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
// TEST550: isBool returns true only when bool marker tag is present
|
|
2318
|
+
function test550_isBool() {
|
|
2319
|
+
assert(MediaUrn.fromString(MEDIA_BOOLEAN).isBool(), 'MEDIA_BOOLEAN should be bool');
|
|
2320
|
+
assert(MediaUrn.fromString(MEDIA_BOOLEAN_ARRAY).isBool(), 'MEDIA_BOOLEAN_ARRAY should be bool');
|
|
2321
|
+
assert(MediaUrn.fromString(MEDIA_DECISION).isBool(), 'MEDIA_DECISION should be bool');
|
|
2322
|
+
assert(MediaUrn.fromString(MEDIA_DECISION_ARRAY).isBool(), 'MEDIA_DECISION_ARRAY should be bool');
|
|
2323
|
+
// Non-bool types
|
|
2324
|
+
assert(!MediaUrn.fromString(MEDIA_STRING).isBool(), 'MEDIA_STRING should not be bool');
|
|
2325
|
+
assert(!MediaUrn.fromString(MEDIA_INTEGER).isBool(), 'MEDIA_INTEGER should not be bool');
|
|
2326
|
+
assert(!MediaUrn.fromString(MEDIA_BINARY).isBool(), 'MEDIA_BINARY should not be bool');
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
// TEST551: isFilePath returns true for scalar file-path, false for array
|
|
2330
|
+
function test551_isFilePath() {
|
|
2331
|
+
assert(MediaUrn.fromString(MEDIA_FILE_PATH).isFilePath(), 'MEDIA_FILE_PATH should be file-path');
|
|
2332
|
+
// Array file-path is NOT isFilePath (it's isFilePathArray)
|
|
2333
|
+
assert(!MediaUrn.fromString(MEDIA_FILE_PATH_ARRAY).isFilePath(), 'MEDIA_FILE_PATH_ARRAY should not be isFilePath');
|
|
2334
|
+
// Non-file-path types
|
|
2335
|
+
assert(!MediaUrn.fromString(MEDIA_STRING).isFilePath(), 'MEDIA_STRING should not be file-path');
|
|
2336
|
+
assert(!MediaUrn.fromString(MEDIA_BINARY).isFilePath(), 'MEDIA_BINARY should not be file-path');
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
// TEST552: isFilePathArray returns true for list file-path, false for scalar
|
|
2340
|
+
function test552_isFilePathArray() {
|
|
2341
|
+
assert(MediaUrn.fromString(MEDIA_FILE_PATH_ARRAY).isFilePathArray(), 'MEDIA_FILE_PATH_ARRAY should be file-path-array');
|
|
2342
|
+
// Scalar file-path is NOT isFilePathArray
|
|
2343
|
+
assert(!MediaUrn.fromString(MEDIA_FILE_PATH).isFilePathArray(), 'MEDIA_FILE_PATH should not be isFilePathArray');
|
|
2344
|
+
// Non-file-path types
|
|
2345
|
+
assert(!MediaUrn.fromString(MEDIA_STRING_ARRAY).isFilePathArray(), 'MEDIA_STRING_ARRAY should not be file-path-array');
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
// TEST553: isAnyFilePath returns true for both scalar and array file-path
|
|
2349
|
+
function test553_isAnyFilePath() {
|
|
2350
|
+
assert(MediaUrn.fromString(MEDIA_FILE_PATH).isAnyFilePath(), 'MEDIA_FILE_PATH should be any-file-path');
|
|
2351
|
+
assert(MediaUrn.fromString(MEDIA_FILE_PATH_ARRAY).isAnyFilePath(), 'MEDIA_FILE_PATH_ARRAY should be any-file-path');
|
|
2352
|
+
// Non-file-path types
|
|
2353
|
+
assert(!MediaUrn.fromString(MEDIA_STRING).isAnyFilePath(), 'MEDIA_STRING should not be any-file-path');
|
|
2354
|
+
assert(!MediaUrn.fromString(MEDIA_STRING_ARRAY).isAnyFilePath(), 'MEDIA_STRING_ARRAY should not be any-file-path');
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
// TEST554: isCollection returns true when collection marker tag is present
|
|
2358
|
+
function test554_isCollection() {
|
|
2359
|
+
assert(MediaUrn.fromString(MEDIA_COLLECTION).isCollection(), 'MEDIA_COLLECTION should be collection');
|
|
2360
|
+
assert(MediaUrn.fromString(MEDIA_COLLECTION_LIST).isCollection(), 'MEDIA_COLLECTION_LIST should be collection');
|
|
2361
|
+
// Non-collection types
|
|
2362
|
+
assert(!MediaUrn.fromString(MEDIA_OBJECT).isCollection(), 'MEDIA_OBJECT should not be collection');
|
|
2363
|
+
assert(!MediaUrn.fromString(MEDIA_STRING_ARRAY).isCollection(), 'MEDIA_STRING_ARRAY should not be collection');
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2366
|
+
// TEST555: N/A for JS (with_tag/without_tag on MediaUrn - JS MediaUrn does not have these methods)
|
|
2367
|
+
|
|
2368
|
+
// TEST556: N/A for JS (image_media_urn_for_ext helper not in JS)
|
|
2369
|
+
|
|
2370
|
+
// TEST557: N/A for JS (audio_media_urn_for_ext helper not in JS)
|
|
2371
|
+
|
|
2372
|
+
// TEST558: predicates are consistent with constants - every constant triggers exactly the expected predicates
|
|
2373
|
+
function test558_predicateConstantConsistency() {
|
|
2374
|
+
// MEDIA_INTEGER must be numeric, text, scalar, NOT binary/bool/image/audio/video
|
|
2375
|
+
const intUrn = MediaUrn.fromString(MEDIA_INTEGER);
|
|
2376
|
+
assert(intUrn.isNumeric(), 'MEDIA_INTEGER must be numeric');
|
|
2377
|
+
assert(intUrn.isText(), 'MEDIA_INTEGER must be text');
|
|
2378
|
+
assert(intUrn.isScalar(), 'MEDIA_INTEGER must be scalar');
|
|
2379
|
+
assert(!intUrn.isBinary(), 'MEDIA_INTEGER must not be binary');
|
|
2380
|
+
assert(!intUrn.isBool(), 'MEDIA_INTEGER must not be bool');
|
|
2381
|
+
assert(!intUrn.isImage(), 'MEDIA_INTEGER must not be image');
|
|
2382
|
+
assert(!intUrn.isList(), 'MEDIA_INTEGER must not be list');
|
|
2383
|
+
|
|
2384
|
+
// MEDIA_BOOLEAN must be bool, text, scalar, NOT numeric
|
|
2385
|
+
const boolUrn = MediaUrn.fromString(MEDIA_BOOLEAN);
|
|
2386
|
+
assert(boolUrn.isBool(), 'MEDIA_BOOLEAN must be bool');
|
|
2387
|
+
assert(boolUrn.isText(), 'MEDIA_BOOLEAN must be text');
|
|
2388
|
+
assert(boolUrn.isScalar(), 'MEDIA_BOOLEAN must be scalar');
|
|
2389
|
+
assert(!boolUrn.isNumeric(), 'MEDIA_BOOLEAN must not be numeric');
|
|
2390
|
+
|
|
2391
|
+
// MEDIA_JSON must be json, text, map, structured, NOT binary
|
|
2392
|
+
const jsonUrn = MediaUrn.fromString(MEDIA_JSON);
|
|
2393
|
+
assert(jsonUrn.isJson(), 'MEDIA_JSON must be json');
|
|
2394
|
+
assert(jsonUrn.isText(), 'MEDIA_JSON must be text');
|
|
2395
|
+
assert(jsonUrn.isMap(), 'MEDIA_JSON must be map');
|
|
2396
|
+
assert(jsonUrn.isStructured(), 'MEDIA_JSON must be structured');
|
|
2397
|
+
assert(!jsonUrn.isBinary(), 'MEDIA_JSON must not be binary');
|
|
2398
|
+
assert(!jsonUrn.isList(), 'MEDIA_JSON must not be list');
|
|
2399
|
+
|
|
2400
|
+
// MEDIA_VOID is void, NOT anything else
|
|
2401
|
+
const voidUrn = MediaUrn.fromString(MEDIA_VOID);
|
|
2402
|
+
assert(voidUrn.isVoid(), 'MEDIA_VOID must be void');
|
|
2403
|
+
assert(!voidUrn.isText(), 'MEDIA_VOID must not be text');
|
|
2404
|
+
assert(!voidUrn.isBinary(), 'MEDIA_VOID must not be binary');
|
|
2405
|
+
assert(!voidUrn.isNumeric(), 'MEDIA_VOID must not be numeric');
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
// ============================================================================
|
|
2409
|
+
// cap_urn.rs: TEST559-TEST567 (CapUrn tier tests)
|
|
2410
|
+
// ============================================================================
|
|
2411
|
+
|
|
2412
|
+
// TEST559: withoutTag removes tag, ignores in/out, case-insensitive for keys
|
|
2413
|
+
function test559_withoutTag() {
|
|
2414
|
+
const cap = CapUrn.fromString('cap:in="media:void";op=test;ext=pdf;out="media:void"');
|
|
2415
|
+
const removed = cap.withoutTag('ext');
|
|
2416
|
+
assertEqual(removed.getTag('ext'), undefined, 'withoutTag should remove ext');
|
|
2417
|
+
assertEqual(removed.getTag('op'), 'test', 'withoutTag should preserve op');
|
|
2418
|
+
|
|
2419
|
+
// Case-insensitive removal
|
|
2420
|
+
const removed2 = cap.withoutTag('EXT');
|
|
2421
|
+
assertEqual(removed2.getTag('ext'), undefined, 'withoutTag should be case-insensitive');
|
|
2422
|
+
|
|
2423
|
+
// Removing in/out is silently ignored
|
|
2424
|
+
const same = cap.withoutTag('in');
|
|
2425
|
+
assertEqual(same.getInSpec(), 'media:void', 'withoutTag must not remove in');
|
|
2426
|
+
const same2 = cap.withoutTag('out');
|
|
2427
|
+
assertEqual(same2.getOutSpec(), 'media:void', 'withoutTag must not remove out');
|
|
2428
|
+
|
|
2429
|
+
// Removing non-existent tag is no-op
|
|
2430
|
+
const same3 = cap.withoutTag('nonexistent');
|
|
2431
|
+
assert(same3.equals(cap), 'Removing non-existent tag is no-op');
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
// TEST560: withInSpec and withOutSpec change direction specs
|
|
2435
|
+
function test560_withInOutSpec() {
|
|
2436
|
+
const cap = CapUrn.fromString('cap:in="media:void";op=test;out="media:void"');
|
|
2437
|
+
|
|
2438
|
+
const changedIn = cap.withInSpec('media:bytes');
|
|
2439
|
+
assertEqual(changedIn.getInSpec(), 'media:bytes', 'withInSpec should change inSpec');
|
|
2440
|
+
assertEqual(changedIn.getOutSpec(), 'media:void', 'withInSpec should preserve outSpec');
|
|
2441
|
+
assertEqual(changedIn.getTag('op'), 'test', 'withInSpec should preserve tags');
|
|
2442
|
+
|
|
2443
|
+
const changedOut = cap.withOutSpec('media:string');
|
|
2444
|
+
assertEqual(changedOut.getInSpec(), 'media:void', 'withOutSpec should preserve inSpec');
|
|
2445
|
+
assertEqual(changedOut.getOutSpec(), 'media:string', 'withOutSpec should change outSpec');
|
|
2446
|
+
|
|
2447
|
+
// Chain both
|
|
2448
|
+
const changedBoth = cap.withInSpec('media:pdf;bytes').withOutSpec('media:txt;textable');
|
|
2449
|
+
assertEqual(changedBoth.getInSpec(), 'media:pdf;bytes', 'Chain should set inSpec');
|
|
2450
|
+
assertEqual(changedBoth.getOutSpec(), 'media:txt;textable', 'Chain should set outSpec');
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
// TEST561: N/A for JS (in_media_urn/out_media_urn not in JS CapUrn)
|
|
2454
|
+
|
|
2455
|
+
// TEST562: N/A for JS (canonical_option not in JS CapUrn)
|
|
2456
|
+
|
|
2457
|
+
// TEST563: CapMatcher.findAllMatches returns all matching caps sorted by specificity
|
|
2458
|
+
function test563_findAllMatches() {
|
|
2459
|
+
const caps = [
|
|
2460
|
+
CapUrn.fromString('cap:in="media:void";op=test;out="media:void"'),
|
|
2461
|
+
CapUrn.fromString('cap:in="media:void";op=test;ext=pdf;out="media:void"'),
|
|
2462
|
+
CapUrn.fromString('cap:in="media:void";op=different;out="media:void"'),
|
|
2463
|
+
];
|
|
2464
|
+
|
|
2465
|
+
const request = CapUrn.fromString('cap:in="media:void";op=test;out="media:void"');
|
|
2466
|
+
const matches = CapMatcher.findAllMatches(caps, request);
|
|
2467
|
+
|
|
2468
|
+
// Should find 2 matches (op=test and op=test;ext=pdf), not op=different
|
|
2469
|
+
assertEqual(matches.length, 2, 'Should find 2 matches');
|
|
2470
|
+
// Sorted by specificity descending: ext=pdf first (more specific)
|
|
2471
|
+
assert(matches[0].specificity() >= matches[1].specificity(), 'First match should be more specific');
|
|
2472
|
+
assertEqual(matches[0].getTag('ext'), 'pdf', 'Most specific match should have ext=pdf');
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
// TEST564: CapMatcher.areCompatible detects bidirectional overlap
|
|
2476
|
+
function test564_areCompatible() {
|
|
2477
|
+
const caps1 = [
|
|
2478
|
+
CapUrn.fromString('cap:in="media:void";op=test;out="media:void"'),
|
|
2479
|
+
];
|
|
2480
|
+
const caps2 = [
|
|
2481
|
+
CapUrn.fromString('cap:in="media:void";op=test;ext=pdf;out="media:void"'),
|
|
2482
|
+
];
|
|
2483
|
+
const caps3 = [
|
|
2484
|
+
CapUrn.fromString('cap:in="media:void";op=different;out="media:void"'),
|
|
2485
|
+
];
|
|
2486
|
+
|
|
2487
|
+
// caps1 (op=test) accepts caps2 (op=test;ext=pdf) -> compatible
|
|
2488
|
+
assert(CapMatcher.areCompatible(caps1, caps2), 'caps1 and caps2 should be compatible');
|
|
2489
|
+
|
|
2490
|
+
// caps1 (op=test) vs caps3 (op=different) -> not compatible
|
|
2491
|
+
assert(!CapMatcher.areCompatible(caps1, caps3), 'caps1 and caps3 should not be compatible');
|
|
2492
|
+
|
|
2493
|
+
// Empty sets are not compatible
|
|
2494
|
+
assert(!CapMatcher.areCompatible([], caps1), 'Empty vs non-empty should not be compatible');
|
|
2495
|
+
assert(!CapMatcher.areCompatible(caps1, []), 'Non-empty vs empty should not be compatible');
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
// TEST565: N/A for JS (tags_to_string not in JS CapUrn)
|
|
2499
|
+
|
|
2500
|
+
// TEST566: withTag silently ignores in/out keys
|
|
2501
|
+
function test566_withTagIgnoresInOut() {
|
|
2502
|
+
const cap = CapUrn.fromString('cap:in="media:void";op=test;out="media:void"');
|
|
2503
|
+
// Attempting to set in/out via withTag is silently ignored
|
|
2504
|
+
const same = cap.withTag('in', 'media:bytes');
|
|
2505
|
+
assertEqual(same.getInSpec(), 'media:void', 'withTag must not change in_spec');
|
|
2506
|
+
|
|
2507
|
+
const same2 = cap.withTag('out', 'media:bytes');
|
|
2508
|
+
assertEqual(same2.getOutSpec(), 'media:void', 'withTag must not change out_spec');
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2511
|
+
// TEST567: N/A for JS (conforms_to_str/accepts_str not in JS CapUrn)
|
|
2512
|
+
|
|
2513
|
+
// ============================================================================
|
|
2514
|
+
// cap_urn.rs: TEST639-TEST653 (Cap URN wildcard tests)
|
|
2515
|
+
// ============================================================================
|
|
2516
|
+
|
|
2517
|
+
// Note: Rust allows missing in/out to default to "media:" wildcard.
|
|
2518
|
+
// JS currently requires in/out (throws MISSING_IN_SPEC/MISSING_OUT_SPEC).
|
|
2519
|
+
// The following tests cover the wildcard behavior that IS applicable to JS.
|
|
2520
|
+
|
|
2521
|
+
// TEST639-642: N/A for JS (JS requires in/out, does not default to media: wildcard)
|
|
2522
|
+
|
|
2523
|
+
// TEST643: cap:in=*;out=* treated as wildcards
|
|
2524
|
+
function test643_explicitAsteriskIsWildcard() {
|
|
2525
|
+
const cap = CapUrn.fromString('cap:in=*;out=*');
|
|
2526
|
+
assertEqual(cap.getInSpec(), '*', 'in=* should be stored as wildcard');
|
|
2527
|
+
assertEqual(cap.getOutSpec(), '*', 'out=* should be stored as wildcard');
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2530
|
+
// TEST644: cap:in=media:bytes;out=* has specific in, wildcard out
|
|
2531
|
+
function test644_specificInWildcardOut() {
|
|
2532
|
+
const cap = CapUrn.fromString('cap:in=media:bytes;out=*');
|
|
2533
|
+
assertEqual(cap.getInSpec(), 'media:bytes', 'Should have specific in');
|
|
2534
|
+
assertEqual(cap.getOutSpec(), '*', 'Should have wildcard out');
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2537
|
+
// TEST645: cap:in=*;out=media:text has wildcard in, specific out
|
|
2538
|
+
function test645_wildcardInSpecificOut() {
|
|
2539
|
+
const cap = CapUrn.fromString('cap:in=*;out=media:text');
|
|
2540
|
+
assertEqual(cap.getInSpec(), '*', 'Should have wildcard in');
|
|
2541
|
+
assertEqual(cap.getOutSpec(), 'media:text', 'Should have specific out');
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
// TEST646: N/A for JS (JS allows in=foo since it just checks for media: or *)
|
|
2545
|
+
// TEST647: N/A for JS (JS allows out=bar since it just checks for media: or *)
|
|
2546
|
+
|
|
2547
|
+
// TEST648: Wildcard in/out match specific caps
|
|
2548
|
+
function test648_wildcardAcceptsSpecific() {
|
|
2549
|
+
const wildcard = CapUrn.fromString('cap:in=*;out=*');
|
|
2550
|
+
const specific = CapUrn.fromString('cap:in="media:bytes";out="media:text"');
|
|
2551
|
+
|
|
2552
|
+
assert(wildcard.accepts(specific), 'Wildcard should accept specific');
|
|
2553
|
+
assert(specific.conformsTo(wildcard), 'Specific should conform to wildcard');
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2556
|
+
// TEST649: Specificity - wildcard has 0, specific has tag count
|
|
2557
|
+
function test649_specificityScoring() {
|
|
2558
|
+
const wildcard = CapUrn.fromString('cap:in=*;out=*');
|
|
2559
|
+
const specific = CapUrn.fromString('cap:in="media:bytes";out="media:text"');
|
|
2560
|
+
|
|
2561
|
+
assertEqual(wildcard.specificity(), 0, 'Wildcard cap should have 0 specificity');
|
|
2562
|
+
assert(specific.specificity() > 0, 'Specific cap should have non-zero specificity');
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
// TEST650: N/A for JS (JS requires in/out, cap:in;out;op=test would fail parsing)
|
|
2566
|
+
|
|
2567
|
+
// TEST651: All identity forms with explicit wildcards produce the same CapUrn
|
|
2568
|
+
function test651_identityFormsEquivalent() {
|
|
2569
|
+
const forms = [
|
|
2570
|
+
'cap:in=*;out=*',
|
|
2571
|
+
'cap:in="media:";out="media:"',
|
|
2572
|
+
];
|
|
2573
|
+
|
|
2574
|
+
const first = CapUrn.fromString(forms[0]);
|
|
2575
|
+
// All forms should produce equivalent caps (wildcard behavior)
|
|
2576
|
+
for (let i = 1; i < forms.length; i++) {
|
|
2577
|
+
const cap = CapUrn.fromString(forms[i]);
|
|
2578
|
+
// Both should accept specific caps
|
|
2579
|
+
const specific = CapUrn.fromString('cap:in="media:bytes";out="media:text"');
|
|
2580
|
+
assert(first.accepts(specific), `Form 0 should accept specific`);
|
|
2581
|
+
assert(cap.accepts(specific), `Form ${i} should accept specific`);
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
|
|
2585
|
+
// TEST652: N/A for JS (CAP_IDENTITY constant not in JS)
|
|
2586
|
+
|
|
2587
|
+
// TEST653: Identity (no extra tags) does not steal routes from specific handlers
|
|
2588
|
+
function test653_identityRoutingIsolation() {
|
|
2589
|
+
const identity = CapUrn.fromString('cap:in=*;out=*');
|
|
2590
|
+
const specificRequest = CapUrn.fromString('cap:in="media:void";op=test;out="media:void"');
|
|
2591
|
+
|
|
2592
|
+
// Identity has specificity 0 (no tags, wildcard directions)
|
|
2593
|
+
assertEqual(identity.specificity(), 0, 'Identity specificity should be 0');
|
|
2594
|
+
|
|
2595
|
+
// Specific request has higher specificity
|
|
2596
|
+
assert(specificRequest.specificity() > identity.specificity(),
|
|
2597
|
+
'Specific request should have higher specificity than identity');
|
|
2598
|
+
|
|
2599
|
+
// CapMatcher should prefer specific over identity
|
|
2600
|
+
const specificCap = CapUrn.fromString('cap:in="media:void";op=test;out="media:void"');
|
|
2601
|
+
const best = CapMatcher.findBestMatch([identity, specificCap], specificRequest);
|
|
2602
|
+
assert(best !== null, 'Should find a match');
|
|
2603
|
+
assertEqual(best.getTag('op'), 'test', 'CapMatcher should prefer specific cap over identity');
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2262
2606
|
// ============================================================================
|
|
2263
2607
|
// Test runner
|
|
2264
2608
|
// ============================================================================
|
|
@@ -2453,6 +2797,48 @@ async function runTests() {
|
|
|
2453
2797
|
runTest('TEST334: plugin_repo_client_needs_sync', test334_pluginRepoClientNeedsSync);
|
|
2454
2798
|
runTest('TEST335: plugin_repo_server_client_integration', test335_pluginRepoServerClientIntegration);
|
|
2455
2799
|
|
|
2800
|
+
// media_urn.rs: TEST546-TEST558 (MediaUrn predicates)
|
|
2801
|
+
console.log('\n--- media_urn.rs (predicates) ---');
|
|
2802
|
+
runTest('TEST546: is_image', test546_isImage);
|
|
2803
|
+
runTest('TEST547: is_audio', test547_isAudio);
|
|
2804
|
+
runTest('TEST548: is_video', test548_isVideo);
|
|
2805
|
+
runTest('TEST549: is_numeric', test549_isNumeric);
|
|
2806
|
+
runTest('TEST550: is_bool', test550_isBool);
|
|
2807
|
+
runTest('TEST551: is_file_path', test551_isFilePath);
|
|
2808
|
+
runTest('TEST552: is_file_path_array', test552_isFilePathArray);
|
|
2809
|
+
runTest('TEST553: is_any_file_path', test553_isAnyFilePath);
|
|
2810
|
+
runTest('TEST554: is_collection', test554_isCollection);
|
|
2811
|
+
console.log(' SKIP TEST555: N/A for JS (with_tag/without_tag on MediaUrn)');
|
|
2812
|
+
console.log(' SKIP TEST556: N/A for JS (image_media_urn_for_ext helper)');
|
|
2813
|
+
console.log(' SKIP TEST557: N/A for JS (audio_media_urn_for_ext helper)');
|
|
2814
|
+
runTest('TEST558: predicate_constant_consistency', test558_predicateConstantConsistency);
|
|
2815
|
+
|
|
2816
|
+
// cap_urn.rs: TEST559-TEST567 (CapUrn tier tests)
|
|
2817
|
+
console.log('\n--- cap_urn.rs (tier tests) ---');
|
|
2818
|
+
runTest('TEST559: without_tag', test559_withoutTag);
|
|
2819
|
+
runTest('TEST560: with_in_out_spec', test560_withInOutSpec);
|
|
2820
|
+
console.log(' SKIP TEST561: N/A for JS (in_media_urn/out_media_urn)');
|
|
2821
|
+
console.log(' SKIP TEST562: N/A for JS (canonical_option)');
|
|
2822
|
+
runTest('TEST563: find_all_matches', test563_findAllMatches);
|
|
2823
|
+
runTest('TEST564: are_compatible', test564_areCompatible);
|
|
2824
|
+
console.log(' SKIP TEST565: N/A for JS (tags_to_string)');
|
|
2825
|
+
runTest('TEST566: with_tag_ignores_in_out', test566_withTagIgnoresInOut);
|
|
2826
|
+
console.log(' SKIP TEST567: N/A for JS (conforms_to_str/accepts_str)');
|
|
2827
|
+
|
|
2828
|
+
// cap_urn.rs: TEST639-TEST653 (Cap URN wildcard tests)
|
|
2829
|
+
console.log('\n--- cap_urn.rs (wildcard tests) ---');
|
|
2830
|
+
console.log(' SKIP TEST639-642: N/A for JS (implicit wildcard defaults)');
|
|
2831
|
+
runTest('TEST643: explicit_asterisk_is_wildcard', test643_explicitAsteriskIsWildcard);
|
|
2832
|
+
runTest('TEST644: specific_in_wildcard_out', test644_specificInWildcardOut);
|
|
2833
|
+
runTest('TEST645: wildcard_in_specific_out', test645_wildcardInSpecificOut);
|
|
2834
|
+
console.log(' SKIP TEST646-647: N/A for JS (invalid spec validation differs)');
|
|
2835
|
+
runTest('TEST648: wildcard_accepts_specific', test648_wildcardAcceptsSpecific);
|
|
2836
|
+
runTest('TEST649: specificity_scoring', test649_specificityScoring);
|
|
2837
|
+
console.log(' SKIP TEST650: N/A for JS (requires in/out)');
|
|
2838
|
+
runTest('TEST651: identity_forms_equivalent', test651_identityFormsEquivalent);
|
|
2839
|
+
console.log(' SKIP TEST652: N/A for JS (CAP_IDENTITY constant)');
|
|
2840
|
+
runTest('TEST653: identity_routing_isolation', test653_identityRoutingIsolation);
|
|
2841
|
+
|
|
2456
2842
|
// Summary
|
|
2457
2843
|
console.log(`\n${passCount + failCount} tests: ${passCount} passed, ${failCount} failed`);
|
|
2458
2844
|
if (failCount > 0) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"author": "Bahram Joharshamshiri",
|
|
3
3
|
"dependencies": {
|
|
4
|
-
"tagged-urn": "^0.
|
|
4
|
+
"tagged-urn": "^0.26.4740"
|
|
5
5
|
},
|
|
6
6
|
"description": "JavaScript implementation of Cap URN (Capability Uniform Resource Names) with strict validation and matching",
|
|
7
7
|
"engines": {
|
|
@@ -32,5 +32,5 @@
|
|
|
32
32
|
"scripts": {
|
|
33
33
|
"test": "node capns.test.js"
|
|
34
34
|
},
|
|
35
|
-
"version": "0.
|
|
36
|
-
}
|
|
35
|
+
"version": "0.84.18923"
|
|
36
|
+
}
|