qubesync 0.1.0 → 0.2.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 (258) hide show
  1. package/CHANGELOG.md +38 -2
  2. package/README.md +188 -5
  3. package/client.js +660 -0
  4. package/index.d.ts +211 -1
  5. package/index.js +5 -1
  6. package/index.mjs +2 -0
  7. package/operations/account.d.ts +263 -0
  8. package/operations/account.js +50 -0
  9. package/operations/account_tax_line_info.d.ts +31 -0
  10. package/operations/account_tax_line_info.js +26 -0
  11. package/operations/aging_report.d.ts +100 -0
  12. package/operations/aging_report.js +26 -0
  13. package/operations/alert.d.ts +118 -0
  14. package/operations/alert.js +50 -0
  15. package/operations/ar_refund_credit_card.d.ts +201 -0
  16. package/operations/ar_refund_credit_card.js +38 -0
  17. package/operations/bar_code.d.ts +40 -0
  18. package/operations/bar_code.js +26 -0
  19. package/operations/bill.d.ts +612 -0
  20. package/operations/bill.js +50 -0
  21. package/operations/bill_payment_check.d.ts +276 -0
  22. package/operations/bill_payment_check.js +50 -0
  23. package/operations/bill_payment_credit_card.d.ts +178 -0
  24. package/operations/bill_payment_credit_card.js +38 -0
  25. package/operations/bill_to_pay.d.ts +54 -0
  26. package/operations/bill_to_pay.js +26 -0
  27. package/operations/billing_rate.d.ts +159 -0
  28. package/operations/billing_rate.js +38 -0
  29. package/operations/budget_summary_report.d.ts +71 -0
  30. package/operations/budget_summary_report.js +26 -0
  31. package/operations/build_assembly.d.ts +264 -0
  32. package/operations/build_assembly.js +50 -0
  33. package/operations/charge.d.ts +330 -0
  34. package/operations/charge.js +50 -0
  35. package/operations/check.d.ts +394 -0
  36. package/operations/check.js +50 -0
  37. package/operations/class.d.ts +170 -0
  38. package/operations/class.js +50 -0
  39. package/operations/cleared_status.d.ts +43 -0
  40. package/operations/cleared_status.js +26 -0
  41. package/operations/client.d.ts +254 -0
  42. package/operations/company.d.ts +36 -0
  43. package/operations/company.js +26 -0
  44. package/operations/company_activity.d.ts +32 -0
  45. package/operations/company_activity.js +26 -0
  46. package/operations/credit_card_charge.d.ts +280 -0
  47. package/operations/credit_card_charge.js +50 -0
  48. package/operations/credit_card_credit.d.ts +280 -0
  49. package/operations/credit_card_credit.js +50 -0
  50. package/operations/credit_memo.d.ts +642 -0
  51. package/operations/credit_memo.js +50 -0
  52. package/operations/currency.d.ts +195 -0
  53. package/operations/currency.js +50 -0
  54. package/operations/custom_detail_report.d.ts +108 -0
  55. package/operations/custom_detail_report.js +26 -0
  56. package/operations/custom_summary_report.d.ts +119 -0
  57. package/operations/custom_summary_report.js +26 -0
  58. package/operations/customer.d.ts +639 -0
  59. package/operations/customer.js +50 -0
  60. package/operations/customer_msg.d.ts +121 -0
  61. package/operations/customer_msg.js +38 -0
  62. package/operations/customer_type.d.ts +127 -0
  63. package/operations/customer_type.js +38 -0
  64. package/operations/data_event_recovery_info.d.ts +61 -0
  65. package/operations/data_event_recovery_info.js +38 -0
  66. package/operations/data_ext.d.ts +197 -0
  67. package/operations/data_ext.js +50 -0
  68. package/operations/data_ext_def.d.ts +173 -0
  69. package/operations/data_ext_def.js +62 -0
  70. package/operations/date_driven_terms.d.ts +141 -0
  71. package/operations/date_driven_terms.js +38 -0
  72. package/operations/deposit.d.ts +402 -0
  73. package/operations/deposit.js +50 -0
  74. package/operations/employee.d.ts +683 -0
  75. package/operations/employee.js +50 -0
  76. package/operations/employee_default.d.ts +133 -0
  77. package/operations/employee_default.js +38 -0
  78. package/operations/entity.d.ts +96 -0
  79. package/operations/entity.js +26 -0
  80. package/operations/estimate.d.ts +590 -0
  81. package/operations/estimate.js +50 -0
  82. package/operations/form1099_category_account_mapping.d.ts +89 -0
  83. package/operations/form1099_category_account_mapping.js +38 -0
  84. package/operations/general_detail_report.d.ts +108 -0
  85. package/operations/general_detail_report.js +26 -0
  86. package/operations/general_summary_report.d.ts +115 -0
  87. package/operations/general_summary_report.js +26 -0
  88. package/operations/host.d.ts +43 -0
  89. package/operations/host.js +26 -0
  90. package/operations/index.js +397 -0
  91. package/operations/inventory_adjustment.d.ts +389 -0
  92. package/operations/inventory_adjustment.js +50 -0
  93. package/operations/inventory_site.d.ts +247 -0
  94. package/operations/inventory_site.js +50 -0
  95. package/operations/invoice.d.ts +682 -0
  96. package/operations/invoice.js +50 -0
  97. package/operations/item.d.ts +100 -0
  98. package/operations/item.js +26 -0
  99. package/operations/item_assemblies_can_build.d.ts +41 -0
  100. package/operations/item_assemblies_can_build.js +26 -0
  101. package/operations/item_discount.d.ts +256 -0
  102. package/operations/item_discount.js +50 -0
  103. package/operations/item_fixed_asset.d.ts +382 -0
  104. package/operations/item_fixed_asset.js +50 -0
  105. package/operations/item_group.d.ts +236 -0
  106. package/operations/item_group.js +50 -0
  107. package/operations/item_inventory.d.ts +350 -0
  108. package/operations/item_inventory.js +50 -0
  109. package/operations/item_inventory_assembly.d.ts +368 -0
  110. package/operations/item_inventory_assembly.js +50 -0
  111. package/operations/item_non_inventory.d.ts +264 -0
  112. package/operations/item_non_inventory.js +50 -0
  113. package/operations/item_other_charge.d.ts +244 -0
  114. package/operations/item_other_charge.js +50 -0
  115. package/operations/item_payment.d.ts +220 -0
  116. package/operations/item_payment.js +50 -0
  117. package/operations/item_receipt.d.ts +362 -0
  118. package/operations/item_receipt.js +50 -0
  119. package/operations/item_sales_tax.d.ts +228 -0
  120. package/operations/item_sales_tax.js +50 -0
  121. package/operations/item_sales_tax_group.d.ts +192 -0
  122. package/operations/item_sales_tax_group.js +50 -0
  123. package/operations/item_service.d.ts +256 -0
  124. package/operations/item_service.js +50 -0
  125. package/operations/item_sites.d.ts +81 -0
  126. package/operations/item_sites.js +26 -0
  127. package/operations/item_subtotal.d.ts +188 -0
  128. package/operations/item_subtotal.js +50 -0
  129. package/operations/job_report.d.ts +99 -0
  130. package/operations/job_report.js +26 -0
  131. package/operations/job_type.d.ts +127 -0
  132. package/operations/job_type.js +38 -0
  133. package/operations/journal_entry.d.ts +339 -0
  134. package/operations/journal_entry.js +50 -0
  135. package/operations/lead.d.ts +291 -0
  136. package/operations/lead.js +50 -0
  137. package/operations/list.d.ts +39 -0
  138. package/operations/list.js +26 -0
  139. package/operations/list_deleted.d.ts +44 -0
  140. package/operations/list_deleted.js +26 -0
  141. package/operations/list_display.d.ts +65 -0
  142. package/operations/list_display.js +38 -0
  143. package/operations/list_merge.d.ts +58 -0
  144. package/operations/list_merge.js +26 -0
  145. package/operations/other_name.d.ts +280 -0
  146. package/operations/other_name.js +50 -0
  147. package/operations/paycheck.d.ts +82 -0
  148. package/operations/paycheck.js +26 -0
  149. package/operations/payment_method.d.ts +131 -0
  150. package/operations/payment_method.js +38 -0
  151. package/operations/payroll_detail_report.d.ts +97 -0
  152. package/operations/payroll_detail_report.js +26 -0
  153. package/operations/payroll_item.d.ts +83 -0
  154. package/operations/payroll_item.js +26 -0
  155. package/operations/payroll_item_addition.d.ts +95 -0
  156. package/operations/payroll_item_addition.js +38 -0
  157. package/operations/payroll_item_company_contribution.d.ts +105 -0
  158. package/operations/payroll_item_company_contribution.js +38 -0
  159. package/operations/payroll_item_deduction.d.ts +101 -0
  160. package/operations/payroll_item_deduction.js +38 -0
  161. package/operations/payroll_item_direct_deposit.d.ts +65 -0
  162. package/operations/payroll_item_direct_deposit.js +38 -0
  163. package/operations/payroll_item_non_wage.d.ts +92 -0
  164. package/operations/payroll_item_non_wage.js +26 -0
  165. package/operations/payroll_item_tax.d.ts +108 -0
  166. package/operations/payroll_item_tax.js +38 -0
  167. package/operations/payroll_item_wage.d.ts +169 -0
  168. package/operations/payroll_item_wage.js +50 -0
  169. package/operations/payroll_item_workers_compensation.d.ts +77 -0
  170. package/operations/payroll_item_workers_compensation.js +38 -0
  171. package/operations/payroll_last_period.d.ts +31 -0
  172. package/operations/payroll_last_period.js +26 -0
  173. package/operations/payroll_liability_adjustment.d.ts +137 -0
  174. package/operations/payroll_liability_adjustment.js +38 -0
  175. package/operations/payroll_liability_check.d.ts +82 -0
  176. package/operations/payroll_liability_check.js +26 -0
  177. package/operations/payroll_prior_payment.d.ts +126 -0
  178. package/operations/payroll_prior_payment.js +38 -0
  179. package/operations/payroll_summary_report.d.ts +107 -0
  180. package/operations/payroll_summary_report.js +26 -0
  181. package/operations/payroll_year_to_date_adjustment.d.ts +156 -0
  182. package/operations/payroll_year_to_date_adjustment.js +38 -0
  183. package/operations/preferences.d.ts +64 -0
  184. package/operations/preferences.js +38 -0
  185. package/operations/price_level.d.ts +234 -0
  186. package/operations/price_level.js +50 -0
  187. package/operations/purchase_order.d.ts +620 -0
  188. package/operations/purchase_order.js +50 -0
  189. package/operations/qb_command_exec.d.ts +49 -0
  190. package/operations/qb_command_exec.js +26 -0
  191. package/operations/receive_payment.d.ts +450 -0
  192. package/operations/receive_payment.js +50 -0
  193. package/operations/receive_payment_to_deposit.d.ts +36 -0
  194. package/operations/receive_payment_to_deposit.js +26 -0
  195. package/operations/sales_order.d.ts +612 -0
  196. package/operations/sales_order.js +50 -0
  197. package/operations/sales_receipt.d.ts +650 -0
  198. package/operations/sales_receipt.js +50 -0
  199. package/operations/sales_rep.d.ts +170 -0
  200. package/operations/sales_rep.js +50 -0
  201. package/operations/sales_tax_code.d.ts +194 -0
  202. package/operations/sales_tax_code.js +50 -0
  203. package/operations/sales_tax_payable.d.ts +42 -0
  204. package/operations/sales_tax_payable.js +26 -0
  205. package/operations/sales_tax_payment_check.d.ts +254 -0
  206. package/operations/sales_tax_payment_check.js +50 -0
  207. package/operations/sales_tax_return.d.ts +35 -0
  208. package/operations/sales_tax_return.js +26 -0
  209. package/operations/sales_tax_return_line.d.ts +36 -0
  210. package/operations/sales_tax_return_line.js +26 -0
  211. package/operations/ship_method.d.ts +121 -0
  212. package/operations/ship_method.js +38 -0
  213. package/operations/special_account.d.ts +40 -0
  214. package/operations/special_account.js +26 -0
  215. package/operations/special_item.d.ts +44 -0
  216. package/operations/special_item.js +26 -0
  217. package/operations/standard_terms.d.ts +133 -0
  218. package/operations/standard_terms.js +38 -0
  219. package/operations/template.d.ts +36 -0
  220. package/operations/template.js +26 -0
  221. package/operations/terms.d.ts +90 -0
  222. package/operations/terms.js +26 -0
  223. package/operations/time_report.d.ts +84 -0
  224. package/operations/time_report.js +26 -0
  225. package/operations/time_tracking.d.ts +214 -0
  226. package/operations/time_tracking.js +50 -0
  227. package/operations/to_do.d.ts +254 -0
  228. package/operations/to_do.js +50 -0
  229. package/operations/transaction.d.ts +244 -0
  230. package/operations/transaction.js +26 -0
  231. package/operations/transfer.d.ts +178 -0
  232. package/operations/transfer.js +50 -0
  233. package/operations/transfer_inventory.d.ts +290 -0
  234. package/operations/transfer_inventory.js +50 -0
  235. package/operations/txn.d.ts +39 -0
  236. package/operations/txn.js +26 -0
  237. package/operations/txn_deleted.d.ts +44 -0
  238. package/operations/txn_deleted.js +26 -0
  239. package/operations/txn_display.d.ts +69 -0
  240. package/operations/txn_display.js +38 -0
  241. package/operations/txn_void.d.ts +39 -0
  242. package/operations/txn_void.js +26 -0
  243. package/operations/types.d.ts +1259 -0
  244. package/operations/unit_of_measure_set.d.ts +168 -0
  245. package/operations/unit_of_measure_set.js +38 -0
  246. package/operations/vehicle.d.ts +168 -0
  247. package/operations/vehicle.js +50 -0
  248. package/operations/vehicle_mileage.d.ts +147 -0
  249. package/operations/vehicle_mileage.js +38 -0
  250. package/operations/vendor.d.ts +541 -0
  251. package/operations/vendor.js +50 -0
  252. package/operations/vendor_credit.d.ts +365 -0
  253. package/operations/vendor_credit.js +50 -0
  254. package/operations/vendor_type.d.ts +127 -0
  255. package/operations/vendor_type.js +38 -0
  256. package/operations/workers_comp_code.d.ts +199 -0
  257. package/operations/workers_comp_code.js +50 -0
  258. package/package.json +14 -4
