@oh-my-pi/pi-catalog 18.13.2 → 18.14.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 +40 -0
- package/README.md +3 -3
- package/dist/types/build.d.ts +12 -4
- package/dist/types/compat/types.d.ts +3 -1
- package/dist/types/discovery/codex.d.ts +4 -5
- package/dist/types/model-cache.d.ts +8 -0
- package/dist/types/model-catalog-format.d.ts +37 -0
- package/dist/types/models.d.ts +6 -3
- package/package.json +5 -10
- package/src/build.ts +50 -3
- package/src/compat/axes.ts +6 -0
- package/src/compat/rules/README.md +11 -11
- package/src/compat/rules/auth/typesafe.kdl +2 -1
- package/src/compat/rules/classes/deepseek.kdl +2 -1
- package/src/compat/rules/classes/openai.kdl +2 -2
- package/src/compat/rules/providers/factory-droid.kdl +24 -0
- package/src/compat/rules/providers/google-vertex.kdl +3 -0
- package/src/compat/rules/providers/kimi-code.kdl +6 -0
- package/src/compat/rules/providers/mistral.kdl +8 -0
- package/src/compat/rules/runtime/behavior.kdl +9 -6
- package/src/compat/rules.json +1 -1
- package/src/compat/types.ts +3 -1
- package/src/discovery/codex.ts +5 -6
- package/src/model-cache.ts +51 -3
- package/src/model-catalog-format.ts +299 -0
- package/src/models.json +1 -1
- package/src/models.ts +16 -10
- package/src/provider-models/cache-provider-id.ts +23 -6
- package/src/provider-models/openai-compat.ts +83 -23
- package/src/provider-models/special.ts +5 -6
- package/THIRD-PARTY-NOTICES.txt +0 -22901
- package/src/models.json.d.ts +0 -9
package/src/compat/types.ts
CHANGED
|
@@ -398,8 +398,10 @@ export type CompiledAuthValidation =
|
|
|
398
398
|
kind: "models-endpoint";
|
|
399
399
|
label?: string;
|
|
400
400
|
url: string;
|
|
401
|
-
/** Env var holding an alternate base URL;
|
|
401
|
+
/** Env var holding an alternate base URL; `baseUrlPath` is appended to it. */
|
|
402
402
|
baseUrlEnv?: string;
|
|
403
|
+
/** Path appended to the `baseUrlEnv` value (default `/models`), for bases that exclude the API version. */
|
|
404
|
+
baseUrlPath?: string;
|
|
403
405
|
/** Hook returning extra request headers (may throw a configuration error). */
|
|
404
406
|
headersHook?: string;
|
|
405
407
|
optional?: boolean;
|
package/src/discovery/codex.ts
CHANGED
|
@@ -113,12 +113,11 @@ export interface CodexModelDiscoveryResult {
|
|
|
113
113
|
models: ModelSpec<"openai-codex-responses">[];
|
|
114
114
|
etag?: string;
|
|
115
115
|
/**
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* discovery can skip the account instead of aborting.
|
|
116
|
+
* A definitive per-account denial (401/403 auth, 402 payment). `models` is
|
|
117
|
+
* empty; unlike transport/parse failures, sibling accounts can still supply
|
|
118
|
+
* an authoritative catalog.
|
|
120
119
|
*/
|
|
121
|
-
rejectedStatus?: 401 | 403;
|
|
120
|
+
rejectedStatus?: 401 | 402 | 403;
|
|
122
121
|
}
|
|
123
122
|
|
|
124
123
|
/**
|
|
@@ -148,7 +147,7 @@ export async function fetchCodexModels(options: CodexModelDiscoveryOptions): Pro
|
|
|
148
147
|
continue;
|
|
149
148
|
}
|
|
150
149
|
|
|
151
|
-
if (response.status === 401 || response.status === 403) {
|
|
150
|
+
if (response.status === 401 || response.status === 402 || response.status === 403) {
|
|
152
151
|
return { models: [], rejectedStatus: response.status };
|
|
153
152
|
}
|
|
154
153
|
if (!response.ok) {
|
package/src/model-cache.ts
CHANGED
|
@@ -173,6 +173,14 @@ export interface CacheEntry<TApi extends Api = Api> {
|
|
|
173
173
|
let sharedDb: Database | null = null;
|
|
174
174
|
let sharedDbPath: string | null = null;
|
|
175
175
|
|
|
176
|
+
/** Connection to a non-default cache path, held open while any retain is outstanding. */
|
|
177
|
+
interface PinnedDb {
|
|
178
|
+
db: Database | null;
|
|
179
|
+
refs: number;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const pinnedDbs = new Map<string, PinnedDb>();
|
|
183
|
+
|
|
176
184
|
interface ReadRowCacheEntry {
|
|
177
185
|
/** `PRAGMA data_version` of the shared connection when validated; null for per-call connections. */
|
|
178
186
|
dataVersion: number | null;
|
|
@@ -286,16 +294,54 @@ export function closeModelCache(): void {
|
|
|
286
294
|
closeSharedDb();
|
|
287
295
|
}
|
|
288
296
|
|
|
297
|
+
/**
|
|
298
|
+
* Keeps one connection to a non-default cache path open until the returned
|
|
299
|
+
* handle is disposed. The default `models.db` already shares a long-lived
|
|
300
|
+
* connection; other paths otherwise open, initialize and close the database on
|
|
301
|
+
* every read and write, which a refresh pass repeats once per provider.
|
|
302
|
+
* Retains nest: the connection closes when the last one is disposed.
|
|
303
|
+
*/
|
|
304
|
+
export function retainModelCacheConnection(dbPath: string | undefined): Disposable {
|
|
305
|
+
const { resolvedPath, shared } = resolveCacheDb(dbPath);
|
|
306
|
+
if (shared) return { [Symbol.dispose]() {} };
|
|
307
|
+
let pin = pinnedDbs.get(resolvedPath);
|
|
308
|
+
if (!pin) {
|
|
309
|
+
pin = { db: null, refs: 0 };
|
|
310
|
+
pinnedDbs.set(resolvedPath, pin);
|
|
311
|
+
}
|
|
312
|
+
pin.refs++;
|
|
313
|
+
const held = pin;
|
|
314
|
+
let released = false;
|
|
315
|
+
return {
|
|
316
|
+
[Symbol.dispose]() {
|
|
317
|
+
if (released) return;
|
|
318
|
+
released = true;
|
|
319
|
+
held.refs--;
|
|
320
|
+
if (held.refs > 0) return;
|
|
321
|
+
pinnedDbs.delete(resolvedPath);
|
|
322
|
+
held.db?.close();
|
|
323
|
+
held.db = null;
|
|
324
|
+
},
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
289
328
|
function runModelCacheDb<T>(resolvedPath: string, shared: boolean, useDb: (db: Database) => T): T {
|
|
290
329
|
if (shared && sharedDb && sharedDbPath !== resolvedPath) closeSharedDb();
|
|
291
|
-
|
|
330
|
+
const pin = shared ? undefined : pinnedDbs.get(resolvedPath);
|
|
331
|
+
const heldDb = shared ? sharedDb : pin?.db;
|
|
332
|
+
if (heldDb) {
|
|
292
333
|
try {
|
|
293
|
-
return useDb(
|
|
334
|
+
return useDb(heldDb);
|
|
294
335
|
} catch (error) {
|
|
295
336
|
if (!isSqliteCorruptionError(error)) throw error;
|
|
296
337
|
// The opener owns recovery for new handles. Drop this stale handle
|
|
297
338
|
// first so its WAL cannot remain attached to the replacement.
|
|
298
|
-
|
|
339
|
+
if (pin) {
|
|
340
|
+
heldDb.close();
|
|
341
|
+
pin.db = null;
|
|
342
|
+
} else {
|
|
343
|
+
closeSharedDb();
|
|
344
|
+
}
|
|
299
345
|
invalidateReadPath(resolvedPath);
|
|
300
346
|
return runModelCacheDb(resolvedPath, shared, useDb);
|
|
301
347
|
}
|
|
@@ -309,6 +355,8 @@ function runModelCacheDb<T>(resolvedPath: string, shared: boolean, useDb: (db: D
|
|
|
309
355
|
if (shared) {
|
|
310
356
|
sharedDb = db;
|
|
311
357
|
sharedDbPath = resolvedPath;
|
|
358
|
+
} else if (pin) {
|
|
359
|
+
pin.db = db;
|
|
312
360
|
} else {
|
|
313
361
|
db.close();
|
|
314
362
|
}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import type { Api, Model } from "./types";
|
|
2
|
+
|
|
3
|
+
// References are distinguished from JSON scalar values by their one-element array.
|
|
4
|
+
// -1 represents an explicitly present undefined value.
|
|
5
|
+
type EncodedValue = string | number | boolean | null | [number];
|
|
6
|
+
type ObjectNode = [0, EncodedValue[], number, number[]];
|
|
7
|
+
type ArrayNode = [1, EncodedValue[]];
|
|
8
|
+
type Node = ObjectNode | ArrayNode;
|
|
9
|
+
type CompatDelta = [number[], EncodedValue];
|
|
10
|
+
type ModelRow = [string, EncodedValue, CompatDelta?];
|
|
11
|
+
type ProviderRow = [string, EncodedValue, ModelRow[]];
|
|
12
|
+
|
|
13
|
+
interface CatalogData {
|
|
14
|
+
version: 1;
|
|
15
|
+
keys: string[];
|
|
16
|
+
booleanShapes: number[][];
|
|
17
|
+
nodes: Node[];
|
|
18
|
+
compat: [string, EncodedValue][];
|
|
19
|
+
providers: ProviderRow[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Parsed compact catalog. Decoded nested objects and arrays can be shared by
|
|
24
|
+
* multiple models; callers must treat those values as immutable.
|
|
25
|
+
*/
|
|
26
|
+
export interface ModelCatalog {
|
|
27
|
+
readonly data: CatalogData;
|
|
28
|
+
readonly values: unknown[];
|
|
29
|
+
readonly decoded: boolean[];
|
|
30
|
+
readonly providers: Map<string, Record<string, Model<Api>>>;
|
|
31
|
+
readonly compatBases: Map<string, EncodedValue>;
|
|
32
|
+
readonly compatValues: Map<string, Map<number, Map<string, Record<string, unknown>>>>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
36
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// JSON serialization with sorted object keys gives both deterministic ordering
|
|
40
|
+
// and value-based interning, independent of the input objects' insertion order.
|
|
41
|
+
function canonical(value: unknown): string {
|
|
42
|
+
if (value === undefined) return "u";
|
|
43
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
44
|
+
if (isObject(value)) {
|
|
45
|
+
return `{${Object.keys(value)
|
|
46
|
+
.sort()
|
|
47
|
+
.map(key => `${JSON.stringify(key)}:${canonical(value[key])}`)
|
|
48
|
+
.join(",")}}`;
|
|
49
|
+
}
|
|
50
|
+
return JSON.stringify(value);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Encode a model catalog into a deterministic, compact JSON representation. */
|
|
54
|
+
export function encodeModelCatalog(models: Record<string, Record<string, Model<Api>>>): string {
|
|
55
|
+
// Match JSON's treatment of undefined and non-finite numbers in generated rows.
|
|
56
|
+
models = JSON.parse(JSON.stringify(models));
|
|
57
|
+
const keys: string[] = [];
|
|
58
|
+
const keyIds = new Map<string, number>();
|
|
59
|
+
const booleanShapes: number[][] = [];
|
|
60
|
+
const shapeIds = new Map<string, number>();
|
|
61
|
+
const nodes: Node[] = [];
|
|
62
|
+
const nodeIds = new Map<string, number>();
|
|
63
|
+
const keyId = (key: string): number => {
|
|
64
|
+
let id = keyIds.get(key);
|
|
65
|
+
if (id === undefined) {
|
|
66
|
+
id = keys.length;
|
|
67
|
+
keys.push(key);
|
|
68
|
+
keyIds.set(key, id);
|
|
69
|
+
}
|
|
70
|
+
return id;
|
|
71
|
+
};
|
|
72
|
+
const encode = (value: unknown): EncodedValue => {
|
|
73
|
+
if (value === undefined) return [-1];
|
|
74
|
+
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
const signature = canonical(value);
|
|
78
|
+
const existing = nodeIds.get(signature);
|
|
79
|
+
if (existing !== undefined) return [existing];
|
|
80
|
+
let node: Node;
|
|
81
|
+
if (Array.isArray(value)) {
|
|
82
|
+
node = [1, value.map(encode)];
|
|
83
|
+
} else if (isObject(value)) {
|
|
84
|
+
const properties: EncodedValue[] = [];
|
|
85
|
+
const boolKeys: number[] = [];
|
|
86
|
+
const masks: number[] = [];
|
|
87
|
+
for (const key of Object.keys(value).sort()) {
|
|
88
|
+
const entry = value[key];
|
|
89
|
+
if (typeof entry === "boolean") {
|
|
90
|
+
const bit = boolKeys.length;
|
|
91
|
+
boolKeys.push(keyId(key));
|
|
92
|
+
const word = Math.floor(bit / 32);
|
|
93
|
+
masks[word] ??= 0;
|
|
94
|
+
if (entry) masks[word] = (masks[word] | (1 << (bit % 32))) >>> 0;
|
|
95
|
+
} else {
|
|
96
|
+
properties.push(keyId(key), encode(entry));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const shape = boolKeys.join(",");
|
|
100
|
+
let shapeId = shapeIds.get(shape);
|
|
101
|
+
if (shapeId === undefined) {
|
|
102
|
+
shapeId = booleanShapes.length;
|
|
103
|
+
booleanShapes.push(boolKeys);
|
|
104
|
+
shapeIds.set(shape, shapeId);
|
|
105
|
+
}
|
|
106
|
+
node = [0, properties, shapeId, masks];
|
|
107
|
+
} else {
|
|
108
|
+
throw new TypeError("Model catalog values must be JSON-compatible");
|
|
109
|
+
}
|
|
110
|
+
const id = nodes.length;
|
|
111
|
+
nodes.push(node);
|
|
112
|
+
nodeIds.set(signature, id);
|
|
113
|
+
return [id];
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// Select the most common complete compat object for each API. A row's
|
|
117
|
+
// delta records changed properties and explicitly removed base properties.
|
|
118
|
+
const compatCounts = new Map<string, Map<string, { value: Record<string, unknown>; count: number }>>();
|
|
119
|
+
for (const provider of Object.keys(models).sort()) {
|
|
120
|
+
for (const id of Object.keys(models[provider]).sort()) {
|
|
121
|
+
const model = models[provider][id];
|
|
122
|
+
if (typeof model.api !== "string" || !isObject(model.compat)) continue;
|
|
123
|
+
let counts = compatCounts.get(model.api);
|
|
124
|
+
if (!counts) compatCounts.set(model.api, (counts = new Map()));
|
|
125
|
+
const signature = canonical(model.compat);
|
|
126
|
+
const entry = counts.get(signature);
|
|
127
|
+
if (entry) entry.count++;
|
|
128
|
+
else counts.set(signature, { value: model.compat, count: 1 });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const bases = new Map<string, Record<string, unknown>>();
|
|
132
|
+
for (const api of [...compatCounts.keys()].sort()) {
|
|
133
|
+
const choices = [...compatCounts.get(api)!.entries()].sort(
|
|
134
|
+
(a, b) => b[1].count - a[1].count || (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0),
|
|
135
|
+
);
|
|
136
|
+
bases.set(api, choices[0][1].value);
|
|
137
|
+
}
|
|
138
|
+
const compat: [string, EncodedValue][] = [...bases].map(([api, value]) => [api, encode(value)]);
|
|
139
|
+
const providers: ProviderRow[] = [];
|
|
140
|
+
for (const provider of Object.keys(models).sort()) {
|
|
141
|
+
const entries = Object.entries(models[provider]).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
142
|
+
const constants: Record<string, unknown> = Object.create(null);
|
|
143
|
+
if (entries.length) {
|
|
144
|
+
const first = entries[0][1] as unknown as Record<string, unknown>;
|
|
145
|
+
for (const key of Object.keys(first).sort()) {
|
|
146
|
+
const value = first[key];
|
|
147
|
+
if (key === "id" || key === "compat" || (value !== null && typeof value === "object")) continue;
|
|
148
|
+
if (
|
|
149
|
+
entries.every(
|
|
150
|
+
([, model]) =>
|
|
151
|
+
Object.hasOwn(model, key) && (model as unknown as Record<string, unknown>)[key] === value,
|
|
152
|
+
)
|
|
153
|
+
) {
|
|
154
|
+
constants[key] = value;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const encodedConstants = encode(constants);
|
|
159
|
+
const rows: ModelRow[] = [];
|
|
160
|
+
for (const [id, model] of entries) {
|
|
161
|
+
const rest: Record<string, unknown> = Object.create(null);
|
|
162
|
+
const source = model as unknown as Record<string, unknown>;
|
|
163
|
+
const base = typeof model.api === "string" ? bases.get(model.api) : undefined;
|
|
164
|
+
const useBase = base !== undefined && isObject(model.compat);
|
|
165
|
+
for (const key of Object.keys(source).sort()) {
|
|
166
|
+
if (Object.hasOwn(constants, key) || (key === "id" && source.id === id) || (key === "compat" && useBase))
|
|
167
|
+
continue;
|
|
168
|
+
rest[key] = source[key];
|
|
169
|
+
}
|
|
170
|
+
const row: ModelRow = [id, encode(rest)];
|
|
171
|
+
if (useBase) {
|
|
172
|
+
const value = model.compat as Record<string, unknown>;
|
|
173
|
+
const removed = Object.keys(base)
|
|
174
|
+
.filter(key => !Object.hasOwn(value, key))
|
|
175
|
+
.sort()
|
|
176
|
+
.map(keyId);
|
|
177
|
+
const changed: Record<string, unknown> = Object.create(null);
|
|
178
|
+
for (const key of Object.keys(value).sort()) {
|
|
179
|
+
if (!Object.hasOwn(base, key) || canonical(value[key]) !== canonical(base[key]))
|
|
180
|
+
changed[key] = value[key];
|
|
181
|
+
}
|
|
182
|
+
row.push([removed, encode(changed)]);
|
|
183
|
+
}
|
|
184
|
+
rows.push(row);
|
|
185
|
+
}
|
|
186
|
+
providers.push([provider, encodedConstants, rows]);
|
|
187
|
+
}
|
|
188
|
+
return JSON.stringify({ version: 1, keys, booleanShapes, nodes, compat, providers } satisfies CatalogData);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Open a parsed catalog without materializing its provider models. */
|
|
192
|
+
export function openModelCatalog(value: unknown): ModelCatalog {
|
|
193
|
+
const data = value as CatalogData;
|
|
194
|
+
if (data.version !== 1) throw new Error("Unsupported model catalog version");
|
|
195
|
+
return {
|
|
196
|
+
data,
|
|
197
|
+
values: [],
|
|
198
|
+
decoded: [],
|
|
199
|
+
providers: new Map(),
|
|
200
|
+
compatBases: new Map(data.compat),
|
|
201
|
+
compatValues: new Map(),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Assignment is safe for ordinary keys, but __proto__ must remain an own key.
|
|
206
|
+
function setProperty(object: Record<string, unknown>, key: string, value: unknown): void {
|
|
207
|
+
if (key === "__proto__") {
|
|
208
|
+
Object.defineProperty(object, key, { value, writable: true, enumerable: true, configurable: true });
|
|
209
|
+
} else {
|
|
210
|
+
object[key] = value;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function decodeObject(catalog: ModelCatalog, object: Record<string, unknown>, node: ObjectNode): void {
|
|
215
|
+
for (let i = 0; i < node[1].length; i += 2) {
|
|
216
|
+
setProperty(object, catalog.data.keys[node[1][i] as number], decode(catalog, node[1][i + 1]));
|
|
217
|
+
}
|
|
218
|
+
const shape = catalog.data.booleanShapes[node[2]];
|
|
219
|
+
for (let i = 0; i < shape.length; i++) {
|
|
220
|
+
setProperty(object, catalog.data.keys[shape[i]], ((node[3][i >>> 5] >>> (i % 32)) & 1) !== 0);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function decode(catalog: ModelCatalog, value: EncodedValue): unknown {
|
|
225
|
+
if (!Array.isArray(value)) return value;
|
|
226
|
+
const id = value[0];
|
|
227
|
+
if (id === -1) return undefined;
|
|
228
|
+
if (catalog.decoded[id]) return catalog.values[id];
|
|
229
|
+
const node = catalog.data.nodes[id];
|
|
230
|
+
let result: unknown;
|
|
231
|
+
if (node[0] === 1) {
|
|
232
|
+
result = node[1].map(entry => decode(catalog, entry));
|
|
233
|
+
} else {
|
|
234
|
+
const object: Record<string, unknown> = {};
|
|
235
|
+
decodeObject(catalog, object, node);
|
|
236
|
+
result = object;
|
|
237
|
+
}
|
|
238
|
+
catalog.values[id] = result;
|
|
239
|
+
catalog.decoded[id] = true;
|
|
240
|
+
return result;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** List catalog provider keys in deterministic order. */
|
|
244
|
+
export function getCatalogProviders(catalog: ModelCatalog): string[] {
|
|
245
|
+
return catalog.data.providers.map(provider => provider[0]);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Materialize and cache one provider, or return undefined if it is absent. */
|
|
249
|
+
export function getCatalogProvider(catalog: ModelCatalog, provider: string): Record<string, Model<Api>> | undefined {
|
|
250
|
+
const cached = catalog.providers.get(provider);
|
|
251
|
+
if (cached) return cached;
|
|
252
|
+
const entry = catalog.data.providers.find(row => row[0] === provider);
|
|
253
|
+
if (!entry) return undefined;
|
|
254
|
+
const constants = decode(catalog, entry[1]) as Record<string, unknown>;
|
|
255
|
+
const models: Record<string, Model<Api>> = {};
|
|
256
|
+
for (const [id, encoded, delta] of entry[2]) {
|
|
257
|
+
if (!Array.isArray(encoded) || encoded[0] < 0) throw new Error("Invalid compact model row");
|
|
258
|
+
const row = catalog.data.nodes[encoded[0]];
|
|
259
|
+
if (!row || row[0] !== 0) throw new Error("Invalid compact model row");
|
|
260
|
+
const model: Record<string, unknown> = { id, ...constants };
|
|
261
|
+
decodeObject(catalog, model, row);
|
|
262
|
+
if (delta) {
|
|
263
|
+
const api = model.api as string;
|
|
264
|
+
const changeId = (delta[1] as [number])[0];
|
|
265
|
+
const changeNode = catalog.data.nodes[changeId] as ObjectNode;
|
|
266
|
+
const base = decode(catalog, catalog.compatBases.get(api)!) as Record<string, unknown>;
|
|
267
|
+
let compat: Record<string, unknown>;
|
|
268
|
+
if (
|
|
269
|
+
delta[0].length === 0 &&
|
|
270
|
+
changeNode[1].length === 0 &&
|
|
271
|
+
catalog.data.booleanShapes[changeNode[2]].length === 0
|
|
272
|
+
) {
|
|
273
|
+
compat = base;
|
|
274
|
+
} else {
|
|
275
|
+
let byChange = catalog.compatValues.get(api);
|
|
276
|
+
if (!byChange) catalog.compatValues.set(api, (byChange = new Map()));
|
|
277
|
+
let byRemoved = byChange.get(changeId);
|
|
278
|
+
if (!byRemoved) byChange.set(changeId, (byRemoved = new Map()));
|
|
279
|
+
const removedKey = delta[0].join(",");
|
|
280
|
+
const cachedCompat = byRemoved.get(removedKey);
|
|
281
|
+
if (cachedCompat) {
|
|
282
|
+
compat = cachedCompat;
|
|
283
|
+
} else {
|
|
284
|
+
compat = { ...base };
|
|
285
|
+
for (const key of delta[0]) delete compat[catalog.data.keys[key]];
|
|
286
|
+
const changes = decode(catalog, delta[1]) as Record<string, unknown>;
|
|
287
|
+
for (const key in changes) {
|
|
288
|
+
if (Object.hasOwn(changes, key)) setProperty(compat, key, changes[key]);
|
|
289
|
+
}
|
|
290
|
+
byRemoved.set(removedKey, compat);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
model.compat = compat;
|
|
294
|
+
}
|
|
295
|
+
setProperty(models as Record<string, unknown>, id, model);
|
|
296
|
+
}
|
|
297
|
+
catalog.providers.set(provider, models);
|
|
298
|
+
return models;
|
|
299
|
+
}
|