hide-a-bed 7.0.0 → 7.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.
@@ -1126,8 +1126,7 @@ const KEYS_TO_QUOTE = [
1126
1126
  "key",
1127
1127
  "keys",
1128
1128
  "startkey",
1129
- "startkey_docid",
1130
- "update"
1129
+ "startkey_docid"
1131
1130
  ];
1132
1131
  /**
1133
1132
  * Serialize CouchDB view options into a URL-safe query string, quoting values CouchDB expects as JSON.
@@ -2039,6 +2038,51 @@ const getDBInfo = async (configInput) => {
2039
2038
  return CouchDBInfo.parse(resp.body);
2040
2039
  };
2041
2040
 
2041
+ //#endregion
2042
+ //#region impl/headDB.mts
2043
+ /**
2044
+ * Performs a health check against the target CouchDB database using `HEAD /{db}`.
2045
+ *
2046
+ * @see {@link https://docs.couchdb.org/en/stable/api/database/common.html#head--db | CouchDB API Documentation}
2047
+ *
2048
+ * @param configInput - The CouchDB configuration input.
2049
+ * @returns A promise that resolves to `true` when the database responds successfully.
2050
+ * @throws {RetryableError} `RetryableError` If a retryable error occurs during the request.
2051
+ * @throws {OperationError} For other non-retryable response failures.
2052
+ */
2053
+ const headDB = async (configInput) => {
2054
+ const config = CouchConfig.parse(configInput);
2055
+ const logger = createLogger(config);
2056
+ const url = createCouchDbUrl(config.couch);
2057
+ let resp;
2058
+ try {
2059
+ resp = await fetchCouchJson({
2060
+ auth: config.auth,
2061
+ method: "HEAD",
2062
+ operation: "headDB",
2063
+ request: config.request,
2064
+ url
2065
+ });
2066
+ if (!isSuccessStatusCode("database", resp.statusCode)) {
2067
+ logger.error(`Non-success status code received: ${resp.statusCode}`);
2068
+ throw createResponseError({
2069
+ body: resp.body,
2070
+ defaultMessage: "Database health check failed",
2071
+ operation: "headDB",
2072
+ statusCode: resp.statusCode
2073
+ });
2074
+ }
2075
+ } catch (err) {
2076
+ logger.error("Error during head operation:", err);
2077
+ RetryableError.handleNetworkError(err, "headDB");
2078
+ }
2079
+ if (!resp) {
2080
+ logger.error("No response received from head request");
2081
+ throw new RetryableError("Database health check failed", 503, { operation: "headDB" });
2082
+ }
2083
+ return true;
2084
+ };
2085
+
2042
2086
  //#endregion
2043
2087
  //#region schema/sugar/lock.mts
