@nesso-how/formats 0.1.0-alpha.32 → 0.1.0-alpha.34

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/dist/index.d.ts CHANGED
@@ -1,25 +1,38 @@
1
1
  import type { Node, Edge } from '@xyflow/react';
2
- import type { ConceptNodeData, GraphDisplaySettings } from '@nesso-how/types';
3
- export interface NessoGraphFile {
2
+ import type { GraphDisplaySettings } from '@nesso-how/types';
3
+ /** Current on-disk graph file schema version. Bump only when the envelope shape changes. */
4
+ export declare const GRAPH_FORMAT_VERSION: 1;
5
+ export interface NessoGraphFile<D extends Record<string, unknown> = Record<string, unknown>> {
6
+ /** File format version — always {@link GRAPH_FORMAT_VERSION} for the current schema. */
7
+ version: typeof GRAPH_FORMAT_VERSION;
8
+ /**
9
+ * Vocabulary this graph was built with: its id (e.g. `@nesso-how/vocab-learning`)
10
+ * and normative version. Opaque to formats, which only round-trips the reference.
11
+ */
12
+ vocabulary?: {
13
+ id: string;
14
+ version: string;
15
+ };
4
16
  /** Internal graph id (desktop sync); omitted in manual exports. */
5
17
  id?: string;
6
18
  /** Last save time (Unix ms); desktop sync metadata. */
7
19
  updatedAt?: number;
8
20
  name: string;
9
- nodes: Node<ConceptNodeData>[];
21
+ nodes: Node<D>[];
10
22
  edges: Edge[];
11
23
  display?: Partial<GraphDisplaySettings>;
12
24
  }
13
- export declare function serializeGraph(file: NessoGraphFile): string;
25
+ /** Graph payload for {@link serializeGraph} format version is injected by the serializer. */
26
+ export type NessoGraphFileInput<D extends Record<string, unknown> = Record<string, unknown>> = Omit<NessoGraphFile<D>, 'version'>;
27
+ /** Serialize a graph to JSON with keys in a stable, human-readable order. */
28
+ export declare function serializeGraph<D extends Record<string, unknown>>(file: NessoGraphFileInput<D>): string;
14
29
  /**
15
30
  * Parse and structurally validate a graph file. Files are user-editable (and
16
31
  * importable from anywhere), so each element is checked before it can reach
17
32
  * the store: a node without a valid id/position crashes the canvas and would
18
- * then be re-persisted. Partial node data (e.g. hand-written files with only
19
- * `text`) is normalized with fresh review fields.
33
+ * then be re-persisted. Vocabulary-specific normalization (e.g. private params)
34
+ * is the caller's responsibility.
20
35
  */
21
- export declare function deserializeGraph(json: string): NessoGraphFile;
22
- /** Strip personal FSRS / review history for shareable graph export. Keeps text, elaboration, layout. */
23
- export declare function nodesForGraphShareExport(nodes: Node<ConceptNodeData>[]): Node<ConceptNodeData>[];
24
- /** Reset review fields on import so shared files never restore someone else's scheduling. */
25
- export declare function nodesFromGraphShareImport(nodes: Node<ConceptNodeData>[]): Node<ConceptNodeData>[];
36
+ export declare function deserializeGraph<D extends Record<string, unknown> = Record<string, unknown>>(json: string): NessoGraphFile<D>;
37
+ /** Overwrite private node params with vocabulary defaults (e.g. for share export/import). */
38
+ export declare function stripPrivateNodeParams<D extends Record<string, unknown>>(nodes: Node<D>[], privateDefaults: Partial<D>): Node<D>[];
package/dist/index.js CHANGED
@@ -1,6 +1,18 @@
1
- import { defaultConceptReviewFields } from '@nesso-how/types';
1
+ /** Current on-disk graph file schema version. Bump only when the envelope shape changes. */
2
+ export const GRAPH_FORMAT_VERSION = 1;
3
+ /** Serialize a graph to JSON with keys in a stable, human-readable order. */
2
4
  export function serializeGraph(file) {
3
- return JSON.stringify(file, null, 2);
5
+ const { vocabulary, id, updatedAt, name, nodes, edges, display } = file;
6
+ return JSON.stringify({
7
+ version: GRAPH_FORMAT_VERSION,
8
+ ...(vocabulary !== undefined && { vocabulary }),
9
+ ...(id !== undefined && { id }),
10
+ ...(updatedAt !== undefined && { updatedAt }),
11
+ name,
12
+ nodes,
13
+ edges,
14
+ ...(display !== undefined && { display }),
15
+ }, null, 2);
4
16
  }
5
17
  function asRecord(value) {
6
18
  return typeof value === 'object' && value !== null ? value : null;
@@ -9,8 +21,8 @@ function asRecord(value) {
9
21
  * Parse and structurally validate a graph file. Files are user-editable (and
10
22
  * importable from anywhere), so each element is checked before it can reach
11
23
  * the store: a node without a valid id/position crashes the canvas and would
12
- * then be re-persisted. Partial node data (e.g. hand-written files with only
13
- * `text`) is normalized with fresh review fields.
24
+ * then be re-persisted. Vocabulary-specific normalization (e.g. private params)
25
+ * is the caller's responsibility.
14
26
  */
15
27
  export function deserializeGraph(json) {
16
28
  const data = JSON.parse(json);
@@ -18,7 +30,9 @@ export function deserializeGraph(json) {
18
30
  if (!root || !Array.isArray(root.nodes) || !Array.isArray(root.edges)) {
19
31
  throw new Error('Invalid Nesso graph file: missing nodes or edges array');
20
32
  }
21
- const review = defaultConceptReviewFields();
33
+ if (typeof root.version === 'number' && root.version !== GRAPH_FORMAT_VERSION) {
34
+ throw new Error(`Unsupported Nesso graph file version: ${root.version}`);
35
+ }
22
36
  const nodes = root.nodes.map((value, i) => {
23
37
  const node = asRecord(value);
24
38
  const pos = node ? asRecord(node.position) : null;
@@ -32,11 +46,7 @@ export function deserializeGraph(json) {
32
46
  !Number.isFinite(pos.y)) {
33
47
  throw new Error(`Invalid Nesso graph file: node ${i} is missing a valid id or position`);
34
48
  }
35
- const d = asRecord(node.data) ?? {};
36
- return {
37
- ...node,
38
- data: { ...review, ...d, text: typeof d.text === 'string' ? d.text : '' },
39
- };
49
+ return node;
40
50
  });
41
51
  const edges = root.edges.map((value, i) => {
42
52
  const edge = asRecord(value);
@@ -48,24 +58,17 @@ export function deserializeGraph(json) {
48
58
  }
49
59
  return edge;
50
60
  });
51
- return { ...root, nodes, edges };
52
- }
53
- /** Strip personal FSRS / review history for shareable graph export. Keeps text, elaboration, layout. */
54
- export function nodesForGraphShareExport(nodes) {
55
- const review = defaultConceptReviewFields();
56
- return nodes.map((node) => {
57
- const { text, elaboration } = node.data ?? { text: '' };
58
- return {
59
- ...node,
60
- data: {
61
- text: text ?? '',
62
- ...(elaboration ? { elaboration } : {}),
63
- ...review,
64
- },
65
- };
66
- });
61
+ return {
62
+ ...root,
63
+ version: GRAPH_FORMAT_VERSION,
64
+ nodes,
65
+ edges,
66
+ };
67
67
  }
68
- /** Reset review fields on import so shared files never restore someone else's scheduling. */
69
- export function nodesFromGraphShareImport(nodes) {
70
- return nodesForGraphShareExport(nodes);
68
+ /** Overwrite private node params with vocabulary defaults (e.g. for share export/import). */
69
+ export function stripPrivateNodeParams(nodes, privateDefaults) {
70
+ return nodes.map((node) => ({
71
+ ...node,
72
+ data: { ...node.data, ...privateDefaults },
73
+ }));
71
74
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nesso-how/formats",
3
- "version": "0.1.0-alpha.32",
3
+ "version": "0.1.0-alpha.34",
4
4
  "description": "Nesso graph serialization formats — JSON serialize/deserialize",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -22,7 +22,7 @@
22
22
  }
23
23
  },
24
24
  "dependencies": {
25
- "@nesso-how/types": "0.1.0-alpha.32"
25
+ "@nesso-how/types": "0.1.0-alpha.34"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "@xyflow/react": "^12.6.4"