capdag 0.177.441 → 0.180.452

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/capdag.js +27 -17
  2. package/capdag.test.js +151 -1
  3. package/package.json +1 -1
package/capdag.js CHANGED
@@ -1118,7 +1118,7 @@ const MEDIA_COLLECTION_LIST = 'media:collection;list;record';
1118
1118
  // Media URN for adapter selection output - JSON record
1119
1119
  const MEDIA_ADAPTER_SELECTION = 'media:adapter-selection;json;record';
1120
1120
  // Fabric registry lookup wire types (consumed/produced by cap:lookup-cap;fabric
1121
- // and cap:lookup-media-spec;fabric, both implemented by netaccesscartridge).
1121
+ // and cap:lookup-media-spec;fabric, both implemented by fetchcartridge).
1122
1122
  const MEDIA_CAP_URN = 'media:cap-urn;textable';
1123
1123
  const MEDIA_MEDIA_URN = 'media:media-urn;textable';
1124
1124
  const MEDIA_CAP_DEFINITION = 'media:cap-definition;json;record;textable';
@@ -1136,7 +1136,7 @@ const CAP_IDENTITY = 'cap:in=media:;out=media:';
1136
1136
  // Cartridges that inspect file content override this with a handler that returns {"media_urns": [...]}.
1137
1137
  const CAP_ADAPTER_SELECTION = 'cap:in="media:";out="media:adapter-selection;json;record"';
1138
1138
 
1139
- // Fabric registry lookup caps. Implemented by netaccesscartridge.
1139
+ // Fabric registry lookup caps. Implemented by fetchcartridge.
1140
1140
  // CAP_LOOKUP_CAP_FABRIC resolves a canonical cap URN to its full flattened
1141
1141
  // cap definition; CAP_LOOKUP_MEDIA_SPEC_FABRIC does the same for media specs.
1142
1142
  const CAP_LOOKUP_CAP_FABRIC = 'cap:in="media:cap-urn;textable";fabric;lookup-cap;out="media:cap-definition;json;record;textable"';
@@ -2118,9 +2118,9 @@ class CapArg {
2118
2118
  this.required = required;
2119
2119
  this.is_sequence = options.is_sequence || false;
2120
2120
  this.sources = sources; // Array of ArgSource
2121
- this.arg_description = options.arg_description || null;
2121
+ this.arg_description = options.arg_description !== undefined ? options.arg_description : null;
2122
2122
  this.default_value = options.default_value !== undefined ? options.default_value : null;
2123
- this.metadata = options.metadata || null;
2123
+ this.metadata = options.metadata !== undefined ? options.metadata : null;
2124
2124
  }
2125
2125
 
2126
2126
  /**
@@ -2154,11 +2154,15 @@ class CapArg {
2154
2154
  sources: this.sources.map(s => s.toJSON())
2155
2155
  };
2156
2156
  if (this.is_sequence) result.is_sequence = true;
2157
- if (this.arg_description) result.arg_description = this.arg_description;
2157
+ if (this.arg_description !== null && this.arg_description !== undefined) {
2158
+ result.arg_description = this.arg_description;
2159
+ }
2158
2160
  if (this.default_value !== null && this.default_value !== undefined) {
2159
2161
  result.default_value = this.default_value;
2160
2162
  }
2161
- if (this.metadata) result.metadata = this.metadata;
2163
+ if (this.metadata !== null && this.metadata !== undefined) {
2164
+ result.metadata = this.metadata;
2165
+ }
2162
2166
  return result;
2163
2167
  }
2164
2168
 
@@ -2618,10 +2622,10 @@ class CapManifest {
2618
2622
  * @param {CapGroup[]} capGroups - Cap groups (all caps must be in a group)
2619
2623
  */