2044
2088
  const LockDoc = CouchDoc.extend({
@@ -2372,6 +2416,7 @@ function doBind(config) {
2372
2416
  bulkSave: config.bindWithRetry ? withRetry(bulkSave.bind(null, config), retryOptions) : bulkSave.bind(null, config),
2373
2417
  bulkSaveTransaction: bulkSaveTransaction.bind(null, config),
2374
2418
  getDBInfo: config.bindWithRetry ? withRetry(getDBInfo.bind(null, config), retryOptions) : getDBInfo.bind(null, config),
2419
+ headDB: config.bindWithRetry ? withRetry(headDB.bind(null, config), retryOptions) : headDB.bind(null, config),
2375
2420
  patch: config.bindWithRetry ? withRetry(patch.bind(null, config), retryOptions) : patch.bind(null, config),
2376
2421
  patchDangerously: patchDangerously.bind(null, config),
2377
2422
  put: config.bindWithRetry ? withRetry(put.bind(null, config), retryOptions) : put.bind(null, config),
@@ -2384,4 +2429,4 @@ function doBind(config) {
2384
2429
  }
2385
2430
 
2386
2431
  //#endregion
2387
- export { ConflictError, HideABedError, NotFoundError, OperationError, QueryBuilder, RetryableError, ValidationError, bindConfig, bulkGet, bulkGetDictionary, bulkRemove, bulkRemoveMap, bulkSave, bulkSaveTransaction, createLock, createQuery, get, getAtRev, getDBInfo, patch, patchDangerously, put, query, queryStream, remove, removeLock, watchDocs, withRetry };
2432
+ export { ConflictError, HideABedError, NotFoundError, OperationError, QueryBuilder, RetryableError, ValidationError, bindConfig, bulkGet, bulkGetDictionary, bulkRemove, bulkRemoveMap, bulkSave, bulkSaveTransaction, createLock, createQuery, get, getAtRev, getDBInfo, headDB, patch, patchDangerously, put, query, queryStream, remove, removeLock, watchDocs, withRetry };
@@ -1,56 +1,58 @@
1
1
  // oxlint-disable typescript/no-explicit-any
2
- import type z from 'zod'
3
- import { CouchConfig, type CouchConfigInput } from '../schema/config.mts'
4
- import { withRetry } from './retry.mts'
2
+ import type z from "zod";
3
+ import { CouchConfig, type CouchConfigInput } from "../schema/config.mts";
4
+ import { withRetry } from "./retry.mts";
5
5
  import {
6
6
  type BulkGetBound,
7
7
  bulkGet,
8
8
  type BulkGetDictionaryBound,
9
- bulkGetDictionary
10
- } from './bulkGet.mts'
11
- import { type GetBound, type GetAtRevBound, getAtRev, get } from './get.mts'
12
- import { queryStream } from './stream.mts'
13
- import { patch, patchDangerously } from './patch.mts'
14
- import { put } from './put.mts'
15
- import type { QueryBound } from './query.mts'
16
- import { query } from './query.mts'
17
- import { bulkRemove, bulkRemoveMap } from './bulkRemove.mts'
18
- import { bulkSave, bulkSaveTransaction } from './bulkSave.mts'
19
- import { getDBInfo } from './getDBInfo.mts'
20
- import { remove } from './remove.mts'
21
- import { createLock, removeLock } from './sugar/lock.mts'
22
- import { watchDocs } from './sugar/watch.mts'
9
+ bulkGetDictionary,
10
+ } from "./bulkGet.mts";
11
+ import { type GetBound, type GetAtRevBound, getAtRev, get } from "./get.mts";
12
+ import { queryStream } from "./stream.mts";
13
+ import { patch, patchDangerously } from "./patch.mts";
14
+ import { put } from "./put.mts";
15
+ import type { QueryBound } from "./query.mts";
16
+ import { query } from "./query.mts";
17
+ import { bulkRemove, bulkRemoveMap } from "./bulkRemove.mts";
18
+ import { bulkSave, bulkSaveTransaction } from "./bulkSave.mts";
19
+ import { getDBInfo } from "./getDBInfo.mts";
20
+ import { headDB } from "./headDB.mts";
21
+ import { remove } from "./remove.mts";
22
+ import { createLock, removeLock } from "./sugar/lock.mts";
23
+ import { watchDocs } from "./sugar/watch.mts";
23
24
 
24
25
  type BoundConfigMethod<T> = T extends (...args: infer Args) => infer Result
25
26
  ? Args extends [unknown, ...infer Rest]
26
27
  ? (...args: Rest) => Result
27
28
  : never
28
- : never
29
+ : never;
29
30
 
30
31
  type BoundMethods = {
31
- bulkGet: BulkGetBound
32
- bulkGetDictionary: BulkGetDictionaryBound
33
- get: GetBound
34
- getAtRev: GetAtRevBound
35
- query: QueryBound
36
- bulkRemove: BoundConfigMethod<typeof bulkRemove>
37
- bulkRemoveMap: BoundConfigMethod<typeof bulkRemoveMap>
38
- bulkSave: BoundConfigMethod<typeof bulkSave>
39
- bulkSaveTransaction: BoundConfigMethod<typeof bulkSaveTransaction>
40
- getDBInfo: BoundConfigMethod<typeof getDBInfo>
41
- patch: BoundConfigMethod<typeof patch>
42
- patchDangerously: BoundConfigMethod<typeof patchDangerously>
43
- put: BoundConfigMethod<typeof put>
44
- queryStream: BoundConfigMethod<typeof queryStream>
45
- remove: BoundConfigMethod<typeof remove>
46
- createLock: BoundConfigMethod<typeof createLock>
47
- removeLock: BoundConfigMethod<typeof removeLock>
48
- watchDocs: BoundConfigMethod<typeof watchDocs>
49
- }
32
+ bulkGet: BulkGetBound;
33
+ bulkGetDictionary: BulkGetDictionaryBound;
34
+ get: GetBound;
35
+ getAtRev: GetAtRevBound;
36
+ query: QueryBound;
37
+ bulkRemove: BoundConfigMethod<typeof bulkRemove>;
38
+ bulkRemoveMap: BoundConfigMethod<typeof bulkRemoveMap>;
39
+ bulkSave: BoundConfigMethod<typeof bulkSave>;
40
+ bulkSaveTransaction: BoundConfigMethod<typeof bulkSaveTransaction>;
41
+ getDBInfo: BoundConfigMethod<typeof getDBInfo>;
42
+ headDB: BoundConfigMethod<typeof headDB>;
43
+ patch: BoundConfigMethod<typeof patch>;
44
+ patchDangerously: BoundConfigMethod<typeof patchDangerously>;
45
+ put: BoundConfigMethod<typeof put>;
46
+ queryStream: BoundConfigMethod<typeof queryStream>;
47
+ remove: BoundConfigMethod<typeof remove>;
48
+ createLock: BoundConfigMethod<typeof createLock>;
49
+ removeLock: BoundConfigMethod<typeof removeLock>;
50
+ watchDocs: BoundConfigMethod<typeof watchDocs>;
51
+ };
50
52
 
51
53
  export type BoundInstance = BoundMethods & {
52
- options(overrides: Partial<z.input<typeof CouchConfig>>): BoundInstance
53
- }
54
+ options(overrides: Partial<z.input<typeof CouchConfig>>): BoundInstance;
55
+ };
54
56
 