package/CHANGELOG.md CHANGED
@@ -1,6 +1,42 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.0 (unreleased)
3
+ ## 0.2.0 (2026-10-25)
4
+
5
+ The v2 API client.
6
+
7
+ - `new Client({ apiKey, baseUrl = "https://qubesync.com", connectTimeout = 5000, timeout = 30000, maxRetries = 2 })`:
8
+ Basic auth with the key passed in (the environment is never read), and the key never in
9
+ `util.inspect`, `JSON.stringify` or an error.
10
+ - Connections: `createConnection`, `getConnection`, `listConnections`, `updateConnection`,
11
+ `deleteConnection`, `regenerateOnboarding`, `setPassword`, `getQwc`.
12
+ - Runs: `startRun` (the `reference` is the Idempotency-Key), `getRun` by link or ids, `listRuns`,
13
+ `cancelRun`, `runEvents`. Questions: `answer`, `search`.
14
+ - Every QuickBooks operation as a method, entity then action: `qube.customer.create(connectionId,
15
+ params, { webhookUrl, idempotencyKey })`, `qube.customerMsg.query(...)`, `qube.txnVoid.execute(...)`
16
+ (261 operations on 124 entities). Each entity's module loads on first use, and can be imported
17
+ alone (`qubesync/operations/<entity>`). Generated from the QuBe Sync operations manifest
18
+ (`test/fixtures/operations.json`, served at https://qubesync.com/libraries/operations.json) by
19
+ `node scripts/generate-operations.js`, with full nested param types and every field's description
20
+ in `operations/*.d.ts`; CI fails when the generated files or the vendored manifest drift.
21
+ - Query params: nested objects go bracketed (`name_range[from]=A`), arrays of scalars as a repeated key.
22
+ - `queue(operationId, connectionId, params, { webhookUrl, idempotencyKey, method, path })` queues by
23
+ operation id; an id this version doesn't know needs `method` and `path`.
24
+ - Requests: `queueQbxml`, `getRequest`, `requestPages`, `listRequests`, `eachRequest` (an async
25
+ iterator over every page), `discardRequest`; `request` for anything else.
26
+ - Links are refused unless they're on the client's own origin.
27
+ - Retries for retry-safe requests on a failed connection, a timeout, 429, 502, 503 and 504, with
28
+ `Retry-After` honoured up to 30 s. `answer` is retried when its link has a `prompt_token`.
29
+ - `totalTimeout` (60000 ms): a call returns or rejects within it, retries and waits included; each
30
+ attempt's timeouts are cut to the time left, and a retry that would leave under 1 s isn't made.
31
+ - `onRetry({ attempt, maxRetries, delayMs, elapsedMs, method, url, status, error })` before each
32
+ retry's wait; the URL without its query, never the key; a throw stops the call.
33
+ - A retried DELETE that gets a 404 after an earlier attempt's answer was lost (a read timeout, a
34
+ reset after connecting, a 502 or 504) resolves to `null`, as a 204 would.
35
+ - `APIError` with `status`, `code`, `message`, `errors`, `body` and `requestId`.
36
+ - Releases publish through npm trusted publishing, from `release.yml`'s OIDC token; no npm
37
+ token is stored.
38
+
39
+ ## 0.1.0 (2026-10-24)
4
40
 
