@vireocodedev/history 0.2.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.
Files changed (3) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +173 -0
  3. package/package.json +53 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 vireocodedev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,173 @@
1
+ # @vireocodedev/history
2
+
3
+ Framework-free entity-history primitives: typed definitions, validated history
4
+ records, and a deterministic diff engine. The package owns no React components,
5
+ renderers, HTTP client, application entity enum, or persistence policy.
6
+
7
+ React presentation belongs to `@vireocodedev/ui`, whose
8
+ `VireoHistoryEntry` component consumes the nodes produced here.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ npm install @vireocodedev/history zod
14
+ ```
15
+
16
+ The package is published publicly on npm; installation requires no registry
17
+ authentication. TypeScript declarations are verified from the packed artifact
18
+ with TypeScript 6, `moduleResolution: "Bundler"`, and `skipLibCheck: false`.
19
+ Relative source maps with embedded source content are published intentionally
20
+ for debugging.
21
+
22
+ `zod >=4.4 <5` is the package's only peer dependency.
23
+
24
+ ## Define and compare an entity
25
+
26
+ ```ts
27
+ import { createHistoryDefinition, createHistoryNodes } from "@vireocodedev/history";
28
+ import { z } from "zod";
29
+
30
+ const CountrySchema = z.object({
31
+ code: z.string(),
32
+ name: z.string(),
33
+ tax: z.number(),
34
+ });
35
+
36
+ const countryHistory = createHistoryDefinition(
37
+ CountrySchema,
38
+ {
39
+ label: "Country",
40
+ key: country => country.code,
41
+ format: country => country.name,
42
+ },
43
+ {
44
+ code: false,
45
+ name: { kind: "field", label: "Name" },
46
+ tax: {
47
+ kind: "field",
48
+ label: "Tax",
49
+ format: tax => `${tax}%`,
50
+ },
51
+ },
52
+ );
53
+
54
+ const nodes = createHistoryNodes(
55
+ countryHistory,
56
+ { code: "HR", name: "Croatia", tax: 25 },
57
+ { code: "HR", name: "Croatia", tax: 24 },
58
+ );
59
+ ```
60
+
61
+ Definitions are inferred from their Zod schema. Every schema property must be
62
+ configured or explicitly ignored with `false`, so a newly added model field
63
+ cannot silently disappear from history.
64
+
65
+ `format` is optional and must return a string. Each emitted value preserves both
66
+ representations:
67
+
68
+ ```ts
69
+ {
70
+ raw: 25,
71
+ formatted: "25%"
72
+ }
73
+ ```
74
+
75
+ That boundary keeps the engine useful in Node and Workers while allowing UI to
76
+ render the formatted text or make a UI-specific decision from `raw`.
77
+
78
+ ## Records
79
+
80
+ ```ts
81
+ import { createHistoryRecordSchema } from "@vireocodedev/history";
82
+ import { z } from "zod";
83
+
84
+ const HistoryRecordSchema = createHistoryRecordSchema({
85
+ entityKind: z.enum(["INVOICE", "BUYER"]),
86
+ snapshot: z.object({ total: z.number() }),
87
+ });
88
+
89
+ const record = HistoryRecordSchema.parse(await response.json());
90
+ ```
91
+
92
+ A record uses neutral actor metadata:
93
+
94
+ ```ts
95
+ {
96
+ id: "history-1",
97
+ timestamp: "2026-08-22T12:00:00Z",
98
+ actor: { id: "user-1", label: "Alice" },
99
+ entity: "INVOICE",
100
+ entityId: "invoice-42",
101
+ snapshotPrevious: null,
102
+ snapshotCurrent: { total: 100 }
103
+ }
104
+ ```
105
+
106
+ Use `actor: null` for system-generated changes. Entity kinds remain
107
+ application-owned; passing a Zod enum narrows and validates them.
108
+
109
+ ## Diff semantics
110
+
111
+ - `null` and `undefined` mean a value is absent.
112
+ - An empty string is a present value and participates in ordinary updates.
113
+ - Added or removed empty arrays and objects remain visible as container changes.
114
+ - `set` arrays ignore order; `ordered` arrays additionally emit deliberate moved
115
+ rows. Insertions and removals do not mark every shifted neighbor as moved.
116
+ - Array item identities must be unique within each snapshot. Duplicate keys
117
+ throw instead of silently overwriting an item.
118
+ - Identities are strings or finite numbers. Node paths preserve that segment
119
+ type; encode paths canonically rather than joining them with a delimiter.
120
+ - Unchanged rows are omitted unless `showUnchanged: true` is requested.
121
+ - Added, updated, removed, and unchanged nodes are emitted in deterministic
122
+ change order.
123
+ - Both snapshots are parsed once by the root definition's Zod schema before
124
+ comparison. Parent schemas must compose the schemas owned by nested
125
+ definitions; nested schemas are not parsed a second time.
126
+
127
+ ## Value comparison and formatting
128
+
129
+ Default comparison supports primitives, Dates, arrays, and plain objects. It
130
+ uses a typed canonical representation with deterministic object-key ordering.
131
+ Cycles, functions, symbols, Maps, Sets, and other unsupported object types throw
132
+ instead of being silently treated as equal.
133
+
134
+ Use a field's `resolveChange` when domain equality differs from structural
135
+ equality. Return `null` to treat the field as unchanged, or `"added"`,
136
+ `"updated"`, or `"removed"` to select an explicit change.
137
+
138
+ Definitions are runtime-validated when created. Labels must be nonempty, modes
139
+ must be supported, callbacks must be functions, and nested configurations must
140
+ be structurally valid.
141
+
142
+ ## Nested definitions and collections
143
+
144
+ An object field references a reusable definition:
145
+
146
+ ```ts
147
+ const CustomerSchema = z.object({ address: AddressSchema });
148
+
149
+ const customerHistory = createHistoryDefinition(
150
+ CustomerSchema,
151
+ { label: "Customer", key: () => "customer" },
152
+ { address: { kind: "object", definition: addressHistory } },
153
+ );
154
+ ```
155
+
156
+ Array object items use the nested definition's `key`. Primitive array items
157
+ identify themselves. Use `mode: "ordered"` only when order is part of the
158
+ audited meaning.
159
+
160
+ ## Public concepts
161
+
162
+ - `createHistoryDefinition` creates a schema-derived entity definition.
163
+ - `createHistoryNodes` validates and compares two optional snapshots.
164
+ - `HistoryDefinition` and field config types describe definitions.
165
+ - `HistoryNode`, `HistoryGroupNode`, `HistoryFieldRow`, and `HistoryValue`
166
+ describe emitted results.
167
+ - `HistoryPath` and `HistoryPathSegment` describe lossless node locations.
168
+ - `createHistoryRecordSchema`, `HistoryRecordSchema`, and the record/snapshot
169
+ types describe transport-neutral audit records. The factory accepts optional
170
+ `entityKind`, `snapshot`, and `timestamp` Zod schemas.
171
+
172
+ The public surface is frozen by `api-surface.json`. Export changes require a
173
+ Changeset and deliberate semver decision.
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@vireocodedev/history",
3
+ "version": "0.2.0",
4
+ "description": "Framework-free entity history definitions, diff nodes, and transport-neutral record schemas for the vireocodedev starter product.",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/vireocodedev/starter.git",
11
+ "directory": "packages/history"
12
+ },
13
+ "keywords": [
14
+ "history",
15
+ "audit-log",
16
+ "diff",
17
+ "zod",
18
+ "starter"
19
+ ],
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js"
24
+ }
25
+ },
26
+ "main": "./dist/index.js",
27
+ "module": "./dist/index.js",
28
+ "types": "./dist/index.d.ts",
29
+ "files": [
30
+ "dist"
31
+ ],
32
+ "scripts": {
33
+ "build": "vite build",
34
+ "dev": "vite build --watch --mode watch",
35
+ "typecheck": "tsc --noEmit -p tsconfig.json",
36
+ "test": "vitest run"
37
+ },
38
+ "peerDependencies": {
39
+ "zod": ">=4.4 <5"
40
+ },
41
+ "devDependencies": {
42
+ "typescript": "npm:@typescript/typescript6@^6.0.2",
43
+ "vite": "^8.2.2",
44
+ "vite-plugin-dts": "^5.0.3",
45
+ "vitest": "^4.1.11",
46
+ "zod": "^4.4.3"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public",
50
+ "provenance": true,
51
+ "registry": "https://registry.npmjs.org"
52
+ }
53
+ }