2620
2624
  constructor(name, version, channel, registryUrl, description, capGroups = []) {
2621
- if (!name || typeof name !== 'string') {
2625
+ if (typeof name !== 'string') {
2622
2626
  throw new Error('CapManifest name is required and must be a string');
2623
2627
  }
2624
- if (!version || typeof version !== 'string') {
2628
+ if (typeof version !== 'string') {
2625
2629
  throw new Error('CapManifest version is required and must be a string');
2626
2630
  }
2627
2631
  if (channel !== 'release' && channel !== 'nightly') {
@@ -2630,7 +2634,7 @@ class CapManifest {
2630
2634
  if (registryUrl !== null && registryUrl !== undefined && typeof registryUrl !== 'string') {
2631
2635
  throw new Error("CapManifest registry_url must be null (dev build) or a string");
2632
2636
  }
2633
- if (!description || typeof description !== 'string') {
2637
+ if (typeof description !== 'string') {
2634
2638
  throw new Error('CapManifest description is required and must be a string');
2635
2639
  }
2636
2640
 
@@ -2664,10 +2668,10 @@ class CapManifest {
2664
2668
  * @throws {Error} If required fields are missing or invalid
2665
2669
  */
2666
2670
  static fromJSON(json) {
2667
- if (!json.name) throw new Error('CapManifest missing required field: name');
2668
- if (!json.version) throw new Error('CapManifest missing required field: version');
2669
- if (!json.channel) throw new Error('CapManifest missing required field: channel');
2670
- if (!json.description) throw new Error('CapManifest missing required field: description');
2671
+ if (!Object.prototype.hasOwnProperty.call(json, 'name')) throw new Error('CapManifest missing required field: name');
2672
+ if (!Object.prototype.hasOwnProperty.call(json, 'version')) throw new Error('CapManifest missing required field: version');
2673
+ if (!Object.prototype.hasOwnProperty.call(json, 'channel')) throw new Error('CapManifest missing required field: channel');
2674
+ if (!Object.prototype.hasOwnProperty.call(json, 'description')) throw new Error('CapManifest missing required field: description');
2671
2675
  if (!Array.isArray(json.cap_groups)) throw new Error('CapManifest missing required field: cap_groups');
2672
2676
 
2673
2677
  // registry_url must be present as a key (may be null for dev builds)
@@ -2700,10 +2704,16 @@ class CapManifest {
2700
2704
  capGroups
2701
2705
  );
2702
2706
 
2703
- if (json.author && typeof json.author === 'string') {
2707
+ if (Object.prototype.hasOwnProperty.call(json, 'author')) {
2708
+ if (typeof json.author !== 'string') {
2709
+ throw new Error('CapManifest author must be a string when present');
2710
+ }
2704
2711
  manifest.author = json.author;
2705
2712
  }
2706
- if (json.page_url && typeof json.page_url === 'string') {
2713
+ if (Object.prototype.hasOwnProperty.call(json, 'page_url')) {
2714
+ if (typeof json.page_url !== 'string') {
2715
+ throw new Error('CapManifest page_url must be a string when present');
2716
+ }
2707
2717
  manifest.page_url = json.page_url;
2708
2718
  }
2709
2719
 
@@ -2723,8 +2733,8 @@ class CapManifest {
2723
2733
  description: this.description,
2724
2734
  cap_groups: this.cap_groups.map(g => g.toJSON())
2725
2735
  };
2726
- if (this.author) result.author = this.author;
2727
- if (this.page_url) result.page_url = this.page_url;
2736
+ if (this.author !== null && this.author !== undefined) result.author = this.author;
2737
+ if (this.page_url !== null && this.page_url !== undefined) result.page_url = this.page_url;
2728
2738
  return result;
2729
2739
  }
2730
2740
  }
package/capdag.test.js CHANGED
@@ -5,7 +5,7 @@
5
5
  const {
6
6
  CapUrn, CapKind, CapUrnBuilder, CapMatcher, CapUrnError, ErrorCodes,
7
7
  MediaUrn, MediaUrnError, MediaUrnErrorCodes,
8
- Cap, MediaSpec, MediaSpecError, MediaSpecErrorCodes,
8
+ Cap, CapGroup, CapManifest, MediaSpec, MediaSpecError, MediaSpecErrorCodes,
9
9
  resolveMediaUrn, buildExtensionIndex, mediaUrnsForExtension, getExtensionMappings,
10
10
  CartridgeInfo, CartridgeCapSummary, CartridgeSuggestion, CartridgeRepoClient, CartridgeRepoServer,
11
11
  CapFabEdge, CapFabStats, CapFab,
@@ -1209,6 +1209,152 @@ function test110_multipleExtensions() {
1209
1209
  assertEqual(resolved.extensions[1], 'jpeg', 'Second extension should be jpeg');
1210
1210
  }
1211
1211
 
1212
+ // TEST115: Test CapArg serialization and deserialization with multiple sources
1213
+ function test115_capArgSerialization() {
1214
+ const arg = new CapArg(
1215
+ MEDIA_STRING,
1216
+ true,
1217
+ [new ArgSource({ cli_flag: '--name' }), new ArgSource({ position: 0 })],
1218
+ {
1219
+ arg_description: 'The name argument',
1220
+ default_value: 400,
1221
+ metadata: { kind: 'example', flags: [true, false] }
1222
+ }
1223
+ );
1224
+
1225
+ const json = arg.toJSON();
1226
+ assertEqual(json.media_urn, MEDIA_STRING, 'media_urn must serialize');
1227
+ assertEqual(json.required, true, 'required must serialize');
1228
+ assertEqual(json.arg_description, 'The name argument', 'arg_description must serialize');
1229
+ assertEqual(json.default_value, 400, 'numeric default_value must serialize as number');
1230
+ assertEqual(JSON.stringify(json.metadata), JSON.stringify({ kind: 'example', flags: [true, false] }),
1231
+ 'metadata must serialize as arbitrary JSON');
1232
+
1233
+ const roundTripped = CapArg.fromJSON(JSON.parse(JSON.stringify(json)));
1234
+ assertEqual(roundTripped.media_urn, arg.media_urn, 'media_urn must round-trip');
1235
+ assertEqual(roundTripped.required, arg.required, 'required must round-trip');
1236
+ assertEqual(roundTripped.arg_description, arg.arg_description, 'arg_description must round-trip');
1237
+ assertEqual(roundTripped.default_value, 400, 'numeric default_value must round-trip');
1238
+ assertEqual(JSON.stringify(roundTripped.metadata), JSON.stringify({ kind: 'example', flags: [true, false] }),
1239
+ 'metadata must round-trip');
1240
+ assertEqual(roundTripped.sources.length, 2, 'sources length must round-trip');
1241
+ assertEqual(roundTripped.sources[0].cli_flag, '--name', 'cli_flag source must round-trip');
1242
+ assertEqual(roundTripped.sources[1].position, 0, 'position source must round-trip');
1243
+ }
1244
+
1245
+ // TEST116: Test CapArg constructor methods basic and with_description create args correctly
1246
+ function test116_capArgConstructors() {
1247
+ const basicArg = new CapArg(
1248
+ MEDIA_STRING,
1249
+ true,
1250
+ [new ArgSource({ cli_flag: '--name' })]
1251
+ );
1252
+ assertEqual(basicArg.media_urn, MEDIA_STRING, 'basic arg media_urn must match');
1253
+ assertEqual(basicArg.required, true, 'basic arg required must match');
1254
+ assertEqual(basicArg.sources.length, 1, 'basic arg must keep one source');
1255
+ assertEqual(basicArg.arg_description, null, 'basic arg arg_description must be absent');
1256
+ assertEqual(basicArg.default_value, null, 'basic arg default_value must be absent');
1257
+
1258
+ const describedArg = new CapArg(
1259
+ MEDIA_INTEGER,
1260
+ false,
1261
+ [new ArgSource({ position: 0 })],
1262
+ {
1263
+ arg_description: 'The count argument',
1264
+ default_value: 10
1265
+ }
1266
+ );
1267
+ assertEqual(describedArg.media_urn, MEDIA_INTEGER, 'described arg media_urn must match');
1268
+ assertEqual(describedArg.required, false, 'described arg required must match');
1269
+ assertEqual(describedArg.arg_description, 'The count argument', 'described arg description must match');
1270
+ assertEqual(describedArg.default_value, 10, 'described arg default_value must match');
1271
+ }
1272
+
1273
+ // TEST150: JSON roundtrip
1274
+ function test150_capManifestJsonSerialization() {
1275
+ const capUrn = CapUrn.fromString(testUrn('extract;target=metadata'));
1276
+ const cap = new Cap(capUrn, 'Extract Metadata', 'extract-metadata');
1277
+ cap.addArg(new CapArg('media:pdf', true, [new ArgSource({ stdin: 'media:pdf' })]));
1278
+ cap.addArg(new CapArg(
1279
+ 'media:chunk-size;textable;numeric',
1280
+ false,
1281
+ [new ArgSource({ cli_flag: '--chunk-size' })],
1282
+ {
1283
+ arg_description: 'Chunk size',
1284
+ default_value: 400,
1285
+ metadata: { unit: 'words' }
1286
+ }
1287
+ ));
1288
+ cap.addArg(new CapArg(
1289
+ 'media:timestamps;textable;bool',
1290
+ false,
1291
+ [new ArgSource({ cli_flag: '--timestamps' })],
1292
+ {
1293
+ arg_description: 'Include timestamps',
1294
+ default_value: false
1295
+ }
1296
+ ));
1297
+
1298
+ const manifest = new CapManifest(
1299
+ 'TestComponent',
1300
+ '0.1.0',
1301
+ 'release',
1302
+ null,
1303
+ 'A test component',
1304
+ [new CapGroup('default', [cap], [])]
1305
+ );
1306
+
1307
+ manifest.author = 'Test Author';
1308
+
1309
+ const json = manifest.toJSON();
1310
+ assertEqual(json.name, 'TestComponent', 'manifest name must serialize');
1311
+ assertEqual(json.author, 'Test Author', 'author must serialize');
1312
+ assert(Array.isArray(json.cap_groups), 'cap_groups must serialize');
1313
+ assertEqual(json.cap_groups.length, 1, 'cap_groups length must serialize');
1314
+ assertEqual(json.cap_groups[0].caps[0].args[1].default_value, 400, 'numeric default must serialize as number');
1315
+ assertEqual(json.cap_groups[0].caps[0].args[1].metadata.unit, 'words', 'metadata must serialize');
1316
+ assertEqual(json.cap_groups[0].caps[0].args[2].default_value, false, 'boolean default must serialize as boolean');
1317
+
1318
+ const roundTripped = CapManifest.fromJSON(JSON.parse(JSON.stringify(json)));
1319
+ const decodedCap = roundTripped.allCaps()[0];
1320
+ assertEqual(roundTripped.name, manifest.name, 'manifest name must round-trip');
1321
+ assertEqual(roundTripped.author, 'Test Author', 'author must round-trip');
1322
+ assertEqual(roundTripped.cap_groups.length, 1, 'cap_groups length must round-trip');
1323
+ assertEqual(decodedCap.args[1].default_value, 400, 'numeric default must round-trip');
1324
+ assertEqual(JSON.stringify(decodedCap.args[1].metadata), JSON.stringify({ unit: 'words' }),
1325
+ 'metadata must round-trip');
1326
+ assertEqual(decodedCap.args[2].default_value, false, 'boolean default must round-trip');
1327
+ }
1328
+
1329
+ // TEST597: CapArg::with_full_definition stores all fields including optional ones
1330
+ function test597_capArgWithFullDefinition() {
1331
+ const arg = new CapArg(
1332
+ MEDIA_STRING,
1333
+ true,
1334
+ [new ArgSource({ cli_flag: '--name' })],
1335
+ {
1336
+ arg_description: 'User name',
1337
+ default_value: { chunk_size: 400, timestamps: false },
1338
+ metadata: { hint: 'enter name' }
1339
+ }
1340
+ );
1341
+
1342
+ assertEqual(arg.media_urn, MEDIA_STRING, 'media_urn must match');
1343
+ assertEqual(arg.required, true, 'required must match');
1344
+ assertEqual(arg.arg_description, 'User name', 'arg_description must match');
1345
+ assertEqual(JSON.stringify(arg.default_value), JSON.stringify({ chunk_size: 400, timestamps: false }),
1346
+ 'object default_value must be preserved');
1347
+ assertEqual(JSON.stringify(arg.metadata), JSON.stringify({ hint: 'enter name' }),
1348
+ 'metadata must be preserved');
1349
+
1350
+ const roundTripped = CapArg.fromJSON(JSON.parse(JSON.stringify(arg.toJSON())));
1351
+ assertEqual(roundTripped.arg_description, 'User name', 'arg_description must round-trip');
1352
+ assertEqual(JSON.stringify(roundTripped.default_value), JSON.stringify({ chunk_size: 400, timestamps: false }),
1353
+ 'object default_value must round-trip');
1354
+ assertEqual(JSON.stringify(roundTripped.metadata), JSON.stringify({ hint: 'enter name' }),
1355
+ 'metadata must round-trip');
1356
+ }
1357
+
1212
1358
  // ============================================================================
1213
1359
  // cap_fab: browse-mode API used by cap-fab-renderer.js
1214
1360
  //
@@ -5785,6 +5931,10 @@ async function runTests() {
5785
5931
  runTest('TEST108: extensions_serialization', test108_extensionsSerialization);
5786
5932
  runTest('TEST109: extensions_with_metadata_and_validation', test109_extensionsWithMetadataAndValidation);
5787
5933
  runTest('TEST110: multiple_extensions', test110_multipleExtensions);
5934
+ runTest('TEST115: cap_arg_serialization', test115_capArgSerialization);
5935
+ runTest('TEST116: cap_arg_constructors', test116_capArgConstructors);
5936
+ runTest('TEST150: cap_manifest_json_serialization', test150_capManifestJsonSerialization);
5937
+ runTest('TEST597: cap_arg_with_full_definition', test597_capArgWithFullDefinition);
5788
5938
 
5789
5939
  // cap-fab-renderer.js uses CapFab in browse mode (static registry from
5790
5940
  // /api/capabilities). These tests guard the minimal API the renderer relies
package/package.json CHANGED
@@ -40,5 +40,5 @@
40
40
  "pretest": "npm run build:parser",
41
41
  "test": "node capdag.test.js"
42
42
  },
43
- "version": "0.177.441"
43
+ "version": "0.180.452"
44
44
  }