@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.
- package/LICENSE +21 -0
- package/README.md +173 -0
- 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
|
+
}
|