@oh-my-pi/pi-catalog 18.7.0 → 18.8.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 +6 -0
- package/dist/types/compat/taxonomy.d.ts +0 -10
- package/package.json +4 -4
- package/src/compat/cascade.ts +6 -1
- package/src/compat/taxonomy.ts +3 -3
- package/src/model-cache.ts +113 -47
package/CHANGELOG.md
CHANGED
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Runtime model-identity taxonomy: classifies wire model ids into
|
|
3
|
-
* `(class, family, revision)` ranks, applies reviewed identity overrides, and
|
|
4
|
-
* exposes the collapse/discovery vocabularies.
|
|
5
|
-
*
|
|
6
|
-
* Faithful port of the o2 reference (`taxonomy.rs` / `classify.rs`):
|
|
7
|
-
* matchers rank `exact(4) > bounded(3) > namespace(2) > prefix(1) > glob(0)`
|
|
8
|
-
* with token byte length as tiebreak; equal cross-class or cross-family ranks
|
|
9
|
-
* throw unless classification is `lenient` (discovery normalization).
|
|
10
|
-
*/
|
|
11
1
|
import type { Effort } from "../effort.js";
|
|
12
2
|
import type { ModelIdentity } from "./types.js";
|
|
13
3
|
/** Two classes, families, or reviewed override patterns tie for one model id. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-catalog",
|
|
3
|
-
"version": "18.
|
|
3
|
+
"version": "18.8.0",
|
|
4
4
|
"description": "Model catalog for omp: bundled model database, provider discovery descriptors, model identity, classification, and equivalence",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -93,12 +93,12 @@
|
|
|
93
93
|
"gen:proto": "bun scripts/generate-protocols.ts"
|
|
94
94
|
},
|
|
95
95
|
"dependencies": {
|
|
96
|
-
"@oh-my-pi/omptype": "18.
|
|
97
|
-
"@oh-my-pi/pi-utils": "18.
|
|
96
|
+
"@oh-my-pi/omptype": "18.8.0",
|
|
97
|
+
"@oh-my-pi/pi-utils": "18.8.0"
|
|
98
98
|
},
|
|
99
99
|
"devDependencies": {
|
|
100
100
|
"@bgotink/kdl": "0.4.0",
|
|
101
|
-
"@oh-my-pi/pi-ai": "18.
|
|
101
|
+
"@oh-my-pi/pi-ai": "18.8.0",
|
|
102
102
|
"@types/bun": "^1.3.14"
|
|
103
103
|
},
|
|
104
104
|
"engines": {
|
package/src/compat/cascade.ts
CHANGED
|
@@ -288,7 +288,12 @@ function collect(winners: WinnerTable, pick: (rule: CompiledRule) => Record<stri
|
|
|
288
288
|
return out;
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
-
|
|
291
|
+
/**
|
|
292
|
+
* Sized above the bundled catalog (~5.6k targets) plus discovered rows so a
|
|
293
|
+
* catalog-wide buildModel pass (discovery, then merge rebuilding the same ids)
|
|
294
|
+
* hits instead of thrashing. Entries share rule values: a few hundred bytes each.
|
|
295
|
+
*/
|
|
296
|
+
const resolveCache = new LRUCache<string, ResolvedAxes>({ max: 16384 });
|
|
292
297
|
|
|
293
298
|
function keyPart(value: string | undefined): string {
|
|
294
299
|
return value === undefined ? "-1:" : `${value.length}:${value}`;
|
package/src/compat/taxonomy.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* with token byte length as tiebreak; equal cross-class or cross-family ranks
|
|
9
9
|
* throw unless classification is `lenient` (discovery normalization).
|
|
10
10
|
*/
|
|
11
|
+
import { LRUCache } from "@oh-my-pi/pi-utils/lru";
|
|
11
12
|
import type { Effort } from "../effort";
|
|
12
13
|
import { globMatch } from "./cascade";
|
|
13
14
|
import { formatRevision, parseRevisionPrefix } from "./revision";
|
|
@@ -433,8 +434,8 @@ export function stripThinkingVariantSuffix(model: string): string | undefined {
|
|
|
433
434
|
* @throws AmbiguousIdentityError on equal-rank cross-class, cross-family, or
|
|
434
435
|
* reviewed-pattern matches unless `opts.lenient`.
|
|
435
436
|
*/
|
|
436
|
-
|
|
437
|
-
const
|
|
437
|
+
// LRU sized above the bundled catalog so catalog-wide passes stay hot (a full `clear()` thrashed them).
|
|
438
|
+
const classifyMemo = new LRUCache<string, ModelIdentity>({ max: 16384 });
|
|
438
439
|
|
|
439
440
|
function classifyMemoKey(provider: string, modelId: string, lenient: boolean): string {
|
|
440
441
|
return `${provider.length}:${provider}${modelId.length}:${modelId}${lenient ? 1 : 0}`;
|
|
@@ -446,7 +447,6 @@ export function classifyModel(provider: string, modelId: string, opts?: Classify
|
|
|
446
447
|
const cached = classifyMemo.get(key);
|
|
447
448
|
if (cached !== undefined) return { ...cached };
|
|
448
449
|
const identity = classifyModelUncached(provider, modelId, opts);
|
|
449
|
-
if (classifyMemo.size >= CLASSIFY_MEMO_MAX) classifyMemo.clear();
|
|
450
450
|
classifyMemo.set(key, Object.freeze(identity));
|
|
451
451
|
return { ...identity };
|
|
452
452
|
}
|
package/src/model-cache.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Replaces per-provider JSON files with a single cache.db.
|
|
4
4
|
*/
|
|
5
5
|
import type { Database } from "bun:sqlite";
|
|
6
|
+
import * as path from "node:path";
|
|
6
7
|
import { getModelDbPath, isSqliteCorruptionError, logger, openSqliteDatabaseSync, VERSION } from "@oh-my-pi/pi-utils";
|
|
7
8
|
import RULES from "./compat/rules.json" with { type: "json" };
|
|
8
9
|
import type { Api, Model } from "./types";
|
|
@@ -46,14 +47,19 @@ function materializationPolicy(): string {
|
|
|
46
47
|
return cachedMaterializationPolicy;
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
interface
|
|
50
|
+
interface CacheRowMeta {
|
|
50
51
|
provider_id: string;
|
|
51
52
|
version: number;
|
|
52
53
|
materialization_policy: string;
|
|
53
54
|
updated_at: number;
|
|
54
55
|
authoritative: number;
|
|
55
56
|
static_fingerprint: string;
|
|
56
|
-
|
|
57
|
+
/**
|
|
58
|
+
* `Bun.hash` of `models`; empty for rows written before the column existed
|
|
59
|
+
* (never equal). An in-place `UPDATE` of `models` that leaves the hash
|
|
60
|
+
* untouched resets it to empty (`model_cache_models_hash_reset` trigger).
|
|
61
|
+
*/
|
|
62
|
+
models_hash: string;
|
|
57
63
|
header_omitted_model_ids: string;
|
|
58
64
|
unrestorable_header_model_ids: string;
|
|
59
65
|
header_restore_version: number;
|
|
@@ -63,6 +69,10 @@ interface CacheRow {
|
|
|
63
69
|
refresh_authoritative: number | null;
|
|
64
70
|
}
|
|
65
71
|
|
|
72
|
+
interface CacheRow extends CacheRowMeta {
|
|
73
|
+
models: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
66
76
|
/**
|
|
67
77
|
* Payload rows are multi-MB and SQLite rewrites a whole record (including its
|
|
68
78
|
* overflow pages) on any column update. Freshness that advances without a
|
|
@@ -71,15 +81,23 @@ interface CacheRow {
|
|
|
71
81
|
* the payload row's `updated_at`, so a payload rewritten by another binary
|
|
72
82
|
* silently invalidates it.
|
|
73
83
|
*/
|
|
74
|
-
const
|
|
75
|
-
SELECT m.*,
|
|
76
|
-
r.payload_updated_at AS refresh_payload_updated_at,
|
|
77
|
-
r.updated_at AS refresh_updated_at,
|
|
78
|
-
r.authoritative AS refresh_authoritative
|
|
84
|
+
const CACHE_ROW_FROM = `
|
|
79
85
|
FROM model_cache m
|
|
80
86
|
LEFT JOIN model_cache_refresh r ON r.provider_id = m.provider_id
|
|
81
87
|
WHERE m.provider_id = ?
|
|
82
88
|
`;
|
|
89
|
+
const CACHE_ROW_REFRESH_COLUMNS = `
|
|
90
|
+
r.payload_updated_at AS refresh_payload_updated_at,
|
|
91
|
+
r.updated_at AS refresh_updated_at,
|
|
92
|
+
r.authoritative AS refresh_authoritative`;
|
|
93
|
+
const SELECT_CACHE_ROW = `SELECT m.*, ${CACHE_ROW_REFRESH_COLUMNS} ${CACHE_ROW_FROM}`;
|
|
94
|
+
/** Scalar columns plus `models_hash`: validates a memoized parse without reading the multi-MB payload. */
|
|
95
|
+
const SELECT_CACHE_ROW_META = `
|
|
96
|
+
SELECT m.provider_id, m.version, m.materialization_policy, m.updated_at, m.authoritative,
|
|
97
|
+
m.static_fingerprint, m.models_hash, m.header_omitted_model_ids,
|
|
98
|
+
m.unrestorable_header_model_ids, m.header_restore_version, ${CACHE_ROW_REFRESH_COLUMNS}
|
|
99
|
+
${CACHE_ROW_FROM}
|
|
100
|
+
`;
|
|
83
101
|
|
|
84
102
|
/** Cumulative model-cache write counters for this process. */
|
|
85
103
|
export interface ModelCacheWriteStats {
|
|
@@ -149,8 +167,11 @@ let sharedDbPath: string | null = null;
|
|
|
149
167
|
interface ReadRowCacheEntry {
|
|
150
168
|
/** `PRAGMA data_version` of the shared connection when validated; null for per-call connections. */
|
|
151
169
|
dataVersion: number | null;
|
|
152
|
-
/**
|
|
153
|
-
|
|
170
|
+
/**
|
|
171
|
+
* Scalar columns (plus `models_hash`) of the row the entry was parsed from;
|
|
172
|
+
* null when the row was absent or rejected. Never the multi-MB payload itself.
|
|
173
|
+
*/
|
|
174
|
+
meta: CacheRowMeta | null;
|
|
154
175
|
entry: CacheEntry<Api> | null;
|
|
155
176
|
}
|
|
156
177
|
|
|
@@ -182,7 +203,7 @@ function withFreshness<TApi extends Api>(
|
|
|
182
203
|
}
|
|
183
204
|
function invalidateReadRow(providerId: string, dbPath?: string): void {
|
|
184
205
|
try {
|
|
185
|
-
readRowCache.delete(readCacheKey(dbPath
|
|
206
|
+
readRowCache.delete(readCacheKey(resolveCacheDb(dbPath).resolvedPath, providerId));
|
|
186
207
|
} catch {
|
|
187
208
|
// Best-effort only; a missed invalidation just costs one extra parse.
|
|
188
209
|
}
|
|
@@ -213,6 +234,7 @@ function initializeDb(db: Database): void {
|
|
|
213
234
|
header_omitted_model_ids TEXT NOT NULL DEFAULT '[]',
|
|
214
235
|
unrestorable_header_model_ids TEXT NOT NULL DEFAULT '[]',
|
|
215
236
|
header_restore_version INTEGER NOT NULL DEFAULT 0,
|
|
237
|
+
models_hash TEXT NOT NULL DEFAULT '',
|
|
216
238
|
models TEXT NOT NULL
|
|
217
239
|
)
|
|
218
240
|
`);
|
|
@@ -225,6 +247,17 @@ function initializeDb(db: Database): void {
|
|
|
225
247
|
)
|
|
226
248
|
`);
|
|
227
249
|
migrateCacheSchema(db);
|
|
250
|
+
// `models_hash` is the only payload-change detector for the read memo and the
|
|
251
|
+
// unchanged-write skip. Writers here replace whole rows; this keeps an
|
|
252
|
+
// in-place edit of `models` by any other tool or binary from reusing a stale hash.
|
|
253
|
+
db.run(`
|
|
254
|
+
CREATE TRIGGER IF NOT EXISTS model_cache_models_hash_reset
|
|
255
|
+
AFTER UPDATE OF models ON model_cache
|
|
256
|
+
WHEN NEW.models_hash = OLD.models_hash
|
|
257
|
+
BEGIN
|
|
258
|
+
UPDATE model_cache SET models_hash = '' WHERE provider_id = NEW.provider_id;
|
|
259
|
+
END
|
|
260
|
+
`);
|
|
228
261
|
}
|
|
229
262
|
|
|
230
263
|
function closeSharedDb(): void {
|
|
@@ -280,8 +313,22 @@ function runModelCacheDb<T>(resolvedPath: string, shared: boolean, useDb: (db: D
|
|
|
280
313
|
}
|
|
281
314
|
|
|
282
315
|
function withModelCacheDb<T>(dbPath: string | undefined, useDb: (db: Database) => T): T {
|
|
283
|
-
const resolvedPath
|
|
284
|
-
return runModelCacheDb(resolvedPath,
|
|
316
|
+
const { resolvedPath, shared } = resolveCacheDb(dbPath);
|
|
317
|
+
return runModelCacheDb(resolvedPath, shared, useDb);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* An explicit path naming the default `models.db` (e.g. the SDK passing
|
|
322
|
+
* `getModelDbPath(agentDir)` for the default agent dir) shares the long-lived
|
|
323
|
+
* handle, so it gets the `data_version` fast path instead of opening,
|
|
324
|
+
* initializing and re-reading the row on every call.
|
|
325
|
+
*/
|
|
326
|
+
function resolveCacheDb(dbPath: string | undefined): { resolvedPath: string; shared: boolean } {
|
|
327
|
+
const defaultPath = getModelDbPath();
|
|
328
|
+
if (dbPath === undefined || dbPath === defaultPath || path.resolve(dbPath) === path.resolve(defaultPath)) {
|
|
329
|
+
return { resolvedPath: defaultPath, shared: true };
|
|
330
|
+
}
|
|
331
|
+
return { resolvedPath: dbPath, shared: false };
|
|
285
332
|
}
|
|
286
333
|
|
|
287
334
|
function migrateCacheSchema(db: Database): void {
|
|
@@ -306,6 +353,12 @@ function migrateCacheSchema(db: Database): void {
|
|
|
306
353
|
// header matching was introduced.
|
|
307
354
|
db.run("ALTER TABLE model_cache ADD COLUMN header_restore_version INTEGER NOT NULL DEFAULT 0");
|
|
308
355
|
}
|
|
356
|
+
if (!columns.some(column => column.name === "models_hash")) {
|
|
357
|
+
// Rows from older binaries keep '' (never equal), so they are re-read in
|
|
358
|
+
// full until the next write records their hash; no version bump needed.
|
|
359
|
+
// Older binaries rewriting a row via INSERT OR REPLACE reset it to ''.
|
|
360
|
+
db.run("ALTER TABLE model_cache ADD COLUMN models_hash TEXT NOT NULL DEFAULT ''");
|
|
361
|
+
}
|
|
309
362
|
} finally {
|
|
310
363
|
stmt.finalize();
|
|
311
364
|
}
|
|
@@ -419,15 +472,14 @@ export function readModelCache<TApi extends Api>(
|
|
|
419
472
|
dbPath?: string,
|
|
420
473
|
): CacheEntry<TApi> | null {
|
|
421
474
|
try {
|
|
422
|
-
const resolvedPath = dbPath ?? getModelDbPath();
|
|
423
|
-
const key = readCacheKey(resolvedPath, providerId);
|
|
424
475
|
// Monotonic change signal: same-shaped WAL overwrites after checkpoint
|
|
425
476
|
// can leave every size:mtime pair identical, so file metadata alone
|
|
426
477
|
// cannot invalidate. PRAGMA data_version increments on each committed
|
|
427
478
|
// write transaction visible to a new reader. It is only comparable
|
|
428
479
|
// across calls on the same connection, so it gates the no-query fast
|
|
429
480
|
// path for the shared handle only.
|
|
430
|
-
const shared = dbPath
|
|
481
|
+
const { resolvedPath, shared } = resolveCacheDb(dbPath);
|
|
482
|
+
const key = readCacheKey(resolvedPath, providerId);
|
|
431
483
|
return runModelCacheDb(resolvedPath, shared, db => {
|
|
432
484
|
const dataVersion = shared ? dbDataVersion(db) : null;
|
|
433
485
|
const cached = readRowCache.get(key);
|
|
@@ -436,34 +488,32 @@ export function readModelCache<TApi extends Api>(
|
|
|
436
488
|
// cached row's updatedAt so a long-lived process goes stale.
|
|
437
489
|
return withFreshness(cached.entry as CacheEntry<TApi> | null, ttlMs, now);
|
|
438
490
|
}
|
|
439
|
-
// Any commit (to any provider's row) bumps data_version.
|
|
440
|
-
//
|
|
441
|
-
|
|
442
|
-
let
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
491
|
+
// Any commit (to any provider's row) bumps data_version. Validate the
|
|
492
|
+
// memoized parse against the row's scalar columns and payload hash
|
|
493
|
+
// first; only a changed payload pays for the multi-MB `models` read.
|
|
494
|
+
let entry: CacheEntry<TApi> | null = null;
|
|
495
|
+
let meta: CacheRowMeta | null = null;
|
|
496
|
+
if (cached?.entry && cached.meta) {
|
|
497
|
+
meta = queryRow<CacheRowMeta>(db, SELECT_CACHE_ROW_META, providerId);
|
|
498
|
+
if (meta && meta.materialization_policy === materializationPolicy() && cacheRowsEqual(cached.meta, meta)) {
|
|
499
|
+
// Payload unchanged; only side-table freshness may have moved.
|
|
500
|
+
entry = withFreshness(withRowState(cached.entry as CacheEntry<TApi>, meta), ttlMs, now);
|
|
501
|
+
} else {
|
|
502
|
+
meta = null;
|
|
503
|
+
}
|
|
447
504
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
cached.row &&
|
|
452
|
-
row &&
|
|
453
|
-
row.materialization_policy === materializationPolicy() &&
|
|
454
|
-
cacheRowsEqual(cached.row, row)
|
|
455
|
-
) {
|
|
456
|
-
// Payload bytes unchanged; only side-table freshness may have moved.
|
|
457
|
-
entry = withFreshness(withRowState(cached.entry as CacheEntry<TApi>, row), ttlMs, now);
|
|
458
|
-
} else {
|
|
505
|
+
if (entry === null) {
|
|
506
|
+
// One statement reads payload and scalars from the same snapshot.
|
|
507
|
+
const row = queryRow<CacheRow>(db, SELECT_CACHE_ROW, providerId);
|
|
459
508
|
entry = parseCacheRow<TApi>(db, providerId, row, ttlMs, now);
|
|
509
|
+
if (entry !== null && row !== null) {
|
|
510
|
+
// Drop the payload so the memo never pins the multi-MB `models` string.
|
|
511
|
+
const { models: _models, ...rowScalars } = row;
|
|
512
|
+
meta = rowScalars;
|
|
513
|
+
}
|
|
460
514
|
}
|
|
461
515
|
if (readRowCache.size >= READ_ROW_CACHE_MAX) readRowCache.clear();
|
|
462
|
-
readRowCache.set(key, {
|
|
463
|
-
dataVersion,
|
|
464
|
-
row: entry === null ? null : row,
|
|
465
|
-
entry: entry as CacheEntry<Api> | null,
|
|
466
|
-
});
|
|
516
|
+
readRowCache.set(key, { dataVersion, meta, entry: entry as CacheEntry<Api> | null });
|
|
467
517
|
return entry;
|
|
468
518
|
});
|
|
469
519
|
} catch {
|
|
@@ -471,8 +521,20 @@ export function readModelCache<TApi extends Api>(
|
|
|
471
521
|
}
|
|
472
522
|
}
|
|
473
523
|
|
|
474
|
-
|
|
475
|
-
|
|
524
|
+
function queryRow<T>(db: Database, sql: string, providerId: string): T | null {
|
|
525
|
+
const stmt = db.query<T, [string]>(sql);
|
|
526
|
+
try {
|
|
527
|
+
return stmt.get(providerId);
|
|
528
|
+
} finally {
|
|
529
|
+
stmt.finalize();
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Whether two rows carry the same payload record (refresh side-row columns
|
|
535
|
+
* excluded). Payloads compare by `models_hash`; rows without one never match.
|
|
536
|
+
*/
|
|
537
|
+
function cacheRowsEqual(left: CacheRowMeta, right: CacheRowMeta): boolean {
|
|
476
538
|
return (
|
|
477
539
|
left.version === right.version &&
|
|
478
540
|
left.materialization_policy === right.materialization_policy &&
|
|
@@ -482,7 +544,8 @@ function cacheRowsEqual(left: CacheRow, right: CacheRow): boolean {
|
|
|
482
544
|
left.header_omitted_model_ids === right.header_omitted_model_ids &&
|
|
483
545
|
left.unrestorable_header_model_ids === right.unrestorable_header_model_ids &&
|
|
484
546
|
left.header_restore_version === right.header_restore_version &&
|
|
485
|
-
left.
|
|
547
|
+
left.models_hash !== "" &&
|
|
548
|
+
left.models_hash === right.models_hash
|
|
486
549
|
);
|
|
487
550
|
}
|
|
488
551
|
|
|
@@ -498,7 +561,7 @@ function rowState(row: StoredRowState): { updatedAt: number; authoritative: bool
|
|
|
498
561
|
return { updatedAt: row.updated_at, authoritative: row.authoritative === 1 };
|
|
499
562
|
}
|
|
500
563
|
|
|
501
|
-
function withRowState<TApi extends Api>(entry: CacheEntry<TApi>, row:
|
|
564
|
+
function withRowState<TApi extends Api>(entry: CacheEntry<TApi>, row: StoredRowState): CacheEntry<TApi> {
|
|
502
565
|
const { updatedAt, authoritative } = rowState(row);
|
|
503
566
|
return entry.updatedAt === updatedAt && entry.authoritative === authoritative
|
|
504
567
|
? entry
|
|
@@ -648,6 +711,8 @@ export function writeModelCache<TApi extends Api>(
|
|
|
648
711
|
const serializedHeaderOmitted = JSON.stringify(headerOmittedModelIds);
|
|
649
712
|
const serializedUnrestorable = JSON.stringify(unrestorableHeaderModelIds);
|
|
650
713
|
const serializedModels = JSON.stringify(cachedModels);
|
|
714
|
+
// Length prefix: a collision would also need an equal payload size.
|
|
715
|
+
const modelsHash = `${serializedModels.length.toString(36)}:${Bun.hash(serializedModels).toString(36)}`;
|
|
651
716
|
const outcome = db
|
|
652
717
|
.transaction((): "skipped" | "refreshed" | "written" => {
|
|
653
718
|
const matchStmt = db.prepare<
|
|
@@ -662,7 +727,7 @@ export function writeModelCache<TApi extends Api>(
|
|
|
662
727
|
LEFT JOIN model_cache_refresh r ON r.provider_id = m.provider_id
|
|
663
728
|
WHERE m.provider_id = ? AND m.version = ? AND m.materialization_policy = ?
|
|
664
729
|
AND m.static_fingerprint = ? AND m.header_omitted_model_ids = ?
|
|
665
|
-
AND m.unrestorable_header_model_ids = ? AND m.header_restore_version = ? AND m.
|
|
730
|
+
AND m.unrestorable_header_model_ids = ? AND m.header_restore_version = ? AND m.models_hash = ?`,
|
|
666
731
|
);
|
|
667
732
|
let stored: StoredRowState | null;
|
|
668
733
|
try {
|
|
@@ -674,7 +739,7 @@ export function writeModelCache<TApi extends Api>(
|
|
|
674
739
|
serializedHeaderOmitted,
|
|
675
740
|
serializedUnrestorable,
|
|
676
741
|
HEADER_RESTORE_VERSION,
|
|
677
|
-
|
|
742
|
+
modelsHash,
|
|
678
743
|
);
|
|
679
744
|
} finally {
|
|
680
745
|
matchStmt.finalize();
|
|
@@ -697,8 +762,8 @@ export function writeModelCache<TApi extends Api>(
|
|
|
697
762
|
`INSERT OR REPLACE INTO model_cache (
|
|
698
763
|
provider_id, version, materialization_policy, updated_at, authoritative, static_fingerprint,
|
|
699
764
|
header_omitted_model_ids, unrestorable_header_model_ids,
|
|
700
|
-
header_restore_version, models
|
|
701
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
765
|
+
header_restore_version, models_hash, models
|
|
766
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
702
767
|
[
|
|
703
768
|
providerId,
|
|
704
769
|
CACHE_SCHEMA_VERSION,
|
|
@@ -709,6 +774,7 @@ export function writeModelCache<TApi extends Api>(
|
|
|
709
774
|
serializedHeaderOmitted,
|
|
710
775
|
serializedUnrestorable,
|
|
711
776
|
HEADER_RESTORE_VERSION,
|
|
777
|
+
modelsHash,
|
|
712
778
|
serializedModels,
|
|
713
779
|
],
|
|
714
780
|
);
|