@shrkcrft/compress 0.1.0-alpha.16 → 0.1.0-alpha.17

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.
@@ -1 +1 @@
1
- {"version":3,"file":"compress-content.d.ts","sourceRoot":"","sources":["../src/compress-content.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAWrE;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,gBAAqB,GAAG,kBAAkB,CA4B7F"}
1
+ {"version":3,"file":"compress-content.d.ts","sourceRoot":"","sources":["../src/compress-content.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAYrE;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,gBAAqB,GAAG,kBAAkB,CAS7F"}
@@ -3,6 +3,7 @@ import { detectContentType } from "./content/detect-content-type.js";
3
3
  import { segmentContent, isRichSegmentType } from "./content/segment.js";
4
4
  import { ECompressionStrategy } from "./result/compression-strategy.js";
5
5
  import { measureSavings } from "./tokens/estimate-tokens.js";
6
+ import { passthroughResult } from "./text/finalize.js";
6
7
  import { compressJson } from "./json/compress-json.js";
7
8
  import { compressLog } from "./text/compress-log.js";
8
9
  import { compressSearch } from "./text/compress-search.js";
@@ -18,6 +19,16 @@ import { compressCode } from "./code/compress-code.js";
18
19
  * and options always yield the same output.
19
20
  */
20
21
  export function compressContent(text, opts = {}) {
22
+ const result = routeCompressContent(text, opts);
23
+ // `lossless` is a hard guard applied at the single entry point so it catches
24
+ // every lossy path (text elision, JSON row-sampling, mixed) uniformly: a
25
+ // result that drops information is replaced by the verbatim original.
26
+ if (opts.lossless && result.lossy) {
27
+ return passthroughResult(text, result.contentType, 'lossless requested — lossy reduction skipped');
28
+ }
29
+ return result;
30
+ }
31
+ function routeCompressContent(text, opts) {
21
32
  const type = opts.contentType ?? detectContentType(text);
22
33
  switch (type) {
23
34
  case EContentType.JsonArray:
@@ -14,6 +14,13 @@ export interface ICompressOptions {
14
14
  contentType?: EContentType;
15
15
  /** Soft cap on retained items/lines/matches/hunks (compressor-specific). */
16
16
  maxItems?: number;
17
+ /**
18
+ * Refuse any lossy reduction: a pass that would drop lines/rows/hunks
19
+ * returns the input untouched (passthrough) instead. Provably-lossless
20
+ * transforms (JSON columnar, dedup that round-trips) still apply. Lets a
21
+ * caller demand "shrink only if fully reconstructable from the output alone".
22
+ */
23
+ lossless?: boolean;
17
24
  /** Below this many lines a lossy text pass returns the input untouched. */
18
25
  minLines?: number;
19
26
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"compress-options.d.ts","sourceRoot":"","sources":["../../src/result/compress-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,oEAAoE;IACpE,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uDAAuD;IACvD,WAAW,CAAC,EAAE,YAAY,CAAC;IAC3B,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB"}
1
+ {"version":3,"file":"compress-options.d.ts","sourceRoot":"","sources":["../../src/result/compress-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,oEAAoE;IACpE,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uDAAuD;IACvD,WAAW,CAAC,EAAE,YAAY,CAAC;IAC3B,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB"}
@@ -1 +1 @@
1
- {"version":3,"file":"compact-object-array.d.ts","sourceRoot":"","sources":["../../src/table/compact-object-array.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAqC9D;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,GAAG,IAAI,CAwC1E"}
1
+ {"version":3,"file":"compact-object-array.d.ts","sourceRoot":"","sources":["../../src/table/compact-object-array.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAqC9D;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,GAAG,IAAI,CA2C1E"}
@@ -63,7 +63,10 @@ export function compactObjectArray(items) {
63
63
  const row = [];
64
64
  for (let c = 0; c < colNames.length; c += 1) {
65
65
  const key = colNames[c];
66
- const present = key in item && item[key] !== undefined;
66
+ // `key in item` walks the prototype chain, so a column named after an
67
+ // Object.prototype member (`toString`, `hasOwnProperty`, …) would read the
68
+ // inherited member as a cell value. Own-property check keeps it lossless.
69
+ const present = Object.prototype.hasOwnProperty.call(item, key) && item[key] !== undefined;
67
70
  if (!present) {
68
71
  absent.push([r, c]);
69
72
  row.push(null);
@@ -1 +1 @@
1
- {"version":3,"file":"object-map.d.ts","sourceRoot":"","sources":["../../src/table/object-map.ts"],"names":[],"mappings":"AAyBA,sDAAsD;AACtD,MAAM,WAAW,UAAU;IACzB,uDAAuD;IACvD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,qEAAqE;IACrE,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,gFAAgF;IAChF,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC;IAClB,yFAAyF;IACzF,MAAM,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACjC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,GAAG,IAAI,CAmClE;AAED,+DAA+D;AAC/D,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI;IAAE,KAAK,EAAE,UAAU,CAAA;CAAE,CAU1E;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAmB9E"}
1
+ {"version":3,"file":"object-map.d.ts","sourceRoot":"","sources":["../../src/table/object-map.ts"],"names":[],"mappings":"AAyBA,sDAAsD;AACtD,MAAM,WAAW,UAAU;IACzB,uDAAuD;IACvD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,qEAAqE;IACrE,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,gFAAgF;IAChF,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC;IAClB,yFAAyF;IACzF,MAAM,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACjC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,GAAG,IAAI,CAqClE;AAED,+DAA+D;AAC/D,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI;IAAE,KAAK,EAAE,UAAU,CAAA;CAAE,CAU1E;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAmB9E"}
@@ -46,7 +46,9 @@ export function compactObjectMap(value) {
46
46
  const row = [];
47
47
  for (let c = 0; c < cols.length; c += 1) {
48
48
  const key = cols[c];
49
- if (key in entry && entry[key] !== undefined) {
49
+ // Own-property check (not `key in entry`, which walks the prototype chain)
50
+ // so a column named after an Object.prototype member isn't read as a value.
51
+ if (Object.prototype.hasOwnProperty.call(entry, key) && entry[key] !== undefined) {
50
52
  row.push(entry[key]);
51
53
  }
52
54
  else {
@@ -1 +1 @@
1
- {"version":3,"file":"finalize.d.ts","sourceRoot":"","sources":["../../src/text/finalize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAC1E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAItE,0DAA0D;AAC1D,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,YAAY,EACzB,IAAI,SAAmC,GACtC,kBAAkB,CASpB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,YAAY,CAAC;IAC1B,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,IAAI,EAAE,gBAAgB,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,kBAAkB,CAgCrB"}
1
+ {"version":3,"file":"finalize.d.ts","sourceRoot":"","sources":["../../src/text/finalize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAC1E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAItE,0DAA0D;AAC1D,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,YAAY,EACzB,IAAI,SAAmC,GACtC,kBAAkB,CASpB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,YAAY,CAAC;IAC1B,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,IAAI,EAAE,gBAAgB,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,kBAAkB,CAsCrB"}
@@ -30,13 +30,19 @@ export function finalizeLossy(params) {
30
30
  let key;
31
31
  if (opts.store) {
32
32
  key = opts.store.put(original);
33
+ // Make inline `… N lines omitted` placeholders self-describing: a reader
34
+ // who only sees a clipped middle of the output otherwise can't tell the
35
+ // dropped detail is retrievable. Annotate each with the recovery key.
36
+ compressed = annotateElisionMarkers(body, key);
33
37
  // Skip the trailing marker when the body already references THIS key inline
34
38
  // (e.g. compressLog's per-drop elision hints) — no need to repeat it. A
35
39
  // different inline key (e.g. a diff's per-section keys) still gets the
36
40
  // whole-blob marker appended. The marker carries only the key: the human
37
41
  // `note` is shipped separately in the result, so repeating it on the wire
38
42
  // would just cost tokens.
39
- compressed = body.includes(`<<ccr:${key}`) ? body : `${body}\n${formatCcrMarker(key)}`;
43
+ compressed = compressed.includes(`<<ccr:${key}`)
44
+ ? compressed
45
+ : `${compressed}\n${formatCcrMarker(key)}`;
40
46
  }
41
47
  const savings = measureSavings(original, compressed, contentType);
42
48
  if (savings.after >= savings.before) {
@@ -52,3 +58,16 @@ export function finalizeLossy(params) {
52
58
  note,
53
59
  };
54
60
  }
61
+ /**
62
+ * Append the recovery key to each `… N line(s) omitted` placeholder produced by
63
+ * {@link elide} (used by the markdown/search/lines compressors), so a clipped
64
+ * view still advertises that the dropped detail is retrievable via `shrk expand`.
65
+ * Deterministic; leaves bodies without such markers (logs/diffs use their own
66
+ * keyed hints) untouched.
67
+ */
68
+ function annotateElisionMarkers(body, key) {
69
+ // Match ONLY a standalone-line `… N lines omitted` (what elide() emits, on its
70
+ // own line). The lookahead for end-of-line excludes compressLog's inline-keyed
71
+ // markers (`… N lines omitted → <<ccr:KEY>>`), which already carry the key.
72
+ return body.replace(/(… \d+ lines? omitted)(?=\n|$)/g, (marker) => `${marker} (shrk expand ${key})`);
73
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shrkcrft/compress",
3
- "version": "0.1.0-alpha.16",
3
+ "version": "0.1.0-alpha.17",
4
4
  "description": "SharkCraft deterministic context-compression engine: content routing, lossless columnar/table compaction, log/search/diff line reduction, and reversible Compress-Cache-Retrieve (CCR). No model inside — every transform is a pure function of its input.",
5
5
  "license": "MIT",
6
6
  "author": "SharkCraft contributors",
@@ -44,7 +44,7 @@
44
44
  "typecheck": "tsc --noEmit -p tsconfig.json"
45
45
  },
46
46
  "dependencies": {
47
- "@shrkcrft/core": "^0.1.0-alpha.16"
47
+ "@shrkcrft/core": "^0.1.0-alpha.17"
48
48
  },
49
49
  "publishConfig": {
50
50
  "access": "public"