@x12i/memorix-format 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ # Changelog
2
+
3
+ ## 2.0.0
4
+
5
+ Breaking relative to 1.0.0:
6
+
7
+ - Depends on **`@x12i/memorix-base-format@^1.0.0`** for the fixed skeleton (`recordId`, `concept`, `data`, `_*`) and unnamed open buckets.
8
+ - Product rules (`associated*`, `analytics` / `enrichment` / `insights` meanings, SCOPE) live only in this package as profiles / layered checks.
9
+ - `classifyRootKey` comes from base — `associatedData` / `analytics` are simply **open**, not special base categories.
10
+ - CLI adds `--profile`, directory validation; prefers requirement IDs (`ASSOC-2`, `SCOPE-2`, …).
11
+ - Re-exports base APIs (`validateMemorixBaseRecord`, `parseContentTypeMeta`, …).
12
+
13
+ ## 1.0.0
14
+
15
+ Initial release (monolithic validator with forced product naming mixed into envelope checks).
package/README.md CHANGED
@@ -1,13 +1,18 @@
1
1
  # @x12i/memorix-format
2
2
 
3
- Memorix record format as an npm package: **types**, **validator**, **CLI**, and the **specification docs**.
3
+ Memorix **product** record format — profiles and conventions on top of
4
+ [`@x12i/memorix-base-format`](https://www.npmjs.com/package/@x12i/memorix-base-format).
4
5
 
5
- Canonical envelope: [docs/memorix-format.md](./docs/memorix-format.md).
6
+ | Package | Owns |
7
+ |---|---|
8
+ | `@x12i/memorix-base-format` | Fixed skeleton: `recordId`, `concept`, `data`, `_…`; unnamed open buckets; ContentTypeMeta |
9
+ | `@x12i/memorix-format` | Memorix open-bucket meanings, `associated*` rules, snapshot SCOPE, built-in profiles |
6
10
 
7
11
  ## Install
8
12
 
9
13
  ```bash
10
14
  npm install @x12i/memorix-format
15
+ # pulls @x12i/memorix-base-format
11
16
  ```
12
17
 
13
18
  ## SDK
@@ -15,76 +20,33 @@ npm install @x12i/memorix-format
15
20
  ```ts
16
21
  import {
17
22
  validateMemorixRecord,
18
- inspectMemorixRecord,
19
- buildConceptIdentifier,
20
- buildAssociatedPropertyName,
23
+ validateMemorixBaseRecord,
24
+ classifyRootKey,
25
+ snapshotsContentTypeMeta,
21
26
  } from "@x12i/memorix-format";
22
27
 
23
- const result = validateMemorixRecord(record, { contentType: "snapshots" });
24
- if (!result.ok) {
25
- console.error(result.issues);
26
- }
27
-
28
- const id = buildConceptIdentifier("ipAddress", "10.100.18.64");
29
- const prop = buildAssociatedPropertyName({
30
- form: "content",
28
+ // Product validation (base + ASSOC/SCOPE + optional profile)
29
+ validateMemorixRecord(record, {
30
+ profile: "snapshots",
31
31
  objectType: "vulnerabilities",
32
- contentType: "analysis",
32
+ contentType: "snapshots",
33
33
  });
34
- ```
35
-
36
- ### Exports
37
-
38
- | Import | Purpose |
39
- |---|---|
40
- | `@x12i/memorix-format` | Types, validate, inspect, concept + associated helpers |
41
- | `@x12i/memorix-format/validate` | Validator only |
42
- | `@x12i/memorix-format/types` | Type-only surface |
43
-
44
- ### Validation rules (clean envelope)
45
-
46
- - Required: `recordId`, `concept` (with `identifier` + `title`), `data` (object)
47
- - `concept.identifier` requires `kind`, kind-named value, and `identifierKey`
48
- - `analytics` / `enrichment` / `insights` must be objects when present
49
- - `associated*` properties must be arrays of objects (omit when empty)
50
- - Legacy roots (`identity`, root `lifecycle`, `sourceFacts`, …) are errors unless `--allow-legacy`
51
- - With `--content-type` other than `snapshots`, associated*/analytics/enrichment/insights are rejected
52
-
53
- ## CLI
54
34
 
55
- ```bash
56
- npx memorix-format validate ./record.json
57
- npx memorix-format validate ./records.ndjson --content-type=snapshots --json
58
- npx memorix-format inspect ./record.json
59
- npx memorix-format docs
60
- npx memorix-format docs memorix-format.md
35
+ // Base only (no product vocabulary)
36
+ validateMemorixBaseRecord(record);
37
+ classifyRootKey("associatedData"); // "open"
61
38
  ```
62
39
 
63
- Exit codes: `0` ok, `1` validation failed, `2` usage/parse error.
64
-
65
- ## Packaged documentation
66
-
67
- | Doc | Topic |
68
- |---|---|
69
- | [memorix-format.md](./docs/memorix-format.md) | Canonical clean record envelope |
70
- | [memorix-format-additions.md](./docs/memorix-format-additions.md) | `data` / `associatedData` / `analytics` / `insights` semantics |
71
- | [association-format.md](./docs/association-format.md) | `associated*` naming and materialization |
72
- | [memorix-workflow-format.md](./docs/memorix-workflow-format.md) | `memorix-workflow/1` |
73
- | [memorix-format-for-dummies-association-corrections.md](./docs/memorix-format-for-dummies-association-corrections.md) | Association corrections |
74
-
75
- ## Develop
40
+ ## CLI
76
41
 
77
42
  ```bash
78
- cd memorix-format
79
- npm install
80
- npm test
81
- npm run build
43
+ npx memorix-format validate ./record.json --profile=snapshots --object-type=assets
44
+ npx memorix-format validate ./dir/ --content-type=events
45
+ npx memorix-format profiles
46
+ npx memorix-format docs memorix-format-requirements.md
82
47
  ```
83
48
 
84
- ## Publish
49
+ ## Migration from 1.0.0
85
50
 
86
- ```bash
87
- cd memorix-format
88
- npm run build && npm test
89
- npm publish --access public
90
- ```
51
+ See [CHANGELOG.md](./CHANGELOG.md). Envelope validation moved to base-format;
52
+ this package no longer pretends product bucket names are part of the base law.
@@ -1,3 +1,28 @@
1
+ /**
2
+ * Memorix product open-bucket helpers.
3
+ * Base envelope rules live in @x12i/memorix-base-format — do not reimplement them here.
4
+ */
5
+ export declare const LEGACY_ASSOCIATED_NAMES: Set<string>;
6
+ export declare const KNOWN_OPEN_BUCKETS: {
7
+ analytics: {
8
+ kind: "rollup";
9
+ shape: "object";
10
+ };
11
+ enrichment: {
12
+ kind: "sourced";
13
+ shape: "object";
14
+ };
15
+ insights: {
16
+ kind: "interpretive";
17
+ shape: "object";
18
+ };
19
+ };
20
+ export declare const DEFAULT_TOP_ASSOCIATED_ITEMS = 5;
21
+ export declare function isAssociatedPropertyName(name: string): boolean;
22
+ export declare function isLegacyAssociatedPropertyName(name: string): boolean;
23
+ export declare function isEnrichmentAssociatedPropertyName(name: string): boolean;
24
+ export declare function isTopAssociatedPropertyName(name: string): boolean;
25
+ export declare function pascalCaseObjectType(value: string): string;
1
26
  export type AssociatedPropertyNameInput = {
2
27
  form: "content";
3
28
  objectType: string;
@@ -21,18 +46,6 @@ export type AssociatedPropertyNameInput = {
21
46
  objectType: string;
22
47
  top?: boolean;
23
48
  };
24
- /** True when a root key is an associated* property. */
25
- export declare function isAssociatedPropertyName(name: string): boolean;
26
- /** True when the property name follows the enrichment association convention. */
27
- export declare function isEnrichmentAssociatedPropertyName(name: string): boolean;
28
- /** True when the property name follows the ranked top association convention. */
29
- export declare function isTopAssociatedPropertyName(name: string): boolean;
30
- /** True for known legacy short associated names. */
31
- export declare function isLegacyAssociatedPropertyName(name: string): boolean;
32
- /** Build canonical v2 associated property name. */
33
49
  export declare function buildAssociatedPropertyName(input: AssociatedPropertyNameInput): string;
34
- /** Validate v2 associated property name conventions (throws on hard errors). */
35
- export declare function assertAssociatedPropertyName(name: string): void;
36
- /** List associated* keys present on a record root. */
37
50
  export declare function listAssociatedProperties(record: Record<string, unknown>): string[];
38
51
  //# sourceMappingURL=associated.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"associated.d.ts","sourceRoot":"","sources":["../src/associated.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,2BAA2B,GACnC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,GAC3E;IAAE,IAAI,EAAE,uBAAuB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,GAC1F;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,GACzD;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,GACvD;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAE7D,uDAAuD;AACvD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED,iFAAiF;AACjF,wBAAgB,kCAAkC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExE;AAED,iFAAiF;AACjF,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEjE;AAED,oDAAoD;AACpD,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpE;AAED,mDAAmD;AACnD,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,2BAA2B,GAAG,MAAM,CAuBtF;AAED,gFAAgF;AAChF,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAO/D;AAED,sDAAsD;AACtD,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,EAAE,CAIlF"}
1
+ {"version":3,"file":"associated.d.ts","sourceRoot":"","sources":["../src/associated.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,eAAO,MAAM,uBAAuB,aAQlC,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;CAI9B,CAAC;AAEF,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAE9C,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpE;AAED,wBAAgB,kCAAkC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExE;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEjE;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAM1D;AAED,MAAM,MAAM,2BAA2B,GACnC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,GAC3E;IAAE,IAAI,EAAE,uBAAuB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,GAC1F;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,GACzD;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,GACvD;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAE7D,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,2BAA2B,GAAG,MAAM,CAmBtF;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,EAAE,CAElF"}
@@ -1,34 +1,49 @@
1
- import { LEGACY_ASSOCIATED_NAMES } from "./constants.js";
2
- import { pascalCaseObjectType } from "./utils.js";
3
- /** True when a root key is an associated* property. */
1
+ /**
2
+ * Memorix product open-bucket helpers.
3
+ * Base envelope rules live in @x12i/memorix-base-format — do not reimplement them here.
4
+ */
5
+ export const LEGACY_ASSOCIATED_NAMES = new Set([
6
+ "associatedData",
7
+ "associatedDiscovery",
8
+ "associatedAnalysis",
9
+ "associatedSiblings",
10
+ "associatedChildren",
11
+ "associatedAssets",
12
+ "associatedSubnets",
13
+ ]);
14
+ export const KNOWN_OPEN_BUCKETS = {
15
+ analytics: { kind: "rollup", shape: "object" },
16
+ enrichment: { kind: "sourced", shape: "object" },
17
+ insights: { kind: "interpretive", shape: "object" },
18
+ };
19
+ export const DEFAULT_TOP_ASSOCIATED_ITEMS = 5;
4
20
  export function isAssociatedPropertyName(name) {
5
21
  return name.startsWith("associated");
6
22
  }
7
- /** True when the property name follows the enrichment association convention. */
23
+ export function isLegacyAssociatedPropertyName(name) {
24
+ return LEGACY_ASSOCIATED_NAMES.has(name);
25
+ }
8
26
  export function isEnrichmentAssociatedPropertyName(name) {
9
27
  return /^associated(?:Top)?[A-Z][A-Za-z0-9]*Enrichment$/.test(name);
10
28
  }
11
- /** True when the property name follows the ranked top association convention. */
12
29
  export function isTopAssociatedPropertyName(name) {
13
30
  return name.startsWith("associatedTop");
14
31
  }
15
- /** True for known legacy short associated names. */
16
- export function isLegacyAssociatedPropertyName(name) {
17
- return LEGACY_ASSOCIATED_NAMES.has(name);
32
+ export function pascalCaseObjectType(value) {
33
+ return value
34
+ .split(/[-_]/)
35
+ .filter(Boolean)
36
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
37
+ .join("");
18
38
  }
19
- /** Build canonical v2 associated property name. */
20
39
  export function buildAssociatedPropertyName(input) {
21
40
  const objectSegment = pascalCaseObjectType(input.objectType);
22
41
  const topPrefix = input.top ? "Top" : "";
23
42
  switch (input.form) {
24
- case "content": {
25
- const contentSegment = pascalCaseObjectType(input.contentType);
26
- return `associated${topPrefix}${objectSegment}${contentSegment}`;
27
- }
28
- case "snapshotsRootProperty": {
29
- const rootSegment = pascalCaseObjectType(input.rootProperty);
30
- return `associated${topPrefix}${objectSegment}Snapshots${rootSegment}`;
31
- }
43
+ case "content":
44
+ return `associated${topPrefix}${objectSegment}${pascalCaseObjectType(input.contentType)}`;
45
+ case "snapshotsRootProperty":
46
+ return `associated${topPrefix}${objectSegment}Snapshots${pascalCaseObjectType(input.rootProperty)}`;
32
47
  case "enrichment":
33
48
  return `associated${topPrefix}${objectSegment}Enrichment`;
34
49
  case "concepts":
@@ -36,24 +51,12 @@ export function buildAssociatedPropertyName(input) {
36
51
  case "recordIds":
37
52
  return `associated${topPrefix}${objectSegment}RecordIds`;
38
53
  default: {
39
- const _exhaustive = input;
40
- throw new Error(`Unsupported associated property name input: ${JSON.stringify(_exhaustive)}`);
54
+ const _e = input;
55
+ throw new Error(`Unsupported input: ${JSON.stringify(_e)}`);
41
56
  }
42
57
  }
43
58
  }
44
- /** Validate v2 associated property name conventions (throws on hard errors). */
45
- export function assertAssociatedPropertyName(name) {
46
- if (!name.startsWith("associated")) {
47
- throw new Error(`Associated property must start with "associated": ${name}`);
48
- }
49
- if (name.includes("RecordsIds")) {
50
- throw new Error(`Use "RecordIds" not "RecordsIds": ${name}`);
51
- }
52
- }
53
- /** List associated* keys present on a record root. */
54
59
  export function listAssociatedProperties(record) {
55
- return Object.keys(record)
56
- .filter(isAssociatedPropertyName)
57
- .sort();
60
+ return Object.keys(record).filter(isAssociatedPropertyName).sort();
58
61
  }
59
62
  //# sourceMappingURL=associated.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"associated.js","sourceRoot":"","sources":["../src/associated.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AASlD,uDAAuD;AACvD,MAAM,UAAU,wBAAwB,CAAC,IAAY;IACnD,OAAO,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AACvC,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,kCAAkC,CAAC,IAAY;IAC7D,OAAO,iDAAiD,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACtE,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,2BAA2B,CAAC,IAAY;IACtD,OAAO,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;AAC1C,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,8BAA8B,CAAC,IAAY;IACzD,OAAO,uBAAuB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAC3C,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,2BAA2B,CAAC,KAAkC;IAC5E,MAAM,aAAa,GAAG,oBAAoB,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAC7D,MAAM,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IACzC,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,SAAS,CAAC,CAAC,CAAC;YACf,MAAM,cAAc,GAAG,oBAAoB,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;YAC/D,OAAO,aAAa,SAAS,GAAG,aAAa,GAAG,cAAc,EAAE,CAAC;QACnE,CAAC;QACD,KAAK,uBAAuB,CAAC,CAAC,CAAC;YAC7B,MAAM,WAAW,GAAG,oBAAoB,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YAC7D,OAAO,aAAa,SAAS,GAAG,aAAa,YAAY,WAAW,EAAE,CAAC;QACzE,CAAC;QACD,KAAK,YAAY;YACf,OAAO,aAAa,SAAS,GAAG,aAAa,YAAY,CAAC;QAC5D,KAAK,UAAU;YACb,OAAO,aAAa,SAAS,GAAG,aAAa,UAAU,CAAC;QAC1D,KAAK,WAAW;YACd,OAAO,aAAa,SAAS,GAAG,aAAa,WAAW,CAAC;QAC3D,OAAO,CAAC,CAAC,CAAC;YACR,MAAM,WAAW,GAAU,KAAK,CAAC;YACjC,MAAM,IAAI,KAAK,CAAC,+CAA+C,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;QAChG,CAAC;IACH,CAAC;AACH,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,4BAA4B,CAAC,IAAY;IACvD,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,qDAAqD,IAAI,EAAE,CAAC,CAAC;IAC/E,CAAC;IACD,IAAI,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CAAC,qCAAqC,IAAI,EAAE,CAAC,CAAC;IAC/D,CAAC;AACH,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,wBAAwB,CAAC,MAA+B;IACtE,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;SACvB,MAAM,CAAC,wBAAwB,CAAC;SAChC,IAAI,EAAE,CAAC;AACZ,CAAC"}
1
+ {"version":3,"file":"associated.js","sourceRoot":"","sources":["../src/associated.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,GAAG,CAAC;IAC7C,gBAAgB;IAChB,qBAAqB;IACrB,oBAAoB;IACpB,oBAAoB;IACpB,oBAAoB;IACpB,kBAAkB;IAClB,mBAAmB;CACpB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,SAAS,EAAE,EAAE,IAAI,EAAE,QAAiB,EAAE,KAAK,EAAE,QAAiB,EAAE;IAChE,UAAU,EAAE,EAAE,IAAI,EAAE,SAAkB,EAAE,KAAK,EAAE,QAAiB,EAAE;IAClE,QAAQ,EAAE,EAAE,IAAI,EAAE,cAAuB,EAAE,KAAK,EAAE,QAAiB,EAAE;CACtE,CAAC;AAEF,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,CAAC;AAE9C,MAAM,UAAU,wBAAwB,CAAC,IAAY;IACnD,OAAO,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,8BAA8B,CAAC,IAAY;IACzD,OAAO,uBAAuB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,kCAAkC,CAAC,IAAY;IAC7D,OAAO,iDAAiD,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,2BAA2B,CAAC,IAAY;IACtD,OAAO,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,OAAO,KAAK;SACT,KAAK,CAAC,MAAM,CAAC;SACb,MAAM,CAAC,OAAO,CAAC;SACf,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;SAC3D,IAAI,CAAC,EAAE,CAAC,CAAC;AACd,CAAC;AASD,MAAM,UAAU,2BAA2B,CAAC,KAAkC;IAC5E,MAAM,aAAa,GAAG,oBAAoB,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAC7D,MAAM,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IACzC,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,SAAS;YACZ,OAAO,aAAa,SAAS,GAAG,aAAa,GAAG,oBAAoB,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC;QAC5F,KAAK,uBAAuB;YAC1B,OAAO,aAAa,SAAS,GAAG,aAAa,YAAY,oBAAoB,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC;QACtG,KAAK,YAAY;YACf,OAAO,aAAa,SAAS,GAAG,aAAa,YAAY,CAAC;QAC5D,KAAK,UAAU;YACb,OAAO,aAAa,SAAS,GAAG,aAAa,UAAU,CAAC;QAC1D,KAAK,WAAW;YACd,OAAO,aAAa,SAAS,GAAG,aAAa,WAAW,CAAC;QAC3D,OAAO,CAAC,CAAC,CAAC;YACR,MAAM,EAAE,GAAU,KAAK,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,sBAAsB,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,MAA+B;IACtE,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,wBAAwB,CAAC,CAAC,IAAI,EAAE,CAAC;AACrE,CAAC"}
package/dist/cli.js CHANGED
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { readFileSync, existsSync, readdirSync } from "node:fs";
2
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
3
3
  import { dirname, join, resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { createRequire } from "node:module";
6
- import { inspectMemorixRecord } from "./inspect.js";
7
- import { parseRecordsJson, validateMemorixRecord } from "./validate.js";
6
+ import { inspectMemorixBaseRecord, parseContentTypeMeta, parseRecordsJson, } from "@x12i/memorix-base-format";
7
+ import { BUILTIN_PROFILES } from "./profiles.js";
8
+ import { validateMemorixRecord } from "./validate.js";
8
9
  const __dirname = dirname(fileURLToPath(import.meta.url));
9
10
  const require = createRequire(import.meta.url);
10
11
  function packageVersion() {
@@ -17,10 +18,7 @@ function packageVersion() {
17
18
  }
18
19
  }
19
20
  function docsDir() {
20
- const candidates = [
21
- join(__dirname, "..", "docs"),
22
- join(process.cwd(), "docs"),
23
- ];
21
+ const candidates = [join(__dirname, "..", "docs"), join(process.cwd(), "docs")];
24
22
  for (const dir of candidates) {
25
23
  if (existsSync(dir))
26
24
  return dir;
@@ -30,26 +28,24 @@ function docsDir() {
30
28
  function printHelp() {
31
29
  console.log(`memorix-format <command> [options]
32
30
 
33
- Memorix record format SDK + CLI (validate, inspect, docs).
31
+ Memorix product format (layers on @x12i/memorix-base-format).
34
32
 
35
33
  Commands:
36
- validate [file|-] Validate JSON / JSON array / NDJSON records
37
- inspect [file|-] Summarize buckets and associated properties
38
- docs [name] List packaged specs, or print one by basename
39
- version Print package version
40
- help Show this help
34
+ validate [path|-] Validate with base + Memorix profile rules
35
+ inspect [path|-] Summarize root categories (via base)
36
+ docs [name] List or print packaged docs
37
+ profiles List built-in Memorix profiles
38
+ version
39
+ help
41
40
 
42
- Validate / inspect options:
43
- --content-type=<ct> Hint content type (enforces snapshot-only buckets)
44
- --allow-legacy Warn on legacy 2.0 roots instead of erroring
45
- --strict Fail on warnings as well as errors
46
- --json Machine-readable JSON output
47
-
48
- Examples:
49
- memorix-format validate ./record.json
50
- memorix-format validate - --content-type=snapshots < records.ndjson
51
- memorix-format inspect ./record.json --json
52
- memorix-format docs memorix-format.md
41
+ Options:
42
+ --profile=<name> Built-in profile: ${BUILTIN_PROFILES.join(" | ")}
43
+ --object-type=<name> Object type for profile resolution
44
+ --content-type=<ct> SCOPE hint (non-snapshots reject known buckets / associated*)
45
+ --meta=<file> Custom ContentTypeMeta JSON (overrides --profile)
46
+ --allow-legacy Warn on retired roots
47
+ --strict
48
+ --json
53
49
  `);
54
50
  }
55
51
  function readFlag(args, name) {
@@ -60,15 +56,58 @@ function readFlag(args, name) {
60
56
  function hasFlag(args, name) {
61
57
  return args.includes(`--${name}`);
62
58
  }
63
- function readInput(pathArg) {
64
- const path = pathArg && pathArg !== "-" ? resolve(pathArg) : null;
65
- if (path) {
66
- return readFileSync(path, "utf8");
67
- }
68
- return readFileSync(0, "utf8");
59
+ function loadMeta(args) {
60
+ const path = readFlag(args, "meta");
61
+ if (!path)
62
+ return undefined;
63
+ const parsed = parseContentTypeMeta(JSON.parse(readFileSync(resolve(path), "utf8")));
64
+ if (!parsed.ok || !parsed.meta) {
65
+ throw new Error(`Invalid --meta: ${parsed.issues.map((i) => i.message).join("; ")}`);
66
+ }
67
+ return parsed.meta;
69
68
  }
70
- function buildValidateOptions(args) {
69
+ function collectInputFiles(pathArg) {
70
+ if (!pathArg || pathArg === "-")
71
+ return ["-"];
72
+ const abs = resolve(pathArg);
73
+ if (!existsSync(abs))
74
+ throw new Error(`Path not found: ${abs}`);
75
+ const st = statSync(abs);
76
+ if (st.isFile())
77
+ return [abs];
78
+ if (!st.isDirectory())
79
+ throw new Error(`Not a file or directory: ${abs}`);
80
+ const out = [];
81
+ const walk = (dir) => {
82
+ for (const name of readdirSync(dir)) {
83
+ const p = join(dir, name);
84
+ const s = statSync(p);
85
+ if (s.isDirectory())
86
+ walk(p);
87
+ else if (name.endsWith(".json") || name.endsWith(".ndjson"))
88
+ out.push(p);
89
+ }
90
+ };
91
+ walk(abs);
92
+ return out.sort();
93
+ }
94
+ function readFileOrStdin(path) {
95
+ if (path === "-")
96
+ return readFileSync(0, "utf8");
97
+ return readFileSync(path, "utf8");
98
+ }
99
+ function buildOptions(args) {
100
+ const profileRaw = readFlag(args, "profile");
101
+ const profile = profileRaw && BUILTIN_PROFILES.includes(profileRaw)
102
+ ? profileRaw
103
+ : undefined;
104
+ if (profileRaw && !profile) {
105
+ throw new Error(`Unknown --profile=${profileRaw}; use ${BUILTIN_PROFILES.join("|")}`);
106
+ }
71
107
  return {
108
+ meta: loadMeta(args),
109
+ profile,
110
+ objectType: readFlag(args, "object-type"),
72
111
  contentType: readFlag(args, "content-type"),
73
112
  allowLegacyEnvelope: hasFlag(args, "allow-legacy"),
74
113
  strict: hasFlag(args, "strict"),
@@ -76,76 +115,87 @@ function buildValidateOptions(args) {
76
115
  }
77
116
  function runValidate(args) {
78
117
  const positional = args.filter((a) => !a.startsWith("--"));
79
- const file = positional[0];
80
- const options = buildValidateOptions(args);
81
118
  const asJson = hasFlag(args, "json");
82
- let records;
119
+ let options;
83
120
  try {
84
- records = parseRecordsJson(readInput(file));
121
+ options = buildOptions(args);
85
122
  }
86
123
  catch (err) {
87
- const message = err instanceof Error ? err.message : String(err);
88
- console.error(`Failed to parse input: ${message}`);
124
+ console.error(err instanceof Error ? err.message : String(err));
89
125
  return 2;
90
126
  }
91
- if (records.length === 0) {
92
- console.error("No records found in input");
127
+ let files;
128
+ try {
129
+ files = collectInputFiles(positional[0]);
130
+ }
131
+ catch (err) {
132
+ console.error(err instanceof Error ? err.message : String(err));
93
133
  return 2;
94
134
  }
95
- const results = records.map((r, i) => ({
96
- index: i,
97
- ...validateMemorixRecord(r, options),
98
- }));
99
- const ok = results.every((r) => r.ok);
135
+ if (files.length === 0) {
136
+ console.error("No .json / .ndjson files found");
137
+ return 2;
138
+ }
139
+ const rows = [];
140
+ for (const file of files) {
141
+ let records;
142
+ try {
143
+ records = parseRecordsJson(readFileOrStdin(file));
144
+ }
145
+ catch (err) {
146
+ console.error(`Failed to parse ${file}: ${err instanceof Error ? err.message : String(err)}`);
147
+ return 2;
148
+ }
149
+ for (let i = 0; i < records.length; i++) {
150
+ const r = validateMemorixRecord(records[i], options);
151
+ rows.push({ file, index: i, ok: r.ok, recordId: r.recordId, issues: r.issues });
152
+ }
153
+ }
154
+ if (rows.length === 0) {
155
+ console.error("No records found");
156
+ return 2;
157
+ }
158
+ const ok = rows.every((r) => r.ok);
100
159
  if (asJson) {
101
- console.log(JSON.stringify({ ok, count: results.length, results }, null, 2));
160
+ console.log(JSON.stringify({ ok, count: rows.length, results: rows }, null, 2));
102
161
  }
103
162
  else {
104
- for (const r of results) {
105
- const label = r.recordId ?? `#${r.index}`;
106
- if (r.ok) {
107
- console.log(`OK ${label}`);
108
- }
109
- else {
110
- console.log(`FAIL ${label}`);
111
- }
163
+ for (const r of rows) {
164
+ console.log(`${r.ok ? "OK" : "FAIL"} ${r.file}#${r.index} ${r.recordId ?? ""}`.trimEnd());
112
165
  for (const issue of r.issues) {
113
166
  console.log(` [${issue.severity}] ${issue.path}: ${issue.message} (${issue.code})`);
114
167
  }
115
168
  }
116
- console.log(`${ok ? "Passed" : "Failed"}: ${results.filter((r) => r.ok).length}/${results.length}`);
169
+ console.log(`${ok ? "Passed" : "Failed"}: ${rows.filter((r) => r.ok).length}/${rows.length}`);
117
170
  }
118
171
  return ok ? 0 : 1;
119
172
  }
120
173
  function runInspect(args) {
121
174
  const positional = args.filter((a) => !a.startsWith("--"));
122
- const file = positional[0];
123
175
  const asJson = hasFlag(args, "json");
124
- let records;
176
+ let files;
125
177
  try {
126
- records = parseRecordsJson(readInput(file));
178
+ files = collectInputFiles(positional[0]);
127
179
  }
128
180
  catch (err) {
129
- const message = err instanceof Error ? err.message : String(err);
130
- console.error(`Failed to parse input: ${message}`);
181
+ console.error(err instanceof Error ? err.message : String(err));
131
182
  return 2;
132
183
  }
133
- const inspections = records.map((r, i) => ({
134
- index: i,
135
- ...inspectMemorixRecord(r),
136
- }));
184
+ const inspections = [];
185
+ for (const file of files) {
186
+ const records = parseRecordsJson(readFileOrStdin(file));
187
+ for (let i = 0; i < records.length; i++) {
188
+ inspections.push({ file, index: i, ...inspectMemorixBaseRecord(records[i]) });
189
+ }
190
+ }
137
191
  if (asJson) {
138
192
  console.log(JSON.stringify({ count: inspections.length, records: inspections }, null, 2));
139
- return 0;
140
193
  }
141
- for (const row of inspections) {
142
- console.log(`#${row.index} recordId=${row.recordId ?? "(missing)"} concept=${row.conceptKind ?? "-"} title=${row.conceptTitle ?? "-"}`);
143
- console.log(` buckets: data=${row.buckets.data} analytics=${row.buckets.analytics} enrichment=${row.buckets.enrichment} insights=${row.buckets.insights} _system=${row.buckets.system}`);
144
- console.log(` associated: ${row.associatedProperties.length ? row.associatedProperties.join(", ") : "(none)"}`);
145
- if (row.legacyRoots.length)
146
- console.log(` legacy roots: ${row.legacyRoots.join(", ")}`);
147
- if (row.unknownRoots.length)
148
- console.log(` unknown roots: ${row.unknownRoots.join(", ")}`);
194
+ else {
195
+ for (const row of inspections) {
196
+ console.log(`#${row.file}:${row.index} recordId=${row.recordId ?? "(missing)"}`);
197
+ console.log(` open: ${row.categories.open.join(", ") || "(none)"}`);
198
+ }
149
199
  }
150
200
  return 0;
151
201
  }
@@ -153,11 +203,11 @@ function runDocs(args) {
153
203
  const dir = docsDir();
154
204
  const name = args.find((a) => !a.startsWith("--"));
155
205
  if (!existsSync(dir)) {
156
- console.error(`Docs directory not found: ${dir}`);
206
+ console.error(`Docs not found: ${dir}`);
157
207
  return 1;
158
208
  }
159
209
  if (!name) {
160
- console.log(`Packaged Memorix format docs (${dir}):`);
210
+ console.log(`Packaged memorix-format docs (${dir}):`);
161
211
  for (const file of readdirSync(dir).sort()) {
162
212
  if (file.endsWith(".md"))
163
213
  console.log(` ${file}`);
@@ -168,7 +218,6 @@ function runDocs(args) {
168
218
  const path = join(dir, base);
169
219
  if (!existsSync(path)) {
170
220
  console.error(`Doc not found: ${base}`);
171
- console.error(`Available: ${readdirSync(dir).filter((f) => f.endsWith(".md")).join(", ")}`);
172
221
  return 1;
173
222
  }
174
223
  process.stdout.write(readFileSync(path, "utf8"));
@@ -184,6 +233,10 @@ async function main(argv) {
184
233
  console.log(packageVersion());
185
234
  return 0;
186
235
  }
236
+ if (cmd === "profiles") {
237
+ console.log(BUILTIN_PROFILES.join("\n"));
238
+ return 0;
239
+ }
187
240
  if (cmd === "validate")
188
241
  return runValidate(args);
189
242
  if (cmd === "inspect")
@@ -194,6 +247,5 @@ async function main(argv) {
194
247
  printHelp();
195
248
  return 2;
196
249
  }
197
- const exitCode = await main(process.argv.slice(2));
198
- process.exit(exitCode);
250
+ process.exit(await main(process.argv.slice(2)));
199
251
  //# sourceMappingURL=cli.js.map