@happyvertical/smrt-facts 0.49.2 → 0.49.4
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/README.md +122 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +229 -39
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +466 -45
- package/dist/smrt-knowledge.json +58 -9
- package/dist/types.d.ts +4 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -121,3 +121,125 @@ const briefing = await facts.getEntityBriefing('Place', placeId);
|
|
|
121
121
|
|
|
122
122
|
See [`AGENTS.md`](./AGENTS.md) for package architecture, invariants, validation,
|
|
123
123
|
and contributor guidance.
|
|
124
|
+
|
|
125
|
+
## Catalog pagination
|
|
126
|
+
|
|
127
|
+
`browseCatalog(query, { limit, offset, latestOnly, tenantId })` defaults to 25
|
|
128
|
+
results and resolves evolution chains unless `latestOnly: false` is supplied.
|
|
129
|
+
Empty-query browsing and successful semantic search hydrate only the requested
|
|
130
|
+
SQL page. Empty-query candidate eligibility remains bounded to
|
|
131
|
+
`offset + 2 * limit`; resolving and deduplicating chains can therefore return a
|
|
132
|
+
short page. Semantic candidate retrieval remains bounded to `offset + limit`.
|
|
133
|
+
These SQL windows use the requested pagination values rather than the collection
|
|
134
|
+
`defaultListLimit`. The full tenant-visible successor set is available to chain resolution, even
|
|
135
|
+
when a successor lies outside the candidate window or status filter. Implicit-scope
|
|
136
|
+
confidence ties retain the newest successor, matching the previous ordered read.
|
|
137
|
+
|
|
138
|
+
Without an explicit tenant, the default candidate status is `active`; with an
|
|
139
|
+
explicit tenant, tenant and global candidates exclude only `superseded`.
|
|
140
|
+
`includeSuperseded` removes that status filter. Active tenant context continues
|
|
141
|
+
to constrain implicit reads, and requesting another tenant is rejected. STI child
|
|
142
|
+
collections constrain both candidates and successors to their discriminator.
|
|
143
|
+
|
|
144
|
+
Catalog reads apply normal `beforeList` authorization predicates to candidates,
|
|
145
|
+
readiness checks, and the complete successor graph, including empty queries.
|
|
146
|
+
Explicit tenant/global reads use `withTenantGlobalRead`, which grants the built-in
|
|
147
|
+
tenancy list hook that narrow scope while business hooks keep the original user,
|
|
148
|
+
permissions, tenant context, and system/non-system identity. Actual system calls
|
|
149
|
+
remain system calls. Interceptor rejection and application SQL failures propagate
|
|
150
|
+
to the caller.
|
|
151
|
+
Only unavailable embedding configuration or query-embedding provider failure
|
|
152
|
+
permits text fallback; a semantic authorization failure never does.
|
|
153
|
+
|
|
154
|
+
When query embeddings are unavailable, text fallback matches the exact JavaScript
|
|
155
|
+
expression `` `${textRefined} ${textRaw}`.toLowerCase().includes(query.toLowerCase()) ``.
|
|
156
|
+
It uses persisted `catalogSearch` storage and the same bounded SQL page traversal.
|
|
157
|
+
The storage encodes lowercased UTF-16 units as aligned ASCII tokens, preserving
|
|
158
|
+
Unicode, whitespace, literal wildcard characters, and code-unit boundaries without
|
|
159
|
+
SQL collation or case-folding differences. A scalar readiness check precedes text
|
|
160
|
+
search; it throws with backfill instructions if any permitted row is unbackfilled.
|
|
161
|
+
The database may scan matching rows and evolution edges internally, but only the
|
|
162
|
+
requested Fact page crosses the database boundary. Arbitrary substring matching
|
|
163
|
+
cannot use a normal B-tree index; no misleading search-column index is added.
|
|
164
|
+
|
|
165
|
+
### Existing deployment migration
|
|
166
|
+
|
|
167
|
+
This is an explicit schema and data migration; ordinary reads never create schema.
|
|
168
|
+
Stop old application writers, deploy the new manifest/code, and run `smrt db:migrate`
|
|
169
|
+
(and `smrt db:status --parity`). This adds nullable `catalog_search`; historical
|
|
170
|
+
rows remain NULL. Before enabling catalog text reads, run the following with your
|
|
171
|
+
application's database configuration and repeat until `remaining` is zero:
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import { FactCollection } from '@happyvertical/smrt-facts';
|
|
175
|
+
import { withSystemContext } from '@happyvertical/smrt-tenancy';
|
|
176
|
+
|
|
177
|
+
const facts = await FactCollection.create({ db });
|
|
178
|
+
await withSystemContext(async () => {
|
|
179
|
+
while ((await facts.backfillCatalogSearch(100)).remaining > 0) {
|
|
180
|
+
// Each call is independently resumable; record progress in your job runner.
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Backfill requires explicit system context, processes at most 100 rows per call by
|
|
186
|
+
default (maximum 1000), and updates only NULL values whose source texts still match
|
|
187
|
+
the read snapshot. It changes no source text, timestamps, embeddings, or revisions.
|
|
188
|
+
A crash or concurrent write is safe to retry; sustained old writers must be stopped
|
|
189
|
+
so the operation can finish. A subtype collection backfills only its discriminator;
|
|
190
|
+
use the base collection for the full deployment. Back up before schema changes;
|
|
191
|
+
roll forward by rerunning migration/backfill rather than dropping historical data.
|
|
192
|
+
|
|
193
|
+
`Fact.save()`, collection create/get-or-insert/get-or-upsert, and generated model
|
|
194
|
+
updates maintain search storage from the source fields. `catalogSearch` is derived;
|
|
195
|
+
callers must not author it, and generated transport surfaces exclude it using
|
|
196
|
+
readonly/sensitive field metadata. Derivation uses the final persistence row after
|
|
197
|
+
mutable `beforeSave` hooks and the complete subclass `transformJSON` chain,
|
|
198
|
+
keeping persisted text and search storage consistent. If a custom transform omits a source column, returns a value other than a string
|
|
199
|
+
or explicit NULL, or returns a string with an unpaired UTF-16 surrogate, the write
|
|
200
|
+
retains its ordinary adapter semantics (retention, defaults, or coercion); search storage is
|
|
201
|
+
invalidated to NULL rather than guessed from instance values. Catalog text reads
|
|
202
|
+
then fail with the readiness error until privileged bounded backfill reads the
|
|
203
|
+
actual persisted columns. Run that backfill after such custom writes before
|
|
204
|
+
resuming catalog text reads. Only well-formed strings and explicit NULL source
|
|
205
|
+
values are known before persistence. Query encoding remains exact UTF-16
|
|
206
|
+
code-unit matching, including queries containing an unpaired surrogate.
|
|
207
|
+
Plain/public serialization
|
|
208
|
+
retains the saved marker instead of recomputing from pre-transform instance text.
|
|
209
|
+
Direct SQL writers must set `catalog_search = NULL` whenever either source text
|
|
210
|
+
changes, then run backfill before text reads resume.
|
|
211
|
+
If a custom writer or a pre-release implementation produced a known stale
|
|
212
|
+
non-NULL search value, explicitly set that affected row's `catalog_search` to
|
|
213
|
+
NULL and run the same bounded backfill. Backfill intentionally selects NULL
|
|
214
|
+
markers; it does not scan or repair non-NULL values. This is a targeted repair,
|
|
215
|
+
not an additional step for a fresh column migration.
|
|
216
|
+
|
|
217
|
+
Do not keep old application writers active after backfill: they cannot maintain
|
|
218
|
+
this new invariant. Future changes to JavaScript lowercasing/encoding require an
|
|
219
|
+
explicit new backfill; the format is not locale dependent.
|
|
220
|
+
|
|
221
|
+
PostgreSQL and SQLite run canonical pagination integration tests. DuckDB query
|
|
222
|
+
coverage uses an explicitly identified SQL-only fixture: canonical Fact schema
|
|
223
|
+
creation currently rejects its evolution self-reference, tracked in
|
|
224
|
+
[#2830](https://github.com/happyvertical/smrt/issues/2830). DuckDB hydration issues
|
|
225
|
+
one `DESCRIBE` plus one data query per page; PostgreSQL and SQLite use one data
|
|
226
|
+
query after semantic candidate retrieval, if any. Text fallback also performs one
|
|
227
|
+
scalar readiness query.
|
|
228
|
+
|
|
229
|
+
Catalog reads preserve hydrated `afterList` policies on every final bounded page,
|
|
230
|
+
including empty pages, after raw-query hooks and similarity annotations. The same
|
|
231
|
+
list context and original caller identity reach before/after hooks. Filtering or
|
|
232
|
+
redaction may shorten a page; browsing does not refill it. Policy rejection
|
|
233
|
+
propagates and never triggers the embedding-unavailable text fallback.
|
|
234
|
+
|
|
235
|
+
Text fallback is selected by core's provider-origin availability result: only
|
|
236
|
+
missing embedding configuration or a failed query embedding enables it. Errors
|
|
237
|
+
from authorization, ranking, SQL, hydration, or result hooks propagate unchanged,
|
|
238
|
+
even when a caller throws `EmbeddingUnavailableError` from those operations.
|
|
239
|
+
|
|
240
|
+
Catalog text-read readiness preserves database permission, transient and unknown
|
|
241
|
+
errors unchanged. Only recognized missing-table/column driver diagnostics receive
|
|
242
|
+
the migration/backfill instruction, retaining the original error as cause.
|
|
243
|
+
`Fact` declares `catalog_search` as its permitted final derived column; custom
|
|
244
|
+
normalization must preserve that declaration and cannot replace framework
|
|
245
|
+
identity, tenant, revision or conflict columns.
|
package/dist/index.d.ts
CHANGED
|
@@ -48,6 +48,8 @@ export declare type EvolutionType = 'original' | 'correction' | 'refinement' | '
|
|
|
48
48
|
export declare class Fact extends SmrtObject {
|
|
49
49
|
textRefined: string;
|
|
50
50
|
textRaw: string;
|
|
51
|
+
/** Derived search storage. NULL requires the explicit catalog backfill. */
|
|
52
|
+
catalogSearch: string | null;
|
|
51
53
|
type: string;
|
|
52
54
|
status: string;
|
|
53
55
|
domain: string;
|
|
@@ -68,6 +70,8 @@ export declare class Fact extends SmrtObject {
|
|
|
68
70
|
createdAt: Date;
|
|
69
71
|
updatedAt: Date;
|
|
70
72
|
constructor(options?: FactOptions);
|
|
73
|
+
protected getPersistenceDerivedColumns(): readonly string[];
|
|
74
|
+
protected normalizePersistenceData(data: Readonly<Record<string, unknown>>): Record<string, unknown>;
|
|
71
75
|
getMetadata(): FactMetadata;
|
|
72
76
|
setMetadata(data: FactMetadata): void;
|
|
73
77
|
updateMetadata(updates: Partial<FactMetadata>): void;
|
|
@@ -132,6 +136,11 @@ export declare type FactClaimSupportStatus = 'supported' | 'unsupported' | 'cont
|
|
|
132
136
|
|
|
133
137
|
export declare class FactCollection extends SmrtCollection<Fact> {
|
|
134
138
|
static readonly _itemClass: typeof Fact;
|
|
139
|
+
/**
|
|
140
|
+
* Fetch one catalog page in SQL. The recursive branch walk may inspect more
|
|
141
|
+
* rows inside the database, but the outer query only materializes the page.
|
|
142
|
+
*/
|
|
143
|
+
private listCatalogPage;
|
|
135
144
|
/**
|
|
136
145
|
* Get all active facts
|
|
137
146
|
*/
|
|
@@ -189,6 +198,15 @@ export declare class FactCollection extends SmrtCollection<Fact> {
|
|
|
189
198
|
includeSuperseded?: boolean;
|
|
190
199
|
latestOnly?: boolean;
|
|
191
200
|
}): Promise<Fact[]>;
|
|
201
|
+
/**
|
|
202
|
+
* Explicit data migration after db:migrate adds catalog_search. Run under
|
|
203
|
+
* withSystemContext with old writers stopped. Repeating a batch is safe;
|
|
204
|
+
* concurrent source changes are protected by compare-and-set predicates.
|
|
205
|
+
* A subtype collection backfills only its STI discriminator.
|
|
206
|
+
*/
|
|
207
|
+
backfillCatalogSearch(batchSize?: number): Promise<{
|
|
208
|
+
remaining: number;
|
|
209
|
+
}>;
|
|
192
210
|
/**
|
|
193
211
|
* Get all facts linked to a content item.
|
|
194
212
|
*/
|