@objectstack/metadata 17.3.0 → 17.4.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/CHANGELOG.md +532 -0
- package/README.md +6 -4
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.cjs +299 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +227 -60
- package/dist/index.d.ts +227 -60
- package/dist/index.js +294 -35
- package/dist/index.js.map +1 -1
- package/dist/migrations/index.cjs +104 -22
- package/dist/migrations/index.cjs.map +1 -1
- package/dist/migrations/index.d.cts +40 -1
- package/dist/migrations/index.d.ts +40 -1
- package/dist/migrations/index.js +107 -22
- package/dist/migrations/index.js.map +1 -1
- package/dist/node.cjs +299 -36
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +24 -1
- package/dist/node.d.ts +24 -1
- package/dist/node.js +294 -35
- package/dist/node.js.map +1 -1
- package/dist/view-container.cjs +37 -0
- package/dist/view-container.cjs.map +1 -0
- package/dist/view-container.d.cts +76 -0
- package/dist/view-container.d.ts +76 -0
- package/dist/view-container.js +12 -0
- package/dist/view-container.js.map +1 -0
- package/package.json +18 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,537 @@
|
|
|
1
1
|
# @objectstack/metadata
|
|
2
2
|
|
|
3
|
+
## 17.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- a56baa2: feat(metadata,objectql): a keyed plural read on `MetadataManager`, `listNames` fault parity, and an action audit that answers from the same identity and sources as the router
|
|
8
|
+
|
|
9
|
+
Two plural reads of one metadata plane could disagree with a by-name read of
|
|
10
|
+
that same plane, and the ADR-0110 D5 action-governance audit stood on the
|
|
11
|
+
disagreement — reporting `registered handler with NO declaration … REFUSED at
|
|
12
|
+
dispatch` about a route the router was resolving and dispatching in the same
|
|
13
|
+
boot.
|
|
14
|
+
|
|
15
|
+
**`MetadataManager.listNames` gains the per-loader `try`/`catch` that
|
|
16
|
+
`loadMany` and `list()` have carried since #5108.** One loader fault used to
|
|
17
|
+
produce two different facts depending only on which plural read a caller
|
|
18
|
+
reached for: `loadMany` swallowed it and answered short, `listNames` threw. It
|
|
19
|
+
now degrades the same way, through the same `reportLoaderReadFailure` /
|
|
20
|
+
`reportLoaderReadRecovered` helpers — one outage, one line, one vocabulary.
|
|
21
|
+
Callers that relied on `listNames` throwing to detect an outage should read
|
|
22
|
+
`listDiagnosed()`, which reports `degraded` explicitly.
|
|
23
|
+
|
|
24
|
+
**New: `MetadataManager.loadManyKeyed(type, options?)`** — `loadMany` read under
|
|
25
|
+
the identity the STORE holds each item by, returning `{ name, data }` pairs. It
|
|
26
|
+
delegates to a loader's own `loadManyKeyed` where one is offered (on
|
|
27
|
+
`DatabaseLoader` that shares `loadMany`'s single query, so it costs nothing
|
|
28
|
+
extra) and otherwise falls back to that loader's `list()` + per-name `load()`.
|
|
29
|
+
⛔ **`loadMany`'s published return shape does not change**, and no existing
|
|
30
|
+
consumer is touched: the key travels *beside* the body, never inside it, so a
|
|
31
|
+
body that deliberately carries no `name` stays byte-identical to what was
|
|
32
|
+
stored (#14205).
|
|
33
|
+
|
|
34
|
+
**The action-governance audit now mirrors the router on both halves of the D5
|
|
35
|
+
bijection.** The declaration half enumerates the plane keyed
|
|
36
|
+
(`loadStandaloneActionsKeyed`), so a row whose body does not name itself — a
|
|
37
|
+
`sys_metadata` row keyed by its `name` column, or a `FilesystemLoader` file
|
|
38
|
+
whose identity is its path — is a declaration to the audit exactly as it is to
|
|
39
|
+
the router; the handler half also probes the plane BY NAME
|
|
40
|
+
(`lookupMetadataAction`, `loadDiagnosed`/`load`, injected like the existing
|
|
41
|
+
registry rung), so a loader fault a plural read swallows can no longer turn a
|
|
42
|
+
dispatchable handler into an accusation. Both probes stay conservative in one
|
|
43
|
+
direction only: a source that throws leaves the handler on the list.
|
|
44
|
+
|
|
45
|
+
Additive on every published signature. `runActionGovernanceInventory` and
|
|
46
|
+
`collectEngineActionDeclarations` gain optional parameters and keep their old
|
|
47
|
+
ones working unchanged; declaration rows gain an optional `storeKey` (the new
|
|
48
|
+
exported `ActionDeclarationRow`).
|
|
49
|
+
|
|
50
|
+
**Population change, reported:** `unboundDeclarations` now sees declarations
|
|
51
|
+
whose identity is the store key. Its BEFORE was **0, structurally rather than
|
|
52
|
+
by sampling** — a nameless row was dropped before reconciliation ran, so it
|
|
53
|
+
could never be reported however many a plane held. Its one deliberate
|
|
54
|
+
subtraction: a row with neither an own `name` nor a store key is no longer
|
|
55
|
+
reported as `actionName: undefined`, which read as a parse failure in the
|
|
56
|
+
warning rather than as a finding.
|
|
57
|
+
|
|
58
|
+
Known boundary, stated in the audit's docblock rather than left to be
|
|
59
|
+
rediscovered: a boot-time audit runs outside any request scope, so if a
|
|
60
|
+
composition ever registered `metadata` as `SCOPED` the audit could not reach
|
|
61
|
+
that instance at all — before any read method runs. No shipped composition does
|
|
62
|
+
(`packages/metadata/src/plugin.ts` registers a static instance), and reaching a
|
|
63
|
+
request-scoped service from a boot-time audit is a separate change.
|
|
64
|
+
- c1d274d: fix(metadata): two files sharing one stem are refused with both paths named, instead of one being listed twice and served by extension precedence (#14921)
|
|
65
|
+
|
|
66
|
+
**BREAKING** accept-set narrowing on `FilesystemLoader`, shipped as `minor`
|
|
67
|
+
under the repo's launch-window convention for breaking changes. Ruled on
|
|
68
|
+
#14921 (2026-09-05, option 1 of three).
|
|
69
|
+
|
|
70
|
+
**Remedy: delete or rename the duplicate file.** The refusal names every
|
|
71
|
+
colliding path and the metadata type, so the fix is visible at the point of
|
|
72
|
+
failure.
|
|
73
|
+
|
|
74
|
+
`FilesystemLoader` derives a metadata name by stripping a flat file's
|
|
75
|
+
extension, and resolves a name back to a file under a FIXED extension
|
|
76
|
+
precedence (`.json` → `.yaml` → `.yml` → `.ts` → `.js`). Two files sharing a
|
|
77
|
+
stem therefore produced one name **twice** in `list()` while only the
|
|
78
|
+
first-precedence file was reachable through any name at all. With
|
|
79
|
+
`object/twin.json` and `object/twin.yaml` both present, `list()` answered
|
|
80
|
+
`['twin', 'twin']`, `twin.yaml` was addressable through nothing, and
|
|
81
|
+
`loadMany()` returned both bodies. `MetadataManager.listNames()` unions loader
|
|
82
|
+
output into a `Set`, which collapsed the duplicate and took the count
|
|
83
|
+
discrepancy with it — the file stayed unreachable either way, so a clean
|
|
84
|
+
`listNames()` was never evidence the collision had been absorbed.
|
|
85
|
+
|
|
86
|
+
The invariant that broke: **what is listed is what is loadable.** The listed
|
|
87
|
+
set and the addressable set stopped being the same set. The failure was silent
|
|
88
|
+
in the direction that matters for authoring — convert `twin.json` to
|
|
89
|
+
`twin.yaml` and leave the old file behind, or land one from each of two
|
|
90
|
+
packages, and the JSON one is served forever with no diagnostic anywhere,
|
|
91
|
+
while `admitLoaderItems()`'s documented "keep the first and say nothing"
|
|
92
|
+
absorbs the collision a second time.
|
|
93
|
+
|
|
94
|
+
`FilesystemLoader.list()` now throws `AmbiguousMetadataStemError`
|
|
95
|
+
(`AMBIGUOUS_METADATA_STEM`, HTTP 500) naming both paths and the type, and the
|
|
96
|
+
same refusal fronts the shared `loadMany()` / `loadManyKeyed()` walk, so the
|
|
97
|
+
two-body answer is gone rather than de-duplicated. `MetadataManager.listNames()`
|
|
98
|
+
and `list()` **propagate** it rather than absorbing it into their per-loader
|
|
99
|
+
degradation: an ambiguous stem is an authoring error no retry fixes, and
|
|
100
|
+
degrading it would drop every item the loader holds into a short-but-served
|
|
101
|
+
list while the server keeps reporting healthy. A real storage outage still
|
|
102
|
+
degrades exactly as before — the seams discriminate on a branded predicate,
|
|
103
|
+
`isAmbiguousMetadataStemError`, not on a blanket rethrow.
|
|
104
|
+
|
|
105
|
+
**Refused shape**, precisely: two or more files **directly under
|
|
106
|
+
`ROOT/TYPE/`** whose basenames differ only by an extension belonging to one of
|
|
107
|
+
**this instance's registered serializers**. Register `javascript` and
|
|
108
|
+
`dual.json` + `dual.js` becomes ambiguous; under the manager's default format
|
|
109
|
+
set (`typescript` / `json` / `yaml`) it is not, because `.js` derives no name.
|
|
110
|
+
Nested files are untouched — they are neither listed nor resolvable (#14486),
|
|
111
|
+
so `crm/solo.json` beside a flat `solo.json` is not a collision. The refusal is
|
|
112
|
+
scoped to the type directory that holds it: a clean `view/` still lists while
|
|
113
|
+
`object/` refuses.
|
|
114
|
+
|
|
115
|
+
New exports from the package root entry: `AmbiguousMetadataStemError`,
|
|
116
|
+
`isAmbiguousMetadataStemError`, `AMBIGUOUS_METADATA_STEM_CODE`,
|
|
117
|
+
`AMBIGUOUS_METADATA_STEM_STATUS`.
|
|
118
|
+
|
|
119
|
+
Measured migration cost, which is what makes this narrowing cheap: **no tree in
|
|
120
|
+
this repository carries the shape.** A walk of all 7,770 tracked files across
|
|
121
|
+
526 directories found zero stem collisions among `.json` / `.yaml` / `.yml` /
|
|
122
|
+
`.ts` / `.js`, confirmed independently by a `git ls-files` pass, and the repo
|
|
123
|
+
holds no `.yaml`/`.yml` metadata file at all outside CI and workspace config.
|
|
124
|
+
No existing tree goes red.
|
|
125
|
+
|
|
126
|
+
<!-- adr-0087: not-required (no-migration-prescription) An accept-set narrowing on a LOADER, not on any authorable key: no property of any spec schema is removed, renamed or re-shaped, so there is no tombstone and nothing for `objectstack migrate meta` to rewrite in a stored document. The affected artifact is a FILESYSTEM LAYOUT — two sibling files — which the ledger cannot address at all: a migration entry rewrites metadata bodies, and neither of the colliding files is wrong on its own. Which one an author wants kept is intent no entry can decide: they may have meant the conversion to `.yaml` to land and forgotten to delete the `.json`, or may have meant the opposite, and the two files carry no evidence of which. The refusal is the channel that reaches them, at the load site, naming both paths, the type, and the remedy. Measured in-repo population of affected trees is zero: a walk of 7,770 tracked files over 526 directories found no directory holding two files with one stem among the registered extensions, and there are no `.yaml`/`.yml` metadata files outside CI and workspace config. -->
|
|
127
|
+
- e9fcd6b: feat(metadata)!: `DatabaseLoaderOptions.cache.ttl` → `cache.ttlMs` — the read-through cache TTL carries its unit in the key name (#14478)
|
|
128
|
+
|
|
129
|
+
<!-- adr-0087: registered metadata-manager-config-cache-ttl-unit-in-key -->
|
|
130
|
+
|
|
131
|
+
**BREAKING** rename on the exported `DatabaseLoaderOptions.cache` shape
|
|
132
|
+
(`DatabaseLoaderCacheOptions.ttl` → `ttlMs`), shipped as `minor` under the
|
|
133
|
+
launch-window convention. `MetadataManager` hands `config.cache.databaseLoader`
|
|
134
|
+
straight to `new DatabaseLoader({ cache })`, so this option is the spec key
|
|
135
|
+
`cache.databaseLoader.ttlMs` one layer down and renames with it: a loader
|
|
136
|
+
configured with `ttlMs: 60_000` expires entries after 60 seconds exactly as
|
|
137
|
+
`ttl: 60_000` did. The README example and the kernel metadata-service docs page
|
|
138
|
+
spell the new key.
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
// before
|
|
142
|
+
new DatabaseLoader({ driver, cache: { enabled: true, maxSize: 500, ttl: 60_000 } });
|
|
143
|
+
// after
|
|
144
|
+
new DatabaseLoader({ driver, cache: { enabled: true, maxSize: 500, ttlMs: 60_000 } });
|
|
145
|
+
```
|
|
146
|
+
- 3bd9b34: feat(metadata): `deriveViewContainerObject` gets a leaf `/view-container` subpath, so objectql's lean ADR-0076 entry stops loading the manager, chokidar, glob and js-yaml for a six-line pure function
|
|
147
|
+
|
|
148
|
+
`packages/objectql/src/engine.ts` reached `deriveViewContainerObject` through
|
|
149
|
+
`@objectstack/metadata`'s ROOT entry. `core.ts` — the ADR-0076 lean entry —
|
|
150
|
+
re-exports `engine.ts`, so `@objectstack/objectql/core`'s module-init closure
|
|
151
|
+
inherited the whole root entry: `MetadataPlugin` -> `NodeMetadataManager` ->
|
|
152
|
+
`chokidar`, plus `glob`, `js-yaml` and `readdirp`.
|
|
153
|
+
|
|
154
|
+
The same file already carried the answer 79 lines above, at its
|
|
155
|
+
`@objectstack/metadata/errors` import: that leaf subpath exists "precisely so a
|
|
156
|
+
cross-package consumer gets the predicate without the manager, the loaders or
|
|
157
|
+
the YAML/filesystem machinery behind the root entry". This is that pattern,
|
|
158
|
+
taken a second time.
|
|
159
|
+
|
|
160
|
+
**Measured on the built artifacts, not asserted** — every module Node actually
|
|
161
|
+
evaluates when `@objectstack/objectql/core` is loaded in a fresh process,
|
|
162
|
+
recorded through a `module.registerHooks` load hook (ESM and CJS) plus
|
|
163
|
+
`require.cache`, byte sizes from `statSync`:
|
|
164
|
+
|
|
165
|
+
| `@objectstack/objectql/core` | modules | bytes |
|
|
166
|
+
|:---|---:|---:|
|
|
167
|
+
| before (ESM `dist/core.mjs`) | 190 | 12,348,424 |
|
|
168
|
+
| after (ESM `dist/core.mjs`) | 185 | 11,849,808 |
|
|
169
|
+
| **delta** | **-5** | **-498,616 (-486.9 KiB)** |
|
|
170
|
+
| before (CJS `dist/core.js`) | 188 | 12,654,238 |
|
|
171
|
+
| after (CJS `dist/core.js`) | 183 | 12,141,034 |
|
|
172
|
+
| **delta** | **-5** | **-513,204 (-501.2 KiB)** |
|
|
173
|
+
|
|
174
|
+
Six modules stop loading — `packages/metadata/dist/index.js` (237,747 B),
|
|
175
|
+
`js-yaml` (114,610 B), `glob` (82,749 B), `chokidar` (2 files, 54,220 B) and
|
|
176
|
+
`readdirp` (9,836 B) — and one 469-byte module takes their place. Marginal
|
|
177
|
+
module-init time for that root entry, measured on a warm lean closure, was
|
|
178
|
+
~22 ms (median of 7; 20.4-27.5 ms) out of ~630 ms.
|
|
179
|
+
|
|
180
|
+
⚠️ The figure the finding was argued on — "~3.6 KB to ~450 KB" — is right about
|
|
181
|
+
the delta and wrong about the baseline: the lean entry's closure was already
|
|
182
|
+
~11.5 MiB before this import existed, dominated by `@objectstack/spec`
|
|
183
|
+
(9,587,914 B) and `zod` (567,918 B), neither of which the metadata root entry
|
|
184
|
+
contributes. What the root import cost was ~487 KiB *on top of* that, not a
|
|
185
|
+
closure of 450 KB.
|
|
186
|
+
|
|
187
|
+
The derivation itself moves to `packages/metadata/src/view-container.ts`, a
|
|
188
|
+
module with **no imports at all**, and `view-container-expansion.ts` imports
|
|
189
|
+
and re-exports it, so `index.ts`'s root export and `plugin.ts` keep their
|
|
190
|
+
spelling and the symbol stays on the root entry — this subpath is an additional
|
|
191
|
+
door, not a relocation. A re-export shim onto `view-container-expansion.ts` was
|
|
192
|
+
tried first and rejected on measurement: esbuild tree-shakes the unused
|
|
193
|
+
`expandRuntimeViewContainer` but keeps its two `@objectstack/spec` import
|
|
194
|
+
statements, so that shim's own closure was 84 modules / 3,035 KiB. The real
|
|
195
|
+
leaf's is 1 module / 469 B.
|
|
196
|
+
|
|
197
|
+
`expandRuntimeViewContainer` is deliberately not exported from the new subpath:
|
|
198
|
+
`metadata-manager.ts` is its only caller, the root entry does not export it
|
|
199
|
+
either, and it is the half that carries the spec machinery.
|
|
200
|
+
- 8647c87: A completed run of the ADR-0030 notification cut-over now records itself in the `sys_migration` deployment ledger, per the ruled claim matrix.
|
|
201
|
+
|
|
202
|
+
`migrateSysNotificationToEvent` reports `migrated` / `already_done` / `not_applicable` / `error` to its caller and — until now — recorded nothing anywhere. Once that line had scrolled, "did this cut-over run here, and when" had no answer in the deployment even in principle. The ledger row is what answers it, and what a run of this migration may claim under `NOTIFICATION_EVENT_MIGRATION_ID` is stated on that constant in `@objectstack/spec/system`:
|
|
203
|
+
|
|
204
|
+
- `last_run_at` — stamped on every completed non-`error` run (`migrated`, `already_done`, `not_applicable` alike).
|
|
205
|
+
- `applied_at` — stamped only on `migrated`. Never cleared: a later `already_done` leaves an earlier backfill's stamp alone, because the backfill really did happen.
|
|
206
|
+
- `verified_at` — never written, in either direction. This migration has no self-check, and `verified_at` means a self-check passed. On a store created after the cut-over the row already exists and `attestFreshDatastore` set `verified_at` at birth; that certificate survives a run untouched, because the column is omitted from the update rather than sent as `null`.
|
|
207
|
+
- `blocking: 0`, and `details` carrying `{ outcome }` verbatim.
|
|
208
|
+
- An `error` run writes no claim at all — it does not know what it did, so it does not say.
|
|
209
|
+
|
|
210
|
+
**Receipt, not gate.** Nothing reads a row under this id as a precondition and nothing may: a gate would need the self-check that does not exist. The row is what an operator reads, in the shape `sys_migration` already documents for the seed-tenancy repair.
|
|
211
|
+
|
|
212
|
+
Two additions to the published surface of `@objectstack/metadata/migrations`, both driven by that: a new `SysNotificationMigrationReceipt` type, and a new `receipt` member on `SysNotificationMigrationResult` reporting what became of the claim (`inserted` / `updated` / `not-claimed` / `no-ledger` / `failed`, with a reason on the last two). This directory takes no logger and reports to its caller, so the claim's own fate is reported the same way the migration's is — a receipt that could not be written is never swallowed. Reading a result is unaffected; code that CONSTRUCTS a `SysNotificationMigrationResult` by hand (a test double) now supplies `receipt`.
|
|
213
|
+
|
|
214
|
+
### Patch Changes
|
|
215
|
+
|
|
216
|
+
- 0c5d035: fix(metadata): a `HistoryCleanupManager` run that loses deletes now says so, at both of `start()`'s triggers
|
|
217
|
+
|
|
218
|
+
A failing history cleanup was completely silent. Three things composed: every inner `catch` on the delete path is a bare `catch {`, so the error object is discarded; the only `console.error` in `runCleanup()` sits in its OUTER catch, which those inner catches prevent execution from reaching; and `start()` invoked the run as `void this.runCleanup()`, throwing away the `{ deleted, errors }` the run returns — at BOTH call sites, the immediate run and every interval tick. A driver whose deletes failed on every scheduled run therefore produced zero output and no reachable error count, while the history table grew past its retention policy with nothing to find.
|
|
219
|
+
|
|
220
|
+
The repair reads the envelope instead of replacing it. `runCleanup()`'s contract, its inner catches and its counting are unchanged: reporting a failure to the CALLER is the third answer AGENTS.md → "Degradation log levels" allows a durability seam, and that same section names a log per failed write as the mirror-image failure. What was missing was a reader — `start()` is where the chain ends, since it returns `void` and an interval tick has no caller at all. Both call sites now go through one shared pass that reads the returned counts and, when a run lost deletes, prints one `error` line naming the consequence (rows past the retention policy are still in the table, nothing retries them, and the system keeps reporting healthy) and where to look. A run that loses nothing stays quiet, and a direct caller of `runCleanup()` sees exactly the same `{ deleted, errors }` as before.
|
|
221
|
+
- 281bf0d: `HistoryCleanupManager` computes its retention cutoff on one calendar, not two.
|
|
222
|
+
|
|
223
|
+
Both call sites — the age-based delete in `runCleanup()` and the preview count in `getCleanupStats()` — built the cutoff with `cutoffDate.setDate(cutoffDate.getDate() - maxAgeDays)` and then rendered it with `toISOString()`. `setDate`/`getDate` read and write the **local** calendar; `toISOString()` renders **UTC**. They now use `setUTCDate`/`getUTCDate`, so the arithmetic and the rendering agree.
|
|
224
|
+
|
|
225
|
+
`setDate` preserves wall-clock time, so shifting the local calendar back `n` days moves the *instant* by exactly `n × 24h` only while every local day in the window is 24 hours long. When the window straddles a DST transition it is 23 hours (spring-forward) or 25 (fall-back), and the cutoff instant that goes into the `recorded_at: { $lt: … }` **delete** filter is off by the size of that transition — one hour in most zones, thirty minutes on Lord Howe Island. History rows within that slip of the retention boundary were deleted early, or retained too long.
|
|
226
|
+
|
|
227
|
+
The exposure is not limited to the two transition days: the window only has to *straddle* a transition, so it grows with `maxAgeDays`. Measured over a 12-zone × 366-day × 48-half-hour sweep of 2026, in `America/New_York` the old spelling produced a wrong cutoff for 0.6% of instants at `maxAgeDays: 1`, 16.4% at 30, 49.7% at 90 and 69.4% at 180. In zones that do not observe DST (`UTC`, `Asia/Shanghai`, `Asia/Kolkata`, `Australia/Perth`) the rate is 0.0% at every `maxAgeDays` — which is why no test had ever gone red on this.
|
|
228
|
+
|
|
229
|
+
This does **not** make retention timezone-aware, and does not change what `maxAgeDays` means. The cutoff was already intended to be `now − maxAgeDays × 24h`; it is now that in every zone rather than only in zones without DST. Nothing else in either filter moved: the `organization_id` scoping, the ADR-0009 `executionPinned` exclusion and the `maxVersions` path are untouched.
|
|
230
|
+
- 3e7ef9c: Serve an Invalid `Date` from a driver instead of raising `RangeError` in `DatabaseLoader.stat`.
|
|
231
|
+
|
|
232
|
+
`canonicalIsoInstant` reached `value.toISOString()` for any `Date`, and that call raises `RangeError: Invalid time value` for the one `Date` whose time value is `NaN`. `stat()` is a hot read path — REST `/meta/*`, ObjectQL plan resolution, runtime overlay merges — so one legacy `sys_metadata` row answered **500** where the spelling this repair replaced had served a visibly-wrong value.
|
|
233
|
+
|
|
234
|
+
The shape is measured: mysql2 3.23.1 hands back a constant literally named `INVALID_DATE` for a zero `DATETIME`, and postgres-date 1.0.7 builds `new Date(NaN)` for every year in 275760..294276, which Postgres itself stores.
|
|
235
|
+
|
|
236
|
+
The `Date` arm now guards on `Number.isNaN(value.getTime())` and answers `undefined`, so `stat()`'s own `?? new Date().toISOString()` — the branch an absent column already takes — publishes a parseable `MetadataStats.mtime`. `undefined` rather than visible text is deliberate here: `mtime` is declared `z.string().datetime()`, so the text `"Invalid Date"` would not produce a readable cell, it would produce a zod refusal at the consumer, moving the failure instead of removing it. A blank is excluded for the opposite reason — it hides the producer's bug.
|
|
237
|
+
- 09c1d91: docs(metadata): the `@objectstack/metadata/errors` header records what the leaf entry actually loads — 4 packages, 83 modules, 2.2 MB — instead of leaving the reader to infer it is light (#15346)
|
|
238
|
+
|
|
239
|
+
`packages/metadata/src/errors.ts` is the leaf subpath `@objectstack/metadata/errors`, and its header explains why the subpath exists: the root entry pulls the manager, every loader and the YAML/filesystem machinery behind them, and "a consumer that wants a 40-line predicate should not have to load any of that". Measured, that promise half holds — and the header did not say which half.
|
|
240
|
+
|
|
241
|
+
**The half that holds, and stays:** MEASURED 2026-09-08 on `origin/main` `8c1515e847`, with `scripts/check-lean-entry-closure.mjs`'s exported `measure()`/`byPackage()` (one fresh child per published condition; module set collected through `module.registerHooks` unioned with `require.cache`). Both published conditions load exactly four packages, and the manager, every loader, `chokidar`, `glob`, `js-yaml` and `readdirp` are absent from all of them. That is the claim the header actually wrote, and it is accurate.
|
|
242
|
+
|
|
243
|
+
**The half that had drifted:** the general conclusion a reader takes away. `import` loads 83 modules / 2,201,709 bytes; `require` loads 83 / 2,355,405. Since the maintainer's 2026-08-30 ruling the file re-exports from `@objectstack/types`, which builds to one bundled module importing **both** `@objectstack/spec/api` (1,401,253 B) and `@objectstack/spec/security` (195,367 B) at module top, as values — `api` is the dominant edge at 7x `security`, and the two spec entries are independent of each other.
|
|
244
|
+
|
|
245
|
+
The header now separates the two costs the way the finding did, because conflating them is the available mistake: **in-repo the marginal contribution is 143 bytes** — the only current consumer of the subpath, `@objectstack/metadata-protocol`, declares `@objectstack/spec` itself and pays for that closure anyway — while the megabytes are what an **out-of-repo** consumer pays, which nothing here can measure. The figures are labelled PROVENANCE, dated and tree-pinned, in the manner the neighbouring gate keeps its own: nothing derives from them, nothing compares against them, and the header says to re-take them rather than quote them. They rot fast — the same measurement four days earlier at `6e67b86c0` found the same 83 modules but 2,487,842 bytes, 286,133 more, without this file changing at all.
|
|
246
|
+
|
|
247
|
+
**Why `patch` and not `skip-changeset`, measured rather than assumed.** `@objectstack/metadata` is released (17.3.0) and `errors.ts` is a shipped source file, so "a comment publishes nothing" needed checking rather than asserting. Building the package before and after the edit: `dist/errors.js`, `dist/errors.cjs`, `dist/errors.d.ts` and `dist/errors.d.cts` are **byte-identical** (esbuild strips the comment, and there is no `sourcesContent`), but `dist/errors.js.map` and `dist/errors.cjs.map` **change** — the export statement moved from source line 65 to line 113, so the `mappings` VLQ moves with it (`;AAgEA,…` → `;AAgHA,…`), at identical file size. `npm pack --dry-run` lists both maps in the tarball. So this diff does publish bytes from a released package, which is exactly what `skip-changeset` is not for. Nothing executable and no type changes: a consumer's stack traces resolve to the right source line, and that is the whole of it.
|
|
248
|
+
- 7629f4d: feat(spec)!: retire the three inert outer keys of `MetadataManagerConfig.cache` — `enabled`, `ttlSeconds` (formerly `ttl`) and `maxSize` — read by nothing; `cache.databaseLoader` is the only live half (#15624, ADR-0049)
|
|
249
|
+
|
|
250
|
+
<!-- adr-0087: registered metadata-manager-config-inert-cache-keys-retired -->
|
|
251
|
+
|
|
252
|
+
**BREAKING** accept-set narrowing, landing after the v17.0.0 cut (the lockstep
|
|
253
|
+
launch-window convention ships it as `minor`; the migration prescription is
|
|
254
|
+
registered under protocol major 18, where `os migrate meta` users will look).
|
|
255
|
+
ADR-0049 enforce-or-remove decides it: a declared-but-unenforced key with zero
|
|
256
|
+
measured readers comes off, and the published reference page stops teaching it.
|
|
257
|
+
|
|
258
|
+
`MetadataManagerConfig.cache` declared three outer knobs — `enabled` (default
|
|
259
|
+
`true`), `ttlSeconds` (default 3600; spelled `ttl` until #14478) and `maxSize`
|
|
260
|
+
("Max cache size in bytes") — beside the nested `databaseLoader` block, and
|
|
261
|
+
**nothing read the outer three**. The only runtime consumer of the block is
|
|
262
|
+
`MetadataManager` (`packages/metadata`), which hands `cache.databaseLoader` and
|
|
263
|
+
nothing else to `new DatabaseLoader({ cache })`; a reader census over
|
|
264
|
+
`packages/**` (tests and changelogs excluded) found no runtime reader of any
|
|
265
|
+
outer key, while the same grep shape found the nested `cache?.databaseLoader`
|
|
266
|
+
read twice — the control that makes the zero a measurement. An author writing
|
|
267
|
+
`cache: { enabled: false }` or `cache: { ttlSeconds: 60 }` got a clean parse
|
|
268
|
+
and a cache that behaved exactly as before, with no error and no warning, and
|
|
269
|
+
the published reference page (`references/kernel/metadata-loader`) documented
|
|
270
|
+
all three as if they configured something.
|
|
271
|
+
|
|
272
|
+
**What is refused:** authoring `cache.enabled`, `cache.ttlSeconds`, `cache.ttl`
|
|
273
|
+
or `cache.maxSize` on `MetadataManagerConfig`, with any value — directly, through
|
|
274
|
+
`MetadataManagerOptions`, or through `MetadataPluginConfig.storage`. The nested
|
|
275
|
+
object is not `.strict()`, so each key is a `retiredKey()` tombstone rather than
|
|
276
|
+
a bare deletion (a deletion would have stripped it in silence — the same no-op
|
|
277
|
+
one layer down): authoring it is a `tsc` error (`never`) and a parse error
|
|
278
|
+
carrying the prescription, which names the live nested knob.
|
|
279
|
+
|
|
280
|
+
**What stays, byte-identical:** the DatabaseLoader read-through cache under
|
|
281
|
+
`cache.databaseLoader` — `enabled` (default `true`), `maxSize` (an entry count,
|
|
282
|
+
default 500) and `ttlMs` (milliseconds, default 60000) — and every runtime
|
|
283
|
+
path. Parsed configs no longer carry the two former defaults (`enabled: true`,
|
|
284
|
+
`ttlSeconds: 3600`) that were materialized and never consulted.
|
|
285
|
+
|
|
286
|
+
**The #14478 rename is folded in.** `cache.ttl` → `cache.ttlSeconds` was
|
|
287
|
+
registered under this same unreleased major and never reached a published
|
|
288
|
+
release, so it is absorbed by the removal: `cache.ttl`'s tombstone now
|
|
289
|
+
prescribes deletion (naming `cache.databaseLoader.ttlMs`) instead of a rename to
|
|
290
|
+
a key that is itself retired — an author upgrading from a published 17.x sees
|
|
291
|
+
one hop. The nested `cache.databaseLoader.ttl` → `ttlMs` half of that rename is
|
|
292
|
+
unchanged.
|
|
293
|
+
|
|
294
|
+
## FROM → TO
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
// before — parsed green; no runtime ever read the three outer numbers
|
|
298
|
+
new MetadataManager({
|
|
299
|
+
datasource: 'default',
|
|
300
|
+
cache: { enabled: true, ttlSeconds: 3600, maxSize: 10_485_760, databaseLoader: { ttlMs: 60_000 } },
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// after — delete the outer keys; the nested block is the cache that runs
|
|
304
|
+
new MetadataManager({
|
|
305
|
+
datasource: 'default',
|
|
306
|
+
cache: { databaseLoader: { enabled: true, maxSize: 500, ttlMs: 60_000 } },
|
|
307
|
+
});
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
**Migration.** Delete `cache.enabled`, `cache.ttlSeconds` / `cache.ttl` and
|
|
311
|
+
`cache.maxSize`; nothing replaces them, because nothing ever consumed them. If
|
|
312
|
+
you meant to switch the cache off, cap it or set its TTL, write
|
|
313
|
+
`cache.databaseLoader.enabled` / `.maxSize` (entries) / `.ttlMs` (milliseconds)
|
|
314
|
+
— those are honoured. No `os migrate meta` conversion runs on this surface: a
|
|
315
|
+
`MetadataManager` config is not a stack collection member and never a stored
|
|
316
|
+
row, so the chain has no seam for it; the D3 semantic entry
|
|
317
|
+
`metadata-manager-config-inert-cache-keys-retired` carries the prescription
|
|
318
|
+
into `spec-changes.json`, the upgrade guide and the `spec_changes` MCP tool.
|
|
319
|
+
|
|
320
|
+
The retirement kit: `retiredKey()` tombstones on all three (and the absorbed
|
|
321
|
+
`ttl`), `RETIRED_KEYS_BY_MAJOR[18]` entries for each, the D3 semantic entry
|
|
322
|
+
above (the #14478 entry's outer half is re-worded from a rename to a deletion),
|
|
323
|
+
negative pins asserting each prescription and a positive pin asserting the
|
|
324
|
+
parse output no longer materializes the retired defaults, the published
|
|
325
|
+
reference pages regenerated, and the hand-written docs page and this package's
|
|
326
|
+
README (`@objectstack/metadata` ships `README.md`, hence its `patch`) no longer
|
|
327
|
+
authoring `cache.enabled`.
|
|
328
|
+
- 6f23f0e: fix(metadata): keep the original notification instant when migrating `sys_notification` to the event model (#16312)
|
|
329
|
+
|
|
330
|
+
`migrateSysNotificationToEvent` materializes each legacy inbox row into a
|
|
331
|
+
`sys_inbox_message` and a `sys_notification_receipt`, back-dating both to the
|
|
332
|
+
notification's own `created_at`. Both writes passed no options bag, so they
|
|
333
|
+
relied on the audit binder's create-side `record.created_at ?? now` — the
|
|
334
|
+
laundering #15964 removed, on the maintainer ruling of 2026-09-06. Without
|
|
335
|
+
that accident, every migrated inbox row and receipt is stamped with the moment
|
|
336
|
+
the migration RAN: a user's whole bell history collapses to "all arrived
|
|
337
|
+
today".
|
|
338
|
+
|
|
339
|
+
The two writes now declare `{ context: { preserveAudit: true } }`, the explicit
|
|
340
|
+
historical-import channel the same ruling deliberately kept (#3493; it is what
|
|
341
|
+
REST import's `treatAsHistorical` sets). This is not a bypass of audit — it is
|
|
342
|
+
the door audit left open for a historical import. No exported symbol, schema or
|
|
343
|
+
config key moves.
|
|
344
|
+
|
|
345
|
+
**Release ordering.** `@objectstack/objectql`'s side of #15964 is itself still
|
|
346
|
+
an unreleased changeset, so no published version of this migration has ever
|
|
347
|
+
written the flattened timeline. Releasing the two together keeps it that way.
|
|
348
|
+
|
|
349
|
+
**If a deployment did run it from a build that has both halves**, the original
|
|
350
|
+
timeline is recoverable rather than lost: the source `sys_notification` rows
|
|
351
|
+
are rewritten in place, never deleted or archived, and `created_at` is not
|
|
352
|
+
among the legacy columns the run clears — so the notification's own instant is
|
|
353
|
+
still on the event row and reachable from both new rows through
|
|
354
|
+
`notification_id`.
|
|
355
|
+
- efc5447: `RemoteLoader.list()` no longer reports a nameless remote body as a literal `undefined`.
|
|
356
|
+
|
|
357
|
+
The method declares `Promise<string[]>` and read the collection as `loadMany<{ name: string }>(type)` before mapping `items.map(i => i.name)`. That type argument is an **assertion** about bodies that arrived over HTTP, and nothing checked it: a body with no top-level `name` yielded `undefined`, which went into an array the signature declares as `string[]`. `MetadataManager.listNames()` unions loader `list()` output unfiltered, so the violation reached consumers — measured on this fixture, `listNames()` answered `[ 'account', undefined, 42 ]`.
|
|
358
|
+
|
|
359
|
+
The guard is `DatabaseLoader.list()`'s, one file away: the same cast-then-map spelling with `.filter(name => typeof name === 'string')` behind it. `RemoteLoader` was the only one of the four loaders in that directory with no guard at all — `MemoryLoader` answers with its store keys, and `FilesystemLoader` reports only names `findFile()` resolves. Dropping silently rather than throwing is the direction those siblings already carry: a name in the list that the door answers `null` for is the silent failure an author reads as their own typo, so the list is narrowed to agree with the door.
|
|
360
|
+
|
|
361
|
+
Nothing that was validly returned before stops being returned: the only entries that disappear are the ones whose type the signature already ruled out. A caller that previously received `[undefined]` now receives `[]`. `loadMany()` is deliberately untouched — it keys nothing, so a body carrying no `name` is still served there; this loader reads over HTTP and holds no store key, so `body.name` is the only identity it has and the family's "identity is the store key" rule cannot be satisfied for it.
|
|
362
|
+
- c5d6803: Published `.js.map` files no longer embed the complete original source text (`sourcesContent`) — comments included. `sourcemap: true` was esbuild shorthand, and esbuild's own default for `sourcesContent` is `true`; nobody had decided to publish every package's full source (including `@internal`/test-only comments) to npm inside its source maps, it fell out of a default nobody had looked at. Measured before this change: 55 of 57 publishable packages shipped embedded source text, and maps were roughly half of `@objectstack/spec`'s published bytes.
|
|
363
|
+
|
|
364
|
+
`sourcesContent: false` is now set at one shared place (`scripts/tsup-drop-sources-content.mjs`, wired into every `tsup.config.ts` via tsup's `esbuildOptions` hook — most packages build through the repo-root config directly and pick this up with no config change of their own). `mappings` are untouched, so stack-trace positions still resolve correctly to the original file/line/column; only the embedded source text is gone.
|
|
365
|
+
|
|
366
|
+
`@objectstack/cli` (built with `tsc`, not `tsup`) never embedded source text to begin with — its maps' `sources` entries point at `src/**` paths that are not part of the published tarball either way. That is not a defect unique to `cli`: every `tsup`-built package's `sources` entries are `../src/**`-relative paths that are equally outside `files: ["dist", …]`, and were merely masked by the embedded content that just stopped shipping. Shipping `src/**` in `files[]` to make `sources` resolve was rejected — it would put most of the removed bytes straight back. So `cli`'s maps are left exactly as `tsc` emits them: this is now the fleet-consistent shape (accurate `mappings`, non-resolving-but-honest `sources` labels, no embedded text), not an outlier.
|
|
367
|
+
|
|
368
|
+
A new gate, `pnpm check:sourcemap-no-sources-content`, sweeps every built, non-private package's `dist/**/*.map` and fails if any of them carries a non-empty `sourcesContent` array — so a future `tsup.config.ts` that skips the shared hook, or a toolchain upgrade that changes esbuild's default back, is caught rather than silently re-publishing source text.
|
|
369
|
+
- Updated dependencies [fe0d9a4]
|
|
370
|
+
- Updated dependencies [ecd2158]
|
|
371
|
+
- Updated dependencies [f2b5e46]
|
|
372
|
+
- Updated dependencies [2ed6be6]
|
|
373
|
+
- Updated dependencies [ed7243d]
|
|
374
|
+
- Updated dependencies [6ba0db4]
|
|
375
|
+
- Updated dependencies [625b0c3]
|
|
376
|
+
- Updated dependencies [233222e]
|
|
377
|
+
- Updated dependencies [07f40e5]
|
|
378
|
+
- Updated dependencies [ceb4877]
|
|
379
|
+
- Updated dependencies [e9fcd6b]
|
|
380
|
+
- Updated dependencies [90e7e6d]
|
|
381
|
+
- Updated dependencies [2bdabe6]
|
|
382
|
+
- Updated dependencies [ca326b5]
|
|
383
|
+
- Updated dependencies [8f404a5]
|
|
384
|
+
- Updated dependencies [159dbad]
|
|
385
|
+
- Updated dependencies [68437d4]
|
|
386
|
+
- Updated dependencies [abb140c]
|
|
387
|
+
- Updated dependencies [8333a6c]
|
|
388
|
+
- Updated dependencies [3e3ecb0]
|
|
389
|
+
- Updated dependencies [3030369]
|
|
390
|
+
- Updated dependencies [d5d8d50]
|
|
391
|
+
- Updated dependencies [e08892d]
|
|
392
|
+
- Updated dependencies [ae05f2e]
|
|
393
|
+
- Updated dependencies [b548e43]
|
|
394
|
+
- Updated dependencies [c463d03]
|
|
395
|
+
- Updated dependencies [64bd6a3]
|
|
396
|
+
- Updated dependencies [13c48c2]
|
|
397
|
+
- Updated dependencies [b0529e1]
|
|
398
|
+
- Updated dependencies [66dc6ab]
|
|
399
|
+
- Updated dependencies [6f94458]
|
|
400
|
+
- Updated dependencies [6e67b86]
|
|
401
|
+
- Updated dependencies [132742f]
|
|
402
|
+
- Updated dependencies [85a2459]
|
|
403
|
+
- Updated dependencies [50dc214]
|
|
404
|
+
- Updated dependencies [e89fa92]
|
|
405
|
+
- Updated dependencies [e9fcd6b]
|
|
406
|
+
- Updated dependencies [8976ea1]
|
|
407
|
+
- Updated dependencies [56fe8c2]
|
|
408
|
+
- Updated dependencies [acabd24]
|
|
409
|
+
- Updated dependencies [ab50c8f]
|
|
410
|
+
- Updated dependencies [6491463]
|
|
411
|
+
- Updated dependencies [89cf4d6]
|
|
412
|
+
- Updated dependencies [21c5dcb]
|
|
413
|
+
- Updated dependencies [6d4d5d3]
|
|
414
|
+
- Updated dependencies [ed5d557]
|
|
415
|
+
- Updated dependencies [bca21f7]
|
|
416
|
+
- Updated dependencies [e9fcd6b]
|
|
417
|
+
- Updated dependencies [2025b1f]
|
|
418
|
+
- Updated dependencies [1a7a7c9]
|
|
419
|
+
- Updated dependencies [e9fcd6b]
|
|
420
|
+
- Updated dependencies [cbca47d]
|
|
421
|
+
- Updated dependencies [acf4d38]
|
|
422
|
+
- Updated dependencies [ef3a138]
|
|
423
|
+
- Updated dependencies [68d5dfd]
|
|
424
|
+
- Updated dependencies [3e21cf0]
|
|
425
|
+
- Updated dependencies [4cfc93b]
|
|
426
|
+
- Updated dependencies [efd6b43]
|
|
427
|
+
- Updated dependencies [859ded3]
|
|
428
|
+
- Updated dependencies [fa125f3]
|
|
429
|
+
- Updated dependencies [74628d9]
|
|
430
|
+
- Updated dependencies [a646120]
|
|
431
|
+
- Updated dependencies [6f1ce7d]
|
|
432
|
+
- Updated dependencies [7778115]
|
|
433
|
+
- Updated dependencies [2c753fe]
|
|
434
|
+
- Updated dependencies [52804cd]
|
|
435
|
+
- Updated dependencies [3f89967]
|
|
436
|
+
- Updated dependencies [53cf263]
|
|
437
|
+
- Updated dependencies [21aabbc]
|
|
438
|
+
- Updated dependencies [9c270bb]
|
|
439
|
+
- Updated dependencies [76c8c5a]
|
|
440
|
+
- Updated dependencies [088f761]
|
|
441
|
+
- Updated dependencies [a84e1ce]
|
|
442
|
+
- Updated dependencies [bf1054a]
|
|
443
|
+
- Updated dependencies [d8d2776]
|
|
444
|
+
- Updated dependencies [222dc0f]
|
|
445
|
+
- Updated dependencies [e9fcd6b]
|
|
446
|
+
- Updated dependencies [32c917d]
|
|
447
|
+
- Updated dependencies [f9a3c32]
|
|
448
|
+
- Updated dependencies [f502898]
|
|
449
|
+
- Updated dependencies [51ae731]
|
|
450
|
+
- Updated dependencies [af7edfe]
|
|
451
|
+
- Updated dependencies [b60f48b]
|
|
452
|
+
- Updated dependencies [c78c918]
|
|
453
|
+
- Updated dependencies [4ca358d]
|
|
454
|
+
- Updated dependencies [cf9bda4]
|
|
455
|
+
- Updated dependencies [784cb92]
|
|
456
|
+
- Updated dependencies [7629f4d]
|
|
457
|
+
- Updated dependencies [51df9fd]
|
|
458
|
+
- Updated dependencies [a7da4de]
|
|
459
|
+
- Updated dependencies [de0bcdd]
|
|
460
|
+
- Updated dependencies [70f7d6d]
|
|
461
|
+
- Updated dependencies [c677cda]
|
|
462
|
+
- Updated dependencies [6acb37e]
|
|
463
|
+
- Updated dependencies [7797102]
|
|
464
|
+
- Updated dependencies [554a160]
|
|
465
|
+
- Updated dependencies [f7da71e]
|
|
466
|
+
- Updated dependencies [7f745c3]
|
|
467
|
+
- Updated dependencies [0a038cc]
|
|
468
|
+
- Updated dependencies [e9fcd6b]
|
|
469
|
+
- Updated dependencies [97adce2]
|
|
470
|
+
- Updated dependencies [a83482c]
|
|
471
|
+
- Updated dependencies [5eb24f8]
|
|
472
|
+
- Updated dependencies [2a3decc]
|
|
473
|
+
- Updated dependencies [cc00df2]
|
|
474
|
+
- Updated dependencies [cc00df2]
|
|
475
|
+
- Updated dependencies [f4e6adf]
|
|
476
|
+
- Updated dependencies [ee4a59b]
|
|
477
|
+
- Updated dependencies [4db3c61]
|
|
478
|
+
- Updated dependencies [5ca314a]
|
|
479
|
+
- Updated dependencies [e0af1a8]
|
|
480
|
+
- Updated dependencies [4771bd9]
|
|
481
|
+
- Updated dependencies [414c1fc]
|
|
482
|
+
- Updated dependencies [22c0279]
|
|
483
|
+
- Updated dependencies [c930f85]
|
|
484
|
+
- Updated dependencies [0db2947]
|
|
485
|
+
- Updated dependencies [92b5d7f]
|
|
486
|
+
- Updated dependencies [613bfbd]
|
|
487
|
+
- Updated dependencies [abae16a]
|
|
488
|
+
- Updated dependencies [094b8fd]
|
|
489
|
+
- Updated dependencies [c7aca0d]
|
|
490
|
+
- Updated dependencies [c1d8f98]
|
|
491
|
+
- Updated dependencies [8e0b297]
|
|
492
|
+
- Updated dependencies [d4f9b2a]
|
|
493
|
+
- Updated dependencies [5f7fa1d]
|
|
494
|
+
- Updated dependencies [87f0ccc]
|
|
495
|
+
- Updated dependencies [aedbaef]
|
|
496
|
+
- Updated dependencies [a727043]
|
|
497
|
+
- Updated dependencies [c5d6803]
|
|
498
|
+
- Updated dependencies [10d05bb]
|
|
499
|
+
- Updated dependencies [69602e5]
|
|
500
|
+
- Updated dependencies [c3ce76c]
|
|
501
|
+
- Updated dependencies [7936b29]
|
|
502
|
+
- Updated dependencies [46803fa]
|
|
503
|
+
- Updated dependencies [c2a336c]
|
|
504
|
+
- Updated dependencies [9f890d3]
|
|
505
|
+
- Updated dependencies [0bb2318]
|
|
506
|
+
- Updated dependencies [f7db8f4]
|
|
507
|
+
- Updated dependencies [1ecee3e]
|
|
508
|
+
- Updated dependencies [9408b7f]
|
|
509
|
+
- Updated dependencies [2bb0614]
|
|
510
|
+
- Updated dependencies [b3820c3]
|
|
511
|
+
- Updated dependencies [e9fcd6b]
|
|
512
|
+
- Updated dependencies [4df2a98]
|
|
513
|
+
- Updated dependencies [9bcd9be]
|
|
514
|
+
- Updated dependencies [b398ad2]
|
|
515
|
+
- Updated dependencies [99261a7]
|
|
516
|
+
- Updated dependencies [81b426f]
|
|
517
|
+
- Updated dependencies [001af1c]
|
|
518
|
+
- Updated dependencies [fb77aa5]
|
|
519
|
+
- Updated dependencies [3d3f60e]
|
|
520
|
+
- Updated dependencies [581d8f8]
|
|
521
|
+
- Updated dependencies [f81afe3]
|
|
522
|
+
- Updated dependencies [40a44b9]
|
|
523
|
+
- Updated dependencies [f89812e]
|
|
524
|
+
- Updated dependencies [7a7fb03]
|
|
525
|
+
- Updated dependencies [8fd246d]
|
|
526
|
+
- Updated dependencies [021a735]
|
|
527
|
+
- Updated dependencies [7bdb163]
|
|
528
|
+
- @objectstack/spec@17.4.0
|
|
529
|
+
- @objectstack/core@17.4.0
|
|
530
|
+
- @objectstack/platform-objects@17.4.0
|
|
531
|
+
- @objectstack/types@17.4.0
|
|
532
|
+
- @objectstack/metadata-core@17.4.0
|
|
533
|
+
- @objectstack/metadata-fs@17.4.0
|
|
534
|
+
|
|
3
535
|
## 17.3.0
|
|
4
536
|
|
|
5
537
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -165,19 +165,21 @@ new MetadataManager({
|
|
|
165
165
|
`DatabaseLoader` wraps `load` / `loadMany` / `list` / `stat` results in a
|
|
166
166
|
generic LRU cache (see `src/utils/lru-cache.ts`). Writes invalidate the
|
|
167
167
|
affected entries, so reads always observe writes made through the same loader
|
|
168
|
-
instance; out-of-band SQL writes are honored within `
|
|
168
|
+
instance; out-of-band SQL writes are honored within `ttlMs` milliseconds.
|
|
169
169
|
|
|
170
|
-
Configuration lives under `cache.databaseLoader
|
|
170
|
+
Configuration lives under `cache.databaseLoader` — the only live member of the
|
|
171
|
+
`cache` block (the outer `cache.enabled` / `cache.ttlSeconds` / `cache.maxSize`
|
|
172
|
+
were removed in #15624: nothing ever read them, and authoring one is now a
|
|
173
|
+
compile-time and parse-time error naming this nested half):
|
|
171
174
|
|
|
172
175
|
```typescript
|
|
173
176
|
new MetadataManager({
|
|
174
177
|
datasource: 'default',
|
|
175
178
|
cache: {
|
|
176
|
-
enabled: true,
|
|
177
179
|
databaseLoader: {
|
|
178
180
|
enabled: true,
|
|
179
181
|
maxSize: 500, // Max cached (type, name) entries
|
|
180
|
-
|
|
182
|
+
ttlMs: 60_000, // Cache TTL in milliseconds
|
|
181
183
|
},
|
|
182
184
|
},
|
|
183
185
|
});
|
package/dist/errors.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts"],"
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAgHA,mBAAoC;","names":[]}
|
package/dist/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts"],"
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts"],"mappings":";AAgHA,SAAS,2BAA2B;","names":[]}
|