hide-a-bed 3.0.0 → 4.0.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.
Files changed (68) hide show
  1. package/README.md +104 -0
  2. package/cjs/impl/bulk.cjs +55 -6
  3. package/cjs/impl/crud.cjs +57 -7
  4. package/cjs/impl/errors.cjs +63 -0
  5. package/cjs/impl/logger.cjs +61 -0
  6. package/cjs/impl/patch.cjs +10 -5
  7. package/cjs/impl/query.cjs +11 -1
  8. package/cjs/impl/retry.cjs +54 -0
  9. package/cjs/impl/stream.cjs +48 -7
  10. package/cjs/index.cjs +27 -3
  11. package/cjs/schema/bind.cjs +40 -0
  12. package/cjs/schema/bulk.cjs +14 -0
  13. package/cjs/schema/config.cjs +20 -1
  14. package/cjs/schema/crud.cjs +9 -1
  15. package/cjs/schema/patch.cjs +8 -8
  16. package/cjs/schema/query.cjs +28 -19
  17. package/cjs/schema/stream.cjs +42 -0
  18. package/impl/bulk.d.mts +7 -0
  19. package/impl/bulk.d.mts.map +1 -0
  20. package/impl/bulk.mjs +58 -6
  21. package/impl/crud.d.mts +5 -0
  22. package/impl/crud.d.mts.map +1 -0
  23. package/impl/crud.mjs +67 -6
  24. package/impl/errors.d.mts +35 -0
  25. package/impl/errors.d.mts.map +1 -0
  26. package/impl/errors.mjs +53 -0
  27. package/impl/logger.d.mts +32 -0
  28. package/impl/logger.d.mts.map +1 -0
  29. package/impl/logger.mjs +59 -0
  30. package/impl/patch.d.mts +4 -0
  31. package/impl/patch.d.mts.map +1 -0
  32. package/impl/patch.mjs +38 -6
  33. package/impl/query.d.mts +170 -0
  34. package/impl/query.d.mts.map +1 -0
  35. package/impl/query.mjs +38 -2
  36. package/impl/retry.d.mts +2 -0
  37. package/impl/retry.d.mts.map +1 -0
  38. package/impl/retry.mjs +39 -0
  39. package/impl/stream.d.mts +3 -0
  40. package/impl/stream.d.mts.map +1 -0
  41. package/impl/stream.mjs +72 -17
  42. package/index.d.mts +39 -0
  43. package/index.d.mts.map +1 -0
  44. package/index.mjs +33 -4
  45. package/package.json +1 -1
  46. package/schema/bind.d.mts +470 -0
  47. package/schema/bind.d.mts.map +1 -0
  48. package/schema/bind.mjs +21 -0
  49. package/schema/bulk.d.mts +358 -0
  50. package/schema/bulk.d.mts.map +1 -0
  51. package/schema/bulk.mjs +17 -0
  52. package/schema/config.d.mts +76 -0
  53. package/schema/config.d.mts.map +1 -0
  54. package/schema/config.mjs +22 -1
  55. package/schema/crud.d.mts +260 -0
  56. package/schema/crud.d.mts.map +1 -0
  57. package/schema/crud.mjs +12 -0
  58. package/schema/patch.d.mts +116 -0
  59. package/schema/patch.d.mts.map +1 -0
  60. package/schema/patch.mjs +11 -7
  61. package/schema/query.d.mts +362 -0
  62. package/schema/query.d.mts.map +1 -0
  63. package/schema/query.mjs +30 -18
  64. package/schema/stream.d.mts +193 -0
  65. package/schema/stream.d.mts.map +1 -0
  66. package/schema/stream.mjs +23 -0
  67. package/build/build.mjs +0 -16
  68. package/build/build.rewrite-imports.mjs +0 -14
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var bind_exports = {};
20
+ __export(bind_exports, {
21
+ Bind: () => Bind
22
+ });
23
+ module.exports = __toCommonJS(bind_exports);
24
+ var import_zod = require("zod");
25
+ var import_config = require("./config.cjs");
26
+ var import_bulk = require("./bulk.cjs");
27
+ var import_crud = require("./crud.cjs");
28
+ var import_patch = require("./patch.cjs");
29
+ var import_query = require("./query.cjs");
30
+ var import_stream = require("./stream.cjs");
31
+ const BindReturns = import_zod.z.object({
32
+ bulkGet: import_bulk.BulkGetBound,
33
+ bulkSave: import_bulk.BulkSaveBound,
34
+ get: import_crud.CouchGetBound,
35
+ put: import_crud.CouchPutBound,
36
+ patch: import_patch.PatchBound,
37
+ query: import_query.SimpleViewQueryBound,
38
+ queryStream: import_stream.SimpleViewQueryStreamBound
39
+ });
40
+ const Bind = import_zod.z.function().args(import_config.CouchConfig).returns(BindReturns);
@@ -19,8 +19,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  var bulk_exports = {};
20
20
  __export(bulk_exports, {
21
21
  BulkGet: () => BulkGet,
22
+ BulkGetBound: () => BulkGetBound,
22
23
  BulkRemove: () => BulkRemove,
24
+ BulkRemoveBound: () => BulkRemoveBound,
23
25
  BulkSave: () => BulkSave,
26
+ BulkSaveBound: () => BulkSaveBound,
24
27
  BulkSaveResponseSchema: () => BulkSaveResponseSchema
25
28
  });
