dt-toolbox 7.4.9 → 7.5.1

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.
@@ -0,0 +1,201 @@
1
+ ---
2
+ name: git-dt-toolbox
3
+ description: |
4
+ Help developers use `dt-toolbox` (v7.4.9): create a `dt-object` with
5
+ `dtbox.init(data, { model })` or `dtbox.load(dtModel)`, query it with
6
+ `dt.query(queryFn)`, reshape it with `dt.model(modelFn)`, register
7
+ precomputed filters with `dt.setupFilter(name, fn)`, add new data
8
+ segments with `dt.insertSegment(name, otherDt)`, and extract results
9
+ with `dt.export()`, `dt.copy(name)`, `dt.index(breadcrumbs)`, or
10
+ `dt.extractList([requests], { as })`. Use when a developer asks for an
11
+ immutable, deep-nested object store with fast filterable queries, a
12
+ tree-shaped data model with breadcrumbs, a way to convert among
13
+ standard / tuple / midFlat / file / flat / breadcrumbs representations,
14
+ or "I want a dt-object that I can scan, filter, and reshape without
15
+ mutating the source." Do NOT use for: small flat objects (use plain JS
16
+ or `@peter.naydenov/walk`), reactive state (use `@peter.naydenov/signals`),
17
+ pub/sub events (use `@peter.naydenov/notice`), or fixing bugs in the
18
+ library itself.
19
+ ---
20
+
21
+ # git-dt-toolbox helper
22
+
23
+ A tool for working with deep-nested JS objects. You give it data; it
24
+ returns a `dt-object` — an immutable wrapper around an internal
25
+ `DT-model` (a flat array of 4-tuples `[name, flatData, breadcrumbs, edges]`).
26
+ The dt-object never mutates its source; queries and models produce new
27
+ dt-objects.
28
+
29
+ Source of truth:
30
+ - `src/main.js` — the `dtbox` API: `init`, `load`, `flat`, `convert`, `getWalk`
31
+ - `src/mainLib.js` — `init`/`load`/`flating`/`converting`/`getWalk` impls;
32
+ the `INIT_DATA_TYPES` list; the `hasCircularRef` guard
33
+ - `src/flatObject/index.js` — `dt-object` API: `insertSegment`, `export`,
34
+ `copy`, `query`, `model`, `setupFilter`, `index`, `listSegments`, `extractList`
35
+ - `src/flatData/index.js` — the `dt-storage` API exposed to query/model
36
+ callbacks: `from`, `use`, `get`, `find`, `like`, `look`, `set`, `connect`,
37
+ `save`, `push`
38
+ - `src/convertors/index.js` — model conversion (std / tuple / breadcrumbs /
39
+ file / midFlat / flat / dt-model)
40
+ - `test/` — executable examples for every pattern below
41
+ - `README.md` — narrative docs, the DT-model anatomy block, all API tables,
42
+ the "How it works" section
43
+
44
+ ## Procedure
45
+
46
+ 1. **Map the developer's intent to the right shape of `dtbox` call**:
47
+ - "I have a nested object — make it queryable without mutating it" → `const dt = dtbox.init(data)` (default `model: 'std'`)
48
+ - "I have a flat list of `breadcrumb/value` pairs" → `dtbox.init(data, { model: 'breadcrumbs' })` or `dtbox.init(data, { model: 'file' })` for `key/key2/.../value` strings
49
+ - "I have a dt-model array already" → `dtbox.load(dtModel)` — `dt-model` is the canonical flat shape: `[[name, flatData, breadcrumbs, edges], ...]`
50
+ - "I just want a dt-model array, not a dt-object" → `dtbox.flat(data, { model })` or `dtbox.convert(data, { model: 'src', as: 'dst' })`
51
+ - "Query the dt-object" → `dt.query((store, ...args) => { ... })`. The store is the `dt-storage` API; use it to scan and build results
52
+ - "Reshape to a specific model" → `dt.model((store, ...args) => { ... return { as: 'std' } })` (or `'tuples'`, `'breadcrumbs'`, `'midFlat'`, `'flat'`, `'file'`, `'dt-model'`)
53
+ - "Pre-index data for fast scanning" → `dt.setupFilter(name, fn)`. The filter returns `true` for dt-lines that should be in the scan-list. Use `store.use(name)` in a query
54
+ - "Add a separate, unrelated data block to the same dt-object" → `dt.insertSegment('extra', otherDt)` (otherDt must be a dt-object)
55
+ - "Get a single dt-line by its path" → `dt.index('root/familyMembers')` returns `[name, flatData, breadcrumbs, edges]`
56
+ - "Extract a list of segments or properties" → `dt.extractList(['root', 'extra/foo'], { as: 'std' })` — the second arg is the output model
57
+ - "Deep-copy the original source object back" → `dt.copy('root')` (default `'root'` when no arg). Note: `copy` returns the SOURCE shape, not a dt-model
58
+ - "Use the same `walk` version that dt-toolbox uses" → `dtbox.getWalk()`
59
+
60
+ 2. **Generate code that follows the real API contract**:
61
+ - ESM import: `import dtbox from 'dt-toolbox'` (CJS: `require('dt-toolbox')` since 7.4.2)
62
+ - The default export is a flat object with five functions: `init`, `load`, `flat`, `convert`, `getWalk`. There is no factory call.
63
+ - **`init(data, options?)`** — `data` is a plain JS value; `options.model` picks the source model. Default `'std'`. Supported: `'std'`, `'standard'`, `'tuple'`, `'tuples'`, `'breadcrumb'`, `'breadcrumbs'`, `'file'`, `'files'`, `'midFlat'`, `'midflat'`, `'flat'`, `'dt-model'`. An unknown model logs an error and returns `null`.
64
+ - **`load(dtModel)`** — `dtModel` is an array of 4-tuples. Returns a dt-object.
65
+ - **`flat(data, options?)`** — like `init` but returns the dt-model array, not a dt-object. Throws on bad model or non-object input.
66
+ - **`convert(data, { model, as })`** — convert from one model to another. Throws if either model name is unknown.
67
+ - **`dtbox.getWalk()`** — returns the same `@peter.naydenov/walk` version this version of dt-toolbox ships with. Useful for "use the same deep-copy semantics."
68
+ - `dt.query(fn, ...args)` and `dt.model(fn, ...args)` pass extra args to the callback after the `store` argument. Use them for context the callback needs.
69
+ - `dt.setupFilter(name, fn)` — `fn({ name, flatData, breadcrumbs, edges })` returns `true`/`false`. Only `true` lines end up in the filter's scan-list.
70
+ - `dt.insertSegment(name, otherDt)` — `otherDt` MUST be a dt-object. The new segment becomes a separate "root" in the storage; its internal `breadcrumbs` start with `name/` not `root/`.
71
+ - `dt.export(segmentName?)` — returns the dt-model array for that segment (default `'root'`). Segment names are returned as `root` in the export, regardless of what they were registered as.
72
+ - `dt.copy(segmentName?)` — returns a deep copy of the SOURCE data (not the dt-model) for that segment. Default `'root'`.
73
+ - `dt.index(breadcrumbs)` — returns a single dt-line `[name, flatData, breadcrumbs, edges]`.
74
+ - `dt.listSegments()` — returns `['root', ...otherSegmentNames]`.
75
+ - `dt.extractList([requests], { as? })` — `requests` is an array of `segmentName` or `segmentName/propertyName`. `as` is the output model; default `'dt-model'`.
76
+
77
+ 3. **Apply the `dt-storage` (store) API contract — used inside query/model**:
78
+ - **`from(breadcrumbs)`** — start scanning from a sub-tree.
79
+ - **`use(filterName)`** — restrict the scan to a precomputed filter's list.
80
+ - **`get(breadcrumbs)`** — fetch a single dt-line by path.
81
+ - **`find(name)`** — exact-name match across the current scan-list.
82
+ - **`like(name)`** — substring match against dt-line `name`.
83
+ - **`look(callback)`** — iterate over the current scan-list, calling `callback(dtLine)` for each.
84
+ - **`set(name, data, edges?)`** — define a new dt-line in the result.
85
+ - **`connect(fromBreadcrumbs, toBreadcrumbs)`** — add a parent/child edge between two existing result dt-lines.
86
+ - **`save(breadcrumbs, key, value)`** — write a property into a result dt-line's `flatData` object.
87
+ - **`push(breadcrumbs, value)`** — push a value into a result dt-line's `flatData` array.
88
+ - The store's mutations only affect the NEW dt-object being built. The host dt-object is never touched.
89
+
90
+ 4. **Apply the order-of-execution rules**:
91
+ - `init` → source data → `convert.from(model).toFlat(...)` → dt-model → `flatObject(...)` → dt-object. The dt-object holds a deep copy of the source.
92
+ - `init` rejects circular references with `Error('Circular reference detected...')`. Catch this up-front if the source might be cyclic; the internal `walk` recurses without tracking, so cycles would otherwise OOM-crash.
93
+ - `init` wraps non-object inputs (`null`, `undefined`, primitives) as `{ value: <primitive> }` so the data is preserved. Before the fix, primitives silently produced empty dt-objects.
94
+ - `query` returns the SAME dt-object if nothing new was added; otherwise a new dt-object with the new data.
95
+ - `model` ALWAYS returns a plain value (object/array/string) — the shape depends on the `as` you return from the callback. Without `as`, it returns a dt-model array.
96
+ - `insertSegment` accepts a dt-object only. Pass a plain object through `dtbox.init` first.
97
+ - Address-list order matters: `setAddresses`-like behavior in `init` walks the source recursively; the dt-model preserves the order of `Object.keys` / array indices.
98
+
99
+ 5. **Surface only the relevant gotcha proactively** — pick at most one from the list below that applies to the current example, and only if the user is unlikely to know it:
100
+ - **`init` is a deep copy.** The returned dt-object holds its own copy of the source. Mutating the source after `init` does NOT affect the dt-object. (And vice versa.)
101
+ - **The 4-tuple shape is `[name, flatData, breadcrumbs, edges]`.** Order matters: it's positional, not an object. Breadcrumbs use `/` as the separator, starting with `root/`.
102
+ - **`copy` returns the SOURCE shape, not the dt-model.** `dt.copy('extra')` returns the original `{ vitamins: [...] }` object, not a dt-model array. Use `export` if you need the dt-model.
103
+ - **`init` is for non-dt-model data; `load` is for dt-model data.** Mixing them up is the most common bug. `load` does NOT call any convertor — it assumes the input is already `[name, flatData, breadcrumbs, edges]` tuples.
104
+ - **`model` callback must return either a `set`/`connect`/`save`/`push`-driven result, or `{ as: 'std' }` for a final conversion.** Without `as`, the return is a dt-model array (the raw internal shape).
105
+ - **`insertSegment` requires a dt-object as the second arg.** Wrapping a plain object with `dtbox.init(...)` first is the typical pattern.
106
+ - **Filters are precomputed once and cached.** A filter is run when `setupFilter` is called; later `store.use(name)` just consults the cached list. The filter is the place to put expensive predicates.
107
+ - **Circular references throw.** `init({ a: {} })` then `data.a.self = data` throws `Error('Circular reference detected in data. dt-toolbox cannot initialise a self-referencing object.')`. Catch this up-front.
108
+
109
+ 6. **If the request is for a small flat object**, the dt-toolbox overhead is unjustified. Use plain JS or `@peter.naydenov/walk` for the deep copy.
110
+
111
+ 7. **If the request is for reactive state** (auto-update views when state changes), `dt-toolbox` is non-reactive. Use `@peter.naydenov/signals` for that.
112
+
113
+ 8. **If the request is for a pub/sub event bus**, use `@peter.naydenov/notice`. `dt-toolbox` doesn't emit events.
114
+
115
+ ## Output contract
116
+
117
+ - One focused code snippet, ESM by default (CJS if asked)
118
+ - One line of context explaining which API (init/load/query/model) is used and why
119
+ - A pointer to the relevant source/test section if the developer wants to dig deeper
120
+ - Surface at most one relevant gotcha proactively, only if it applies to the example
121
+ - Never include `init(dtModelArray)` — that's `load`, not `init`
122
+ - Never include `load({ key: 'value' })` — that's `init`, not `load`
123
+ - Never include a model name not in `INIT_DATA_TYPES` — it logs an error and returns `null`
124
+
125
+ ## Failure handling
126
+
127
+ - The developer's use case is genuinely ambiguous (e.g., "I need a data structure") → start with `dtbox.init(data)` and a single `dt.query(...)`; mention `model` for the source shape, `setupFilter` for performance
128
+ - Developer reports a bug or unexpected behavior in `dt-toolbox` itself → do NOT try to fix from this skill; route to the project source or maintainer
129
+ - Developer wants a feature `dt-toolbox` doesn't have (mutation, event emission, async queries) → say so plainly, don't invent an API
130
+ - Wrong model name → `init` returns `null` and logs; `flat`/`convert` throw with a clear "Supported: …" message. Tell the user which one they hit.
131
+
132
+ ## Examples
133
+
134
+ **"Make a nested object queryable"**
135
+
136
+ ```js
137
+ import dtbox from 'dt-toolbox'
138
+
139
+ const data = {
140
+ name: 'Peter',
141
+ familyMembers: ['Veselina', 'Iskra', 'Maria', 'Vasil', 'Vladimir', 'Petya'],
142
+ shoes: { winter: ['Keen', 'Head'], summer: ['Lotto', 'Asics'] },
143
+ }
144
+
145
+ const dt = dtbox.init(data) // model: 'std' is the default
146
+
147
+ // dt.query() and dt.model() return new dt-objects; the original is never mutated.
148
+ const justNames = dt.model((store) => {
149
+ store.set('names', [])
150
+ store.look(({ flatData, breadcrumbs }) => {
151
+ if (breadcrumbs === 'root/familyMembers') {
152
+ flatData.forEach((n) => store.push('names', n))
153
+ }
154
+ })
155
+ return { as: 'std' } // return a plain object instead of a dt-model
156
+ })
157
+ // justNames -> { names: ['Veselina', 'Iskra', 'Maria', 'Vasil', 'Vladimir', 'Petya'] }
158
+ ```
159
+
160
+ `dtbox.init(data)` walks the source, builds the internal dt-model, and returns a dt-object. The dt-object holds a deep copy. Query/model callbacks receive a `store` (the dt-storage API) and can use `set`/`push`/`look` to build a new result. Return `{ as: 'std' }` from a model callback to convert the raw dt-model to a plain object. See `src/mainLib.js` `init` and `src/flatData/index.js`.
161
+
162
+ **"Convert a flat list of `key/subkey/value` strings into a nested object"**
163
+
164
+ ```js
165
+ import dtbox from 'dt-toolbox'
166
+
167
+ const flat = [
168
+ 'name/Peter',
169
+ 'shoes/winter/Keen',
170
+ 'shoes/winter/Head',
171
+ 'shoes/summer/Lotto',
172
+ 'shoes/summer/Asics',
173
+ ]
174
+
175
+ const dt = dtbox.init(flat, { model: 'file' })
176
+ const nested = dt.model(() => {}, { as: 'std' })
177
+ // nested -> {
178
+ // name: 'Peter',
179
+ // shoes: { winter: ['Keen', 'Head'], summer: ['Lotto', 'Asics'] }
180
+ // }
181
+ ```
182
+
183
+ `model: 'file'` parses a list of `'a/b/c'` strings into a tree. The final `as: 'std'` (inside the second arg of `dt.model`, not the callback) is the output model. The empty callback `() => {}` means "use the dt-object as-is, just convert the shape on the way out." See `src/convertors/index.js` and the "Init/Export Data-Models" section in the README.
184
+
185
+ **"Precompute a filter for fast scanning"**
186
+
187
+ ```js
188
+ import dtbox from 'dt-toolbox'
189
+
190
+ const dt = dtbox.init(people) // people: { alice: { eyes: 'blue' }, bob: { eyes: 'green' }, ... }
191
+
192
+ dt.setupFilter('blueEyes', ({ flatData }) => flatData.eyes === 'blue')
193
+
194
+ const result = dt.query((store) => {
195
+ store.use('blueEyes').look(({ flatData, name }) => {
196
+ console.log(name, 'has blue eyes:', flatData)
197
+ })
198
+ })
199
+ ```
200
+
201
+ `setupFilter(name, fn)` runs `fn` against every dt-line ONCE and caches the matching list. `store.use(name)` consults that cache instead of re-evaluating. Useful when the same predicate is needed many times across queries. See `src/flatObject/setupFilter.js` and the "Filters" section in the README.
package/Changelog.md CHANGED
@@ -1,6 +1,19 @@
1
1
  ## Release History
