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.
- package/CHANGELOG.md +38 -2
- package/README.md +188 -5
- package/client.js +660 -0
- package/index.d.ts +211 -1
- package/index.js +5 -1
- package/index.mjs +2 -0
- package/operations/account.d.ts +263 -0
- package/operations/account.js +50 -0
- package/operations/account_tax_line_info.d.ts +31 -0
- package/operations/account_tax_line_info.js +26 -0
- package/operations/aging_report.d.ts +100 -0
- package/operations/aging_report.js +26 -0
- package/operations/alert.d.ts +118 -0
- package/operations/alert.js +50 -0
- package/operations/ar_refund_credit_card.d.ts +201 -0
- package/operations/ar_refund_credit_card.js +38 -0
- package/operations/bar_code.d.ts +40 -0
- package/operations/bar_code.js +26 -0
- package/operations/bill.d.ts +612 -0
- package/operations/bill.js +50 -0
- package/operations/bill_payment_check.d.ts +276 -0
- package/operations/bill_payment_check.js +50 -0
- package/operations/bill_payment_credit_card.d.ts +178 -0
- package/operations/bill_payment_credit_card.js +38 -0
- package/operations/bill_to_pay.d.ts +54 -0
- package/operations/bill_to_pay.js +26 -0
- package/operations/billing_rate.d.ts +159 -0
- package/operations/billing_rate.js +38 -0
- package/operations/budget_summary_report.d.ts +71 -0
- package/operations/budget_summary_report.js +26 -0
- package/operations/build_assembly.d.ts +264 -0
- package/operations/build_assembly.js +50 -0
- package/operations/charge.d.ts +330 -0
- package/operations/charge.js +50 -0
- package/operations/check.d.ts +394 -0
- package/operations/check.js +50 -0
- package/operations/class.d.ts +170 -0
- package/operations/class.js +50 -0
- package/operations/cleared_status.d.ts +43 -0
- package/operations/cleared_status.js +26 -0
- package/operations/client.d.ts +254 -0
- package/operations/company.d.ts +36 -0
- package/operations/company.js +26 -0
- package/operations/company_activity.d.ts +32 -0
- package/operations/company_activity.js +26 -0
- package/operations/credit_card_charge.d.ts +280 -0
- package/operations/credit_card_charge.js +50 -0
- package/operations/credit_card_credit.d.ts +280 -0
- package/operations/credit_card_credit.js +50 -0
- package/operations/credit_memo.d.ts +642 -0
- package/operations/credit_memo.js +50 -0
- package/operations/currency.d.ts +195 -0
- package/operations/currency.js +50 -0
- package/operations/custom_detail_report.d.ts +108 -0
- package/operations/custom_detail_report.js +26 -0
- package/operations/custom_summary_report.d.ts +119 -0
- package/operations/custom_summary_report.js +26 -0
- package/operations/customer.d.ts +639 -0
- package/operations/customer.js +50 -0
- package/operations/customer_msg.d.ts +121 -0
- package/operations/customer_msg.js +38 -0
- package/operations/customer_type.d.ts +127 -0
- package/operations/customer_type.js +38 -0
- package/operations/data_event_recovery_info.d.ts +61 -0
- package/operations/data_event_recovery_info.js +38 -0
- package/operations/data_ext.d.ts +197 -0
- package/operations/data_ext.js +50 -0
- package/operations/data_ext_def.d.ts +173 -0
- package/operations/data_ext_def.js +62 -0
- package/operations/date_driven_terms.d.ts +141 -0
- package/operations/date_driven_terms.js +38 -0
- package/operations/deposit.d.ts +402 -0
- package/operations/deposit.js +50 -0
- package/operations/employee.d.ts +683 -0
- package/operations/employee.js +50 -0
- package/operations/employee_default.d.ts +133 -0
- package/operations/employee_default.js +38 -0
- package/operations/entity.d.ts +96 -0
- package/operations/entity.js +26 -0
- package/operations/estimate.d.ts +590 -0
- package/operations/estimate.js +50 -0
- package/operations/form1099_category_account_mapping.d.ts +89 -0
- package/operations/form1099_category_account_mapping.js +38 -0
- package/operations/general_detail_report.d.ts +108 -0
- package/operations/general_detail_report.js +26 -0
- package/operations/general_summary_report.d.ts +115 -0
- package/operations/general_summary_report.js +26 -0
- package/operations/host.d.ts +43 -0
- package/operations/host.js +26 -0
- package/operations/index.js +397 -0
- package/operations/inventory_adjustment.d.ts +389 -0
- package/operations/inventory_adjustment.js +50 -0
- package/operations/inventory_site.d.ts +247 -0
- package/operations/inventory_site.js +50 -0
- package/operations/invoice.d.ts +682 -0
- package/operations/invoice.js +50 -0
- package/operations/item.d.ts +100 -0
- package/operations/item.js +26 -0
- package/operations/item_assemblies_can_build.d.ts +41 -0
- package/operations/item_assemblies_can_build.js +26 -0
- package/operations/item_discount.d.ts +256 -0
- package/operations/item_discount.js +50 -0
- package/operations/item_fixed_asset.d.ts +382 -0
- package/operations/item_fixed_asset.js +50 -0
- package/operations/item_group.d.ts +236 -0
- package/operations/item_group.js +50 -0
- package/operations/item_inventory.d.ts +350 -0
- package/operations/item_inventory.js +50 -0
- package/operations/item_inventory_assembly.d.ts +368 -0
- package/operations/item_inventory_assembly.js +50 -0
- package/operations/item_non_inventory.d.ts +264 -0
- package/operations/item_non_inventory.js +50 -0
- package/operations/item_other_charge.d.ts +244 -0
- package/operations/item_other_charge.js +50 -0
- package/operations/item_payment.d.ts +220 -0
- package/operations/item_payment.js +50 -0
- package/operations/item_receipt.d.ts +362 -0
- package/operations/item_receipt.js +50 -0
- package/operations/item_sales_tax.d.ts +228 -0
- package/operations/item_sales_tax.js +50 -0
- package/operations/item_sales_tax_group.d.ts +192 -0
- package/operations/item_sales_tax_group.js +50 -0
- package/operations/item_service.d.ts +256 -0
- package/operations/item_service.js +50 -0
- package/operations/item_sites.d.ts +81 -0
- package/operations/item_sites.js +26 -0
- package/operations/item_subtotal.d.ts +188 -0
- package/operations/item_subtotal.js +50 -0
- package/operations/job_report.d.ts +99 -0
- package/operations/job_report.js +26 -0
- package/operations/job_type.d.ts +127 -0
- package/operations/job_type.js +38 -0
- package/operations/journal_entry.d.ts +339 -0
- package/operations/journal_entry.js +50 -0
- package/operations/lead.d.ts +291 -0
- package/operations/lead.js +50 -0
- package/operations/list.d.ts +39 -0
- package/operations/list.js +26 -0
- package/operations/list_deleted.d.ts +44 -0
- package/operations/list_deleted.js +26 -0
- package/operations/list_display.d.ts +65 -0
- package/operations/list_display.js +38 -0
- package/operations/list_merge.d.ts +58 -0
- package/operations/list_merge.js +26 -0
- package/operations/other_name.d.ts +280 -0
- package/operations/other_name.js +50 -0
- package/operations/paycheck.d.ts +82 -0
- package/operations/paycheck.js +26 -0
- package/operations/payment_method.d.ts +131 -0
- package/operations/payment_method.js +38 -0
- package/operations/payroll_detail_report.d.ts +97 -0
- package/operations/payroll_detail_report.js +26 -0
- package/operations/payroll_item.d.ts +83 -0
- package/operations/payroll_item.js +26 -0
- package/operations/payroll_item_addition.d.ts +95 -0
- package/operations/payroll_item_addition.js +38 -0
- package/operations/payroll_item_company_contribution.d.ts +105 -0
- package/operations/payroll_item_company_contribution.js +38 -0
- package/operations/payroll_item_deduction.d.ts +101 -0
- package/operations/payroll_item_deduction.js +38 -0
- package/operations/payroll_item_direct_deposit.d.ts +65 -0
- package/operations/payroll_item_direct_deposit.js +38 -0
- package/operations/payroll_item_non_wage.d.ts +92 -0
- package/operations/payroll_item_non_wage.js +26 -0
- package/operations/payroll_item_tax.d.ts +108 -0
- package/operations/payroll_item_tax.js +38 -0
- package/operations/payroll_item_wage.d.ts +169 -0
- package/operations/payroll_item_wage.js +50 -0
- package/operations/payroll_item_workers_compensation.d.ts +77 -0
- package/operations/payroll_item_workers_compensation.js +38 -0
- package/operations/payroll_last_period.d.ts +31 -0
- package/operations/payroll_last_period.js +26 -0
- package/operations/payroll_liability_adjustment.d.ts +137 -0
- package/operations/payroll_liability_adjustment.js +38 -0
- package/operations/payroll_liability_check.d.ts +82 -0
- package/operations/payroll_liability_check.js +26 -0
- package/operations/payroll_prior_payment.d.ts +126 -0
- package/operations/payroll_prior_payment.js +38 -0
- package/operations/payroll_summary_report.d.ts +107 -0
- package/operations/payroll_summary_report.js +26 -0
- package/operations/payroll_year_to_date_adjustment.d.ts +156 -0
- package/operations/payroll_year_to_date_adjustment.js +38 -0
- package/operations/preferences.d.ts +64 -0
- package/operations/preferences.js +38 -0
- package/operations/price_level.d.ts +234 -0
- package/operations/price_level.js +50 -0
- package/operations/purchase_order.d.ts +620 -0
- package/operations/purchase_order.js +50 -0
- package/operations/qb_command_exec.d.ts +49 -0
- package/operations/qb_command_exec.js +26 -0
- package/operations/receive_payment.d.ts +450 -0
- package/operations/receive_payment.js +50 -0
- package/operations/receive_payment_to_deposit.d.ts +36 -0
- package/operations/receive_payment_to_deposit.js +26 -0
- package/operations/sales_order.d.ts +612 -0
- package/operations/sales_order.js +50 -0
- package/operations/sales_receipt.d.ts +650 -0
- package/operations/sales_receipt.js +50 -0
- package/operations/sales_rep.d.ts +170 -0
- package/operations/sales_rep.js +50 -0
- package/operations/sales_tax_code.d.ts +194 -0
- package/operations/sales_tax_code.js +50 -0
- package/operations/sales_tax_payable.d.ts +42 -0
- package/operations/sales_tax_payable.js +26 -0
- package/operations/sales_tax_payment_check.d.ts +254 -0
- package/operations/sales_tax_payment_check.js +50 -0
- package/operations/sales_tax_return.d.ts +35 -0
- package/operations/sales_tax_return.js +26 -0
- package/operations/sales_tax_return_line.d.ts +36 -0
- package/operations/sales_tax_return_line.js +26 -0
- package/operations/ship_method.d.ts +121 -0
- package/operations/ship_method.js +38 -0
- package/operations/special_account.d.ts +40 -0
- package/operations/special_account.js +26 -0
- package/operations/special_item.d.ts +44 -0
- package/operations/special_item.js +26 -0
- package/operations/standard_terms.d.ts +133 -0
- package/operations/standard_terms.js +38 -0
- package/operations/template.d.ts +36 -0
- package/operations/template.js +26 -0
- package/operations/terms.d.ts +90 -0
- package/operations/terms.js +26 -0
- package/operations/time_report.d.ts +84 -0
- package/operations/time_report.js +26 -0
- package/operations/time_tracking.d.ts +214 -0
- package/operations/time_tracking.js +50 -0
- package/operations/to_do.d.ts +254 -0
- package/operations/to_do.js +50 -0
- package/operations/transaction.d.ts +244 -0
- package/operations/transaction.js +26 -0
- package/operations/transfer.d.ts +178 -0
- package/operations/transfer.js +50 -0
- package/operations/transfer_inventory.d.ts +290 -0
- package/operations/transfer_inventory.js +50 -0
- package/operations/txn.d.ts +39 -0
- package/operations/txn.js +26 -0
- package/operations/txn_deleted.d.ts +44 -0
- package/operations/txn_deleted.js +26 -0
- package/operations/txn_display.d.ts +69 -0
- package/operations/txn_display.js +38 -0
- package/operations/txn_void.d.ts +39 -0
- package/operations/txn_void.js +26 -0
- package/operations/types.d.ts +1259 -0
- package/operations/unit_of_measure_set.d.ts +168 -0
- package/operations/unit_of_measure_set.js +38 -0
- package/operations/vehicle.d.ts +168 -0
- package/operations/vehicle.js +50 -0
- package/operations/vehicle_mileage.d.ts +147 -0
- package/operations/vehicle_mileage.js +38 -0
- package/operations/vendor.d.ts +541 -0
- package/operations/vendor.js +50 -0
- package/operations/vendor_credit.d.ts +365 -0
- package/operations/vendor_credit.js +50 -0
- package/operations/vendor_type.d.ts +127 -0
- package/operations/vendor_type.js +38 -0
- package/operations/workers_comp_code.d.ts +199 -0
- package/operations/workers_comp_code.js +50 -0
- package/package.json +14 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.
|
|
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
|
|
55
|
+
- A `v*` tag publishes to npm (`release.yml`).
|
package/README.md
CHANGED
|
@@ -1,13 +1,192 @@
|
|
|
1
1
|
# qubesync (Node)
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
qubesync.com. Plain JavaScript, zero dependencies, Node 16.17 and
|
|
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
|
-
|
|
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
|
|