26
29
  module.exports = __toCommonJS(bulk_exports);
@@ -40,11 +43,22 @@ const BulkSave = import_zod.z.function().args(
40
43
  _id: import_zod.z.string()
41
44
  }).passthrough())
42
45
  ).returns(import_zod.z.promise(BulkSaveResponseSchema));
46
+ const BulkSaveBound = import_zod.z.function().args(
47
+ import_zod.z.array(import_zod.z.object({
48
+ _id: import_zod.z.string()
49
+ }).passthrough())
50
+ ).returns(import_zod.z.promise(BulkSaveResponseSchema));
43
51
  const BulkGet = import_zod.z.function().args(
44
52
  import_config.CouchConfig,
45
53
  import_zod.z.array(import_zod.z.string().describe("the ids to get"))
46
54
  ).returns(import_zod.z.promise(import_zod.z.array(import_crud.CouchDoc)));
55
+ const BulkGetBound = import_zod.z.function().args(
56
+ import_zod.z.array(import_zod.z.string().describe("the ids to get"))
57
+ ).returns(import_zod.z.promise(import_zod.z.array(import_crud.CouchDoc)));
47
58
  const BulkRemove = import_zod.z.function().args(
48
59
  import_config.CouchConfig,
49
60
  import_zod.z.array(import_zod.z.string().describe("the ids to delete"))
50
61
  ).returns(import_zod.z.promise(BulkSaveResponseSchema));
62
+ const BulkRemoveBound = import_zod.z.function().args(
63
+ import_zod.z.array(import_zod.z.string().describe("the ids to delete"))
64
+ ).returns(import_zod.z.promise(BulkSaveResponseSchema));
@@ -22,6 +22,25 @@ __export(config_exports, {
22
22
  });
23
23
  module.exports = __toCommonJS(config_exports);
24
24
  var import_zod = require("zod");
25
+ const LoggerSchema = import_zod.z.object({
26
+ error: import_zod.z.function().args(import_zod.z.any()).returns(import_zod.z.void()).optional(),
27
+ warn: import_zod.z.function().args(import_zod.z.any()).returns(import_zod.z.void()).optional(),
28
+ info: import_zod.z.function().args(import_zod.z.any()).returns(import_zod.z.void()).optional(),
29
+ debug: import_zod.z.function().args(import_zod.z.any()).returns(import_zod.z.void()).optional()
30
+ }).or(import_zod.z.function().args(
31
+ import_zod.z.string(),
32
+ // level
33
+ import_zod.z.any()
34
+ // message/args
35
+ ).returns(import_zod.z.void()));
25
36
  const CouchConfig = import_zod.z.object({
26
- couch: import_zod.z.string().describe("the url of the couch db")
37
+ throwOnGetNotFound: import_zod.z.boolean().optional().default(false).describe("if a get is 404 should we throw or return undefined"),
38
+ couch: import_zod.z.string().describe("the url of the couch db"),
39
+ bindWithRetry: import_zod.z.boolean().optional().default(true).describe("should we bind with retry"),
40
+ maxRetries: import_zod.z.number().optional().default(3).describe("maximum number of retry attempts"),
41
+ initialDelay: import_zod.z.number().optional().default(1e3).describe("initial retry delay in milliseconds"),
42
+ backoffFactor: import_zod.z.number().optional().default(2).describe("multiplier for exponential backoff"),
43
+ logger: LoggerSchema.optional().describe("logging interface supporting winston-like or simple function interface"),
44
+ _normalizedLogger: import_zod.z.any().optional()
45
+ // Internal property for caching normalized logger
27
46
  }).passthrough().describe("The std config object");
