@onyx.dev/onyx-database 1.0.2 → 1.1.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/dist/index.cjs CHANGED
@@ -211,6 +211,10 @@ async function resolveConfig(input) {
211
211
  const fetchImpl = merged.fetch ?? (typeof gfetch === "function" ? (u, i) => gfetch(u, i) : async () => {
212
212
  throw new OnyxConfigError("No fetch available; provide OnyxConfig.fetch");
213
213
  });
214
+ const retryConfig = input?.retry ?? env.retry ?? cfgPath.retry ?? project.retry ?? home.retry ?? {};
215
+ const retryEnabled = retryConfig.enabled ?? true;
216
+ const maxRetries = retryConfig.maxRetries ?? 3;
217
+ const retryInitialDelayMs = retryConfig.initialDelayMs ?? 300;
214
218
  const missing = [];
215
219
  if (!databaseId) missing.push("databaseId");
216
220
  if (!apiKey) missing.push("apiKey");
@@ -233,7 +237,16 @@ async function resolveConfig(input) {
233
237
  `Missing required config: ${missing.join(", ")}. Sources: ${sources.join(", ")}`
234
238
  );
235
239
  }
236
- const resolved = { baseUrl, databaseId, apiKey, apiSecret, fetch: fetchImpl };
240
+ const resolved = {
241
+ baseUrl,
242
+ databaseId,
243
+ apiKey,
244
+ apiSecret,
245
+ fetch: fetchImpl,
246
+ retryEnabled,
247
+ maxRetries,
248
+ retryInitialDelayMs
249
+ };
237
250
  const source = {
238
251
  databaseId: input?.databaseId ? "explicit config" : env.databaseId ? "env" : cfgPath.databaseId ? "env ONYX_CONFIG_PATH" : project.databaseId ? "project file" : home.databaseId ? "home profile" : "unknown",
239
252
  apiKey: input?.apiKey ? "explicit config" : env.apiKey ? "env" : cfgPath.apiKey ? "env ONYX_CONFIG_PATH" : project.apiKey ? "project file" : home.apiKey ? "home profile" : "unknown",
@@ -290,7 +303,7 @@ function parseJsonAllowNaN(txt) {
290
303
  return JSON.parse(fixed);
291
304
  }
292
305
  }