5
41
  First version: webhooks only.
6
42
 
@@ -16,4 +52,4 @@ First version: webhooks only.
16
52
  - CommonJS and ESM entry points, TypeScript declarations, zero dependencies, Node 16.17 and later.
17
53
  - Tested against the server's own test vectors (`test/fixtures/test_vectors.json`), and CI checks
18
54
  that copy against https://qubesync.com/webhooks/test_vectors.json.
19
- - A `v*` tag publishes to npm (`release.yml`, with the `NPM_TOKEN` secret).
55
+ - A `v*` tag publishes to npm (`release.yml`).
package/README.md CHANGED
@@ -1,13 +1,192 @@
1
1
  # qubesync (Node)
2
2
 
3
- Verify and parse [QuBe Sync](https://qubesync.com) webhooks: QuickBooks Desktop through
4
- qubesync.com. Plain JavaScript, zero dependencies, Node 16.17 and later, CommonJS and ESM,
5
- TypeScript declarations included.
3
+ The [QuBe Sync](https://qubesync.com) v2 API client, and webhook verification and parsing:
4
+ QuickBooks Desktop through qubesync.com. Plain JavaScript, zero dependencies, Node 16.17 and
5
+ later, CommonJS and ESM, TypeScript declarations included. API docs: https://docs.qubesync.com.
6
6
 
7
7
  ```bash
8
8
  npm install qubesync
9
9
  ```
10
10
 
11
+ ## The v2 client
12
+
13
+ Make one client with your app's API key. The library never reads the environment, so pass the
14
+ key in (and hold a sandbox and a production client at once if you need both):
15
+
16
+ ```js
17
+ const { Client, APIError } = require("qubesync"); // or: import { Client, APIError } from "qubesync";
18
+
19
+ const qube = new Client({ apiKey: process.env.QUBE_API_KEY });
20
+ ```
21
+
22
+ `new Client({ apiKey, baseUrl = "https://qubesync.com", connectTimeout = 5000, timeout = 30000, maxRetries = 2, totalTimeout = 60000, onRetry })`.
23
+ Timeouts are in milliseconds: `connectTimeout` to open the connection, `timeout` for the socket to
24
+ sit idle waiting for the answer, and `totalTimeout` for the whole call (see below). Every method returns a Promise of the response's `data`, with
25
+ the API's own snake_case keys (`webhook_url`, `total_pages`), or `null` for an empty answer.
26
+
27
+ ### Onboarding a customer
28
+
29
+ ```js
30
+ const connection = await qube.createConnection({ webhookUrl: "https://app.example.com/webhooks/qube", name: "Acme" });
31
+ // Store connection.id, and send your customer to connection.links.onboarding.
32
+ // A connection.connected webhook arrives once their Web Connector checks in.
33
+
34
+ const fresh = await qube.regenerateOnboarding(connection.id); // a new onboarding link
35
+ ```
36
+
37
+ Also `getConnection(id)`, `listConnections()`, `updateConnection(id, { webhookUrl, name })`,
38
+ `deleteConnection(id)`, and for setup without hosted onboarding `setPassword(id, { password })`
39
+ (leave `password` out to have one generated) and `getQwc(id)` (the QWC file's XML, a string).
40
+
41
+ ### Starting a run and answering its question
42
+
43
+ ```js
44
+ const run = await qube.startRun(connectionId, "customer_sync", { customer: { name: "Acme" } }, {
45
+ reference: "customer-1042", // your own id for the run, and its Idempotency-Key
46
+ webhookUrl: "https://app.example.com/webhooks/qube",
47
+ });
48
+
49
+ // Later, in the workflow_run.awaiting_input webhook (event.prompt), answer by choice:
50
+ await qube.answer(event.prompt.links.decisions, { choice: "use_existing", data: { customer: "80000001-1700000000" } });
51
+ // A question's record field: search QuickBooks with the prompt's search link.
52
+ const matches = await qube.search(event.prompt.links.search, { field: "customer", q: "Acm" });
53
+ ```
54
+
55
+ Also `getRun(runUrl)` (a run's `links.run`) or `getRun(connectionId, runId)`,
56
+ `listRuns(connectionId, { reference, state, outcome })`, `cancelRun(run.links.cancel, { reason })` and
57
+ `runEvents(connectionId, runId, { after, limit })`, which returns the whole body,
58
+ `{ data, next_after }`.
59
+
60
+ Links (`links.run`, `links.decisions`, `links.search`, `links.cancel`) are used as they come, query
61
+ string included. A link on another origin than the client's `baseUrl` (scheme, host and port) is
62
+ refused with a `TypeError` before any request, so the API key only ever goes to QuBe Sync.
63
+
64
+ ### Queueing an operation
65
+
66
+ Every QuickBooks operation is a method, entity then action: `create`, `update`, `query`, `delete` or
67
+ `execute`. Each takes the connection id, the params (the API's own snake_case fields) and
68
+ `{ webhookUrl, idempotencyKey }`, and resolves to the queued request; the answer arrives at
69
+ `webhookUrl`.
70
+
71
+ ```js
72
+ // POST /connections/{id}/customers
73
+ const queued = await qube.customer.create(connectionId, {
74
+ name: "Acme",
75
+ bill_address: { addr1: "1 Main St", city: "Austin", state: "TX", postal_code: "78701" },
76
+ }, { webhookUrl: "https://app.example.com/webhooks/qube" });
77
+
78
+ // GET /connections/{id}/invoices: a query's params go in the query string.
79
+ await qube.invoice.query(connectionId, {
80
+ modified_date_range_filter: { from_modified_date: "2026-10-01T00:00:00Z" },
81
+ include_line_items: true,
82
+ }, { webhookUrl: "https://app.example.com/webhooks/qube" });
83
+
84
+ await qube.customerMsg.query(connectionId, {}, { webhookUrl: "https://app.example.com/webhooks/qube" });
85
+ await qube.txnVoid.execute(connectionId, { txn_id: "1A2B-1700000000", txn_void_type: "Invoice" }, {
86
+ webhookUrl: "https://app.example.com/webhooks/qube",
87
+ });
88
+ ```
89
+
90
+ Each entity's module loads the first time you use it (`qube.customer`), so the 124 of them cost
91
+ nothing until then. A bundled app can import one alone:
92
+ `const { Customer } = require("qubesync/operations/customer"); new Customer(qube).create(...)`.
93
+
94
+ The types are in `operations/<entity>.d.ts` (and nested ones shared between entities in
95
+ `operations/types.d.ts`): every field with its description, required where QuickBooks requires it,
96
+ enums as literal unions, so an editor completes and checks the params. Each method's doc says whether
97
+ QuickBooks answers in pages (each page comes as its own webhook) and links its
98
+ [API reference](https://docs.qubesync.com) page. They're generated from the QuBe Sync operations
99
+ manifest (`node scripts/generate-operations.js`), not written by hand.
100
+
101
+ `queue(operationId, connectionId, params, { webhookUrl, idempotencyKey, method, path })` queues an
102
+ operation by its id, the same as the method: `qube.queue("customer.create", connectionId, params, options)`.
103
+ For an operation newer than this version, pass `method` and `path` (relative to the connection):
104
+ `qube.queue("widget.create", connectionId, params, { webhookUrl, method: "POST", path: "widgets" })`.
105
+
106
+ qbXML as written:
107
+
108
+ ```js
109
+ await qube.queueQbxml(connectionId, requestXml, { webhookUrl: "https://app.example.com/webhooks/qube" });
110
+ ```
111
+
112
+ Also `getRequest(connectionId, id)`, `requestPages(connectionId, id)`, `discardRequest(connectionId, id)`,
113
+ and `listRequests(connectionId, { page, pageSize, state, webhookState, sort, sortDirection, searchPhrase })`,
114
+ which returns the whole body, `{ data, meta: { page, total_pages } }`. To walk every page:
115
+
116
+ ```js
117
+ for await (const request of qube.eachRequest(connectionId, { state: "errored" })) {
118
+ console.log(request.id);
119
+ }
120
+ ```
121
+
122
+ ### Anything else
123
+
124
+ `request(method, pathOrUrl, body, { query, headers })` sends any v2 call, a path under `/api/v2`
125
+ (`"/workflows"`) or a same-origin URL, and returns the parsed body whole.
126
+
127
+ ### Retries and idempotency
128
+
129
+ A request that's safe to send again (a GET, PUT or DELETE, or one with an `Idempotency-Key`) is
130
+ retried up to `maxRetries` times, 0.5 s then 1 s apart, when the connection fails or times out, or
131
+ QuBe Sync answers 429, 502, 503 or 504 (a 429 or 503's `Retry-After` is honoured, up to 30 s).
132
+
133
+ - `startRun` sends `reference` as the Idempotency-Key, so a retried start never starts two runs.
134
+ - Every operation method, `queue` and `queueQbxml` take `idempotencyKey`; left out, the client makes
135
+ a UUID per call and sends it again on that call's retries, so a retry never queues a request twice.
136
+ - `answer` is retried when its link has a `prompt_token` (a run's `links.decisions` always does
137
+ while it asks). A retry after a lost answer may reject with a `409` whose `code` is
138
+ `prompt_changed`: the question has been answered, by the first attempt or by someone else.
139
+ Without a `prompt_token` it's sent once, since a repeat could be taken for the run's next question.
140
+ - The other POSTs (`createConnection`, `cancelRun`, `setPassword`, `getQwc`,
141
+ `regenerateOnboarding`, `discardRequest`) are sent once.
142
+ - A retried DELETE (`deleteConnection`, or `request("DELETE", ...)`) that gets a `404` resolves to
143
+ `null`, as a 204 would, when an earlier attempt's answer was lost after the request could have
144
+ arrived (a read timeout, a connection reset after it opened, a 502 or 504): that attempt may have
145
+ deleted it. Any other 404 still rejects, including a retry's after a refused connection, a
146
+ connect timeout, a 429 or a 503.
147
+
148
+ A call returns or rejects within `totalTimeout` (60 s), retries and their waits included: each
149
+ attempt's timeouts are cut to the time left, and a retry is skipped (the last answer or error
150
+ rejects) when its wait, a `Retry-After` included, would leave under a second. Pass `maxRetries: 0`
151
+ for calls made while a user waits on a page, if 60 s is too long.
152
+
153
+ `onRetry` is called before each retry's wait with a frozen object: `attempt` (1, 2...),
154
+ `maxRetries`, `delayMs`, `elapsedMs`, `method`, `url` (without its query string), and `status` for
155
+ a retried status or `error` for a network failure or timeout (the other is `null`). It's never
156
+ given the API key, headers or body, and it isn't called when no retry is made. Anything it throws
157
+ stops the call, which rejects with that error:
158
+
159
+ ```js
160
+ const qube = new Client({
161
+ apiKey: process.env.QUBE_API_KEY,
162
+ onRetry: ({ attempt, maxRetries, delayMs, method, url, status, error }) =>
163
+ console.warn(`QuBe Sync retry ${attempt}/${maxRetries} in ${Math.round(delayMs)} ms: ${method} ${url} (${status ?? error.code})`),
164
+ });
165
+ ```
166
+
167
+ ### Errors
168
+
169
+ Any answer outside 2xx rejects with `APIError`:
170
+
171
+ ```js
172
+ try {
173
+ await qube.answer(decisionsUrl, { choice: "create" });
174
+ } catch (error) {
175
+ if (!(error instanceof APIError)) throw error; // Node's own error: the connection failed after the retries
176
+ error.status; // 409
177
+ error.code; // "prompt_changed", when errors carries a code
178
+ error.errors; // the body's errors as parsed: a sentence, { detail, code }, fields, or [{ path, message }]
179
+ error.message; // "QuBe Sync answered 409: ...", one line, safe to log
180
+ error.body; // the raw body
181
+ error.requestId; // the x-request-id header, for support
182
+ }
183
+ ```
184
+
185
+ A connection that still fails after the retries rejects with Node's own error (`ECONNREFUSED`,
186
+ `ECONNRESET`, or `ETIMEDOUT` for a timeout), unchanged. Neither ever holds the API key, and
187
+ `util.inspect(client)` and `JSON.stringify(client)` show only the `baseUrl`. See
188
+ [error handling](https://docs.qubesync.com/error_handling).
189
+
11
190
  ## Webhooks
12
191
 
13
192
  Every delivery carries an `X-QUBE-Signature` header: `t=<unix seconds>,v1=<hex>[,v1=<hex>...]`,
@@ -132,10 +311,14 @@ app.post("/webhooks/qube", express.raw({ type: "application/json" }), (req, res)
132
311
  ```sh
133
312
  npm test # node:test, no dependencies
134
313
  npm run test:types # tsc --strict against test/types.ts (fetches TypeScript with npx)
314
+ node scripts/generate-operations.js # regenerate operations/ from test/fixtures/operations.json
315
+ node scripts/generate-operations.js --check # fail when operations/ is stale
135
316
  ```
136
317
 
137
- `test/fixtures/test_vectors.json` comes from the QuBe Sync server (`mix webhook_vectors.gen`).
138
- Copy a new version in; don't edit it.
318
+ `test/fixtures/test_vectors.json` comes from the QuBe Sync server (`mix webhook_vectors.gen`), and
319
+ `test/fixtures/operations.json` from https://qubesync.com/libraries/operations.json
320
+ (`mix library_operations.gen`). Copy a new version in, then regenerate; don't edit either, or
321
+ anything in `operations/`.
139
322
 
140
323
  ## License
141
324