@@ -21,7 +21,9 @@ __export(crud_exports, {
21
21
  CouchDoc: () => CouchDoc,
22
22
  CouchDocResponse: () => CouchDocResponse,
23
23
  CouchGet: () => CouchGet,
24
- CouchPut: () => CouchPut
24
+ CouchGetBound: () => CouchGetBound,
25
+ CouchPut: () => CouchPut,
26
+ CouchPutBound: () => CouchPutBound
25
27
  });
26
28
  module.exports = __toCommonJS(crud_exports);
27
29
  var import_zod = require("zod");
@@ -41,7 +43,13 @@ const CouchPut = import_zod.z.function().args(
41
43
  import_config.CouchConfig,
42
44
  CouchDoc
43
45
  ).returns(import_zod.z.promise(CouchDocResponse));
46
+ const CouchPutBound = import_zod.z.function().args(
47
+ CouchDoc
48
+ ).returns(import_zod.z.promise(CouchDocResponse));
44
49
  const CouchGet = import_zod.z.function().args(
45
50
  import_config.CouchConfig,
46
51
  import_zod.z.string().describe("the couch doc id")
47
52
  ).returns(import_zod.z.promise(CouchDoc.nullable()));
53
+ const CouchGetBound = import_zod.z.function().args(
54
+ import_zod.z.string().describe("the couch doc id")
55
+ ).returns(import_zod.z.promise(CouchDoc.nullable()));
@@ -18,21 +18,21 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var patch_exports = {};
20
20
  __export(patch_exports, {
21
- CouchPatch: () => CouchPatch,
22
- PatchConfig: () => PatchConfig,
21
+ Patch: () => Patch,
22
+ PatchBound: () => PatchBound,
23
23
  PatchProperties: () => PatchProperties
24
24
  });
25
25
  module.exports = __toCommonJS(patch_exports);
26
26
  var import_zod = require("zod");
27
27
  var import_config = require("./config.cjs");
28
28
  var import_crud = require("./crud.cjs");
29
- const PatchConfig = import_config.CouchConfig.extend({
30
- retries: import_zod.z.number().min(0).max(100).optional(),
31
- delay: import_zod.z.number().min(0).optional()
32
- });
33
29
  const PatchProperties = import_zod.z.record(import_zod.z.string(), import_zod.z.any());