293
- var HttpClient = class {
306
+ var HttpClient = class _HttpClient {
294
307
  baseUrl;
295
308
  apiKey;
296
309
  apiSecret;
@@ -298,6 +311,25 @@ var HttpClient = class {
298
311
  defaults;
299
312
  requestLoggingEnabled;
300
313
  responseLoggingEnabled;
314
+ retryEnabled;
315
+ maxRetries;
316
+ retryInitialDelayMs;
317
+ shouldRetry;
318
+ static parseRetryAfter(header) {
319
+ if (!header) return null;
320
+ const trimmed = header.trim();
321
+ if (trimmed === "") return null;
322
+ const seconds = Number(trimmed);
323
+ if (Number.isFinite(seconds)) {
324
+ return Math.max(0, seconds * 1e3);
325
+ }
326
+ const dateMs = Date.parse(trimmed);
327
+ if (!Number.isNaN(dateMs)) {
328
+ const now = Date.now();
329
+ return Math.max(0, dateMs - now);
330
+ }
331
+ return null;
332
+ }
301
333
  constructor(opts) {
302
334
  if (!opts.baseUrl || opts.baseUrl.trim() === "") {
303
335
  throw new OnyxConfigError("baseUrl is required");
@@ -322,6 +354,10 @@ var HttpClient = class {
322
354
  const envDebug = globalThis.process?.env?.ONYX_DEBUG === "true";
323
355
  this.requestLoggingEnabled = !!opts.requestLoggingEnabled || envDebug;
324
356
  this.responseLoggingEnabled = !!opts.responseLoggingEnabled || envDebug;
357
+ this.retryEnabled = opts.retryEnabled ?? true;
358
+ this.maxRetries = Math.max(0, opts.maxRetries ?? 2);
359
+ this.retryInitialDelayMs = Math.max(0, opts.retryInitialDelayMs ?? 100);
360
+ this.shouldRetry = (method, path) => method === "GET" || path.startsWith("/query/");
325
361
  }
326
362
  headers(extra) {
327
363
  const extras = { ...extra ?? {} };
@@ -362,9 +398,8 @@ var HttpClient = class {
362
398
  headers,
363
399
  body: payload
364
400
  };
365
- const isQuery = path.includes("/query/") && !/\/query\/(?:update|delete)\//.test(path);
366
- const canRetry = method === "GET" || isQuery;
367
- const maxAttempts = canRetry ? 3 : 1;
401
+ const canRetry = this.retryEnabled && this.shouldRetry(method, path);
402
+ const maxAttempts = canRetry ? this.maxRetries + 1 : 1;
368
403
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
369
404
  try {
370
405
  const res = await this.fetchImpl(url, init);
@@ -382,7 +417,10 @@ var HttpClient = class {
382
417
  if (!res.ok) {
383
418
  const msg = typeof data === "object" && data !== null && "error" in data && typeof data.error?.message === "string" ? String(data.error.message) : `${res.status} ${res.statusText}`;
384
419
  if (canRetry && res.status >= 500 && attempt + 1 < maxAttempts) {
385
- await new Promise((r) => setTimeout(r, 100 * 2 ** attempt));
420
+ const serverRetry = _HttpClient.parseRetryAfter(res.headers.get("retry-after"));
421
+ const backoff = this.retryInitialDelayMs * 2 ** attempt;
422
+ const delay = serverRetry ?? backoff;
423
+ await new Promise((r) => setTimeout(r, delay));
386
424
  continue;
387
425
  }
388
426
  throw new OnyxHttpError(msg, res.status, res.statusText, data, raw);
@@ -391,7 +429,8 @@ var HttpClient = class {
391
429
  } catch (err) {
392
430
  const retryable = canRetry && (!(err instanceof OnyxHttpError) || err.status >= 500);
393
431
  if (attempt + 1 < maxAttempts && retryable) {
394
- await new Promise((r) => setTimeout(r, 100 * 2 ** attempt));
432
+ const delay = this.retryInitialDelayMs * 2 ** attempt;
433
+ await new Promise((r) => setTimeout(r, delay));
395
434
  continue;
396
435
  }
397
436
  throw err;
@@ -952,14 +991,6 @@ var CascadeRelationshipBuilder = class {
952
991
  }
953
992
  };
954
993
 
955
- // src/errors/onyx-error.ts
956
- var OnyxError = class extends Error {
957
- name = "OnyxError";
958
- constructor(message) {
959
- super(message);
960
- }
961
- };
962
-
963
994
  // src/helpers/schema-diff.ts
964
995
  function mapByName(items) {
965
996
  const map = /* @__PURE__ */ new Map();
@@ -1148,23 +1179,8 @@ function computeSchemaDiff(apiSchema, localSchema) {
1148
1179
  return { newTables, removedTables, changedTables };
1149
1180
  }
1150
1181
 
1151
- // src/impl/onyx.ts
1182
+ // src/impl/onyx-core.ts
1152
1183
  var DEFAULT_CACHE_TTL = 5 * 60 * 1e3;
1153
- var cachedCfg = null;
1154
- function resolveConfigWithCache(config) {
1155
- const ttl = config?.ttl ?? DEFAULT_CACHE_TTL;
1156
- const now = Date.now();
1157
- if (cachedCfg && cachedCfg.expires > now) {
1158
- return cachedCfg.promise;
1159
- }
1160
- const { ttl: _ttl, requestLoggingEnabled: _reqLog, responseLoggingEnabled: _resLog, ...rest } = config ?? {};
1161
- const promise = resolveConfig(rest);
1162
- cachedCfg = { promise, expires: now + ttl };
1163
- return promise;
1164
- }
1165
- function clearCacheConfig() {
1166
- cachedCfg = null;
1167
- }
1168
1184
  function toSingleCondition(criteria) {
1169
1185
  return { conditionType: "SingleCondition", criteria };
1170
1186
  }
@@ -1254,7 +1270,7 @@ var OnyxDatabaseImpl = class {
1254
1270
  requestLoggingEnabled;
1255
1271
  responseLoggingEnabled;
1256
1272
  defaultPartition;
1257
- constructor(config) {
1273
+ constructor(config, resolveConfigWithCache) {
1258
1274
  this.requestLoggingEnabled = !!config?.requestLoggingEnabled;
1259
1275
  this.responseLoggingEnabled = !!config?.responseLoggingEnabled;
1260
1276
  this.defaultPartition = config?.partition;
@@ -1271,7 +1287,10 @@ var OnyxDatabaseImpl = class {
1271
1287
  apiSecret: this.resolved.apiSecret,
1272
1288
  fetchImpl: this.resolved.fetch,
1273
1289
  requestLoggingEnabled: this.requestLoggingEnabled,
1274
- responseLoggingEnabled: this.responseLoggingEnabled
1290
+ responseLoggingEnabled: this.responseLoggingEnabled,
1291
+ retryEnabled: this.resolved.retryEnabled,
1292
+ maxRetries: this.resolved.maxRetries,
1293
+ retryInitialDelayMs: this.resolved.retryInitialDelayMs
1275
1294
  });
1276
1295
  }
1277
1296
  return {
@@ -1306,6 +1325,14 @@ var OnyxDatabaseImpl = class {
1306
1325
  qb.select(...fields);
1307
1326
  return qb;
1308
1327
  }
1328
+ search(queryText, minScore) {
1329
+ const qb = new QueryBuilderImpl(
1330
+ this,
1331
+ "ALL",
1332
+ this.defaultPartition
1333
+ );
1334
+ return qb.search(queryText, minScore);
1335
+ }
1309
1336
  cascade(...relationships) {
1310
1337
  const cb = new CascadeBuilderImpl(this);
1311
1338
  return cb.cascade(...relationships);
@@ -1592,6 +1619,7 @@ var QueryBuilderImpl = class {
1592
1619
  toSelectQuery() {
1593
1620
  return {
1594
1621
  type: "SelectQuery",
1622
+ table: this.table,
1595
1623
  fields: this.fields,
1596
1624
  conditions: this.serializableConditions(),
1597
1625
  sort: this.sort,
@@ -1631,6 +1659,13 @@ var QueryBuilderImpl = class {
1631
1659
  this.resolvers = flat.length > 0 ? flat : null;
1632
1660
  return this;
1633
1661
  }
1662
+ search(queryText, minScore) {
1663
+ return this.and({
1664
+ field: "__full_text__",
1665
+ operator: "MATCHES",
1666
+ value: { queryText, minScore: minScore ?? null }
1667
+ });
1668
+ }
1634
1669
  where(condition) {
1635
1670
  const c2 = toCondition(condition);
1636
1671
  if (!this.conditions) {
@@ -1736,7 +1771,6 @@ var QueryBuilderImpl = class {
1736
1771
  }
1737
1772
  async firstOrNull() {
1738
1773
  if (this.mode !== "select") throw new Error("Cannot call firstOrNull() in update mode.");
1739
- if (!this.conditions) throw new OnyxError("firstOrNull() requires a where() clause.");
1740
1774
  this.limitValue = 1;
1741
1775
  const pg = await this.page();
1742
1776
  return Array.isArray(pg.records) && pg.records.length > 0 ? pg.records[0] : null;
@@ -1828,12 +1862,32 @@ var CascadeBuilderImpl = class {
1828
1862
  return this.db.delete(table, primaryKey, opts);
1829
1863
  }
1830
1864
  };
1831
- var onyx = {
1832
- init(config) {
1833
- return new OnyxDatabaseImpl(config);
1834
- },
1835
- clearCacheConfig
1836
- };
1865
+ function createOnyxFacade(resolveConfig2) {
1866
+ let cachedCfg = null;
1867
+ function resolveConfigWithCache(config) {
1868
+ const ttl = config?.ttl ?? DEFAULT_CACHE_TTL;
1869
+ const now = Date.now();
1870
+ if (cachedCfg && cachedCfg.expires > now) {
1871
+ return cachedCfg.promise;
1872
+ }
1873
+ const { ttl: _ttl, requestLoggingEnabled: _reqLog, responseLoggingEnabled: _resLog, ...rest } = config ?? {};
1874
+ const promise = resolveConfig2(rest);
1875
+ cachedCfg = { promise, expires: now + ttl };
1876
+ return promise;
1877
+ }
1878
+ function clearCacheConfig() {
1879
+ cachedCfg = null;
1880
+ }
1881
+ return {
1882
+ init(config) {
1883
+ return new OnyxDatabaseImpl(config, resolveConfigWithCache);
1884
+ },
1885
+ clearCacheConfig
1886
+ };
1887
+ }
1888
+
1889
+ // src/impl/onyx.ts
1890
+ var onyx = createOnyxFacade((config) => resolveConfig(config));
1837
1891
 
1838
1892
  // src/helpers/sort.ts
1839
1893
  var asc = (field) => ({ field, order: "ASC" });
@@ -1963,6 +2017,10 @@ var ConditionBuilderImpl = class {
1963
2017
 
1964
2018
  // src/helpers/conditions.ts
1965
2019
  var c = (field, operator, value) => new ConditionBuilderImpl({ field, operator, value });
2020
+ var fullText = (queryText, minScore) => ({
2021
+ queryText,
2022
+ minScore: minScore ?? null
2023
+ });
1966
2024
  var eq = (field, value) => c(field, "EQUAL", value);
1967
2025
  var neq = (field, value) => c(field, "NOT_EQUAL", value);
1968
2026
  function inOp(field, values) {
@@ -1985,6 +2043,7 @@ var gte = (field, value) => c(field, "GREATER_THAN_EQUAL", value);
1985
2043
  var lt = (field, value) => c(field, "LESS_THAN", value);
1986
2044
  var lte = (field, value) => c(field, "LESS_THAN_EQUAL", value);
1987
2045
  var matches = (field, regex) => c(field, "MATCHES", regex);
2046
+ var search = (queryText, minScore) => c("__full_text__", "MATCHES", fullText(queryText, minScore));
1988
2047
  var notMatches = (field, regex) => c(field, "NOT_MATCHES", regex);
1989
2048
  var like = (field, pattern) => c(field, "LIKE", pattern);
1990
2049
  var notLike = (field, pattern) => c(field, "NOT_LIKE", pattern);
@@ -2051,6 +2110,7 @@ exports.percentile = percentile;
2051
2110
  exports.replace = replace;
2052
2111
  exports.sdkName = sdkName;
2053
2112
  exports.sdkVersion = sdkVersion;
2113
+ exports.search = search;
2054
2114
  exports.startsWith = startsWith;
2055
2115
  exports.std = std;
2056
2116
  exports.substring = substring;