55
57
  /**
56
58
  * Build a validated binding that exposes CouchDB helpers plus an options() helper for overrides.
@@ -58,20 +60,22 @@ export type BoundInstance = BoundMethods & {
58
60
  * @returns A bound instance with CouchDB operations and an options() method for overrides
59
61
  */
60
62
  export const bindConfig = (config: CouchConfigInput): BoundInstance => {
61
- const parsedConfig = CouchConfig.parse(config)
63
+ const parsedConfig = CouchConfig.parse(config);
62
64
 
63
- const funcs = doBind(parsedConfig)
65
+ const funcs = doBind(parsedConfig);
64
66
 
65
67
  // Add the options function that returns a new bound instance
66
68
  // this allows the user to override some options
67
- const reconfigure: BoundInstance['options'] = (overrides: Partial<CouchConfigInput>) => {
68
- const newConfig: z.input<typeof CouchConfig> = { ...config, ...overrides }
69
- return bindConfig(newConfig)
70
- }
69
+ const reconfigure: BoundInstance["options"] = (
70
+ overrides: Partial<CouchConfigInput>,
71
+ ) => {
72
+ const newConfig: z.input<typeof CouchConfig> = { ...config, ...overrides };
73
+ return bindConfig(newConfig);
74
+ };
71
75
 
72
- const bound: BoundInstance = { ...funcs, options: reconfigure }
73
- return bound
74
- }
76
+ const bound: BoundInstance = { ...funcs, options: reconfigure };
77
+ return bound;
78
+ };
75
79
 
76
80
  /**
77
81
  * @internal
@@ -82,19 +86,21 @@ export const bindConfig = (config: CouchConfigInput): BoundInstance => {
82
86
  * @param config The CouchDB configuration
83
87
  * @returns The bound function, possibly wrapped with retry logic
84
88
  */
85
- export function getBoundWithRetry<TBound extends (...args: any[]) => Promise<any>>(
89
+ export function getBoundWithRetry<
90
+ TBound extends (...args: any[]) => Promise<any>,
91
+ >(
86
92
  func: (config: CouchConfig, ...args: any[]) => Promise<any>,
87
- config: CouchConfig
93
+ config: CouchConfig,
88
94
  ) {
89
- const bound = func.bind(null, config)
95
+ const bound = func.bind(null, config);
90
96
  if (config.bindWithRetry) {
91
97
  return withRetry(bound, {
92
98
  maxRetries: config.maxRetries ?? 10,
93
99
  initialDelay: config.initialDelay ?? 1000,
94
- backoffFactor: config.backoffFactor ?? 2
95
- }) as TBound
100
+ backoffFactor: config.backoffFactor ?? 2,
101
+ }) as TBound;
96
102
  } else {
97
- return bound as TBound
103
+ return bound as TBound;
98
104
  }
99
105
  }
100
106
 
@@ -110,8 +116,8 @@ function doBind(config: CouchConfig): BoundMethods {
110
116
  const retryOptions = {
111
117
  maxRetries: config.maxRetries ?? 10,
112
118
  initialDelay: config.initialDelay ?? 1000,
113
- backoffFactor: config.backoffFactor ?? 2
114
- }
119
+ backoffFactor: config.backoffFactor ?? 2,
120
+ };
115
121
 
