@shop-os/modelkit 0.2.2 → 0.2.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/CHANGELOG.md CHANGED
@@ -9,6 +9,19 @@ header in the release PR.
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [0.2.4] - 2026-09-15
13
+
14
+ ### Fixed
15
+ - Catalog prime and fallback log lines carry origin, row count, revision and the error in the message string. pino-style loggers dropped the trailing object argument, so services such as template-service logged no origin.
16
+
17
+ ## [0.2.3] - 2026-09-15
18
+
19
+ ### Changed
20
+ - `display.sortOrder` sorts descending: a higher number shows first, so a new model takes the next number up. No published row set it before.
21
+
22
+ ### Fixed
23
+ - `primeCatalog` treats a remote response with zero rows as a failure and keeps the last-good or snapshot rows, so a pro-backend whose catalog table is not seeded yet cannot empty every consumer's pickers.
24
+
12
25
  ## [0.2.2] - 2026-09-15
13
26
 
14
27
  Option audit against provider docs, limited to options a product surface sets.
package/dist/catalog.cjs CHANGED
@@ -164,6 +164,12 @@ var GEMINI_RATIOS = [
164
164
  "adaptive"
165
165
  ];
166
166
  function geminiImage(id, label, aliases, sizes, display, exposure, references) {
167
+ const constraints = {
168
+ aspectRatios: [...GEMINI_RATIOS],
169
+ sizes,
170
+ supports: ["safetySettings", "temperature"]
171
+ };
172
+ if (references !== void 0) constraints.references = references;
167
173
  const model = {
168
174
  id,
169
175
  aliases,
@@ -173,12 +179,7 @@ function geminiImage(id, label, aliases, sizes, display, exposure, references) {
173
179
  providerModelId: id,
174
180
  lifecycle: "active",
175
181
  exposure,
176
- constraints: {
177
- aspectRatios: [...GEMINI_RATIOS],
178
- sizes,
179
- supports: ["safetySettings", "temperature"],
180
- ...references ? { references } : {}
181
- },
182
+ constraints,
182
183
  defaults: { size: "1K", aspectRatio: "1:1" },
183
184
  usage: { ...IMAGE_USAGE, slug: "gemini" },
184
185
  pricingKind: "image_size",
@@ -2079,31 +2080,31 @@ var liveState = store[KEY] ??= {
2079
2080
  var liveCatalog = liveState.catalog;
2080
2081
 
2081
2082
  // src/catalog/load.ts
2083
+ var import_zod2 = require("zod");
2082
2084
  function rowCount() {
2083
2085
  return liveCatalog.list({ lifecycle: ["active", "deprecated", "retired"] }).length;
2084
2086
  }
2085
2087
  function authHeaders(auth) {
2086
2088
  return "bearer" in auth ? { Authorization: `Bearer ${auth.bearer}` } : { "x-api-key": auth.apiKey };
2087
2089
  }
2090
+ var EnvelopeSchema = import_zod2.z.object({
2091
+ rows: CatalogSchema,
2092
+ revision: import_zod2.z.string().optional()
2093
+ });
2094
+ function toEnvelope(data) {
2095
+ if (data.rows.length === 0) throw new Error("invalid catalog response: no rows");
2096
+ const envelope = { rows: data.rows };
2097
+ if (data.revision !== void 0) envelope.revision = data.revision;
2098
+ return envelope;
2099
+ }
2088
2100
  function describeError(err) {
2089
2101
  return err instanceof Error ? err.message : String(err);
2090
2102
  }
2091
- function parseEnvelope(json) {
2092
- if (typeof json !== "object" || json === null || !("rows" in json)) {
2093
- throw new Error("invalid catalog response: missing rows");
2094
- }
2095
- const rows = CatalogSchema.parse(json.rows);
2096
- const rev = json.revision;
2097
- if (typeof rev === "string") return { rows, revision: rev };
2098
- return { rows };
2099
- }
2100
2103
  function logPrimed(logger, result) {
2101
- const { origin: loggedOrigin, rowCount: loggedCount, revision: loggedRevision } = result;
2102
- logger.info("[modelkit] catalog primed", {
2103
- origin: loggedOrigin,
2104
- rowCount: loggedCount,
2105
- revision: loggedRevision
2106
- });
2104
+ const revision = result.revision === void 0 ? "" : ` revision=${result.revision}`;
2105
+ logger.info(
2106
+ `[modelkit] catalog primed origin=${result.origin} rowCount=${result.rowCount}${revision}`
2107
+ );
2107
2108
  }
2108
2109
  async function fetchRemote(source, logger) {
2109
2110
  const fetchImpl = source.fetch ?? globalThis.fetch;
@@ -2116,7 +2117,9 @@ async function fetchRemote(source, logger) {
2116
2117
  if (res.status === 304) return { kept: true };
2117
2118
  if (!res.ok) throw new Error(`catalog fetch failed with status ${res.status}`);
2118
2119
  const json = await res.json();
2119
- const { rows, revision: rev } = parseEnvelope(json);
2120
+ const parsed = EnvelopeSchema.safeParse(json);
2121
+ if (!parsed.success) throw new Error("invalid catalog response: missing rows");
2122
+ const { rows, revision: rev } = toEnvelope(parsed.data);
2120
2123
  if (rev === void 0) return { rows, fromCache: false };
2121
2124
  return { rows, revision: rev, fromCache: false };
2122
2125
  }
@@ -2125,7 +2128,7 @@ async function writeCache(source, logger, rows, rev) {
2125
2128
  try {
2126
2129
  await source.cache.set(JSON.stringify({ rows, revision: rev }));
2127
2130
  } catch (err) {
2128
- logger.warn("[modelkit] catalog cache write failed", err);
2131
+ logger.warn(`[modelkit] catalog cache write failed: ${describeError(err)}`);
2129
2132
  }
2130
2133
  }
2131
2134
  async function prime(source, logger) {
@@ -2146,12 +2149,14 @@ async function prime(source, logger) {
2146
2149
  logPrimed(logger, result);
2147
2150
  return result;
2148
2151
  } catch (err) {
2149
- logger.warn("[modelkit] catalog remote failed, trying cache", err);
2152
+ logger.warn(`[modelkit] catalog remote failed, trying cache: ${describeError(err)}`);
2150
2153
  const error = describeError(err);
2151
2154
  try {
2152
2155
  const cached = await source.cache?.get();
2153
2156
  if (!cached) throw new Error("no cached catalog");
2154
- const { rows, revision: rev } = parseEnvelope(JSON.parse(cached));
2157
+ const cachedParsed = EnvelopeSchema.safeParse(JSON.parse(cached));
2158
+ if (!cachedParsed.success) throw new Error("invalid catalog response: missing rows");
2159
+ const { rows, revision: rev } = toEnvelope(cachedParsed.data);
2155
2160
  liveCatalog.replace(rows);
2156
2161
  liveState.origin = "last-good";
2157
2162
  liveState.revision = rev;
@@ -2164,7 +2169,9 @@ async function prime(source, logger) {
2164
2169
  logPrimed(logger, result);
2165
2170
  return result;
2166
2171
  } catch (cacheErr) {
2167
- logger.warn("[modelkit] catalog cache failed, using snapshot", cacheErr);
2172
+ logger.warn(
2173
+ `[modelkit] catalog cache failed, using snapshot: ${describeError(cacheErr)}`
2174
+ );
2168
2175
  liveCatalog.replace([...MODELS]);
2169
2176
  liveState.origin = "snapshot";
2170
2177
  liveState.revision = void 0;
@@ -2187,23 +2194,24 @@ async function refresh(source, logger) {
2187
2194
  liveState.revision = fetched.revision;
2188
2195
  await writeCache(source, logger, fetched.rows, fetched.revision);
2189
2196
  } catch (err) {
2190
- logger.warn("[modelkit] catalog refresh failed, keeping current rows", err);
2197
+ logger.warn(
2198
+ `[modelkit] catalog refresh failed, keeping current rows: ${describeError(err)}`
2199
+ );
2191
2200
  }
2192
2201
  }
2193
2202
  function primeCatalog(source) {
2194
- const memo = liveState.memo;
2195
- if (memo) return memo;
2203
+ if (liveState.memo) return liveState.memo;
2196
2204
  const logger = source.logger ?? console;
2197
- liveState.memo = prime(source, logger).then((result) => {
2205
+ const pending = prime(source, logger).then((result) => {
2198
2206
  const ttlMs = source.ttlMs ?? 3e5;
2199
2207
  if (ttlMs > 0 && liveState.timer === void 0) {
2200
2208
  liveState.timer = setInterval(() => void refresh(source, logger), ttlMs);
2201
- const maybeUnref = liveState.timer;
2202
- maybeUnref.unref?.();
2209
+ liveState.timer.unref?.();
2203
2210
  }
2204
2211
  return result;
2205
2212
  });
2206
- return liveState.memo;
2213
+ liveState.memo = pending;
2214
+ return pending;
2207
2215
  }
2208
2216
  function resetLiveCatalog() {
2209
2217
  if (liveState.timer !== void 0) {