@objectstack/service-analytics 17.0.0 → 17.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/CHANGELOG.md +444 -0
- package/README.md +136 -335
- package/dist/index.cjs +307 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +113 -13
- package/dist/index.d.ts +113 -13
- package/dist/index.js +278 -30
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,449 @@
|
|
|
1
1
|
# Changelog — @objectstack/service-analytics
|
|
2
2
|
|
|
3
|
+
## 17.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 57e4571: **BREAKING**: `/analytics/query` now refuses a cross-object filter nested inside a
|
|
8
|
+
combinator on the ObjectQL path, instead of silently answering the wrong number
|
|
9
|
+
(#10759).
|
|
10
|
+
|
|
11
|
+
`ObjectQLStrategy` runs one cross-object envelope check, from two call sites.
|
|
12
|
+
`generateSql()` (the `/analytics/sql` preview) asked it about every member the
|
|
13
|
+
`where` touches, flattened out of the filter tree. `execute()` asked it about the
|
|
14
|
+
built engine filter — where an AND-ed leaf sits at the top level and is seen, but
|
|
15
|
+
anything structural (an `$or`, a `$not`, a nested `$and` that cannot merge) has
|
|
16
|
+
been folded into `filter.$and`, so the only key readable for it was the literal
|
|
17
|
+
`$and`, which is never a field name.
|
|
18
|
+
|
|
19
|
+
One query therefore got two answers, measured over one fixture in one run:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
where: { $or: [{ 'account.region': 'West' }, { stage: 'won' }] }
|
|
23
|
+
|
|
24
|
+
before /analytics/sql 400 INVALID_FIELD cross-object filter "account.region"
|
|
25
|
+
/analytics/query 200, rows
|
|
26
|
+
after both 400 INVALID_FIELD cross-object filter "account.region"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`engine.aggregate` cannot join. The half that returned rows was not answering the
|
|
30
|
+
cross-object query: the disjunct naming a column the base object does not have
|
|
31
|
+
can never match, so the query silently collapsed to its remaining branches and
|
|
32
|
+
reported a narrower figure as if it were the answer. Both call sites now derive
|
|
33
|
+
the member list from one shared view, so the invariant the strategy already
|
|
34
|
+
stated for itself — the preview accepts and rejects the same set the execution
|
|
35
|
+
door does — holds by construction rather than by two call sites agreeing.
|
|
36
|
+
|
|
37
|
+
Who is affected: a deployment whose driver reports `objectqlAggregate` but not
|
|
38
|
+
`nativeSql` (Mongo, the memory driver), running an analytics query that puts a
|
|
39
|
+
related object's field inside `$or` or `$not`. Such a query now returns
|
|
40
|
+
`400 INVALID_FIELD` naming the member. The refusal already existed and already
|
|
41
|
+
had these words; what changed is that the execution door reaches it too. Nothing
|
|
42
|
+
an author writes in metadata changes, no stored shape is affected, and queries
|
|
43
|
+
whose combinators name only base-object fields are untouched — that set is pinned
|
|
44
|
+
in `crossobject-conjunct-refusal.test.ts` alongside the new refusal, because a
|
|
45
|
+
fix that refused every combinator would have looked identical from the refusal
|
|
46
|
+
side alone.
|
|
47
|
+
|
|
48
|
+
The remedy for an affected query is the one the error message has always carried:
|
|
49
|
+
run it on a native-SQL driver, which can join, or drop the cross-object member
|
|
50
|
+
from the filter.
|
|
51
|
+
|
|
52
|
+
<!-- adr-0087: not-required (no-migration-prescription) A runtime query-shape refusal on /analytics/query, not a metadata surface: no authorable key, export or config field is removed or renamed, so `objectstack migrate meta` has nothing to rewrite and an upgrader has no stored shape to convert. The affected input is an ad-hoc request body, and the error itself names the member and the two ways out. -->
|
|
53
|
+
- 13a3dca: **BREAKING**: on the ObjectQL path, a compiled dataset whose definition-level
|
|
54
|
+
`filter` is itself cross-object is now refused by both analytics doors instead
|
|
55
|
+
of reaching `engine.aggregate` with a predicate it cannot join (#10861).
|
|
56
|
+
|
|
57
|
+
PR #10758 gave the dataset's own definition-level `filter` a route onto this
|
|
58
|
+
door for the first time. That route was outside the member view the cross-object
|
|
59
|
+
envelope check judges, so nothing ever saw it:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
dataset: object 'opportunity', include: ['account'],
|
|
63
|
+
filter: { 'account.region': 'West' }
|
|
64
|
+
|
|
65
|
+
before /analytics/query 200, rows -> engine.aggregate received
|
|
66
|
+
{"$and":[{"account.region":"West"}]}
|
|
67
|
+
/analytics/sql 200, SQL
|
|
68
|
+
after both 400 INVALID_FIELD, member "account.region",
|
|
69
|
+
cube "<dataset>"; the engine is never reached
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`engine.aggregate` cannot join. `account.region` is not a column of
|
|
73
|
+
`opportunity`, so on any driver that evaluates the predicate honestly it matches
|
|
74
|
+
nothing, and the widget answered a number that was neither the scoped number nor
|
|
75
|
+
the unscoped one — with no error anywhere. That is the silent mis-bucket #3654's
|
|
76
|
+
loud refusal exists to prevent, arriving through a producer #3654 predates.
|
|
77
|
+
|
|
78
|
+
**Breaking, and argued rather than assumed.** A query that returns `200` with
|
|
79
|
+
rows today starts answering `400`, on a *saved* dataset rather than on anything
|
|
80
|
+
in the request — a dashboard that renders today can start showing an error. That
|
|
81
|
+
is the strongest reading of "breaking" and it is why this is called out here
|
|
82
|
+
rather than filed as a quiet fix. What is *not* lost is any correct answer: the
|
|
83
|
+
rows that stop being served were already wrong, and wrong in the way that hides
|
|
84
|
+
itself. The refusal names the member, names the dataset, and says the same
|
|
85
|
+
definition is valid on a native-SQL deployment, so the operator has somewhere to
|
|
86
|
+
go; the previous behaviour gave them a plausible number and nothing to notice.
|
|
87
|
+
Rejecting the dataset at compile time in `dataset-compiler.ts` was considered and
|
|
88
|
+
not taken (maintainer ruling, 2026-08-22): the compiler cannot see which driver
|
|
89
|
+
will serve the dataset, and the same definition is legal on a native-SQL one.
|
|
90
|
+
|
|
91
|
+
Who is affected: a deployment whose driver reports `objectqlAggregate` but not
|
|
92
|
+
`nativeSql` (Mongo, the memory driver), serving a dataset whose definition-level
|
|
93
|
+
`filter` names a field on a related object. Nothing an author writes changes
|
|
94
|
+
shape, no stored document is rewritten, and an **ordinary** dataset scope
|
|
95
|
+
(`filter: { is_deleted: false }`) still passes both doors and still reaches the
|
|
96
|
+
engine carrying its predicate — that direction is pinned one character away from
|
|
97
|
+
the new refusal in `crossobject-conjunct-refusal.test.ts`, because an
|
|
98
|
+
implementation that refused *every* dataset scope would look identical from the
|
|
99
|
+
refusal side alone and would break every scoped dataset shipping today.
|
|
100
|
+
|
|
101
|
+
<!-- adr-0087: not-required (no-migration-prescription) No authorable surface is
|
|
102
|
+
retired, renamed or re-shaped: `DatasetSchema`'s `filter` key stays exactly as it
|
|
103
|
+
is, every stored dataset document stays valid as written, and the very same
|
|
104
|
+
document remains correct on a native-SQL deployment. There is therefore nothing
|
|
105
|
+
`objectstack migrate meta` could rewrite — a mechanical rewrite would have to
|
|
106
|
+
know which driver will serve the dataset, which is precisely the capability the
|
|
107
|
+
2026-08-22 ruling records as invisible to the compile-time placement. This is a
|
|
108
|
+
query-time refusal on one driver family, not a surface retirement, so the ledger
|
|
109
|
+
has no entry to carry and the upgrade guide has no prescription to print. -->
|
|
110
|
+
|
|
111
|
+
### Patch Changes
|
|
112
|
+
|
|
113
|
+
- 7bf3fb7: Point every documentation link in these packages' published READMEs — and in
|
|
114
|
+
the project `create-objectstack` scaffolds — at the canonical docs origin
|
|
115
|
+
`https://objectstack.ai`, replacing the `docs.objectstack.ai` spelling.
|
|
116
|
+
|
|
117
|
+
Both spellings reach the same pages (the alias redirects to the apex,
|
|
118
|
+
path-preserving), so no link was broken. The reason it needs a release rather
|
|
119
|
+
than an in-repo fix alone: a README ships inside the npm tarball, so the
|
|
120
|
+
version already on npm keeps showing the old host to every reader of the
|
|
121
|
+
package page until a new one is published.
|
|
122
|
+
- 112a8c6: Apply a dataset's definition-level `filter` on the ObjectQL analytics path
|
|
123
|
+
(#10413, phase 1). `/api/v1/analytics/query` served by a driver that reports
|
|
124
|
+
`objectqlAggregate` but not `nativeSql` (MongoDB, the memory driver) reached
|
|
125
|
+
`engine.aggregate` with no `filter` key at all: the dataset's own scope — a
|
|
126
|
+
`filter: { is_deleted: false }` on the dataset definition — was dropped, so
|
|
127
|
+
every measure aggregated the whole table while the dashboard door, on the same
|
|
128
|
+
cube and the same measure names, answered the scoped numbers. The scope is now
|
|
129
|
+
ANDed into the strategy's whole-call filter (never merged key-by-key, so a
|
|
130
|
+
caller's own `where` and the time windows cannot be overwritten by it), and the
|
|
131
|
+
representative SQL echo renders it too.
|
|
132
|
+
|
|
133
|
+
Per-MEASURE `filter`s on this path are still not applied: an
|
|
134
|
+
`engine.aggregate` aggregation is `{ field, method, alias }` and cannot carry a
|
|
135
|
+
predicate of its own. Widening that contract is #10576; lowering the measure
|
|
136
|
+
filters into it is phase 2 of #10413. The native-SQL path already applies both
|
|
137
|
+
(#10298).
|
|
138
|
+
- 6439f8b: Analytics measures are now compiled from everything they declare — `aggregate`, `field` **and** `filter` — on both the dashboard path and `POST /api/v1/analytics/query`.
|
|
139
|
+
|
|
140
|
+
**Reported figures change, and the new ones are the declared ones.** Two corrections, both of which move numbers a dashboard or an API consumer is already reading:
|
|
141
|
+
|
|
142
|
+
- A measure written `{ aggregate: 'count', field: 'some_column' }` used to compile to `COUNT(*)` and count **rows**. It now compiles to `COUNT("some_column")` and counts **non-null values**. Any such measure will report the same number as before or a **smaller** one, and a rate built on top of it (a numerator over a total) will drop accordingly — a "100%" tile whose column was mostly empty was reading its own denominator.
|
|
143
|
+
- `POST /api/v1/analytics/query` used to drop every per-measure `filter`, and the dataset's definition-level `filter` with it, returning unfiltered aggregates under the author's measure names. It now applies both, so the endpoint answers what the dashboard already answered for the same cube. Figures pulled through the API — agent tools, exports, downstream reports — will move to the filtered values; a measure declaring `filter: { stage: 'closed_won' }` stops counting every row.
|
|
144
|
+
|
|
145
|
+
Measures that declare no `field` still compile to `COUNT(*)`, and a cube that is not a compiled dataset (an inferred or manifest cube) emits byte-for-byte the statement it did before. Measure filters lower to portable `CASE WHEN` conditional aggregates rather than `FILTER (WHERE …)`, which MySQL does not have.
|
|
146
|
+
|
|
147
|
+
If a saved figure or a screenshot disagrees with what the platform now reports, the new number is the one the metadata declares.
|
|
148
|
+
- Updated dependencies [6936d07]
|
|
149
|
+
- Updated dependencies [59eb04d]
|
|
150
|
+
- Updated dependencies [9f05b7d]
|
|
151
|
+
- Updated dependencies [3b2af5e]
|
|
152
|
+
- Updated dependencies [7d2d112]
|
|
153
|
+
- Updated dependencies [5fa0d72]
|
|
154
|
+
- Updated dependencies [02b3b07]
|
|
155
|
+
- Updated dependencies [46d34ab]
|
|
156
|
+
- Updated dependencies [914c413]
|
|
157
|
+
- Updated dependencies [55809a0]
|
|
158
|
+
- Updated dependencies [ee2ff45]
|
|
159
|
+
- Updated dependencies [47cd3ec]
|
|
160
|
+
- Updated dependencies [52db1d1]
|
|
161
|
+
- Updated dependencies [5649efb]
|
|
162
|
+
- Updated dependencies [9d7d2de]
|
|
163
|
+
- Updated dependencies [c815c50]
|
|
164
|
+
- Updated dependencies [795ea05]
|
|
165
|
+
- Updated dependencies [2306a76]
|
|
166
|
+
- Updated dependencies [e5ea701]
|
|
167
|
+
- Updated dependencies [a40dcc1]
|
|
168
|
+
- Updated dependencies [def0d3e]
|
|
169
|
+
- Updated dependencies [8d0bb79]
|
|
170
|
+
- Updated dependencies [5acb58d]
|
|
171
|
+
- Updated dependencies [2e3cf95]
|
|
172
|
+
- Updated dependencies [4c93387]
|
|
173
|
+
- Updated dependencies [504c8d5]
|
|
174
|
+
- Updated dependencies [a037f7c]
|
|
175
|
+
- Updated dependencies [3ee8ddf]
|
|
176
|
+
- Updated dependencies [16cef97]
|
|
177
|
+
- Updated dependencies [a79bd35]
|
|
178
|
+
- Updated dependencies [6ceaa4b]
|
|
179
|
+
- Updated dependencies [15ea214]
|
|
180
|
+
- Updated dependencies [de19489]
|
|
181
|
+
- Updated dependencies [c684d00]
|
|
182
|
+
- Updated dependencies [923c424]
|
|
183
|
+
- Updated dependencies [1ec36b7]
|
|
184
|
+
- Updated dependencies [5f2e54c]
|
|
185
|
+
- Updated dependencies [189373b]
|
|
186
|
+
- Updated dependencies [35ad101]
|
|
187
|
+
- Updated dependencies [ceb33a9]
|
|
188
|
+
- Updated dependencies [73d9795]
|
|
189
|
+
- Updated dependencies [8012960]
|
|
190
|
+
- Updated dependencies [f34f56b]
|
|
191
|
+
- Updated dependencies [f399618]
|
|
192
|
+
- Updated dependencies [75e9301]
|
|
193
|
+
- Updated dependencies [2810695]
|
|
194
|
+
- @objectstack/spec@17.2.0
|
|
195
|
+
- @objectstack/core@17.2.0
|
|
196
|
+
- @objectstack/types@17.2.0
|
|
197
|
+
|
|
198
|
+
## 17.1.0
|
|
199
|
+
|
|
200
|
+
### Patch Changes
|
|
201
|
+
|
|
202
|
+
- d09d0fd: Source the comparand-type allow-list and the accepted-set refusal sentence from the shared `@objectstack/spec/data` door instead of re-spelling them locally.
|
|
203
|
+
|
|
204
|
+
`comparand-shape.ts`'s `isBindableComparand` / `isRenderableTextComparand` spelled the same six accepted comparand types (`string | number | bigint | boolean | null | Date`) that `isAcceptedFilterComparand` single-sources for the SQL driver family, and two refusal messages hand-copied the accepted-set sentence. Both predicates now delegate the type membership to the door and quote `ACCEPTED_FILTER_COMPARAND_TYPES_SENTENCE`, matching how `driver-sql` and `driver-turso` consume it.
|
|
205
|
+
|
|
206
|
+
No comparand is accepted or refused differently: the local copies already agreed with the door, and the full accept/refuse matrix is pinned end to end at both analytics filter doors, in three comparand positions each, measured before the change and re-run unchanged after it.
|
|
207
|
+
|
|
208
|
+
One user-visible wording correction falls out of removing the copy: the hand-copied sentence omitted `bigint`, a type both predicates have always accepted and both doors have always compiled, so a refusal message under-described the values it accepts. The message now names the full set. The package-local extras — a binary bindable, and the `undefined` arm both doors already refuse upstream — are unchanged and recorded at their use sites.
|
|
209
|
+
- 0425db9: Published READMEs link to the docs site in the one form that works on npm, on GitHub and on the docs site (#9632)
|
|
210
|
+
|
|
211
|
+
**Seven docs links in these READMEs pointed nowhere.** They were spelled as a repo
|
|
212
|
+
path rooted at `/` — `[Flows](/content/docs/automation/flows.mdx)` — and a README in a
|
|
213
|
+
package's `files` array with `private` unset is rendered on the **npm package page** and
|
|
214
|
+
on **GitHub**, not only in this repository. There a root-relative href resolves against
|
|
215
|
+
`npmjs.com` and `github.com` respectively. It was not a docs-site route either:
|
|
216
|
+
`apps/docs/lib/source.ts` mounts `loader({ baseUrl: '/docs' })` over `content/docs`, so
|
|
217
|
+
the route for that first link is `/docs/automation/flows`, and `apps/docs/redirects.mjs`
|
|
218
|
+
carries no `/content` source that would rescue the written form. Every target page
|
|
219
|
+
existed and every one of them was reachable — only the links were not.
|
|
220
|
+
|
|
221
|
+
All seven now use the absolute form the repo had already established in
|
|
222
|
+
`create-objectstack`'s published READMEs: `https://docs.objectstack.ai/docs/...`, with
|
|
223
|
+
the path taken under `content/docs` and the page extension dropped, because the route
|
|
224
|
+
carries none. Each target was re-verified at the route level rather than as a file — the
|
|
225
|
+
two that named a **directory** (`/content/docs/automation/`,
|
|
226
|
+
`/content/docs/references/automation/`) resolve only because those directories carry an
|
|
227
|
+
`index.mdx`; a directory without one is a 404, not a section.
|
|
228
|
+
|
|
229
|
+
**Two more links in the same class were converted in the same pass.**
|
|
230
|
+
`service-knowledge` and `knowledge-ragflow` pointed at
|
|
231
|
+
`../../../content/docs/protocol/knowledge.mdx`. Those relative paths do resolve on both
|
|
232
|
+
GitHub and npm, so they are a milder defect than the seven — but they land the reader on
|
|
233
|
+
**raw MDX source** instead of the rendered page. They now point at the rendered page as
|
|
234
|
+
well. `service-knowledge`'s link text changed with it: it was the source filename in a
|
|
235
|
+
code span, which stops being an honest label once the destination is the page.
|
|
236
|
+
|
|
237
|
+
No API, behaviour or type surface changes — this is the published documentation these
|
|
238
|
+
packages ship.
|
|
239
|
+
- f01c0ee: docs: five published service READMEs stop documenting an API that does not exist (#9532)
|
|
240
|
+
|
|
241
|
+
A version bump is the point, not a side effect: these five READMEs are in their
|
|
242
|
+
packages' `files` arrays with `private` unset, so they are the pages npm renders —
|
|
243
|
+
and a docs-only fix with no bump never reaches npm at all.
|
|
244
|
+
|
|
245
|
+
Each of the five told a reader to an import of a `Service…` class from its own package
|
|
246
|
+
and call a static `.configure({...})` on it. Neither has ever existed: no class in
|
|
247
|
+
this repo exposes a static `configure`, and none of `ServiceAnalytics`,
|
|
248
|
+
`ServiceAutomation`, `ServiceCache`, `ServiceI18n` or `ServiceJob` is exported by
|
|
249
|
+
anything. A reader following any of them wrote code that could not compile. The real
|
|
250
|
+
entry point in every case is a kernel plugin constructed with `new`:
|
|
251
|
+
`AnalyticsServicePlugin`, `AutomationServicePlugin`, `CacheServicePlugin`,
|
|
252
|
+
`I18nServicePlugin`, `JobServicePlugin`.
|
|
253
|
+
|
|
254
|
+
⛔ A name swap alone would not have been enough, and the gate landed in #9546 is what
|
|
255
|
+
proves it: substituting the genuine class while keeping `.configure(...)` turns the
|
|
256
|
+
import finding into a call-site finding rather than into silence. Each README is
|
|
257
|
+
rewritten against the package's built type surface, and each package's entry is
|
|
258
|
+
deleted from `scripts/published-readme-exports.baseline.json` in the same change
|
|
259
|
+
(the baseline is reconciled in both directions, so a stale entry fails too).
|
|
260
|
+
|
|
261
|
+
What was removed as fabricated, beyond the entry point:
|
|
262
|
+
|
|
263
|
+
- **service-analytics** — a nine-endpoint REST surface (`/analytics/count`, `/sum`,
|
|
264
|
+
`/avg`, `/min`, `/max`, `/group-by`, `/time-series`, `/metrics`, `/metrics/:name`)
|
|
265
|
+
of which none exists; the real surface is `POST /analytics/query`,
|
|
266
|
+
`GET /analytics/meta`, `POST /analytics/sql` and `POST /analytics/dataset/query`.
|
|
267
|
+
Also removed: `defineMetric`, `getMetric`, `compare`, `funnel`,
|
|
268
|
+
`executeDashboard`, `invalidateCache`, and an `AnalyticsServiceConfig` block whose
|
|
269
|
+
four keys (`defaultDriver`, `enableCaching`, `cacheTTL`, `maxMemoryResults`) are
|
|
270
|
+
none of the real ones.
|
|
271
|
+
- **service-automation** — `executeFlow`/`getFlow`/`listFlows`/`getFlowHistory`/
|
|
272
|
+
`registerTrigger` as the contract (the real contract is `execute(flowName, context?)`
|
|
273
|
+
plus `listFlows()` and a set of optional members), and a five-endpoint REST list that
|
|
274
|
+
matches no mounted route. The flow-authoring half of that README was already accurate
|
|
275
|
+
and is kept.
|
|
276
|
+
- **service-cache** — `mget`/`mset`/`del`/`delPattern`/`namespace`/`ttl`/`expire`/
|
|
277
|
+
`persist`/`incr`/`incrby`/`decr`/`getOrSet`/`invalidateTag`/`resetStats`, none of
|
|
278
|
+
which exist; `ICacheService` has six members. `CacheStats.keys`/`hitRate` corrected to
|
|
279
|
+
`keyCount` (there is no `hitRate`), and `set(key, value, { ttl })` corrected to the
|
|
280
|
+
real positional `set(key, value, ttl?)` in seconds.
|
|
281
|
+
- **service-i18n** — an `await i18n.t('ns:key')` dialect with namespaces, plural
|
|
282
|
+
suffixes, `context`, `returnObjects`, `setLocale`/`getLocale`, `formatDate`/
|
|
283
|
+
`formatNumber`/`formatRelative`, `addLocale`/`removeLocale`/`reload`, `getCoverage`/
|
|
284
|
+
`getMissingKeys`, and a `{{lng}}/{{ns}}` file layout. The real `t()` is synchronous
|
|
285
|
+
and takes the locale positionally — `t(key, locale, params?)` — over one
|
|
286
|
+
`{locale}.json` file per locale. The `POST /i18n/translate` endpoint does not exist.
|
|
287
|
+
- **service-job** — `scheduleInterval`/`scheduleOnce`/`getJob`/`stopJob`/`resumeJob`/
|
|
288
|
+
`deleteJob`/`runNow`/`getJobHistory`/`clearHistory`/`getLastExecution`, and a
|
|
289
|
+
`schedule({ name, schedule, handler })` options-object call. The real `schedule` is
|
|
290
|
+
positional — `schedule(name, schedule, handler, options?)` — and returns `void`.
|
|
291
|
+
Retry defaults corrected to the enforced ones (`maxRetries: 0`,
|
|
292
|
+
`backoffMultiplier: 1`).
|
|
293
|
+
|
|
294
|
+
Two capability claims are corrected rather than deleted, because the source is what
|
|
295
|
+
decides:
|
|
296
|
+
|
|
297
|
+
- **service-cache** advertised Redis as production support. `RedisCacheAdapter` throws
|
|
298
|
+
`RedisCacheAdapter not yet implemented` from every method, and
|
|
299
|
+
`new CacheServicePlugin({ adapter: 'redis' })` throws during `init` rather than
|
|
300
|
+
falling back to memory. The README now says so at the top and points at registering
|
|
301
|
+
a custom `ICacheService` under the slot instead.
|
|
302
|
+
- **service-job**'s `adapter: 'interval'` stores cron registrations that never fire.
|
|
303
|
+
That is now stated in the adapter table rather than left for a reader to discover.
|
|
304
|
+
|
|
305
|
+
No compliance claim (SOC 2 / HIPAA / GDPR or similar) was found in any of the five —
|
|
306
|
+
the shape that raised `plugin-audit`'s severity in #9517 is absent here.
|
|
307
|
+
- 402c125: fix(objectql): a temporal filter comparand the platform cannot interpret is refused at the engine door instead of answering 200 with zero rows (#8690)
|
|
308
|
+
|
|
309
|
+
<!-- adr-0087: not-required (no-migration-prescription) Nothing authorable is
|
|
310
|
+
renamed, retired or tombstoned — no spec schema is touched at all. The change
|
|
311
|
+
is a new runtime refusal at the engine's filter collection point, plus the
|
|
312
|
+
routing decline that stops the raw-SQL analytics path bypassing it. -->
|
|
313
|
+
|
|
314
|
+
A `datetime` / `date` / `time` field filtered with a bare string the platform
|
|
315
|
+
cannot read — `last_30_days`, `not-a-date-at-all` — was bound **as written**
|
|
316
|
+
all the way to the driver, where the comparison is false for every row. The
|
|
317
|
+
caller received `HTTP 200`, an empty result set, and nothing to indicate the
|
|
318
|
+
filter was meaningless. An unknown `{placeholder}` in the same position was
|
|
319
|
+
already refused loudly (`FILTER_TOKEN_UNKNOWN` / 400, listing the resolvable
|
|
320
|
+
tokens), so one API answered two shapes of unusable comparand two different
|
|
321
|
+
ways.
|
|
322
|
+
|
|
323
|
+
It is concretely reachable rather than theoretical: `last_7_days` /
|
|
324
|
+
`last_30_days` / `last_90_days` are **declared preset names** in the dashboard
|
|
325
|
+
schema. The shipped console lowers them to `{N_days_ago}` macros before they
|
|
326
|
+
reach the API, so the console path was always safe — but a saved report, an
|
|
327
|
+
integration, an MCP client or an AI-authored query sends the preset name itself
|
|
328
|
+
and got a silent zero. An empty chart is the hardest failure to debug: it is
|
|
329
|
+
indistinguishable from "there is genuinely no data".
|
|
330
|
+
|
|
331
|
+
Such a comparand is now refused at the ObjectQL engine's single filter
|
|
332
|
+
collection point, with `code: 'INVALID_FILTER'` and `status: 400`, naming the
|
|
333
|
+
field, the value, the key path and the spellings that would work. That seam is
|
|
334
|
+
the one place holding the caller's comparand and the field's **declared type**
|
|
335
|
+
at the same moment, and every verb (`find` / `findOne` / `count` / `aggregate`
|
|
336
|
+
/ `update` / `delete`) and both filter spellings (the array sugar and the
|
|
337
|
+
lowered condition) pass through it, so all four backends inherit one answer
|
|
338
|
+
rather than four. `NativeSQLStrategy` additionally **declines** such a query so
|
|
339
|
+
the raw-SQL analytics path falls through to that door instead of binding the
|
|
340
|
+
value into its own statement.
|
|
341
|
+
|
|
342
|
+
Deliberately unchanged, each by ruling: a `{placeholder}` keeps its existing
|
|
343
|
+
refusal one layer down (the door runs before token resolution and steps around
|
|
344
|
+
them, so `{30_days_ago}` still resolves normally); non-string comparands are
|
|
345
|
+
untouched (a number is epoch milliseconds, a `Date` is an instant); and the
|
|
346
|
+
**empty string** keeps today's behaviour exactly — it binds as `''` and matches
|
|
347
|
+
every non-null row, which is a separate question that remains its own card.
|
|
348
|
+
- Updated dependencies [56656aa]
|
|
349
|
+
- Updated dependencies [07e630e]
|
|
350
|
+
- Updated dependencies [2f65b1b]
|
|
351
|
+
- Updated dependencies [720ee95]
|
|
352
|
+
- Updated dependencies [f287435]
|
|
353
|
+
- Updated dependencies [2782805]
|
|
354
|
+
- Updated dependencies [e43d63a]
|
|
355
|
+
- Updated dependencies [9aa8890]
|
|
356
|
+
- Updated dependencies [7c9c1dd]
|
|
357
|
+
- Updated dependencies [75b7c24]
|
|
358
|
+
- Updated dependencies [d5552ca]
|
|
359
|
+
- Updated dependencies [d9813a9]
|
|
360
|
+
- Updated dependencies [8640fb2]
|
|
361
|
+
- Updated dependencies [2420641]
|
|
362
|
+
- Updated dependencies [2ad91c3]
|
|
363
|
+
- Updated dependencies [f57fb38]
|
|
364
|
+
- Updated dependencies [00777a0]
|
|
365
|
+
- Updated dependencies [d491625]
|
|
366
|
+
- Updated dependencies [2d0af57]
|
|
367
|
+
- Updated dependencies [420804d]
|
|
368
|
+
- Updated dependencies [716ac9b]
|
|
369
|
+
- Updated dependencies [a38408a]
|
|
370
|
+
- Updated dependencies [62b1427]
|
|
371
|
+
- Updated dependencies [7ea1372]
|
|
372
|
+
- Updated dependencies [23abe27]
|
|
373
|
+
- Updated dependencies [985a9cd]
|
|
374
|
+
- Updated dependencies [5f5e234]
|
|
375
|
+
- Updated dependencies [a8189ae]
|
|
376
|
+
- Updated dependencies [26e70fb]
|
|
377
|
+
- Updated dependencies [27a567d]
|
|
378
|
+
- Updated dependencies [42b05af]
|
|
379
|
+
- Updated dependencies [2b292ce]
|
|
380
|
+
- Updated dependencies [abcf853]
|
|
381
|
+
- Updated dependencies [8b9eba5]
|
|
382
|
+
- Updated dependencies [d575779]
|
|
383
|
+
- Updated dependencies [94f7ef8]
|
|
384
|
+
- Updated dependencies [c5ac5e4]
|
|
385
|
+
- Updated dependencies [a777944]
|
|
386
|
+
- Updated dependencies [dd88e1c]
|
|
387
|
+
- Updated dependencies [856527c]
|
|
388
|
+
- Updated dependencies [870f710]
|
|
389
|
+
- Updated dependencies [79c46da]
|
|
390
|
+
- Updated dependencies [7ff3975]
|
|
391
|
+
- Updated dependencies [29d055b]
|
|
392
|
+
- Updated dependencies [65589d6]
|
|
393
|
+
- Updated dependencies [2c86fe3]
|
|
394
|
+
- Updated dependencies [e196c6a]
|
|
395
|
+
- Updated dependencies [24173e9]
|
|
396
|
+
- Updated dependencies [4ab7523]
|
|
397
|
+
- Updated dependencies [19539b4]
|
|
398
|
+
- Updated dependencies [f8eb736]
|
|
399
|
+
- Updated dependencies [11b779e]
|
|
400
|
+
- Updated dependencies [739fe5b]
|
|
401
|
+
- Updated dependencies [4bfe1a5]
|
|
402
|
+
- Updated dependencies [2065e31]
|
|
403
|
+
- Updated dependencies [b69d0f5]
|
|
404
|
+
- Updated dependencies [4d47afe]
|
|
405
|
+
- Updated dependencies [e4e5c6e]
|
|
406
|
+
- Updated dependencies [9a56784]
|
|
407
|
+
- Updated dependencies [d00d2f6]
|
|
408
|
+
- Updated dependencies [df0c12d]
|
|
409
|
+
- Updated dependencies [d31785f]
|
|
410
|
+
- Updated dependencies [c308a4f]
|
|
411
|
+
- Updated dependencies [e2899f6]
|
|
412
|
+
- Updated dependencies [3851f87]
|
|
413
|
+
- Updated dependencies [2a29caa]
|
|
414
|
+
- Updated dependencies [09a6eee]
|
|
415
|
+
- Updated dependencies [1a7f907]
|
|
416
|
+
- Updated dependencies [cd455c8]
|
|
417
|
+
- Updated dependencies [e1bb0ca]
|
|
418
|
+
- Updated dependencies [30d3752]
|
|
419
|
+
- Updated dependencies [c80e7ae]
|
|
420
|
+
- Updated dependencies [09a9a8a]
|
|
421
|
+
- Updated dependencies [07026cf]
|
|
422
|
+
- Updated dependencies [5d4f3d5]
|
|
423
|
+
- Updated dependencies [4d80e8b]
|
|
424
|
+
- Updated dependencies [30b1c63]
|
|
425
|
+
- Updated dependencies [079b457]
|
|
426
|
+
- Updated dependencies [e43b211]
|
|
427
|
+
- Updated dependencies [890b38f]
|
|
428
|
+
- Updated dependencies [8bee54b]
|
|
429
|
+
- Updated dependencies [7a537ce]
|
|
430
|
+
- Updated dependencies [593c4bf]
|
|
431
|
+
- Updated dependencies [ff08691]
|
|
432
|
+
- Updated dependencies [60e0f90]
|
|
433
|
+
- Updated dependencies [90c5285]
|
|
434
|
+
- Updated dependencies [402c125]
|
|
435
|
+
- Updated dependencies [7901b2d]
|
|
436
|
+
- Updated dependencies [56bca91]
|
|
437
|
+
- Updated dependencies [79394d7]
|
|
438
|
+
- Updated dependencies [730fd9a]
|
|
439
|
+
- Updated dependencies [44bc51d]
|
|
440
|
+
- Updated dependencies [bbbfcfc]
|
|
441
|
+
- Updated dependencies [73cfddf]
|
|
442
|
+
- Updated dependencies [d634e66]
|
|
443
|
+
- @objectstack/spec@17.1.0
|
|
444
|
+
- @objectstack/types@17.1.0
|
|
445
|
+
- @objectstack/core@17.1.0
|
|
446
|
+
|
|
3
447
|
## 17.0.0
|
|
4
448
|
|
|
5
449
|
### Major Changes
|