116
122
  // Create the object without the config property first
117
123
  const result: BoundMethods = {
@@ -120,7 +126,10 @@ function doBind(config: CouchConfig): BoundMethods {
120
126
  * To preserve the overloads we need dedicated Bound types
121
127
  */
122
128
  bulkGet: getBoundWithRetry<BulkGetBound>(bulkGet, config),
123
- bulkGetDictionary: getBoundWithRetry<BulkGetDictionaryBound>(bulkGetDictionary, config),
129
+ bulkGetDictionary: getBoundWithRetry<BulkGetDictionaryBound>(
130
+ bulkGetDictionary,
131
+ config,
132
+ ),
124
133
  get: getBoundWithRetry<GetBound>(get, config),
125
134
  getAtRev: getBoundWithRetry<GetAtRevBound>(getAtRev, config),
126
135
  query: getBoundWithRetry<QueryBound>(query, config),
@@ -141,6 +150,9 @@ function doBind(config: CouchConfig): BoundMethods {
141
150
  getDBInfo: config.bindWithRetry
142
151
  ? withRetry(getDBInfo.bind(null, config), retryOptions)
143
152
  : getDBInfo.bind(null, config),
153
+ headDB: config.bindWithRetry
154
+ ? withRetry(headDB.bind(null, config), retryOptions)
155
+ : headDB.bind(null, config),
144
156
  patch: config.bindWithRetry
145
157
  ? withRetry(patch.bind(null, config), retryOptions)
146
158
  : patch.bind(null, config),
@@ -157,8 +169,8 @@ function doBind(config: CouchConfig): BoundMethods {
157
169
 
158
170
  createLock: createLock.bind(null, config),
159
171
  removeLock: removeLock.bind(null, config),
160
- watchDocs: watchDocs.bind(null, config)
161
- }
172
+ watchDocs: watchDocs.bind(null, config),
173
+ };
162
174
 
163
- return result
175
+ return result;
164
176
  }
@@ -0,0 +1,55 @@
1
+ import { RetryableError, createResponseError } from "./utils/errors.mts";
2
+ import { createLogger } from "./utils/logger.mts";
3
+ import { CouchConfig, type CouchConfigInput } from "../schema/config.mts";
4
+ import { fetchCouchJson } from "./utils/fetch.mts";
5
+ import { isSuccessStatusCode } from "./utils/response.mts";
6
+ import { createCouchDbUrl } from "./utils/url.mts";
7
+
8
+ /**
9
+ * Performs a health check against the target CouchDB database using `HEAD /{db}`.
10
+ *
11
+ * @see {@link https://docs.couchdb.org/en/stable/api/database/common.html#head--db | CouchDB API Documentation}
12
+ *
13
+ * @param configInput - The CouchDB configuration input.
14
+ * @returns A promise that resolves to `true` when the database responds successfully.
15
+ * @throws {RetryableError} `RetryableError` If a retryable error occurs during the request.
16
+ * @throws {OperationError} For other non-retryable response failures.
17
+ */
18
+ export const headDB = async (configInput: CouchConfigInput): Promise<true> => {
19
+ const config = CouchConfig.parse(configInput);
20
+ const logger = createLogger(config);
21
+ const url = createCouchDbUrl(config.couch);
22
+
23
+ let resp;
24
+ try {
25
+ resp = await fetchCouchJson({
26
+ auth: config.auth,
27
+ method: "HEAD",
28
+ operation: "headDB",
29
+ request: config.request,
30
+ url,
31
+ });
32
+
33
+ if (!isSuccessStatusCode("database", resp.statusCode)) {
34
+ logger.error(`Non-success status code received: ${resp.statusCode}`);
35
+ throw createResponseError({
36
+ body: resp.body,
37
+ defaultMessage: "Database health check failed",
38
+ operation: "headDB",
39
+ statusCode: resp.statusCode,
40
+ });
41
+ }
42
+ } catch (err) {
43
+ logger.error("Error during head operation:", err);
44
+ RetryableError.handleNetworkError(err, "headDB");
45
+ }
46
+
47
+ if (!resp) {
48
+ logger.error("No response received from head request");
49
+ throw new RetryableError("Database health check failed", 503, {
50
+ operation: "headDB",
51
+ });
52
+ }
53
+
54
+ return true;
55
+ };