34
- const CouchPatch = import_zod.z.function().args(
35
- PatchConfig,
30
+ const Patch = import_zod.z.function().args(
31
+ import_config.CouchConfig,
32
+ import_zod.z.string().describe("the couch doc id"),
33
+ PatchProperties
34
+ ).returns(import_zod.z.promise(import_crud.CouchDocResponse));
35
+ const PatchBound = import_zod.z.function().args(
36
36
  import_zod.z.string().describe("the couch doc id"),
37
37
  PatchProperties
38
38
  ).returns(import_zod.z.promise(import_crud.CouchDocResponse));
@@ -18,34 +18,43 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var query_exports = {};
20
20
  __export(query_exports, {
21
+ SimpleViewOptions: () => SimpleViewOptions,
21
22
  SimpleViewQuery: () => SimpleViewQuery,
22
- SimpleViewQueryResponse: () => SimpleViewQueryResponse
23
+ SimpleViewQueryBound: () => SimpleViewQueryBound,
24
+ SimpleViewQueryResponse: () => SimpleViewQueryResponse,
25
+ ViewRow: () => ViewRow
23
26
  });
24
27
  module.exports = __toCommonJS(query_exports);
25
28
  var import_zod = require("zod");
26
29
  var import_config = require("./config.cjs");
30
+ const ViewRow = import_zod.z.object({
31
+ id: import_zod.z.string().optional(),
32
+ key: import_zod.z.any().nullable(),
33
+ value: import_zod.z.any().nullable(),
34
+ doc: import_zod.z.object({}).passthrough().optional()
35
+ });
27
36
  const SimpleViewQueryResponse = import_zod.z.object({
28
37
  error: import_zod.z.string().optional().describe("if something is wrong"),
29
- rows: import_zod.z.array(import_zod.z.object({
30
- id: import_zod.z.string().optional(),
31
- key: import_zod.z.any().nullable(),
32
- value: import_zod.z.any().nullable(),
33
- doc: import_zod.z.object({}).passthrough().optional()
34
- }))
38
+ rows: import_zod.z.array(ViewRow)
35
39
  }).passthrough();
40
+ const SimpleViewOptions = import_zod.z.object({
41
+ startkey: import_zod.z.any().optional(),
42
+ endkey: import_zod.z.any().optional(),
43
+ descending: import_zod.z.boolean().optional().describe("sort results descending"),
44
+ skip: import_zod.z.number().positive().optional().describe("skip this many rows"),
45
+ limit: import_zod.z.number().positive().optional().describe("limit the results to this many rows"),
46
+ key: import_zod.z.any().optional(),
47
+ include_docs: import_zod.z.boolean().optional().describe("join the id to the doc and return it"),
48
+ reduce: import_zod.z.boolean().optional().describe("reduce the results"),
49
+ group: import_zod.z.boolean().optional().describe("group the results"),
50
+ group_level: import_zod.z.number().positive().optional().describe("group the results at this level")
51
+ }).optional().describe("query options");
36
52
  const SimpleViewQuery = import_zod.z.function().args(
37
53
  import_config.CouchConfig,
38
54
  import_zod.z.string().describe("the view name"),
39
- import_zod.z.object({
40
- startkey: import_zod.z.any().optional(),
41
- endkey: import_zod.z.any().optional(),
42
- descending: import_zod.z.boolean().optional().describe("sort results descending"),
43
- skip: import_zod.z.number().positive().optional().describe("skip this many rows"),
44
- limit: import_zod.z.number().positive().optional().describe("limit the results to this many rows"),
45
- key: import_zod.z.any().optional(),
46
- include_docs: import_zod.z.boolean().optional().describe("join the id to the doc and return it"),
47
- reduce: import_zod.z.boolean().optional().describe("reduce the results"),
48
- group: import_zod.z.boolean().optional().describe("group the results"),
49
- group_level: import_zod.z.number().positive().optional().describe("group the results at this level")
50
- }).optional().describe("query options")
55
+ SimpleViewOptions
56
+ ).returns(import_zod.z.promise(SimpleViewQueryResponse));
57
+ const SimpleViewQueryBound = import_zod.z.function().args(
58
+ import_zod.z.string().describe("the view name"),
59
+ SimpleViewOptions
51
60
  ).returns(import_zod.z.promise(SimpleViewQueryResponse));
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var stream_exports = {};
20
+ __export(stream_exports, {
21
+ OnRow: () => OnRow,
22
+ SimpleViewQueryStream: () => SimpleViewQueryStream,
23
+ SimpleViewQueryStreamBound: () => SimpleViewQueryStreamBound
24
+ });
25
+ module.exports = __toCommonJS(stream_exports);
26
+ var import_zod = require("zod");
27
+ var import_config = require("./config.cjs");
28
+ var import_query = require("./query.cjs");
29
+ const OnRow = import_zod.z.function().args(
30
+ import_query.ViewRow
31
+ ).returns(import_zod.z.undefined());
32
+ const SimpleViewQueryStream = import_zod.z.function().args(
33
+ import_config.CouchConfig,
34
+ import_zod.z.string().describe("the view name"),
35
+ import_query.SimpleViewOptions,
36
+ OnRow
37
+ ).returns(import_zod.z.promise(import_zod.z.undefined()));
38
+ const SimpleViewQueryStreamBound = import_zod.z.function().args(
39
+ import_zod.z.string().describe("the view name"),
40
+ import_query.SimpleViewOptions,
41
+ OnRow
42
+ ).returns(import_zod.z.promise(import_zod.z.undefined()));
@@ -0,0 +1,7 @@
1
+ /** @type { import('../schema/bulk.mjs').BulkSaveSchema } */
2
+ export const bulkSave: import("../schema/bulk.mjs").BulkSaveSchema;
3
+ /** @type { import('../schema/bulk.mjs').BulkGetSchema } */
4
+ export const bulkGet: import("../schema/bulk.mjs").BulkGetSchema;
5
+ /** @type { import('../schema/bulk.mjs').BulkRemoveSchema } */
6
+ export const bulkRemove: import("../schema/bulk.mjs").BulkRemoveSchema;
7
+ //# sourceMappingURL=bulk.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bulk.d.mts","sourceRoot":"","sources":["bulk.mjs"],"names":[],"mappings":"AAaA,4DAA4D;AAC5D,uBADY,OAAO,oBAAoB,EAAE,cAAc,CAsCrD;AAEF,2DAA2D;AAC3D,sBADY,OAAO,oBAAoB,EAAE,aAAa,CA0CpD;AAEF,8DAA8D;AAC9D,yBADY,OAAO,oBAAoB,EAAE,gBAAgB,CAOvD"}
package/impl/bulk.mjs CHANGED
@@ -1,6 +1,8 @@
1
1
  // @ts-check
2
2
  import needle from 'needle'
3
3
  import { BulkSave, BulkGet, BulkRemove } from '../schema/bulk.mjs'
4
+ import { RetryableError } from './errors.mjs'
5
+ import { createLogger } from './logger.mjs'
4
6
 
5
7
  const opts = {
6
8
  json: true,
@@ -11,24 +13,71 @@ const opts = {
11
13
 
12
14
  /** @type { import('../schema/bulk.mjs').BulkSaveSchema } */
13
15
  export const bulkSave = BulkSave.implement(async (config, docs) => {
14
- if (!docs) return { ok: false, error: 'noDocs', reason: 'no docs provided' }
15
- if (!docs.length) return { ok: false, error: 'noDocs', reason: 'no docs provided' }
16
+ /** @type {import('./logger.mjs').Logger } */
17
+ const logger = createLogger(config)
18
+
19
+ if (!docs) {
20
+ logger.warn('bulkSave called with no docs')
21
+ return { ok: false, error: 'noDocs', reason: 'no docs provided' }
22
+ }
23
+ if (!docs.length) {
24
+ logger.warn('bulkSave called with empty docs array')
25
+ return { ok: false, error: 'noDocs', reason: 'no docs provided' }
26
+ }
16
27
 
28
+ logger.info(`Starting bulk save of ${docs.length} documents`)
17
29
  const url = `${config.couch}/_bulk_docs`
18
30
  const body = { docs }
19
- const resp = await needle('post', url, body, opts)
20
- if (resp.statusCode !== 201) throw new Error('could not save')
31
+ let resp
32
+ try {
33
+ resp = await needle('post', url, body, opts)
34
+ } catch (err) {
35
+ logger.error('Network error during bulk save:', err)
36
+ RetryableError.handleNetworkError(err)
37
+ }
38
+ if (!resp) {
39
+ logger.error('No response received from bulk save request')
40
+ throw new RetryableError('no response', 503)
41
+ }
42
+ if (RetryableError.isRetryableStatusCode(resp.statusCode)) {
43
+ logger.warn(`Retryable status code received: ${resp.statusCode}`)
44
+ throw new RetryableError('retryable error during bulk save', resp.statusCode)
45
+ }
46
+ if (resp.statusCode !== 201) {
47
+ logger.error(`Unexpected status code: ${resp.statusCode}`)
48
+ throw new Error('could not save')
49
+ }
21
50
  const results = resp?.body || []
22
51
  return results
23
52
  })
24
53
 
25
54
  /** @type { import('../schema/bulk.mjs').BulkGetSchema } */
26
55
  export const bulkGet = BulkGet.implement(async (config, ids) => {
56
+ const logger = createLogger(config)
27
57
  const keys = ids
58
+
59
+ logger.info(`Starting bulk get for ${keys.length} documents`)
28
60
  const url = `${config.couch}/_all_docs?include_docs=true`
29
61
  const body = { keys }
30
- const resp = await needle('post', url, body, opts)
31
- if (resp.statusCode !== 200) throw new Error('could not fetch')
62
+ let resp
63
+ try {
64
+ resp = await needle('post', url, body, opts)
65
+ } catch (err) {
66
+ logger.error('Network error during bulk get:', err)
67
+ RetryableError.handleNetworkError(err)
68
+ }
69
+ if (!resp) {
70
+ logger.error('No response received from bulk get request')
71
+ throw new RetryableError('no response', 503)
72
+ }
73
+ if (RetryableError.isRetryableStatusCode(resp.statusCode)) {
74
+ logger.warn(`Retryable status code received: ${resp.statusCode}`)
75
+ throw new RetryableError('retryable error during bulk get', resp.statusCode)
76
+ }
77
+ if (resp.statusCode !== 200) {
78
+ logger.error(`Unexpected status code: ${resp.statusCode}`)
79
+ throw new Error('could not fetch')
80
+ }
32
81
  const rows = resp?.body?.rows || []
33
82
  /** @type {Array<import('../schema/crud.mjs').CouchDocSchema>} */
34
83
  const docs = []
@@ -42,11 +91,14 @@ export const bulkGet = BulkGet.implement(async (config, ids) => {
42
91
  const doc = r.doc
43
92
  docs.push(doc)
44
93
  })
94
+ logger.info(`Successfully retrieved ${docs.length} documents`)
45
95
  return docs
46
96
  })
47
97
 
48
98
  /** @type { import('../schema/bulk.mjs').BulkRemoveSchema } */
49
99
  export const bulkRemove = BulkRemove.implement(async (config, ids) => {
100
+ const logger = createLogger(config)
101
+ logger.info(`Starting bulk remove for ${ids.length} documents`)
50
102
  const docs = await bulkGet(config, ids)
51
103
  docs.forEach(d => { d._deleted = true })
52
104
  return bulkSave(config, docs)
@@ -0,0 +1,5 @@
1
+ /** @type { import('../schema/crud.mjs').CouchGetSchema } */
2
+ export const get: import("../schema/crud.mjs").CouchGetSchema;
3
+ /** @type { import('../schema/crud.mjs').CouchPutSchema } */
4
+ export const put: import("../schema/crud.mjs").CouchPutSchema;
5
+ //# sourceMappingURL=crud.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crud.d.mts","sourceRoot":"","sources":["crud.mjs"],"names":[],"mappings":"AAaA,4DAA4D;AAC5D,kBADY,OAAO,oBAAoB,EAAE,cAAc,CAwCrD;AAEF,4DAA4D;AAC5D,kBADY,OAAO,oBAAoB,EAAE,cAAc,CAqCrD"}
package/impl/crud.mjs CHANGED
@@ -1,6 +1,8 @@
1
1
  // @ts-check
2
2
  import needle from 'needle'
3
3
  import { CouchGet, CouchPut } from '../schema/crud.mjs'
4
+ import { RetryableError } from './errors.mjs'
5
+ import { createLogger } from './logger.mjs'
4
6
 
5
7
  const opts = {
6
8
  json: true,
@@ -9,24 +11,83 @@ const opts = {
9
11
  }
10
12
  }
11
13
 
14
+ /** @type { import('../schema/crud.mjs').CouchGetSchema } */
12
15
  export const get = CouchGet.implement(async (config, id) => {
16
+ const logger = createLogger(config)
13
17
  const url = `${config.couch}/${id}`
14
- const resp = await needle('get', url, opts)
15
- if (resp.statusCode === 404) return null
16
- const result = resp?.body || {}
17
- if (resp.statusCode !== 200) { throw new Error(result.reason || 'failed') }
18
- return result
18
+ logger.info(`Getting document with id: ${id}`)
19
+
20
+ try {
21
+ const resp = await needle('get', url, opts)
22
+ if (!resp) {
23
+ logger.error('No response received from get request')
24
+ throw new RetryableError('no response', 503)
25
+ }
26
+ if (resp.statusCode === 404) {
27
+ logger.debug(`Document not found: ${id}`)
28
+ return null
29
+ }
30
+ const result = resp?.body || {}
31
+ if (resp.statusCode === 404) {
32
+ if (config.throwOnGetNotFound) {
33
+ logger.warn(`Document not found (throwing error): ${id}`)
34
+ throw new Error(result.reason || 'not_found')
35
+ } else {
36
+ logger.debug(`Document not found (returning undefined): ${id}`)
37
+ return undefined
38
+ }
39
+ }
40
+ if (RetryableError.isRetryableStatusCode(resp.statusCode)) {
41
+ logger.warn(`Retryable status code received: ${resp.statusCode}`)
42
+ throw new RetryableError(result.reason || 'retryable error', resp.statusCode)
43
+ }
44
+ if (resp.statusCode !== 200) {
45
+ logger.error(`Unexpected status code: ${resp.statusCode}`)
46
+ throw new Error(result.reason || 'failed')
47
+ }
48
+ logger.info(`Successfully retrieved document: ${id}`)
49
+ return result
50
+ } catch (err) {
51
+ logger.error('Error during get operation:', err)
52
+ RetryableError.handleNetworkError(err)
53
+ }
19
54
  })
20
55
 
56
+ /** @type { import('../schema/crud.mjs').CouchPutSchema } */
21
57
  export const put = CouchPut.implement(async (config, doc) => {
58
+ const logger = createLogger(config)
22
59
  const url = `${config.couch}/${doc._id}`
23
60
  const body = doc
24
- const resp = await needle('put', url, body, opts)
61
+
62
+ logger.info(`Putting document with id: ${doc._id}`)
63
+ let resp
64
+ try {
65
+ resp = await needle('put', url, body, opts)
66
+ } catch (err) {
67
+ logger.error('Error during put operation:', err)
68
+ RetryableError.handleNetworkError(err)
69
+ }
70
+
71
+ if (!resp) {
72
+ logger.error('No response received from put request')
73
+ throw new RetryableError('no response', 503)
74
+ }
75
+
25
76
  const result = resp?.body || {}
26
77
  result.statusCode = resp.statusCode
78
+
27
79
  if (resp.statusCode === 409) {
80
+ logger.warn(`Conflict detected for document: ${doc._id}`)
28
81
  result.ok = false
29
82
  result.error = 'conflict'
83
+ return result
84
+ }
85
+
86
+ if (RetryableError.isRetryableStatusCode(resp.statusCode)) {
87
+ logger.warn(`Retryable status code received: ${resp.statusCode}`)
88
+ throw new RetryableError(result.reason || 'retryable error', resp.statusCode)
30
89
  }
90
+
91
+ logger.info(`Successfully saved document: ${doc._id}`)
31
92
  return result
32
93
  })
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @typedef {Object} NetworkError
3
+ * @property {string} code - The error code
4
+ * @property {string} [message] - Optional error message
5
+ */
6
+ export class RetryableError extends Error {
7
+ /**
8
+ * @param {number|undefined} statusCode - The HTTP status code to check
9
+ * @returns {boolean} Whether the status code is retryable
10
+ */
11
+ static isRetryableStatusCode(statusCode: number | undefined): boolean;
12
+ /**
13
+ * @param {NetworkError | unknown} err - The network error to handle
14
+ * @throws {RetryableError} If the error is retryable
15
+ * @throws {Error} If the error is not retryable
16
+ */
17
+ static handleNetworkError(err: NetworkError | unknown): void;
18
+ /**
19
+ * @param {string} message - The error message
20
+ * @param {number|undefined} statusCode - The HTTP status code
21
+ */
22
+ constructor(message: string, statusCode: number | undefined);
23
+ statusCode: number | undefined;
24
+ }
25
+ export type NetworkError = {
26
+ /**
27
+ * - The error code
28
+ */
29
+ code: string;
30
+ /**
31
+ * - Optional error message
32
+ */
33
+ message?: string | undefined;
34
+ };
35
+ //# sourceMappingURL=errors.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.mts","sourceRoot":"","sources":["errors.mjs"],"names":[],"mappings":"AAEA;;;;GAIG;AAEH;IAWE;;;OAGG;IACH,yCAHW,MAAM,GAAC,SAAS,GACd,OAAO,CAKnB;IAED;;;;OAIG;IACH,+BAJW,YAAY,GAAG,OAAO,QAsBhC;IA1CD;;;OAGG;IACH,qBAHW,MAAM,cACN,MAAM,GAAC,SAAS,EAM1B;IADC,+BAA4B;CAoC/B;;;;;UAhDa,MAAM"}
@@ -0,0 +1,53 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * @typedef {Object} NetworkError
5
+ * @property {string} code - The error code
6
+ * @property {string} [message] - Optional error message
7
+ */
8
+
9
+ export class RetryableError extends Error {
10
+ /**
11
+ * @param {string} message - The error message
12
+ * @param {number|undefined} statusCode - The HTTP status code
13
+ */
14
+ constructor(message, statusCode) {
15
+ super(message);
16
+ this.name = 'RetryableError';
17
+ this.statusCode = statusCode;
18
+ }
19
+
20
+ /**
21
+ * @param {number|undefined} statusCode - The HTTP status code to check
22
+ * @returns {boolean} Whether the status code is retryable
23
+ */
24
+ static isRetryableStatusCode(statusCode) {
25
+ if (statusCode === undefined) return false;
26
+ return [408, 429, 500, 502, 503, 504].includes(statusCode);
27
+ }
28
+
29
+ /**
30
+ * @param {NetworkError | unknown} err - The network error to handle
31
+ * @throws {RetryableError} If the error is retryable
32
+ * @throws {Error} If the error is not retryable
33
+ */
34
+ static handleNetworkError(err) {
35
+ /** @type {Record<string, number>} */
36
+ const networkErrors = {
37
+ ECONNREFUSED: 503,
38
+ ECONNRESET: 503,
39
+ ETIMEDOUT: 503,
40
+ ENETUNREACH: 503,
41
+ ENOTFOUND: 503,
42
+ EPIPE: 503,
43
+ EHOSTUNREACH: 503,
44
+ ESOCKETTIMEDOUT: 503
45
+ };
46
+
47
+ // Type guard for NetworkError shape
48
+ if (typeof err === 'object' && err !== null && 'code' in err && typeof err.code === 'string' && networkErrors[err.code]) {
49
+ throw new RetryableError(`Network error: ${err.code}`, networkErrors[err.code]);
50
+ }
51
+ throw err;
52
+ }
53
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @typedef {Object} Logger
3
+ * @property {(...args: any[]) => void} error - Log error messages
4
+ * @property {(...args: any[]) => void} warn - Log warning messages
5
+ * @property {(...args: any[]) => void} info - Log info messages
6
+ * @property {(...args: any[]) => void} debug - Log debug messages
7
+ */
8
+ /**
9
+ * Creates a unified logger interface that works with both function and object-style loggers
10
+ * @param {import('../schema/config.mjs').CouchConfigSchema} config
11
+ * @returns {Logger} Normalized logger interface
12
+ */
13
+ export function createLogger(config: import("../schema/config.mjs").CouchConfigSchema): Logger;
14
+ export type Logger = {
15
+ /**
16
+ * - Log error messages
17
+ */
18
+ error: (...args: any[]) => void;
19
+ /**
20
+ * - Log warning messages
21
+ */
22
+ warn: (...args: any[]) => void;
23
+ /**
24
+ * - Log info messages
25
+ */
26
+ info: (...args: any[]) => void;
27
+ /**
28
+ * - Log debug messages
29
+ */
30
+ debug: (...args: any[]) => void;
31
+ };
32
+ //# sourceMappingURL=logger.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.mts","sourceRoot":"","sources":["logger.mjs"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;GAIG;AACH,qCAHW,OAAO,sBAAsB,EAAE,iBAAiB,GAC9C,MAAM,CAsClB;;;;;WA/Ca,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI;;;;UACxB,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI;;;;UACxB,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI;;;;WACxB,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI"}