@nesso-how/vocab-learning 0.1.0-alpha.41 → 0.2.0-beta.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/README.md CHANGED
@@ -37,8 +37,9 @@ const freshNode = defaultConceptReviewFields()
37
37
  import { deserialize, serialize, type NessoGraphDocument } from '@nesso-how/vocab-learning'
38
38
 
39
39
  const json = serialize({
40
+ vocabulary: { id: '@nesso-how/vocab-learning', version: '0.1.0' },
40
41
  name: 'My graph',
41
- concepts: [{ id: 'n1', label: 'Idea', x: 0, y: 0, data: { elaboration: { ... } } }],
42
+ concepts: [{ id: 'n1', label: 'Idea', x: 0, y: 0, data: { elaboration: { definition: '...' } } }],
42
43
  relations: [{ id: 'e1', source: 'n1', target: 'n2', type: 'causes' }],
43
44
  })
44
45
  const doc: NessoGraphDocument = deserialize(json)
@@ -48,6 +49,16 @@ const doc: NessoGraphDocument = deserialize(json)
48
49
 
49
50
  Relation types reference: [Relation types](https://nesso.how/docs/reference/relation-types/).
50
51
 
52
+ ## Persisted vocabulary compatibility
53
+
54
+ Learning-vocabulary documents must declare
55
+ `@nesso-how/vocab-learning` and a supported normative vocabulary version.
56
+ The first protected baseline is vocabulary `0.1.0` with definition-only
57
+ concept elaboration.
58
+
59
+ Removed alpha-only `examples`, `notes`, and image fields are not migrated or
60
+ discarded. Documents containing them are rejected.
61
+
51
62
  ## License
52
63
 
53
64
  MIT
@@ -1,5 +1,25 @@
1
1
  import type { NessoGraphDocument, NessoGraphDocumentInput } from './graphDocument.js';
2
- /** Serialize a Nesso Learning Vocabulary graph document to JSON. */
2
+ /**
3
+ * Strict post-migration document shape validation.
4
+ * Rejects: missing/foreign vocabulary, newer vocabulary versions, non-current
5
+ * vocabulary versions (older versions should be migrated before calling this),
6
+ * extra elaboration keys, and unknown relation types.
7
+ */
8
+ export declare function validateNessoDocument<M extends Record<string, unknown>>(doc: NessoGraphDocument<M>): NessoGraphDocument<M>;
9
+ /**
10
+ * Parse a Nesso graph document envelope without vocabulary-level validation.
11
+ * Validates JSON structure and schema format only; vocabulary identity, version,
12
+ * elaboration shape, and relation types are NOT checked. Callers must run
13
+ * {@link validateNessoDocument} (or equivalent checks) before the document
14
+ * reaches the store.
15
+ */
16
+ export declare function deserializeEnvelope<M extends Record<string, unknown> = Record<string, unknown>>(json: string): NessoGraphDocument<M>;
17
+ /** Serialize a Nesso Learning Vocabulary graph document to JSON. Always injects the current vocabulary metadata. */
3
18
  export declare function serialize<M extends Record<string, unknown>>(doc: NessoGraphDocumentInput<M>): string;
4
- /** Parse and validate a Nesso Learning Vocabulary graph document. */
19
+ /**
20
+ * Parse and strictly validate a Nesso Learning Vocabulary graph document.
21
+ * This is the convenience entry point that does envelope parsing + full
22
+ * validation. Use {@link deserializeEnvelope} + manual checks when vocabulary
23
+ * migration must run between parsing and validation.
24
+ */
5
25
  export declare function deserialize<M extends Record<string, unknown> = Record<string, unknown>>(json: string): NessoGraphDocument<M>;
package/dist/document.js CHANGED
@@ -4,40 +4,105 @@
4
4
  // and adds validation (elaboration shape, known `relation.type` ids).
5
5
  import { deserialize as deserializeSchema, serialize as serializeSchema } from '@nesso-how/schema';
6
6
  import { RELATION_TYPES } from './relationTypes.js';
7
+ import { VOCABULARY } from './vocabularyIdentity.js';
7
8
  const VALID_RELATION_TYPES = new Set(Object.keys(RELATION_TYPES));
8
9
  function asRecord(value) {
9
10
  return typeof value === 'object' && value !== null ? value : null;
10
11
  }
11
- function validateElaboration(value, path) {
12
- const elab = asRecord(value);
13
- if (!elab) {
14
- throw new Error(`Invalid Nesso graph document: ${path}.elaboration must be an object`);
12
+ /** Compare two semver strings. Returns positive if a > b, negative if a < b, 0 if equal. */
13
+ function compareVersions(a, b) {
14
+ const pa = a.split('.').map(Number);
15
+ const pb = b.split('.').map(Number);
16
+ for (let i = 0; i < 3; i++) {
17
+ const na = pa[i] ?? 0;
18
+ const nb = pb[i] ?? 0;
19
+ if (Number.isNaN(na) || Number.isNaN(nb))
20
+ return 0;
21
+ if (na > nb)
22
+ return 1;
23
+ if (na < nb)
24
+ return -1;
25
+ }
26
+ return 0;
27
+ }
28
+ /**
29
+ * Validate vocabulary identity: ensures a vocabulary is declared, its id matches
30
+ * the learning vocabulary, and its version is not newer than current (forward guard).
31
+ * Older versions pass through so the normalizer can migrate them.
32
+ */
33
+ function validateVocabularyIdentity(vocabulary) {
34
+ if (vocabulary === undefined) {
35
+ throw new Error('Graph document must declare a vocabulary');
15
36
  }
16
- if (typeof elab.definition !== 'string') {
17
- throw new Error(`Invalid Nesso graph document: ${path}.elaboration.definition must be a string`);
37
+ if (vocabulary.id !== VOCABULARY.id) {
38
+ throw new Error(`Unsupported graph vocabulary: ${vocabulary.id}`);
39
+ }
40
+ // Forward guard: reject documents from a newer vocabulary version.
41
+ if (compareVersions(vocabulary.version, VOCABULARY.version) > 0) {
42
+ throw new Error(`Unsupported learning vocabulary version: ${vocabulary.version}`);
18
43
  }
19
44
  }
20
- function validateNessoDocument(doc) {
21
- for (let i = 0; i < doc.concepts.length; i++) {
22
- const data = doc.concepts[i].data;
23
- if (data?.elaboration !== undefined) {
24
- validateElaboration(data.elaboration, `concepts[${i}].data`);
25
- }
45
+ function validateElaboration(value) {
46
+ const elab = asRecord(value);
47
+ if (!elab ||
48
+ typeof elab.definition !== 'string' ||
49
+ Object.keys(elab).length !== 1 ||
50
+ !Object.hasOwn(elab, 'definition')) {
51
+ throw new Error('Concept elaboration must contain only definition');
26
52
  }
53
+ }
54
+ function validateRelations(doc) {
27
55
  for (let i = 0; i < doc.relations.length; i++) {
28
56
  const rel = doc.relations[i];
29
57
  if (rel.type !== undefined && !VALID_RELATION_TYPES.has(rel.type)) {
30
58
  throw new Error(`Invalid Nesso graph document: unknown relation type "${rel.type}" at relations[${i}]`);
31
59
  }
32
60
  }
61
+ }
62
+ /**
63
+ * Strict post-migration document shape validation.
64
+ * Rejects: missing/foreign vocabulary, newer vocabulary versions, non-current
65
+ * vocabulary versions (older versions should be migrated before calling this),
66
+ * extra elaboration keys, and unknown relation types.
67
+ */
68
+ export function validateNessoDocument(doc) {
69
+ validateVocabularyIdentity(doc.vocabulary);
70
+ // After migration the version must match exactly.
71
+ if (doc.vocabulary.version !== VOCABULARY.version) {
72
+ throw new Error(`Unsupported learning vocabulary version: ${doc.vocabulary.version}`);
73
+ }
74
+ for (let i = 0; i < doc.concepts.length; i++) {
75
+ const data = doc.concepts[i].data;
76
+ if (data?.elaboration !== undefined) {
77
+ validateElaboration(data.elaboration);
78
+ }
79
+ }
80
+ validateRelations(doc);
33
81
  return doc;
34
82
  }
35
- /** Serialize a Nesso Learning Vocabulary graph document to JSON. */
83
+ /**
84
+ * Parse a Nesso graph document envelope without vocabulary-level validation.
85
+ * Validates JSON structure and schema format only; vocabulary identity, version,
86
+ * elaboration shape, and relation types are NOT checked. Callers must run
87
+ * {@link validateNessoDocument} (or equivalent checks) before the document
88
+ * reaches the store.
89
+ */
90
+ export function deserializeEnvelope(json) {
91
+ return deserializeSchema(json);
92
+ }
93
+ /** Serialize a Nesso Learning Vocabulary graph document to JSON. Always injects the current vocabulary metadata. */
36
94
  export function serialize(doc) {
37
- return serializeSchema(doc);
95
+ return serializeSchema({
96
+ ...doc,
97
+ vocabulary: VOCABULARY,
98
+ });
38
99
  }
39
- /** Parse and validate a Nesso Learning Vocabulary graph document. */
100
+ /**
101
+ * Parse and strictly validate a Nesso Learning Vocabulary graph document.
102
+ * This is the convenience entry point that does envelope parsing + full
103
+ * validation. Use {@link deserializeEnvelope} + manual checks when vocabulary
104
+ * migration must run between parsing and validation.
105
+ */
40
106
  export function deserialize(json) {
41
- const doc = deserializeSchema(json);
42
- return validateNessoDocument(doc);
107
+ return validateNessoDocument(deserializeEnvelope(json));
43
108
  }
package/dist/index.d.ts CHANGED
@@ -1,12 +1,6 @@
1
1
  export type { ConceptElaboration, NessoConceptData, NessoRelationData, NessoGraphDocument, NessoGraphDocumentInput, } from './graphDocument.js';
2
- /** OKG vocabulary identity version bumps only on normative vocabulary changes, not npm releases. */
3
- export declare const VOCABULARY: {
4
- readonly id: "@nesso-how/vocab-learning";
5
- readonly name: "Nesso Learning Vocabulary";
6
- readonly domain: "learning";
7
- readonly version: "0.1.0";
8
- };
2
+ export { VOCABULARY } from './vocabularyIdentity.js';
9
3
  export { defaultConceptReviewFields, type ConceptNodeData, type LearningNodeParams, } from './nodeParams.js';
10
4
  export { RELATION_CATEGORIES, RELATION_TYPES, RELATION_TYPE_VALUES, isPrimaryRelationType, type Cardinality, type GlyphKind, type Polarity, type RelationCategory, type RelationTypeDef, type RelationTypeName, type Transitivity, } from './relationTypes.js';
11
5
  export { CategoryPalette, GLYPH_PATHS, PALETTES } from './visual.js';
12
- export { deserialize, serialize } from './document.js';
6
+ export { deserialize, deserializeEnvelope, serialize, validateNessoDocument } from './document.js';
package/dist/index.js CHANGED
@@ -2,14 +2,8 @@
2
2
  //
3
3
  // Public entry point for `@nesso-how/vocab-learning`. Re-exports the split modules
4
4
  // below; import from here unless you need a specific submodule in-package.
5
- /** OKG vocabulary identity version bumps only on normative vocabulary changes, not npm releases. */
6
- export const VOCABULARY = {
7
- id: '@nesso-how/vocab-learning',
8
- name: 'Nesso Learning Vocabulary',
9
- domain: 'learning',
10
- version: '0.1.0',
11
- };
5
+ export { VOCABULARY } from './vocabularyIdentity.js';
12
6
  export { defaultConceptReviewFields, } from './nodeParams.js';
13
7
  export { RELATION_CATEGORIES, RELATION_TYPES, RELATION_TYPE_VALUES, isPrimaryRelationType, } from './relationTypes.js';
14
8
  export { GLYPH_PATHS, PALETTES } from './visual.js';
15
- export { deserialize, serialize } from './document.js';
9
+ export { deserialize, deserializeEnvelope, serialize, validateNessoDocument } from './document.js';
@@ -0,0 +1,7 @@
1
+ /** OKG vocabulary identity — version bumps only on normative vocabulary changes, not npm releases. */
2
+ export declare const VOCABULARY: {
3
+ readonly id: "@nesso-how/vocab-learning";
4
+ readonly name: "Nesso Learning Vocabulary";
5
+ readonly domain: "learning";
6
+ readonly version: "0.1.0";
7
+ };
@@ -0,0 +1,11 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // Vocabulary identity constant shared between document validation and the public API.
4
+ // Extracted to avoid a circular dependency: both index.ts and document.ts import it.
5
+ /** OKG vocabulary identity — version bumps only on normative vocabulary changes, not npm releases. */
6
+ export const VOCABULARY = {
7
+ id: '@nesso-how/vocab-learning',
8
+ name: 'Nesso Learning Vocabulary',
9
+ domain: 'learning',
10
+ version: '0.1.0',
11
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nesso-how/vocab-learning",
3
- "version": "0.1.0-alpha.41",
3
+ "version": "0.2.0-beta.0",
4
4
  "description": "Nesso Learning Vocabulary — graph vocabulary (relation types, node params, palettes) shared across app and MCP",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -22,7 +22,7 @@
22
22
  }
23
23
  },
24
24
  "dependencies": {
25
- "@nesso-how/schema": "0.1.0-alpha.41"
25
+ "@nesso-how/schema": "0.2.0-beta.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "typescript": "~5.8.3"