capns 0.84.18923 → 0.85.19364
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/capns.js +15 -15
- package/capns.test.js +89 -82
- package/package.json +2 -2
package/capns.js
CHANGED
|
@@ -732,18 +732,18 @@ const MEDIA_INTEGER_ARRAY = 'media:integer;textable;numeric;form=list';
|
|
|
732
732
|
const MEDIA_NUMBER_ARRAY = 'media:textable;numeric;form=list';
|
|
733
733
|
const MEDIA_BOOLEAN_ARRAY = 'media:bool;textable;form=list';
|
|
734
734
|
const MEDIA_OBJECT_ARRAY = 'media:form=list;textable';
|
|
735
|
-
const MEDIA_BINARY = 'media:
|
|
735
|
+
const MEDIA_BINARY = 'media:';
|
|
736
736
|
const MEDIA_VOID = 'media:void';
|
|
737
737
|
// Semantic content types
|
|
738
|
-
const MEDIA_PNG = 'media:image;png
|
|
739
|
-
const MEDIA_AUDIO = 'media:wav;audio
|
|
740
|
-
const MEDIA_VIDEO = 'media:video
|
|
738
|
+
const MEDIA_PNG = 'media:image;png';
|
|
739
|
+
const MEDIA_AUDIO = 'media:wav;audio';
|
|
740
|
+
const MEDIA_VIDEO = 'media:video';
|
|
741
741
|
// Semantic AI input types
|
|
742
|
-
const MEDIA_AUDIO_SPEECH = 'media:audio;wav;
|
|
743
|
-
const MEDIA_IMAGE_THUMBNAIL = 'media:image;png;
|
|
742
|
+
const MEDIA_AUDIO_SPEECH = 'media:audio;wav;speech';
|
|
743
|
+
const MEDIA_IMAGE_THUMBNAIL = 'media:image;png;thumbnail';
|
|
744
744
|
// Document types (PRIMARY naming - type IS the format)
|
|
745
|
-
const MEDIA_PDF = 'media:pdf
|
|
746
|
-
const MEDIA_EPUB = 'media:epub
|
|
745
|
+
const MEDIA_PDF = 'media:pdf';
|
|
746
|
+
const MEDIA_EPUB = 'media:epub';
|
|
747
747
|
// Text format types (PRIMARY naming - type IS the format)
|
|
748
748
|
const MEDIA_MD = 'media:md;textable';
|
|
749
749
|
const MEDIA_TXT = 'media:txt;textable';
|
|
@@ -775,8 +775,8 @@ const MEDIA_LLM_INFERENCE_OUTPUT = 'media:generated-text;textable;form=map';
|
|
|
775
775
|
const MEDIA_FILE_PATH = 'media:file-path;textable;form=scalar';
|
|
776
776
|
const MEDIA_FILE_PATH_ARRAY = 'media:file-path;textable;form=list';
|
|
777
777
|
// Collection types
|
|
778
|
-
const MEDIA_COLLECTION = 'media:collection;form=map';
|
|
779
|
-
const MEDIA_COLLECTION_LIST = 'media:collection;form=list';
|
|
778
|
+
const MEDIA_COLLECTION = 'media:collection;textable;form=map';
|
|
779
|
+
const MEDIA_COLLECTION_LIST = 'media:collection;textable;form=list';
|
|
780
780
|
|
|
781
781
|
// =============================================================================
|
|
782
782
|
// STANDARD CAP URN CONSTANTS
|
|
@@ -825,7 +825,7 @@ class MediaUrn {
|
|
|
825
825
|
|
|
826
826
|
/**
|
|
827
827
|
* Parse a media URN string. Validates the prefix is 'media'.
|
|
828
|
-
* @param {string} str - The media URN string (e.g., "media:
|
|
828
|
+
* @param {string} str - The media URN string (e.g., "media:pdf")
|
|
829
829
|
* @returns {MediaUrn}
|
|
830
830
|
* @throws {MediaUrnError} If prefix is not 'media'
|
|
831
831
|
*/
|
|
@@ -834,8 +834,8 @@ class MediaUrn {
|
|
|
834
834
|
return new MediaUrn(urn);
|
|
835
835
|
}
|
|
836
836
|
|
|
837
|
-
/** @returns {boolean} True if the "
|
|
838
|
-
isBinary() { return this._urn.getTag('
|
|
837
|
+
/** @returns {boolean} True if the "textable" marker tag is NOT present (binary = not textable) */
|
|
838
|
+
isBinary() { return this._urn.getTag('textable') === undefined; }
|
|
839
839
|
|
|
840
840
|
/** @returns {boolean} True if form=map tag is present */
|
|
841
841
|
isMap() { return this._urn.hasTag('form', 'map'); }
|
|
@@ -1073,7 +1073,7 @@ class MediaSpec {
|
|
|
1073
1073
|
return this._parsedMediaUrn;
|
|
1074
1074
|
}
|
|
1075
1075
|
|
|
1076
|
-
/** @returns {boolean} True if binary (
|
|
1076
|
+
/** @returns {boolean} True if binary (textable marker tag absent) */
|
|
1077
1077
|
isBinary() {
|
|
1078
1078
|
const mu = this.parsedMediaUrn();
|
|
1079
1079
|
return mu ? mu.isBinary() : false;
|
|
@@ -1280,7 +1280,7 @@ function buildExtensionIndex(mediaSpecs) {
|
|
|
1280
1280
|
*
|
|
1281
1281
|
* @example
|
|
1282
1282
|
* const urns = mediaUrnsForExtension('pdf', mediaSpecs);
|
|
1283
|
-
* // May return ['media:pdf
|
|
1283
|
+
* // May return ['media:pdf']
|
|
1284
1284
|
*/
|
|
1285
1285
|
function mediaUrnsForExtension(extension, mediaSpecs) {
|
|
1286
1286
|
const index = buildExtensionIndex(mediaSpecs);
|
package/capns.test.js
CHANGED
|
@@ -441,12 +441,12 @@ function test025_bestMatch() {
|
|
|
441
441
|
// TEST026: merge combines tags, subset keeps only specified
|
|
442
442
|
function test026_mergeAndSubset() {
|
|
443
443
|
const cap1 = CapUrn.fromString(testUrn('op=generate'));
|
|
444
|
-
const cap2 = CapUrn.fromString('cap:in="media:textable;form=scalar";ext=pdf;format=binary;out="media:
|
|
444
|
+
const cap2 = CapUrn.fromString('cap:in="media:textable;form=scalar";ext=pdf;format=binary;out="media:"');
|
|
445
445
|
|
|
446
446
|
// Merge (other takes precedence)
|
|
447
447
|
const merged = cap1.merge(cap2);
|
|
448
448
|
assertEqual(merged.getInSpec(), 'media:textable;form=scalar', 'Merge should take inSpec from other');
|
|
449
|
-
assertEqual(merged.getOutSpec(), 'media:
|
|
449
|
+
assertEqual(merged.getOutSpec(), 'media:', 'Merge should take outSpec from other');
|
|
450
450
|
assertEqual(merged.getTag('op'), 'generate', 'Merge should keep original tags');
|
|
451
451
|
assertEqual(merged.getTag('ext'), 'pdf', 'Merge should add other tags');
|
|
452
452
|
|
|
@@ -647,8 +647,8 @@ function test046_matchingSemanticsFallbackPattern() {
|
|
|
647
647
|
|
|
648
648
|
// TEST047: Thumbnail with void input matches specific ext request
|
|
649
649
|
function test047_matchingSemanticsThumbnailVoidInput() {
|
|
650
|
-
const cap = CapUrn.fromString('cap:in="media:void";op=generate_thumbnail;out="media:image;png;
|
|
651
|
-
const request = CapUrn.fromString('cap:ext=pdf;in="media:void";op=generate_thumbnail;out="media:image
|
|
650
|
+
const cap = CapUrn.fromString('cap:in="media:void";op=generate_thumbnail;out="media:image;png;thumbnail"');
|
|
651
|
+
const request = CapUrn.fromString('cap:ext=pdf;in="media:void";op=generate_thumbnail;out="media:image"');
|
|
652
652
|
assert(cap.accepts(request), 'Void input cap should accept request; cap output conforms to less-specific request output');
|
|
653
653
|
}
|
|
654
654
|
|
|
@@ -666,7 +666,7 @@ function test049_matchingSemanticsCrossDimension() {
|
|
|
666
666
|
assert(cap.accepts(request), 'Independent tags should not block matching');
|
|
667
667
|
}
|
|
668
668
|
|
|
669
|
-
// TEST050: media:string vs media:
|
|
669
|
+
// TEST050: media:string vs media: (wildcard) -> no match
|
|
670
670
|
function test050_matchingSemanticsDirectionMismatch() {
|
|
671
671
|
const cap = CapUrn.fromString(
|
|
672
672
|
`cap:in="${MEDIA_STRING}";op=generate;out="${MEDIA_OBJECT}"`
|
|
@@ -677,79 +677,79 @@ function test050_matchingSemanticsDirectionMismatch() {
|
|
|
677
677
|
assert(!cap.accepts(request), 'Incompatible direction types should not match');
|
|
678
678
|
}
|
|
679
679
|
|
|
680
|
-
// TEST051: Generic media:
|
|
681
|
-
// specific pdf cap rejects generic
|
|
680
|
+
// TEST051: Generic media: (wildcard) provider accepts media:pdf request;
|
|
681
|
+
// specific pdf cap rejects generic wildcard request;
|
|
682
682
|
// output direction: more specific output satisfies less specific request
|
|
683
683
|
function test051_directionSemanticMatching() {
|
|
684
|
-
// Generic
|
|
684
|
+
// Generic wildcard cap accepts specific pdf request
|
|
685
685
|
const genericCap = CapUrn.fromString(
|
|
686
|
-
'cap:in="media:
|
|
686
|
+
'cap:in="media:";op=generate_thumbnail;out="media:image;png;thumbnail"'
|
|
687
687
|
);
|
|
688
688
|
const pdfRequest = CapUrn.fromString(
|
|
689
|
-
'cap:in="media:pdf
|
|
689
|
+
'cap:in="media:pdf";op=generate_thumbnail;out="media:image;png;thumbnail"'
|
|
690
690
|
);
|
|
691
|
-
assert(genericCap.accepts(pdfRequest), 'Generic
|
|
691
|
+
assert(genericCap.accepts(pdfRequest), 'Generic wildcard cap must accept pdf request');
|
|
692
692
|
|
|
693
|
-
// Also accepts epub
|
|
693
|
+
// Also accepts epub
|
|
694
694
|
const epubRequest = CapUrn.fromString(
|
|
695
|
-
'cap:in="media:epub
|
|
695
|
+
'cap:in="media:epub";op=generate_thumbnail;out="media:image;png;thumbnail"'
|
|
696
696
|
);
|
|
697
|
-
assert(genericCap.accepts(epubRequest), 'Generic
|
|
697
|
+
assert(genericCap.accepts(epubRequest), 'Generic wildcard cap must accept epub request');
|
|
698
698
|
|
|
699
699
|
// Reverse: specific pdf cap does NOT accept generic bytes request
|
|
700
700
|
const pdfCap = CapUrn.fromString(
|
|
701
|
-
'cap:in="media:pdf
|
|
701
|
+
'cap:in="media:pdf";op=generate_thumbnail;out="media:image;png;thumbnail"'
|
|
702
702
|
);
|
|
703
703
|
const genericRequest = CapUrn.fromString(
|
|
704
|
-
'cap:in="media:
|
|
704
|
+
'cap:in="media:";op=generate_thumbnail;out="media:image;png;thumbnail"'
|
|
705
705
|
);
|
|
706
|
-
assert(!pdfCap.accepts(genericRequest), 'Specific pdf cap must NOT accept generic
|
|
706
|
+
assert(!pdfCap.accepts(genericRequest), 'Specific pdf cap must NOT accept generic wildcard request');
|
|
707
707
|
|
|
708
708
|
// PDF cap does NOT accept epub request
|
|
709
709
|
assert(!pdfCap.accepts(epubRequest), 'PDF cap must NOT accept epub request');
|
|
710
710
|
|
|
711
711
|
// Output direction: cap producing more specific output satisfies less specific request
|
|
712
712
|
const specificOutCap = CapUrn.fromString(
|
|
713
|
-
'cap:in="media:
|
|
713
|
+
'cap:in="media:";op=generate_thumbnail;out="media:image;png;thumbnail"'
|
|
714
714
|
);
|
|
715
715
|
const genericOutRequest = CapUrn.fromString(
|
|
716
|
-
'cap:in="media:
|
|
716
|
+
'cap:in="media:";op=generate_thumbnail;out="media:image"'
|
|
717
717
|
);
|
|
718
718
|
assert(specificOutCap.accepts(genericOutRequest),
|
|
719
|
-
'Cap producing image;png;
|
|
719
|
+
'Cap producing image;png;thumbnail must satisfy request for image');
|
|
720
720
|
|
|
721
721
|
// Reverse output: generic output cap does NOT satisfy specific output request
|
|
722
722
|
const genericOutCap = CapUrn.fromString(
|
|
723
|
-
'cap:in="media:
|
|
723
|
+
'cap:in="media:";op=generate_thumbnail;out="media:image"'
|
|
724
724
|
);
|
|
725
725
|
const specificOutRequest = CapUrn.fromString(
|
|
726
|
-
'cap:in="media:
|
|
726
|
+
'cap:in="media:";op=generate_thumbnail;out="media:image;png;thumbnail"'
|
|
727
727
|
);
|
|
728
728
|
assert(!genericOutCap.accepts(specificOutRequest),
|
|
729
729
|
'Generic output cap must NOT satisfy specific output request');
|
|
730
730
|
}
|
|
731
731
|
|
|
732
|
-
// TEST052: Specificity: media:
|
|
733
|
-
// pdf;
|
|
732
|
+
// TEST052: Specificity: media:(0 tags) + image;png;thumbnail(3 tags) + op(1) = 4;
|
|
733
|
+
// pdf(1) + image;png;thumbnail(3) + op(1) = 5; CapMatcher prefers higher
|
|
734
734
|
function test052_directionSemanticSpecificity() {
|
|
735
735
|
const genericCap = CapUrn.fromString(
|
|
736
|
-
'cap:in="media:
|
|
736
|
+
'cap:in="media:";op=generate_thumbnail;out="media:image;png;thumbnail"'
|
|
737
737
|
);
|
|
738
738
|
const specificCap = CapUrn.fromString(
|
|
739
|
-
'cap:in="media:pdf
|
|
739
|
+
'cap:in="media:pdf";op=generate_thumbnail;out="media:image;png;thumbnail"'
|
|
740
740
|
);
|
|
741
741
|
|
|
742
|
-
assertEqual(genericCap.specificity(),
|
|
743
|
-
assertEqual(specificCap.specificity(),
|
|
744
|
-
assert(specificCap.specificity() > genericCap.specificity(), 'pdf
|
|
742
|
+
assertEqual(genericCap.specificity(), 4, 'media:(0) + image;png;thumbnail(3) + op(1) = 4');
|
|
743
|
+
assertEqual(specificCap.specificity(), 5, 'pdf(1) + image;png;thumbnail(3) + op(1) = 5');
|
|
744
|
+
assert(specificCap.specificity() > genericCap.specificity(), 'pdf should be more specific');
|
|
745
745
|
|
|
746
746
|
// CapMatcher should prefer more specific
|
|
747
747
|
const pdfRequest = CapUrn.fromString(
|
|
748
|
-
'cap:in="media:pdf
|
|
748
|
+
'cap:in="media:pdf";op=generate_thumbnail;out="media:image;png;thumbnail"'
|
|
749
749
|
);
|
|
750
750
|
const best = CapMatcher.findBestMatch([genericCap, specificCap], pdfRequest);
|
|
751
751
|
assert(best !== null, 'Should find a match');
|
|
752
|
-
assertEqual(best.getInSpec(), 'media:pdf
|
|
752
|
+
assertEqual(best.getInSpec(), 'media:pdf', 'Should prefer more specific pdf cap');
|
|
753
753
|
}
|
|
754
754
|
|
|
755
755
|
// ============================================================================
|
|
@@ -811,13 +811,20 @@ function test060_wrongPrefixFails() {
|
|
|
811
811
|
);
|
|
812
812
|
}
|
|
813
813
|
|
|
814
|
-
// TEST061: isBinary true
|
|
814
|
+
// TEST061: isBinary true when textable tag is absent (binary = not textable)
|
|
815
815
|
function test061_isBinary() {
|
|
816
|
-
|
|
816
|
+
// Binary types: no textable tag
|
|
817
|
+
assert(MediaUrn.fromString(MEDIA_BINARY).isBinary(), 'MEDIA_BINARY (media:) should be binary');
|
|
817
818
|
assert(MediaUrn.fromString(MEDIA_PNG).isBinary(), 'MEDIA_PNG should be binary');
|
|
818
819
|
assert(MediaUrn.fromString(MEDIA_PDF).isBinary(), 'MEDIA_PDF should be binary');
|
|
819
|
-
assert(MediaUrn.fromString(
|
|
820
|
+
assert(MediaUrn.fromString('media:video').isBinary(), 'media:video should be binary');
|
|
821
|
+
assert(MediaUrn.fromString('media:epub').isBinary(), 'media:epub should be binary');
|
|
822
|
+
// Textable types: is_binary is false
|
|
820
823
|
assert(!MediaUrn.fromString('media:textable').isBinary(), 'media:textable should not be binary');
|
|
824
|
+
assert(!MediaUrn.fromString('media:textable;form=map').isBinary(), 'textable map should not be binary');
|
|
825
|
+
assert(!MediaUrn.fromString(MEDIA_STRING).isBinary(), 'MEDIA_STRING should not be binary');
|
|
826
|
+
assert(!MediaUrn.fromString(MEDIA_JSON).isBinary(), 'MEDIA_JSON should not be binary');
|
|
827
|
+
assert(!MediaUrn.fromString(MEDIA_MD).isBinary(), 'MEDIA_MD should not be binary');
|
|
821
828
|
}
|
|
822
829
|
|
|
823
830
|
// TEST062: isMap true for MEDIA_OBJECT (form=map); false for MEDIA_STRING (form=scalar), MEDIA_STRING_ARRAY (form=list)
|
|
@@ -911,7 +918,7 @@ function test072_constantsParse() {
|
|
|
911
918
|
|
|
912
919
|
// TEST073: N/A for JS (Rust has binary_media_urn_for_ext/text_media_urn_for_ext)
|
|
913
920
|
|
|
914
|
-
// TEST074: MEDIA_PDF (media:pdf
|
|
921
|
+
// TEST074: MEDIA_PDF (media:pdf) conformsTo media:pdf; MEDIA_MD conformsTo media:md; same URNs conform
|
|
915
922
|
function test074_mediaUrnMatching() {
|
|
916
923
|
const pdfUrn = MediaUrn.fromString(MEDIA_PDF);
|
|
917
924
|
const pdfPattern = MediaUrn.fromString('media:pdf');
|
|
@@ -938,11 +945,11 @@ function test075_accepts() {
|
|
|
938
945
|
|
|
939
946
|
// TEST076: More tags = higher specificity
|
|
940
947
|
function test076_specificity() {
|
|
941
|
-
const s1 = MediaUrn.fromString('media:
|
|
942
|
-
const s2 = MediaUrn.fromString('media:pdf
|
|
943
|
-
const s3 = MediaUrn.fromString('media:image;png;
|
|
944
|
-
assert(s2.specificity() > s1.specificity(), 'pdf
|
|
945
|
-
assert(s3.specificity() > s2.specificity(), 'image;png;
|
|
948
|
+
const s1 = MediaUrn.fromString('media:');
|
|
949
|
+
const s2 = MediaUrn.fromString('media:pdf');
|
|
950
|
+
const s3 = MediaUrn.fromString('media:image;png;thumbnail');
|
|
951
|
+
assert(s2.specificity() > s1.specificity(), 'pdf should be more specific than wildcard');
|
|
952
|
+
assert(s3.specificity() > s2.specificity(), 'image;png;thumbnail should be more specific than pdf');
|
|
946
953
|
}
|
|
947
954
|
|
|
948
955
|
// TEST077: N/A for JS (Rust serde) - but we test JSON.stringify round-trip
|
|
@@ -1015,7 +1022,7 @@ function test093_resolveUnresolvableFailsHard() {
|
|
|
1015
1022
|
// TEST097: N/A for JS (Rust validation function)
|
|
1016
1023
|
// TEST098: N/A for JS
|
|
1017
1024
|
|
|
1018
|
-
// TEST099: MediaSpec with media:
|
|
1025
|
+
// TEST099: MediaSpec with media: (no textable tag) -> isBinary() true
|
|
1019
1026
|
function test099_resolvedIsBinary() {
|
|
1020
1027
|
const spec = new MediaSpec('application/octet-stream', null, null, 'Binary', null, MEDIA_BINARY);
|
|
1021
1028
|
assert(spec.isBinary(), 'Resolved binary spec should be binary');
|
|
@@ -1097,13 +1104,13 @@ function test106_metadataWithValidation() {
|
|
|
1097
1104
|
function test107_extensionsPropagation() {
|
|
1098
1105
|
const mediaSpecs = [
|
|
1099
1106
|
{
|
|
1100
|
-
urn: 'media:pdf
|
|
1107
|
+
urn: 'media:pdf',
|
|
1101
1108
|
media_type: 'application/pdf',
|
|
1102
1109
|
title: 'PDF Document',
|
|
1103
1110
|
extensions: ['pdf']
|
|
1104
1111
|
}
|
|
1105
1112
|
];
|
|
1106
|
-
const resolved = resolveMediaUrn('media:pdf
|
|
1113
|
+
const resolved = resolveMediaUrn('media:pdf', mediaSpecs);
|
|
1107
1114
|
assert(Array.isArray(resolved.extensions), 'Extensions should be an array');
|
|
1108
1115
|
assertEqual(resolved.extensions.length, 1, 'Should have one extension');
|
|
1109
1116
|
assertEqual(resolved.extensions[0], 'pdf', 'Should have pdf extension');
|
|
@@ -1112,7 +1119,7 @@ function test107_extensionsPropagation() {
|
|
|
1112
1119
|
// TEST108: N/A for JS (Rust serde) - but we test MediaSpec with extensions
|
|
1113
1120
|
function test108_extensionsSerialization() {
|
|
1114
1121
|
// Test that MediaSpec can hold extensions correctly
|
|
1115
|
-
const spec = new MediaSpec('application/pdf', null, null, 'PDF', null, 'media:pdf
|
|
1122
|
+
const spec = new MediaSpec('application/pdf', null, null, 'PDF', null, 'media:pdf', null, null, ['pdf']);
|
|
1116
1123
|
assert(Array.isArray(spec.extensions), 'Extensions should be array');
|
|
1117
1124
|
assertEqual(spec.extensions[0], 'pdf', 'Should have pdf extension');
|
|
1118
1125
|
}
|
|
@@ -1140,13 +1147,13 @@ function test109_extensionsWithMetadataAndValidation() {
|
|
|
1140
1147
|
function test110_multipleExtensions() {
|
|
1141
1148
|
const mediaSpecs = [
|
|
1142
1149
|
{
|
|
1143
|
-
urn: 'media:image;jpeg
|
|
1150
|
+
urn: 'media:image;jpeg',
|
|
1144
1151
|
media_type: 'image/jpeg',
|
|
1145
1152
|
title: 'JPEG Image',
|
|
1146
1153
|
extensions: ['jpg', 'jpeg']
|
|
1147
1154
|
}
|
|
1148
1155
|
];
|
|
1149
|
-
const resolved = resolveMediaUrn('media:image;jpeg
|
|
1156
|
+
const resolved = resolveMediaUrn('media:image;jpeg', mediaSpecs);
|
|
1150
1157
|
assertEqual(resolved.extensions.length, 2, 'Should have two extensions');
|
|
1151
1158
|
assertEqual(resolved.extensions[0], 'jpg', 'First extension should be jpg');
|
|
1152
1159
|
assertEqual(resolved.extensions[1], 'jpeg', 'Second extension should be jpeg');
|
|
@@ -1524,7 +1531,7 @@ function test157_stdinSourceFromFileReference() {
|
|
|
1524
1531
|
const trackedFileId = 'tracked-file-123';
|
|
1525
1532
|
const originalPath = '/path/to/original.pdf';
|
|
1526
1533
|
const securityBookmark = new Uint8Array([0x62, 0x6f, 0x6f, 0x6b]);
|
|
1527
|
-
const mediaUrn = 'media:pdf
|
|
1534
|
+
const mediaUrn = 'media:pdf';
|
|
1528
1535
|
|
|
1529
1536
|
const source = StdinSource.fromFileReference(trackedFileId, originalPath, securityBookmark, mediaUrn);
|
|
1530
1537
|
assert(source !== null, 'Should create source');
|
|
@@ -1580,7 +1587,7 @@ function test276_capArgumentValueAsStrValid() {
|
|
|
1580
1587
|
|
|
1581
1588
|
// TEST277: CapArgumentValue.valueAsStr fails for non-UTF-8 binary data
|
|
1582
1589
|
function test277_capArgumentValueAsStrInvalidUtf8() {
|
|
1583
|
-
const arg = new CapArgumentValue('media:pdf
|
|
1590
|
+
const arg = new CapArgumentValue('media:pdf', new Uint8Array([0xFF, 0xFE, 0x80]));
|
|
1584
1591
|
let threw = false;
|
|
1585
1592
|
try {
|
|
1586
1593
|
arg.valueAsStr();
|
|
@@ -1611,7 +1618,7 @@ function test283_capArgumentValueLargeBinary() {
|
|
|
1611
1618
|
for (let i = 0; i < 10000; i++) {
|
|
1612
1619
|
data[i] = i % 256;
|
|
1613
1620
|
}
|
|
1614
|
-
const arg = new CapArgumentValue('media:pdf
|
|
1621
|
+
const arg = new CapArgumentValue('media:pdf', data);
|
|
1615
1622
|
assertEqual(arg.value.length, 10000, 'large binary must preserve all bytes');
|
|
1616
1623
|
assertEqual(arg.value[0], 0, 'first byte check');
|
|
1617
1624
|
assertEqual(arg.value[255], 255, 'byte 255 check');
|
|
@@ -1629,7 +1636,7 @@ function test304_mediaAvailabilityOutputConstant() {
|
|
|
1629
1636
|
const urn = TaggedUrn.fromString(MEDIA_AVAILABILITY_OUTPUT);
|
|
1630
1637
|
assert(urn.getTag('textable') !== undefined, 'model-availability must be textable');
|
|
1631
1638
|
assertEqual(urn.getTag('form'), 'map', 'model-availability must be form=map');
|
|
1632
|
-
assert(urn.getTag('
|
|
1639
|
+
assert(urn.getTag('textable') !== undefined, 'model-availability must not be binary (has textable)');
|
|
1633
1640
|
const reparsed = TaggedUrn.fromString(urn.toString());
|
|
1634
1641
|
assert(urn.conformsTo(reparsed), 'roundtrip must match original');
|
|
1635
1642
|
}
|
|
@@ -1639,7 +1646,7 @@ function test305_mediaPathOutputConstant() {
|
|
|
1639
1646
|
const urn = TaggedUrn.fromString(MEDIA_PATH_OUTPUT);
|
|
1640
1647
|
assert(urn.getTag('textable') !== undefined, 'model-path must be textable');
|
|
1641
1648
|
assertEqual(urn.getTag('form'), 'map', 'model-path must be form=map');
|
|
1642
|
-
assert(urn.getTag('
|
|
1649
|
+
assert(urn.getTag('textable') !== undefined, 'model-path must not be binary (has textable)');
|
|
1643
1650
|
const reparsed = TaggedUrn.fromString(urn.toString());
|
|
1644
1651
|
assert(urn.conformsTo(reparsed), 'roundtrip must match original');
|
|
1645
1652
|
}
|
|
@@ -1731,8 +1738,8 @@ function test312_allUrnBuildersProduceValidUrns() {
|
|
|
1731
1738
|
|
|
1732
1739
|
function testJS_buildExtensionIndex() {
|
|
1733
1740
|
const mediaSpecs = [
|
|
1734
|
-
{ urn: 'media:pdf
|
|
1735
|
-
{ urn: 'media:image;jpeg
|
|
1741
|
+
{ urn: 'media:pdf', media_type: 'application/pdf', extensions: ['pdf'] },
|
|
1742
|
+
{ urn: 'media:image;jpeg', media_type: 'image/jpeg', extensions: ['jpg', 'jpeg'] },
|
|
1736
1743
|
{ urn: 'media:json;textable', media_type: 'application/json', extensions: ['json'] }
|
|
1737
1744
|
];
|
|
1738
1745
|
const index = buildExtensionIndex(mediaSpecs);
|
|
@@ -1742,12 +1749,12 @@ function testJS_buildExtensionIndex() {
|
|
|
1742
1749
|
assert(index.has('jpg'), 'Should have jpg');
|
|
1743
1750
|
assert(index.has('jpeg'), 'Should have jpeg');
|
|
1744
1751
|
assert(index.has('json'), 'Should have json');
|
|
1745
|
-
assertEqual(index.get('pdf')[0], 'media:pdf
|
|
1752
|
+
assertEqual(index.get('pdf')[0], 'media:pdf', 'pdf should map correctly');
|
|
1746
1753
|
}
|
|
1747
1754
|
|
|
1748
1755
|
function testJS_mediaUrnsForExtension() {
|
|
1749
1756
|
const mediaSpecs = [
|
|
1750
|
-
{ urn: 'media:pdf
|
|
1757
|
+
{ urn: 'media:pdf', media_type: 'application/pdf', extensions: ['pdf'] },
|
|
1751
1758
|
{ urn: 'media:json;textable;form=map', media_type: 'application/json', extensions: ['json'] },
|
|
1752
1759
|
{ urn: 'media:json;textable;form=list', media_type: 'application/json', extensions: ['json'] }
|
|
1753
1760
|
];
|
|
@@ -1775,8 +1782,8 @@ function testJS_mediaUrnsForExtension() {
|
|
|
1775
1782
|
|
|
1776
1783
|
function testJS_getExtensionMappings() {
|
|
1777
1784
|
const mediaSpecs = [
|
|
1778
|
-
{ urn: 'media:pdf
|
|
1779
|
-
{ urn: 'media:image;jpeg
|
|
1785
|
+
{ urn: 'media:pdf', media_type: 'application/pdf', extensions: ['pdf'] },
|
|
1786
|
+
{ urn: 'media:image;jpeg', media_type: 'image/jpeg', extensions: ['jpg', 'jpeg'] }
|
|
1780
1787
|
];
|
|
1781
1788
|
const mappings = getExtensionMappings(mediaSpecs);
|
|
1782
1789
|
assert(Array.isArray(mappings), 'Should return an array');
|
|
@@ -1885,7 +1892,7 @@ function testJS_binaryArgPassedToExecuteCap() {
|
|
|
1885
1892
|
const cube = new CapBlock();
|
|
1886
1893
|
cube.addRegistry('test', registry);
|
|
1887
1894
|
|
|
1888
|
-
const binaryArg = new CapArgumentValue('media:pdf
|
|
1895
|
+
const binaryArg = new CapArgumentValue('media:pdf', new Uint8Array([0x89, 0x50, 0x4E, 0x47]));
|
|
1889
1896
|
const { compositeHost } = cube.can('cap:in="media:void";op=test;out="media:string"');
|
|
1890
1897
|
|
|
1891
1898
|
return compositeHost.executeCap(
|
|
@@ -1893,7 +1900,7 @@ function testJS_binaryArgPassedToExecuteCap() {
|
|
|
1893
1900
|
[binaryArg]
|
|
1894
1901
|
).then(() => {
|
|
1895
1902
|
assert(receivedArgs !== null, 'Should receive arguments');
|
|
1896
|
-
assertEqual(receivedArgs[0].mediaUrn, 'media:pdf
|
|
1903
|
+
assertEqual(receivedArgs[0].mediaUrn, 'media:pdf', 'Correct mediaUrn');
|
|
1897
1904
|
assertEqual(receivedArgs[0].value[0], 0x89, 'First byte check');
|
|
1898
1905
|
assertEqual(receivedArgs[0].value.length, 4, 'Correct data length');
|
|
1899
1906
|
});
|
|
@@ -1930,12 +1937,12 @@ const sampleRegistry = {
|
|
|
1930
1937
|
tags: ['pdf', 'extractor'],
|
|
1931
1938
|
caps: [
|
|
1932
1939
|
{
|
|
1933
|
-
urn: 'cap:in="media:pdf
|
|
1940
|
+
urn: 'cap:in="media:pdf";op=disbind;out="media:disbound-page;textable;form=list"',
|
|
1934
1941
|
title: 'Disbind PDF',
|
|
1935
1942
|
description: 'Extract pages'
|
|
1936
1943
|
},
|
|
1937
1944
|
{
|
|
1938
|
-
urn: 'cap:in="media:pdf
|
|
1945
|
+
urn: 'cap:in="media:pdf";op=extract_metadata;out="media:file-metadata;textable;form=map"',
|
|
1939
1946
|
title: 'Extract Metadata',
|
|
1940
1947
|
description: 'Get PDF metadata'
|
|
1941
1948
|
}
|
|
@@ -2143,13 +2150,13 @@ function test328_pluginRepoServerGetByCategory() {
|
|
|
2143
2150
|
function test329_pluginRepoServerGetByCap() {
|
|
2144
2151
|
const server = new PluginRepoServer(sampleRegistry);
|
|
2145
2152
|
|
|
2146
|
-
const disbindCap = 'cap:in="media:pdf
|
|
2153
|
+
const disbindCap = 'cap:in="media:pdf";op=disbind;out="media:disbound-page;textable;form=list"';
|
|
2147
2154
|
const plugins = server.getPluginsByCap(disbindCap);
|
|
2148
2155
|
|
|
2149
2156
|
assert(plugins.length === 1, 'Should find 1 plugin with this cap');
|
|
2150
2157
|
assert(plugins[0].id === 'pdfcartridge', 'Should be pdfcartridge');
|
|
2151
2158
|
|
|
2152
|
-
const metadataCap = 'cap:in="media:pdf
|
|
2159
|
+
const metadataCap = 'cap:in="media:pdf";op=extract_metadata;out="media:file-metadata;textable;form=map"';
|
|
2153
2160
|
const metadataPlugins = server.getPluginsByCap(metadataCap);
|
|
2154
2161
|
assert(metadataPlugins.length === 1, 'Should find metadata cap');
|
|
2155
2162
|
}
|
|
@@ -2176,7 +2183,7 @@ function test331_pluginRepoClientGetSuggestions() {
|
|
|
2176
2183
|
|
|
2177
2184
|
client.updateCache('https://example.com/api/plugins', plugins);
|
|
2178
2185
|
|
|
2179
|
-
const disbindCap = 'cap:in="media:pdf
|
|
2186
|
+
const disbindCap = 'cap:in="media:pdf";op=disbind;out="media:disbound-page;textable;form=list"';
|
|
2180
2187
|
const suggestions = client.getSuggestionsForCap(disbindCap);
|
|
2181
2188
|
|
|
2182
2189
|
assert(suggestions.length === 1, 'Should find 1 suggestion');
|
|
@@ -2254,7 +2261,7 @@ function test335_pluginRepoServerClientIntegration() {
|
|
|
2254
2261
|
assert(plugin.hasBinary(), 'Plugin should have binary');
|
|
2255
2262
|
|
|
2256
2263
|
// Client can get suggestions
|
|
2257
|
-
const capUrn = 'cap:in="media:pdf
|
|
2264
|
+
const capUrn = 'cap:in="media:pdf";op=disbind;out="media:disbound-page;textable;form=list"';
|
|
2258
2265
|
const suggestions = client.getSuggestionsForCap(capUrn);
|
|
2259
2266
|
assert(suggestions.length === 1, 'Should get suggestions');
|
|
2260
2267
|
assert(suggestions[0].pluginId === 'pdfcartridge', 'Should suggest correct plugin');
|
|
@@ -2273,7 +2280,7 @@ function test335_pluginRepoServerClientIntegration() {
|
|
|
2273
2280
|
function test546_isImage() {
|
|
2274
2281
|
assert(MediaUrn.fromString(MEDIA_PNG).isImage(), 'MEDIA_PNG should be image');
|
|
2275
2282
|
assert(MediaUrn.fromString(MEDIA_IMAGE_THUMBNAIL).isImage(), 'MEDIA_IMAGE_THUMBNAIL should be image');
|
|
2276
|
-
assert(MediaUrn.fromString('media:image;jpg
|
|
2283
|
+
assert(MediaUrn.fromString('media:image;jpg').isImage(), 'media:image;jpg should be image');
|
|
2277
2284
|
// Non-image types
|
|
2278
2285
|
assert(!MediaUrn.fromString(MEDIA_PDF).isImage(), 'MEDIA_PDF should not be image');
|
|
2279
2286
|
assert(!MediaUrn.fromString(MEDIA_STRING).isImage(), 'MEDIA_STRING should not be image');
|
|
@@ -2285,7 +2292,7 @@ function test546_isImage() {
|
|
|
2285
2292
|
function test547_isAudio() {
|
|
2286
2293
|
assert(MediaUrn.fromString(MEDIA_AUDIO).isAudio(), 'MEDIA_AUDIO should be audio');
|
|
2287
2294
|
assert(MediaUrn.fromString(MEDIA_AUDIO_SPEECH).isAudio(), 'MEDIA_AUDIO_SPEECH should be audio');
|
|
2288
|
-
assert(MediaUrn.fromString('media:audio;mp3
|
|
2295
|
+
assert(MediaUrn.fromString('media:audio;mp3').isAudio(), 'media:audio;mp3 should be audio');
|
|
2289
2296
|
// Non-audio types
|
|
2290
2297
|
assert(!MediaUrn.fromString(MEDIA_VIDEO).isAudio(), 'MEDIA_VIDEO should not be audio');
|
|
2291
2298
|
assert(!MediaUrn.fromString(MEDIA_PNG).isAudio(), 'MEDIA_PNG should not be audio');
|
|
@@ -2295,7 +2302,7 @@ function test547_isAudio() {
|
|
|
2295
2302
|
// TEST548: isVideo returns true only when video marker tag is present
|
|
2296
2303
|
function test548_isVideo() {
|
|
2297
2304
|
assert(MediaUrn.fromString(MEDIA_VIDEO).isVideo(), 'MEDIA_VIDEO should be video');
|
|
2298
|
-
assert(MediaUrn.fromString('media:video;mp4
|
|
2305
|
+
assert(MediaUrn.fromString('media:video;mp4').isVideo(), 'media:video;mp4 should be video');
|
|
2299
2306
|
// Non-video types
|
|
2300
2307
|
assert(!MediaUrn.fromString(MEDIA_AUDIO).isVideo(), 'MEDIA_AUDIO should not be video');
|
|
2301
2308
|
assert(!MediaUrn.fromString(MEDIA_PNG).isVideo(), 'MEDIA_PNG should not be video');
|
|
@@ -2397,11 +2404,11 @@ function test558_predicateConstantConsistency() {
|
|
|
2397
2404
|
assert(!jsonUrn.isBinary(), 'MEDIA_JSON must not be binary');
|
|
2398
2405
|
assert(!jsonUrn.isList(), 'MEDIA_JSON must not be list');
|
|
2399
2406
|
|
|
2400
|
-
// MEDIA_VOID is void, NOT
|
|
2407
|
+
// MEDIA_VOID is void, NOT text/numeric — but IS binary (no textable tag)
|
|
2401
2408
|
const voidUrn = MediaUrn.fromString(MEDIA_VOID);
|
|
2402
2409
|
assert(voidUrn.isVoid(), 'MEDIA_VOID must be void');
|
|
2403
2410
|
assert(!voidUrn.isText(), 'MEDIA_VOID must not be text');
|
|
2404
|
-
assert(
|
|
2411
|
+
assert(voidUrn.isBinary(), 'MEDIA_VOID must be binary (no textable tag)');
|
|
2405
2412
|
assert(!voidUrn.isNumeric(), 'MEDIA_VOID must not be numeric');
|
|
2406
2413
|
}
|
|
2407
2414
|
|
|
@@ -2435,8 +2442,8 @@ function test559_withoutTag() {
|
|
|
2435
2442
|
function test560_withInOutSpec() {
|
|
2436
2443
|
const cap = CapUrn.fromString('cap:in="media:void";op=test;out="media:void"');
|
|
2437
2444
|
|
|
2438
|
-
const changedIn = cap.withInSpec('media:
|
|
2439
|
-
assertEqual(changedIn.getInSpec(), 'media:
|
|
2445
|
+
const changedIn = cap.withInSpec('media:');
|
|
2446
|
+
assertEqual(changedIn.getInSpec(), 'media:', 'withInSpec should change inSpec');
|
|
2440
2447
|
assertEqual(changedIn.getOutSpec(), 'media:void', 'withInSpec should preserve outSpec');
|
|
2441
2448
|
assertEqual(changedIn.getTag('op'), 'test', 'withInSpec should preserve tags');
|
|
2442
2449
|
|
|
@@ -2445,8 +2452,8 @@ function test560_withInOutSpec() {
|
|
|
2445
2452
|
assertEqual(changedOut.getOutSpec(), 'media:string', 'withOutSpec should change outSpec');
|
|
2446
2453
|
|
|
2447
2454
|
// Chain both
|
|
2448
|
-
const changedBoth = cap.withInSpec('media:pdf
|
|
2449
|
-
assertEqual(changedBoth.getInSpec(), 'media:pdf
|
|
2455
|
+
const changedBoth = cap.withInSpec('media:pdf').withOutSpec('media:txt;textable');
|
|
2456
|
+
assertEqual(changedBoth.getInSpec(), 'media:pdf', 'Chain should set inSpec');
|
|
2450
2457
|
assertEqual(changedBoth.getOutSpec(), 'media:txt;textable', 'Chain should set outSpec');
|
|
2451
2458
|
}
|
|
2452
2459
|
|
|
@@ -2501,10 +2508,10 @@ function test564_areCompatible() {
|
|
|
2501
2508
|
function test566_withTagIgnoresInOut() {
|
|
2502
2509
|
const cap = CapUrn.fromString('cap:in="media:void";op=test;out="media:void"');
|
|
2503
2510
|
// Attempting to set in/out via withTag is silently ignored
|
|
2504
|
-
const same = cap.withTag('in', 'media:
|
|
2511
|
+
const same = cap.withTag('in', 'media:');
|
|
2505
2512
|
assertEqual(same.getInSpec(), 'media:void', 'withTag must not change in_spec');
|
|
2506
2513
|
|
|
2507
|
-
const same2 = cap.withTag('out', 'media:
|
|
2514
|
+
const same2 = cap.withTag('out', 'media:');
|
|
2508
2515
|
assertEqual(same2.getOutSpec(), 'media:void', 'withTag must not change out_spec');
|
|
2509
2516
|
}
|
|
2510
2517
|
|
|
@@ -2527,10 +2534,10 @@ function test643_explicitAsteriskIsWildcard() {
|
|
|
2527
2534
|
assertEqual(cap.getOutSpec(), '*', 'out=* should be stored as wildcard');
|
|
2528
2535
|
}
|
|
2529
2536
|
|
|
2530
|
-
// TEST644: cap:in=media
|
|
2537
|
+
// TEST644: cap:in=media:;out=* has specific in, wildcard out
|
|
2531
2538
|
function test644_specificInWildcardOut() {
|
|
2532
|
-
const cap = CapUrn.fromString('cap:in=media
|
|
2533
|
-
assertEqual(cap.getInSpec(), 'media:
|
|
2539
|
+
const cap = CapUrn.fromString('cap:in=media:;out=*');
|
|
2540
|
+
assertEqual(cap.getInSpec(), 'media:', 'Should have specific in');
|
|
2534
2541
|
assertEqual(cap.getOutSpec(), '*', 'Should have wildcard out');
|
|
2535
2542
|
}
|
|
2536
2543
|
|
|
@@ -2547,7 +2554,7 @@ function test645_wildcardInSpecificOut() {
|
|
|
2547
2554
|
// TEST648: Wildcard in/out match specific caps
|
|
2548
2555
|
function test648_wildcardAcceptsSpecific() {
|
|
2549
2556
|
const wildcard = CapUrn.fromString('cap:in=*;out=*');
|
|
2550
|
-
const specific = CapUrn.fromString('cap:in="media:
|
|
2557
|
+
const specific = CapUrn.fromString('cap:in="media:";out="media:text"');
|
|
2551
2558
|
|
|
2552
2559
|
assert(wildcard.accepts(specific), 'Wildcard should accept specific');
|
|
2553
2560
|
assert(specific.conformsTo(wildcard), 'Specific should conform to wildcard');
|
|
@@ -2556,7 +2563,7 @@ function test648_wildcardAcceptsSpecific() {
|
|
|
2556
2563
|
// TEST649: Specificity - wildcard has 0, specific has tag count
|
|
2557
2564
|
function test649_specificityScoring() {
|
|
2558
2565
|
const wildcard = CapUrn.fromString('cap:in=*;out=*');
|
|
2559
|
-
const specific = CapUrn.fromString('cap:in="media:
|
|
2566
|
+
const specific = CapUrn.fromString('cap:in="media:";out="media:text"');
|
|
2560
2567
|
|
|
2561
2568
|
assertEqual(wildcard.specificity(), 0, 'Wildcard cap should have 0 specificity');
|
|
2562
2569
|
assert(specific.specificity() > 0, 'Specific cap should have non-zero specificity');
|
|
@@ -2576,7 +2583,7 @@ function test651_identityFormsEquivalent() {
|
|
|
2576
2583
|
for (let i = 1; i < forms.length; i++) {
|
|
2577
2584
|
const cap = CapUrn.fromString(forms[i]);
|
|
2578
2585
|
// Both should accept specific caps
|
|
2579
|
-
const specific = CapUrn.fromString('cap:in="media:
|
|
2586
|
+
const specific = CapUrn.fromString('cap:in="media:";out="media:text"');
|
|
2580
2587
|
assert(first.accepts(specific), `Form 0 should accept specific`);
|
|
2581
2588
|
assert(cap.accepts(specific), `Form ${i} should accept specific`);
|
|
2582
2589
|
}
|
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.28.4918"
|
|
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.
|
|
35
|
+
"version": "0.85.19364"
|
|
36
36
|
}
|