2
2
 
3
3
 
4
+
5
+ ### 7.5.1 ( 2026-09-24)
6
+ - [x] Dependency update. @peter.naydenov/walk to version 6.1.0;
7
+
8
+
9
+
10
+ ### 7.5.0 ( 2026-09-01)
11
+ - [x] Feature: Added a skill at `.agents/skills/git-dt-toolbox/SKILL.md`;
12
+ - [x] Chore: Added a `"files"` allow-list to `package.json` so the skill at `.agents/skills/git-dt-toolbox/SKILL.md` is **explicitly included** in the published npm package.
13
+ - [x] Side benefit: the old `README_v.*.x.md` snapshots, `rollup.config.js`, and `vitest.config.js` are no longer shipped. Resolves the previous "Stale" note about `.npmignore`;
14
+
15
+
16
+
4
17
  ### 7.4.9 ( 2026-07-21)
5
18
  - Dependency update. @peter.naydenov/walk to version 6.0.0;
6
19
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dt-toolbox",
3
3
  "description": "Data manipulation tool",
4
- "version": "7.4.9",
4
+ "version": "7.5.1",
5
5
  "license": "MIT",
6
6
  "author": "Peter Naydenov",
7
7
  "main": "./dist/dtbox.umd.js",
@@ -31,15 +31,15 @@
31
31
  },
32
32
  "homepage": "https://github.com/PeterNaydenov/dt-toolbox#readme",
33
33
  "dependencies": {
34
- "@peter.naydenov/walk": "6.0.0"
34
+ "@peter.naydenov/walk": "6.1.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@rollup/plugin-commonjs": "^29.0.3",
38
38
  "@rollup/plugin-node-resolve": "^16.0.3",
39
39
  "@rollup/plugin-terser": "^1.0.0",
40
- "@vitest/coverage-v8": "^4.1.10",
41
- "rollup": "^4.62.4",
42
- "vitest": "^4.1.10"
40
+ "@vitest/coverage-v8": "^5.0.1",
41
+ "rollup": "^4.63.4",
42
+ "vitest": "^5.0.1"
43
43
  },
44
44
  "keywords": [
45
45
  "flatten",
@@ -49,6 +49,15 @@
49
49
  "structure",
50
50
  "dt"
51
51
  ],
52
+ "files": [
53
+ ".agents",
54
+ "dist",
55
+ "src",
56
+ "README.md",
57
+ "Changelog.md",
58
+ "Migration.guide.md",
59
+ "LICENSE"
60
+ ],
52
61
  "allowScripts": {
53
62
  "fsevents@2.3.3": true
54
63
  }