@onyx.dev/onyx-database 1.0.0 → 1.0.3

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.
@@ -76,7 +76,7 @@ function readEnv(targetId) {
76
76
  return res;
77
77
  }
78
78
  async function readProjectFile(databaseId) {
79
- if (!isNode) return {};
79
+ if (!isNode) return { config: {} };
80
80
  const fs = await nodeImport("node:fs/promises");
81
81
  const path = await nodeImport("node:path");
82
82
  const cwd = gProcess?.cwd?.() ?? ".";
@@ -85,7 +85,7 @@ async function readProjectFile(databaseId) {
85
85
  const sanitized = txt.replace(/[\r\n]+/g, "");
86
86
  const json = dropUndefined(JSON.parse(sanitized));
87
87
  dbg("project file:", p, "\u2192", mask(json));
88
- return json;
88
+ return { config: json, path: p };
89
89
  };
90
90
  if (databaseId) {
91
91
  const specific = path.resolve(cwd, `onyx-database-${databaseId}.json`);
@@ -100,11 +100,11 @@ async function readProjectFile(databaseId) {
100
100
  return await tryRead(fallback);
101
101
  } catch {
102
102
  dbg("project file not found:", fallback);
103
- return {};
103
+ return { config: {} };
104
104
  }
105
105
  }
106
106
  async function readHomeProfile(databaseId) {
107
- if (!isNode) return {};
107
+ if (!isNode) return { config: {} };
108
108
  const fs = await nodeImport("node:fs/promises");
109
109
  const os = await nodeImport("node:os");
110
110
  const path = await nodeImport("node:path");
@@ -124,7 +124,7 @@ async function readHomeProfile(databaseId) {
124
124
  const sanitized = txt.replace(/[\r\n]+/g, "");
125
125
  const json = dropUndefined(JSON.parse(sanitized));
126
126
  dbg("home profile used:", p, "\u2192", mask(json));
127
- return json;
127
+ return { config: json, path: p };
128
128
  } catch (e) {
129
129
  const msg = e instanceof Error ? e.message : String(e);
130
130
  throw new OnyxConfigError(`Failed to read ${p}: ${msg}`);
@@ -143,7 +143,7 @@ async function readHomeProfile(databaseId) {
143
143
  dbg("no home-root fallback:", defaultInHome);
144
144
  if (!await fileExists(dir)) {
145
145
  dbg("~/.onyx does not exist:", dir);
146
- return {};
146
+ return { config: {} };
147
147
  }
148
148
  const files = await fs.readdir(dir).catch(() => []);
149
149
  const matches = files.filter((f) => f.startsWith("onyx-database-") && f.endsWith(".json"));
@@ -157,10 +157,10 @@ async function readHomeProfile(databaseId) {
157
157
  );
158
158
  }
159
159
  dbg("no usable home profiles found in", dir);
160
- return {};
160
+ return { config: {} };
161
161
  }
162
162
  async function readConfigPath(p) {
163
- if (!isNode) return {};
163
+ if (!isNode) return { config: {} };
164
164
  const fs = await nodeImport("node:fs/promises");
165
165
  const path = await nodeImport("node:path");
166
166
  const cwd = gProcess?.cwd?.() ?? ".";
@@ -170,7 +170,7 @@ async function readConfigPath(p) {
170
170
  const sanitized = txt.replace(/[\r\n]+/g, "");
171
171
  const json = dropUndefined(JSON.parse(sanitized));
172
172
  dbg("config path:", resolved, "\u2192", mask(json));
173
- return json;
173
+ return { config: json, path: resolved };
174
174
  } catch (e) {
175
175
  const msg = e instanceof Error ? e.message : String(e);
176
176
  throw new OnyxConfigError(`Failed to read ${resolved}: ${msg}`);
@@ -181,7 +181,8 @@ async function resolveConfig(input) {
181
181
  const env = readEnv(input?.databaseId);
182
182
  let cfgPath = {};
183
183
  if (configPath) {
184
- cfgPath = await readConfigPath(configPath);
184
+ const cfgRes = await readConfigPath(configPath);
185
+ cfgPath = cfgRes.config;
185
186
  }
186
187
  const targetId = input?.databaseId ?? env.databaseId ?? cfgPath.databaseId;
187
188
  let haveDbId = !!(input?.databaseId ?? env.databaseId ?? cfgPath.databaseId);
@@ -189,14 +190,16 @@ async function resolveConfig(input) {
189
190
  let haveApiSecret = !!(input?.apiSecret ?? env.apiSecret ?? cfgPath.apiSecret);
190
191
  let project = {};
191
192
  if (!(haveDbId && haveApiKey && haveApiSecret)) {
192
- project = await readProjectFile(targetId);
193
+ const projRes = await readProjectFile(targetId);
194
+ project = projRes.config;
193
195
  if (project.databaseId) haveDbId = true;
194
196
  if (project.apiKey) haveApiKey = true;
195
197
  if (project.apiSecret) haveApiSecret = true;
196
198
  }
197
199
  let home = {};
198
200
  if (!(haveDbId && haveApiKey && haveApiSecret)) {
199
- home = await readHomeProfile(targetId);
201
+ const homeRes = await readHomeProfile(targetId);
202
+ home = homeRes.config;
200
203
  }
201
204
  const merged = {
202
205
  baseUrl: DEFAULT_BASE_URL,
@@ -215,6 +218,10 @@ async function resolveConfig(input) {
215
218
  const fetchImpl = merged.fetch ?? (typeof gfetch === "function" ? (u, i) => gfetch(u, i) : async () => {
216
219
  throw new OnyxConfigError("No fetch available; provide OnyxConfig.fetch");
217
220
  });
221
+ const retryConfig = input?.retry ?? env.retry ?? cfgPath.retry ?? project.retry ?? home.retry ?? {};
222
+ const retryEnabled = retryConfig.enabled ?? true;
223
+ const maxRetries = retryConfig.maxRetries ?? 3;
224
+ const retryInitialDelayMs = retryConfig.initialDelayMs ?? 300;
218
225
  const missing = [];
219
226
  if (!databaseId) missing.push("databaseId");
220
227
  if (!apiKey) missing.push("apiKey");
@@ -237,7 +244,16 @@ async function resolveConfig(input) {
237
244
  `Missing required config: ${missing.join(", ")}. Sources: ${sources.join(", ")}`
238
245
  );
239
246
  }
240
- const resolved = { baseUrl, databaseId, apiKey, apiSecret, fetch: fetchImpl };
247
+ const resolved = {
248
+ baseUrl,
249
+ databaseId,
250
+ apiKey,
251
+ apiSecret,
252
+ fetch: fetchImpl,
253
+ retryEnabled,
254
+ maxRetries,
255
+ retryInitialDelayMs
256
+ };
241
257
  const source = {
242
258
  databaseId: input?.databaseId ? "explicit config" : env.databaseId ? "env" : cfgPath.databaseId ? "env ONYX_CONFIG_PATH" : project.databaseId ? "project file" : home.databaseId ? "home profile" : "unknown",
243
259
  apiKey: input?.apiKey ? "explicit config" : env.apiKey ? "env" : cfgPath.apiKey ? "env ONYX_CONFIG_PATH" : project.apiKey ? "project file" : home.apiKey ? "home profile" : "unknown",
@@ -310,7 +326,6 @@ function emitTypes(schema, options) {
310
326
  };
311
327
  const lines = [];
312
328
  lines.push(`// AUTO-GENERATED BY onyx-gen. DO NOT EDIT.`);
313
- lines.push(`// Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`);
314
329
  lines.push("");
315
330
  for (const t of schema.tables) {
316
331
  const typeName = `${opts.modelNamePrefix}${toPascalCase(t.name)}`;
@@ -385,7 +400,7 @@ function parseJsonAllowNaN(txt) {
385
400
  return JSON.parse(fixed);
386
401
  }
387
402
  }
388
- var HttpClient = class {
403
+ var HttpClient = class _HttpClient {
389
404
  baseUrl;
390
405
  apiKey;
391
406
  apiSecret;
@@ -393,6 +408,25 @@ var HttpClient = class {
393
408
  defaults;
394
409
  requestLoggingEnabled;
395
410
  responseLoggingEnabled;
411
+ retryEnabled;
412
+ maxRetries;
413
+ retryInitialDelayMs;
414
+ shouldRetry;
415
+ static parseRetryAfter(header) {
416
+ if (!header) return null;
417
+ const trimmed = header.trim();
418
+ if (trimmed === "") return null;
419
+ const seconds = Number(trimmed);
420
+ if (Number.isFinite(seconds)) {
421
+ return Math.max(0, seconds * 1e3);
422
+ }
423
+ const dateMs = Date.parse(trimmed);
424
+ if (!Number.isNaN(dateMs)) {
425
+ const now = Date.now();
426
+ return Math.max(0, dateMs - now);
427
+ }
428
+ return null;
429
+ }
396
430
  constructor(opts) {
397
431
  if (!opts.baseUrl || opts.baseUrl.trim() === "") {
398
432
  throw new OnyxConfigError("baseUrl is required");
@@ -417,6 +451,10 @@ var HttpClient = class {
417
451
  const envDebug = globalThis.process?.env?.ONYX_DEBUG === "true";
418
452
  this.requestLoggingEnabled = !!opts.requestLoggingEnabled || envDebug;
419
453
  this.responseLoggingEnabled = !!opts.responseLoggingEnabled || envDebug;
454
+ this.retryEnabled = opts.retryEnabled ?? true;
455
+ this.maxRetries = Math.max(0, opts.maxRetries ?? 2);
456
+ this.retryInitialDelayMs = Math.max(0, opts.retryInitialDelayMs ?? 100);
457
+ this.shouldRetry = (method, path) => method === "GET" || path.startsWith("/query/");
420
458
  }
421
459
  headers(extra) {
422
460
  const extras = { ...extra ?? {} };
@@ -457,9 +495,8 @@ var HttpClient = class {
457
495
  headers,
458
496
  body: payload
459
497
  };
460
- const isQuery = path.includes("/query/") && !/\/query\/(?:update|delete)\//.test(path);
461
- const canRetry = method === "GET" || isQuery;
462
- const maxAttempts = canRetry ? 3 : 1;
498
+ const canRetry = this.retryEnabled && this.shouldRetry(method, path);
499
+ const maxAttempts = canRetry ? this.maxRetries + 1 : 1;
463
500
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
464
501
  try {
465
502
  const res = await this.fetchImpl(url, init);
@@ -477,7 +514,10 @@ var HttpClient = class {
477
514
  if (!res.ok) {
478
515
  const msg = typeof data === "object" && data !== null && "error" in data && typeof data.error?.message === "string" ? String(data.error.message) : `${res.status} ${res.statusText}`;
479
516
  if (canRetry && res.status >= 500 && attempt + 1 < maxAttempts) {
480
- await new Promise((r) => setTimeout(r, 100 * 2 ** attempt));
517
+ const serverRetry = _HttpClient.parseRetryAfter(res.headers.get("retry-after"));
518
+ const backoff = this.retryInitialDelayMs * 2 ** attempt;
519
+ const delay = serverRetry ?? backoff;
520
+ await new Promise((r) => setTimeout(r, delay));
481
521
  continue;
482
522
  }
483
523
  throw new OnyxHttpError(msg, res.status, res.statusText, data, raw);
@@ -486,7 +526,8 @@ var HttpClient = class {
486
526
  } catch (err) {
487
527
  const retryable = canRetry && (!(err instanceof OnyxHttpError) || err.status >= 500);
488
528
  if (attempt + 1 < maxAttempts && retryable) {
489
- await new Promise((r) => setTimeout(r, 100 * 2 ** attempt));
529
+ const delay = this.retryInitialDelayMs * 2 ** attempt;
530
+ await new Promise((r) => setTimeout(r, delay));
490
531
  continue;
491
532
  }
492
533
  throw err;
@@ -1047,31 +1088,196 @@ var CascadeRelationshipBuilder = class {
1047
1088
  }
1048
1089
  };
1049
1090
 
1050
- // src/errors/onyx-error.ts
1051
- var OnyxError = class extends Error {
1052
- name = "OnyxError";
1053
- constructor(message) {
1054
- super(message);
1091
+ // src/helpers/schema-diff.ts
1092
+ function mapByName(items) {
1093
+ const map = /* @__PURE__ */ new Map();
1094
+ for (const item of items ?? []) {
1095
+ if (!item?.name) continue;
1096
+ map.set(item.name, item);
1097
+ }
1098
+ return map;
1099
+ }
1100
+ function normalizeEntities(schema) {
1101
+ if (Array.isArray(schema.entities)) {
1102
+ return schema.entities ?? [];
1103
+ }
1104
+ const tables = schema.tables;
1105
+ if (!Array.isArray(tables)) return [];
1106
+ return tables.map((table) => ({
1107
+ name: table.name,
1108
+ attributes: table.attributes ?? []
1109
+ }));
1110
+ }
1111
+ function normalizePartition(partition) {
1112
+ if (partition == null) return "";
1113
+ const trimmed = partition.trim();
1114
+ return trimmed;
1115
+ }
1116
+ function identifiersEqual(a, b) {
1117
+ if (!a && !b) return true;
1118
+ if (!a || !b) return false;
1119
+ return a.name === b.name && a.generator === b.generator && a.type === b.type;
1120
+ }
1121
+ function diffAttributes(apiAttrs, localAttrs) {
1122
+ const apiMap = mapByName(apiAttrs);
1123
+ const localMap = mapByName(localAttrs);
1124
+ const added = [];
1125
+ const removed = [];
1126
+ const changed = [];
1127
+ for (const [name, local] of localMap.entries()) {
1128
+ if (!apiMap.has(name)) {
1129
+ added.push(local);
1130
+ continue;
1131
+ }
1132
+ const api = apiMap.get(name);
1133
+ const apiNull = Boolean(api.isNullable);
1134
+ const localNull = Boolean(local.isNullable);
1135
+ if (api.type !== local.type || apiNull !== localNull) {
1136
+ changed.push({
1137
+ name,
1138
+ from: { type: api.type, isNullable: apiNull },
1139
+ to: { type: local.type, isNullable: localNull }
1140
+ });
1141
+ }
1055
1142
  }
1056
- };
1057
-
1058
- // src/impl/onyx.ts
1059
- var DEFAULT_CACHE_TTL = 5 * 60 * 1e3;
1060
- var cachedCfg = null;
1061
- function resolveConfigWithCache(config) {
1062
- const ttl = config?.ttl ?? DEFAULT_CACHE_TTL;
1063
- const now = Date.now();
1064
- if (cachedCfg && cachedCfg.expires > now) {
1065
- return cachedCfg.promise;
1066
- }
1067
- const { ttl: _ttl, requestLoggingEnabled: _reqLog, responseLoggingEnabled: _resLog, ...rest } = config ?? {};
1068
- const promise = resolveConfig(rest);
1069
- cachedCfg = { promise, expires: now + ttl };
1070
- return promise;
1143
+ for (const name of apiMap.keys()) {
1144
+ if (!localMap.has(name)) removed.push(name);
1145
+ }
1146
+ added.sort((a, b) => a.name.localeCompare(b.name));
1147
+ removed.sort();
1148
+ changed.sort((a, b) => a.name.localeCompare(b.name));
1149
+ if (!added.length && !removed.length && !changed.length) return null;
1150
+ return { added, removed, changed };
1071
1151
  }
1072
- function clearCacheConfig() {
1073
- cachedCfg = null;
1152
+ function diffIndexes(apiIndexes, localIndexes) {
1153
+ const apiMap = mapByName(apiIndexes);
1154
+ const localMap = mapByName(localIndexes);
1155
+ const added = [];
1156
+ const removed = [];
1157
+ const changed = [];
1158
+ for (const [name, local] of localMap.entries()) {
1159
+ if (!apiMap.has(name)) {
1160
+ added.push(local);
1161
+ continue;
1162
+ }
1163
+ const api = apiMap.get(name);
1164
+ const apiType = api.type ?? "DEFAULT";
1165
+ const localType = local.type ?? "DEFAULT";
1166
+ const apiScore = api.minimumScore;
1167
+ const localScore = local.minimumScore;
1168
+ if (apiType !== localType || apiScore !== localScore) {
1169
+ changed.push({ name, from: api, to: local });
1170
+ }
1171
+ }
1172
+ for (const name of apiMap.keys()) {
1173
+ if (!localMap.has(name)) removed.push(name);
1174
+ }
1175
+ added.sort((a, b) => a.name.localeCompare(b.name));
1176
+ removed.sort();
1177
+ changed.sort((a, b) => a.name.localeCompare(b.name));
1178
+ if (!added.length && !removed.length && !changed.length) return null;
1179
+ return { added, removed, changed };
1180
+ }
1181
+ function diffResolvers(apiResolvers, localResolvers) {
1182
+ const apiMap = mapByName(apiResolvers);
1183
+ const localMap = mapByName(localResolvers);
1184
+ const added = [];
1185
+ const removed = [];
1186
+ const changed = [];
1187
+ for (const [name, local] of localMap.entries()) {
1188
+ if (!apiMap.has(name)) {
1189
+ added.push(local);
1190
+ continue;
1191
+ }
1192
+ const api = apiMap.get(name);
1193
+ if (api.resolver !== local.resolver) {
1194
+ changed.push({ name, from: api, to: local });
1195
+ }
1196
+ }
1197
+ for (const name of apiMap.keys()) {
1198
+ if (!localMap.has(name)) removed.push(name);
1199
+ }
1200
+ added.sort((a, b) => a.name.localeCompare(b.name));
1201
+ removed.sort();
1202
+ changed.sort((a, b) => a.name.localeCompare(b.name));
1203
+ if (!added.length && !removed.length && !changed.length) return null;
1204
+ return { added, removed, changed };
1074
1205
  }
1206
+ function diffTriggers(apiTriggers, localTriggers) {
1207
+ const apiMap = mapByName(apiTriggers);
1208
+ const localMap = mapByName(localTriggers);
1209
+ const added = [];
1210
+ const removed = [];
1211
+ const changed = [];
1212
+ for (const [name, local] of localMap.entries()) {
1213
+ if (!apiMap.has(name)) {
1214
+ added.push(local);
1215
+ continue;
1216
+ }
1217
+ const api = apiMap.get(name);
1218
+ if (api.event !== local.event || api.trigger !== local.trigger) {
1219
+ changed.push({ name, from: api, to: local });
1220
+ }
1221
+ }
1222
+ for (const name of apiMap.keys()) {
1223
+ if (!localMap.has(name)) removed.push(name);
1224
+ }
1225
+ added.sort((a, b) => a.name.localeCompare(b.name));
1226
+ removed.sort();
1227
+ changed.sort((a, b) => a.name.localeCompare(b.name));
1228
+ if (!added.length && !removed.length && !changed.length) return null;
1229
+ return { added, removed, changed };
1230
+ }
1231
+ function computeSchemaDiff(apiSchema, localSchema) {
1232
+ const apiEntities = normalizeEntities(apiSchema);
1233
+ const localEntities = normalizeEntities(localSchema);
1234
+ const apiMap = mapByName(apiEntities);
1235
+ const localMap = mapByName(localEntities);
1236
+ const newTables = [];
1237
+ const removedTables = [];
1238
+ const changedTables = [];
1239
+ for (const [name, localEntity] of localMap.entries()) {
1240
+ if (!apiMap.has(name)) {
1241
+ newTables.push(name);
1242
+ continue;
1243
+ }
1244
+ const apiEntity = apiMap.get(name);
1245
+ const tableDiff = { name };
1246
+ const partitionFrom = normalizePartition(apiEntity.partition);
1247
+ const partitionTo = normalizePartition(localEntity.partition);
1248
+ if (partitionFrom !== partitionTo) {
1249
+ tableDiff.partition = { from: partitionFrom || null, to: partitionTo || null };
1250
+ }
1251
+ if (!identifiersEqual(apiEntity.identifier, localEntity.identifier)) {
1252
+ tableDiff.identifier = {
1253
+ from: apiEntity.identifier ?? null,
1254
+ to: localEntity.identifier ?? null
1255
+ };
1256
+ }
1257
+ const attrs = diffAttributes(apiEntity.attributes, localEntity.attributes);
1258
+ if (attrs) tableDiff.attributes = attrs;
1259
+ const indexes = diffIndexes(apiEntity.indexes, localEntity.indexes);
1260
+ if (indexes) tableDiff.indexes = indexes;
1261
+ const resolvers = diffResolvers(apiEntity.resolvers, localEntity.resolvers);
1262
+ if (resolvers) tableDiff.resolvers = resolvers;
1263
+ const triggers = diffTriggers(apiEntity.triggers, localEntity.triggers);
1264
+ if (triggers) tableDiff.triggers = triggers;
1265
+ const hasChange = tableDiff.partition || tableDiff.identifier || tableDiff.attributes || tableDiff.indexes || tableDiff.resolvers || tableDiff.triggers;
1266
+ if (hasChange) {
1267
+ changedTables.push(tableDiff);
1268
+ }
1269
+ }
1270
+ for (const name of apiMap.keys()) {
1271
+ if (!localMap.has(name)) removedTables.push(name);
1272
+ }
1273
+ newTables.sort();
1274
+ removedTables.sort();
1275
+ changedTables.sort((a, b) => a.name.localeCompare(b.name));
1276
+ return { newTables, removedTables, changedTables };
1277
+ }
1278
+
1279
+ // src/impl/onyx-core.ts
1280
+ var DEFAULT_CACHE_TTL = 5 * 60 * 1e3;
1075
1281
  function toSingleCondition(criteria) {
1076
1282
  return { conditionType: "SingleCondition", criteria };
1077
1283
  }
@@ -1125,7 +1331,20 @@ function normalizeDate(value) {
1125
1331
  return Number.isNaN(ts.getTime()) ? void 0 : ts;
1126
1332
  }
1127
1333
  function normalizeSchemaRevision(input, fallbackDatabaseId) {
1128
- const { meta, createdAt, publishedAt, revisionId, entityText, ...rest } = input;
1334
+ const {
1335
+ meta,
1336
+ createdAt,
1337
+ publishedAt,
1338
+ revisionId,
1339
+ entityText,
1340
+ databaseId,
1341
+ entities,
1342
+ revisionDescription,
1343
+ ...rest
1344
+ } = input;
1345
+ const dbId = typeof databaseId === "string" ? databaseId : fallbackDatabaseId;
1346
+ const entityList = Array.isArray(entities) ? entities : [];
1347
+ const revisionDesc = typeof revisionDescription === "string" ? revisionDescription : void 0;
1129
1348
  const mergedMeta = {
1130
1349
  revisionId: meta?.revisionId ?? revisionId,
1131
1350
  createdAt: normalizeDate(meta?.createdAt ?? createdAt),
@@ -1133,10 +1352,11 @@ function normalizeSchemaRevision(input, fallbackDatabaseId) {
1133
1352
  };
1134
1353
  const cleanedMeta = mergedMeta.revisionId || mergedMeta.createdAt || mergedMeta.publishedAt ? mergedMeta : void 0;
1135
1354
  return {
1136
- ...rest,
1137
- databaseId: input.databaseId ?? fallbackDatabaseId,
1355
+ databaseId: dbId,
1356
+ revisionDescription: revisionDesc,
1357
+ entities: entityList,
1138
1358
  meta: cleanedMeta,
1139
- entities: input.entities ?? []
1359
+ ...rest
1140
1360
  };
1141
1361
  }
1142
1362
  var OnyxDatabaseImpl = class {
@@ -1147,7 +1367,7 @@ var OnyxDatabaseImpl = class {
1147
1367
  requestLoggingEnabled;
1148
1368
  responseLoggingEnabled;
1149
1369
  defaultPartition;
1150
- constructor(config) {
1370
+ constructor(config, resolveConfigWithCache) {
1151
1371
  this.requestLoggingEnabled = !!config?.requestLoggingEnabled;
1152
1372
  this.responseLoggingEnabled = !!config?.responseLoggingEnabled;
1153
1373
  this.defaultPartition = config?.partition;
@@ -1164,7 +1384,10 @@ var OnyxDatabaseImpl = class {
1164
1384
  apiSecret: this.resolved.apiSecret,
1165
1385
  fetchImpl: this.resolved.fetch,
1166
1386
  requestLoggingEnabled: this.requestLoggingEnabled,
1167
- responseLoggingEnabled: this.responseLoggingEnabled
1387
+ responseLoggingEnabled: this.responseLoggingEnabled,
1388
+ retryEnabled: this.resolved.retryEnabled,
1389
+ maxRetries: this.resolved.maxRetries,
1390
+ retryInitialDelayMs: this.resolved.retryInitialDelayMs
1168
1391
  });
1169
1392
  }
1170
1393
  return {
@@ -1248,7 +1471,8 @@ var OnyxDatabaseImpl = class {
1248
1471
  const path = `/data/${encodeURIComponent(databaseId)}/${encodeURIComponent(
1249
1472
  table
1250
1473
  )}/${encodeURIComponent(primaryKey)}${params.toString() ? `?${params.toString()}` : ""}`;
1251
- return http.request("DELETE", path);
1474
+ await http.request("DELETE", path);
1475
+ return true;
1252
1476
  }
1253
1477
  async saveDocument(doc) {
1254
1478
  const { http, databaseId } = await this.ensureClient();
@@ -1291,6 +1515,10 @@ var OnyxDatabaseImpl = class {
1291
1515
  const res = await http.request("GET", path);
1292
1516
  return Array.isArray(res) ? res.map((entry) => normalizeSchemaRevision(entry, databaseId)) : [];
1293
1517
  }
1518
+ async diffSchema(localSchema) {
1519
+ const apiSchema = await this.getSchema();
1520
+ return computeSchemaDiff(apiSchema, localSchema);
1521
+ }
1294
1522
  async updateSchema(schema, options) {
1295
1523
  const { http, databaseId } = await this.ensureClient();
1296
1524
  const params = new URLSearchParams();
@@ -1624,7 +1852,6 @@ var QueryBuilderImpl = class {
1624
1852
  }
1625
1853
  async firstOrNull() {
1626
1854
  if (this.mode !== "select") throw new Error("Cannot call firstOrNull() in update mode.");
1627
- if (!this.conditions) throw new OnyxError("firstOrNull() requires a where() clause.");
1628
1855
  this.limitValue = 1;
1629
1856
  const pg = await this.page();
1630
1857
  return Array.isArray(pg.records) && pg.records.length > 0 ? pg.records[0] : null;
@@ -1716,12 +1943,32 @@ var CascadeBuilderImpl = class {
1716
1943
  return this.db.delete(table, primaryKey, opts);
1717
1944
  }
1718
1945
  };
1719
- var onyx = {
1720
- init(config) {
1721
- return new OnyxDatabaseImpl(config);
1722
- },
1723
- clearCacheConfig
1724
- };
1946
+ function createOnyxFacade(resolveConfig2) {
1947
+ let cachedCfg = null;
1948
+ function resolveConfigWithCache(config) {
1949
+ const ttl = config?.ttl ?? DEFAULT_CACHE_TTL;
1950
+ const now = Date.now();
1951
+ if (cachedCfg && cachedCfg.expires > now) {
1952
+ return cachedCfg.promise;
1953
+ }
1954
+ const { ttl: _ttl, requestLoggingEnabled: _reqLog, responseLoggingEnabled: _resLog, ...rest } = config ?? {};
1955
+ const promise = resolveConfig2(rest);
1956
+ cachedCfg = { promise, expires: now + ttl };
1957
+ return promise;
1958
+ }
1959
+ function clearCacheConfig() {
1960
+ cachedCfg = null;
1961
+ }
1962
+ return {
1963
+ init(config) {
1964
+ return new OnyxDatabaseImpl(config, resolveConfigWithCache);
1965
+ },
1966
+ clearCacheConfig
1967
+ };
1968
+ }
1969
+
1970
+ // src/impl/onyx.ts
1971
+ var onyx = createOnyxFacade((config) => resolveConfig(config));
1725
1972
 
1726
1973
  // gen/generate.ts
1727
1974
  var DEFAULT_SCHEMA_PATH = "./onyx.schema.json";