@beyondplusmm/doehpos-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/LICENSE +21 -0
- package/README.md +75 -0
- package/dist/client.d.ts +53 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +54 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +25 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +20 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +63 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +96 -0
- package/dist/errors.js.map +1 -0
- package/dist/idempotency.d.ts +17 -0
- package/dist/idempotency.d.ts.map +1 -0
- package/dist/idempotency.js +30 -0
- package/dist/idempotency.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/modules/delivery.d.ts +26 -0
- package/dist/modules/delivery.d.ts.map +1 -0
- package/dist/modules/delivery.js +46 -0
- package/dist/modules/delivery.js.map +1 -0
- package/dist/modules/experimental/kitchen.d.ts +30 -0
- package/dist/modules/experimental/kitchen.d.ts.map +1 -0
- package/dist/modules/experimental/kitchen.js +32 -0
- package/dist/modules/experimental/kitchen.js.map +1 -0
- package/dist/modules/experimental/loyalty.d.ts +30 -0
- package/dist/modules/experimental/loyalty.d.ts.map +1 -0
- package/dist/modules/experimental/loyalty.js +34 -0
- package/dist/modules/experimental/loyalty.js.map +1 -0
- package/dist/modules/experimental/marketplace.d.ts +31 -0
- package/dist/modules/experimental/marketplace.d.ts.map +1 -0
- package/dist/modules/experimental/marketplace.js +32 -0
- package/dist/modules/experimental/marketplace.js.map +1 -0
- package/dist/modules/experimental/rider.d.ts +29 -0
- package/dist/modules/experimental/rider.d.ts.map +1 -0
- package/dist/modules/experimental/rider.js +32 -0
- package/dist/modules/experimental/rider.js.map +1 -0
- package/dist/queue.d.ts +78 -0
- package/dist/queue.d.ts.map +1 -0
- package/dist/queue.js +105 -0
- package/dist/queue.js.map +1 -0
- package/dist/transport.d.ts +36 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +105 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.d.ts +54 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/package.json +57 -0
- package/src/client.ts +92 -0
- package/src/config.ts +30 -0
- package/src/errors.ts +119 -0
- package/src/idempotency.ts +33 -0
- package/src/index.ts +64 -0
- package/src/modules/delivery.ts +57 -0
- package/src/modules/experimental/kitchen.ts +51 -0
- package/src/modules/experimental/loyalty.ts +52 -0
- package/src/modules/experimental/marketplace.ts +52 -0
- package/src/modules/experimental/rider.ts +50 -0
- package/src/queue.ts +157 -0
- package/src/transport.ts +140 -0
- package/src/types.ts +65 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The transport: one place that knows how to shape, send, and retry a request.
|
|
3
|
+
*
|
|
4
|
+
* Faithful to the validated golden client:
|
|
5
|
+
* - Authorization: Bearer <key>
|
|
6
|
+
* - User-Agent is MANDATORY (the edge/WAF rejects default library agents
|
|
7
|
+
* before they ever reach the API).
|
|
8
|
+
* - Trace-Id is sent and echoed; surfaced on errors for log correlation.
|
|
9
|
+
* - Idempotency-Key (NOT X-Idempotency-Key) enables safe create retries.
|
|
10
|
+
* - 429 and transport failures are retried with linear backoff; nothing else.
|
|
11
|
+
*/
|
|
12
|
+
import { DEFAULTS } from "./config.js";
|
|
13
|
+
import { DoehTransportError, mapApiError, isRetryable } from "./errors.js";
|
|
14
|
+
import { generateIdempotencyKey } from "./idempotency.js";
|
|
15
|
+
const defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
16
|
+
function parseBody(text) {
|
|
17
|
+
if (!text)
|
|
18
|
+
return {};
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(text);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// A non-JSON body (e.g. a WAF/proxy HTML error) — surface it structured.
|
|
24
|
+
return { ok: false, code: "NON_JSON_RESPONSE", raw: text.slice(0, 200) };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export class Transport {
|
|
28
|
+
constructor(cfg) {
|
|
29
|
+
this.cfg = cfg;
|
|
30
|
+
}
|
|
31
|
+
/** Send one logical request, retrying transport errors and 429 only. */
|
|
32
|
+
async request(spec) {
|
|
33
|
+
const sleep = this.cfg.sleep ?? defaultSleep;
|
|
34
|
+
const traceId = spec.traceId ?? generateIdempotencyKey("trace");
|
|
35
|
+
const max = this.cfg.maxRetries;
|
|
36
|
+
let attempt = 0;
|
|
37
|
+
// eslint-disable-next-line no-constant-condition
|
|
38
|
+
while (true) {
|
|
39
|
+
try {
|
|
40
|
+
return await this.attempt(spec, traceId);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
if (isRetryable(err) && attempt < max) {
|
|
44
|
+
attempt += 1;
|
|
45
|
+
await sleep(this.cfg.backoffBaseMs * attempt);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
throw err;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async attempt(spec, traceId) {
|
|
53
|
+
const url = this.cfg.baseUrl.replace(/\/$/, "") + spec.path;
|
|
54
|
+
const headers = {
|
|
55
|
+
"Content-Type": "application/json",
|
|
56
|
+
"User-Agent": this.cfg.userAgent,
|
|
57
|
+
"Trace-Id": traceId,
|
|
58
|
+
};
|
|
59
|
+
if (!spec.anonymous)
|
|
60
|
+
headers["Authorization"] = `Bearer ${this.cfg.apiKey}`;
|
|
61
|
+
if (spec.idempotencyKey)
|
|
62
|
+
headers["Idempotency-Key"] = spec.idempotencyKey;
|
|
63
|
+
// Compose the caller signal with our timeout.
|
|
64
|
+
const timeout = new AbortController();
|
|
65
|
+
const timer = setTimeout(() => timeout.abort(), this.cfg.timeoutMs);
|
|
66
|
+
const signal = spec.signal
|
|
67
|
+
? anySignal([spec.signal, timeout.signal])
|
|
68
|
+
: timeout.signal;
|
|
69
|
+
let res;
|
|
70
|
+
try {
|
|
71
|
+
res = await this.cfg.fetch(url, {
|
|
72
|
+
method: spec.method,
|
|
73
|
+
headers,
|
|
74
|
+
body: spec.body !== undefined ? JSON.stringify(spec.body) : undefined,
|
|
75
|
+
signal,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
catch (cause) {
|
|
79
|
+
const aborted = cause?.name === "AbortError";
|
|
80
|
+
throw new DoehTransportError(aborted ? `request timed out after ${this.cfg.timeoutMs}ms` : "network request failed", { cause, timeout: aborted });
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
clearTimeout(timer);
|
|
84
|
+
}
|
|
85
|
+
const text = await res.text();
|
|
86
|
+
const body = parseBody(text);
|
|
87
|
+
if (res.status >= 200 && res.status < 300) {
|
|
88
|
+
return { status: res.status, body: body, traceId };
|
|
89
|
+
}
|
|
90
|
+
throw mapApiError(res.status, body, traceId);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** Minimal AbortSignal.any polyfill (not available on all RN runtimes). */
|
|
94
|
+
function anySignal(signals) {
|
|
95
|
+
const controller = new AbortController();
|
|
96
|
+
for (const s of signals) {
|
|
97
|
+
if (s.aborted) {
|
|
98
|
+
controller.abort();
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
s.addEventListener("abort", () => controller.abort(), { once: true });
|
|
102
|
+
}
|
|
103
|
+
return controller.signal;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire types, mirrored from the public OpenAPI specs and the golden client.
|
|
3
|
+
*
|
|
4
|
+
* Money is ALWAYS integer minor units (1500 == 15.00). Never a decimal.
|
|
5
|
+
*/
|
|
6
|
+
export type Currency = "MMK" | "THB" | "USD" | "CNY" | "SGD" | "INR";
|
|
7
|
+
export type OrderStatus = "pending" | "confirmed" | "dispatched" | "delivered" | "cancelled";
|
|
8
|
+
/** Request body for POST /v1/delivery/orders. */
|
|
9
|
+
export interface OrderCreate {
|
|
10
|
+
currency: Currency;
|
|
11
|
+
/** Integer minor units, >= 1. e.g. 1500 = 15.00. */
|
|
12
|
+
amount_minor: number;
|
|
13
|
+
}
|
|
14
|
+
export interface Order {
|
|
15
|
+
id: string;
|
|
16
|
+
status: OrderStatus;
|
|
17
|
+
shop_id: number;
|
|
18
|
+
branch_id: number;
|
|
19
|
+
currency: Currency;
|
|
20
|
+
amount_minor: number;
|
|
21
|
+
created_at_utc: string;
|
|
22
|
+
/** created_at rendered in the branch's native timezone. */
|
|
23
|
+
created_at_local: string;
|
|
24
|
+
idempotency_key?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface OrderResponse {
|
|
27
|
+
ok: boolean;
|
|
28
|
+
/** true if this was an idempotent replay (HTTP 200 instead of 201). */
|
|
29
|
+
idempotent?: boolean;
|
|
30
|
+
order: Order;
|
|
31
|
+
}
|
|
32
|
+
/** The stable error envelope returned for every non-2xx response. */
|
|
33
|
+
export interface ErrorBody {
|
|
34
|
+
ok: false;
|
|
35
|
+
/** Stable, append-only error code (part of the API ABI). */
|
|
36
|
+
code: string;
|
|
37
|
+
/** Internal verification step (diagnostic only). */
|
|
38
|
+
step?: string;
|
|
39
|
+
}
|
|
40
|
+
/** Per-call options that map onto request headers. */
|
|
41
|
+
export interface CallOptions {
|
|
42
|
+
/**
|
|
43
|
+
* Idempotency key. STRONGLY recommended on every create. For offline-safe
|
|
44
|
+
* retries the key must be minted once when the mutation is created and reused
|
|
45
|
+
* on every attempt — see OfflineQueue. If omitted on a create, the SDK mints
|
|
46
|
+
* one for this single call only.
|
|
47
|
+
*/
|
|
48
|
+
idempotencyKey?: string;
|
|
49
|
+
/** Caller trace id, propagated and echoed on the response. */
|
|
50
|
+
traceId?: string;
|
|
51
|
+
/** Per-call AbortSignal (in addition to the configured timeout). */
|
|
52
|
+
signal?: AbortSignal;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;AAErE,MAAM,MAAM,WAAW,GACnB,SAAS,GACT,WAAW,GACX,YAAY,GACZ,WAAW,GACX,WAAW,CAAC;AAEhB,iDAAiD;AACjD,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,QAAQ,CAAC;IACnB,oDAAoD;IACpD,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,QAAQ,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,2DAA2D;IAC3D,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,OAAO,CAAC;IACZ,uEAAuE;IACvE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,EAAE,KAAK,CAAC;CACd;AAED,qEAAqE;AACrE,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,KAAK,CAAC;IACV,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,sDAAsD;AACtD,MAAM,WAAW,WAAW;IAC1B;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@beyondplusmm/doehpos-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Official TypeScript SDK for the Doeh POS public API — a typed port of the validated golden client.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"src",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE",
|
|
20
|
+
"CHANGELOG.md"
|
|
21
|
+
],
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc -p tsconfig.json",
|
|
25
|
+
"prepack": "tsc -p tsconfig.json",
|
|
26
|
+
"test": "pnpm build && node --test test/",
|
|
27
|
+
"test:integration": "pnpm build && node test/golden.integration.mjs"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"doeh",
|
|
31
|
+
"pos",
|
|
32
|
+
"sdk",
|
|
33
|
+
"delivery",
|
|
34
|
+
"expo",
|
|
35
|
+
"react-native"
|
|
36
|
+
],
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"author": "bplusmyanmar <bplusmyanmar@gmail.com>",
|
|
39
|
+
"homepage": "https://github.com/beyondplusmyanmar-lab/doeh-sdk/tree/main/packages/sdk#readme",
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "git+https://github.com/beyondplusmyanmar-lab/doeh-sdk.git",
|
|
43
|
+
"directory": "packages/sdk"
|
|
44
|
+
},
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/beyondplusmyanmar-lab/doeh-sdk/issues"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=18"
|
|
50
|
+
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"typescript": "^5.6.3"
|
|
56
|
+
}
|
|
57
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DoehClient — the public entry point.
|
|
3
|
+
*
|
|
4
|
+
* const client = new DoehClient({ apiKey: "sk_test_…", environment: "sandbox" });
|
|
5
|
+
* const { order } = await client.delivery.create({ currency: "MMK", amount_minor: 1500 });
|
|
6
|
+
*
|
|
7
|
+
* There is intentionally NO shop concept: scope is derived from the key. To act
|
|
8
|
+
* as a different shop, construct a client with a different key. Cutover from
|
|
9
|
+
* sandbox to production is `environment: "production"` + a `sk_live_` key — no
|
|
10
|
+
* code change.
|
|
11
|
+
*/
|
|
12
|
+
import { BASE_URLS, DEFAULTS, SDK_VERSION, type Environment } from "./config.js";
|
|
13
|
+
import { Transport, type FetchLike } from "./transport.js";
|
|
14
|
+
import { DeliveryModule } from "./modules/delivery.js";
|
|
15
|
+
import { KitchenModule } from "./modules/experimental/kitchen.js";
|
|
16
|
+
import { LoyaltyModule } from "./modules/experimental/loyalty.js";
|
|
17
|
+
import { MarketplaceModule } from "./modules/experimental/marketplace.js";
|
|
18
|
+
import { RiderModule } from "./modules/experimental/rider.js";
|
|
19
|
+
|
|
20
|
+
export interface DoehClientOptions {
|
|
21
|
+
apiKey: string;
|
|
22
|
+
/** "sandbox" (default) or "production". Ignored if baseUrl is given. */
|
|
23
|
+
environment?: Environment;
|
|
24
|
+
/** Override the base URL entirely (advanced / self-host / tests). */
|
|
25
|
+
baseUrl?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Override the (mandatory) User-Agent. The SDK always sends one; supplying
|
|
28
|
+
* your own is encouraged so your app is identifiable in server logs, e.g.
|
|
29
|
+
* "MyMerchantApp/2.1". The SDK token is appended automatically.
|
|
30
|
+
*/
|
|
31
|
+
userAgent?: string;
|
|
32
|
+
/** Inject a fetch implementation (RN / undici / mock). Defaults to global. */
|
|
33
|
+
fetch?: FetchLike;
|
|
34
|
+
timeoutMs?: number;
|
|
35
|
+
maxRetries?: number;
|
|
36
|
+
/** Test hook to make backoff instant. */
|
|
37
|
+
sleep?: (ms: number) => Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class DoehClient {
|
|
41
|
+
readonly delivery: DeliveryModule;
|
|
42
|
+
|
|
43
|
+
/** @experimental Not yet exercised by the reference app. */
|
|
44
|
+
readonly kitchen: KitchenModule;
|
|
45
|
+
/** @experimental Not yet exercised by the reference app. */
|
|
46
|
+
readonly loyalty: LoyaltyModule;
|
|
47
|
+
/** @experimental Not yet exercised by the reference app. */
|
|
48
|
+
readonly marketplace: MarketplaceModule;
|
|
49
|
+
/** @experimental Not yet exercised by the reference app. */
|
|
50
|
+
readonly rider: RiderModule;
|
|
51
|
+
|
|
52
|
+
private readonly transport: Transport;
|
|
53
|
+
|
|
54
|
+
constructor(opts: DoehClientOptions) {
|
|
55
|
+
if (!opts.apiKey) throw new Error("DoehClient requires an apiKey");
|
|
56
|
+
const env = opts.environment ?? "sandbox";
|
|
57
|
+
const baseUrl = opts.baseUrl ?? BASE_URLS[env];
|
|
58
|
+
const userAgent = opts.userAgent
|
|
59
|
+
? `${opts.userAgent} doeh-sdk/${SDK_VERSION}`
|
|
60
|
+
: `doeh-sdk/${SDK_VERSION}`;
|
|
61
|
+
|
|
62
|
+
const fetchImpl = opts.fetch ?? (globalThis.fetch as FetchLike | undefined);
|
|
63
|
+
if (typeof fetchImpl !== "function") {
|
|
64
|
+
throw new Error(
|
|
65
|
+
"No fetch implementation available. Pass `fetch` in DoehClientOptions " +
|
|
66
|
+
"(e.g. from undici on older Node, or the RN global).",
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this.transport = new Transport({
|
|
71
|
+
baseUrl,
|
|
72
|
+
apiKey: opts.apiKey,
|
|
73
|
+
userAgent,
|
|
74
|
+
fetch: fetchImpl,
|
|
75
|
+
timeoutMs: opts.timeoutMs ?? DEFAULTS.timeoutMs,
|
|
76
|
+
maxRetries: opts.maxRetries ?? DEFAULTS.maxRetries,
|
|
77
|
+
backoffBaseMs: DEFAULTS.backoffBaseMs,
|
|
78
|
+
sleep: opts.sleep,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
this.delivery = new DeliveryModule(this.transport);
|
|
82
|
+
this.kitchen = new KitchenModule(this.transport);
|
|
83
|
+
this.loyalty = new LoyaltyModule(this.transport);
|
|
84
|
+
this.marketplace = new MarketplaceModule(this.transport);
|
|
85
|
+
this.rider = new RiderModule(this.transport);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Internal: the configured transport (used by OfflineQueue wiring/tests). */
|
|
89
|
+
get _transport(): Transport {
|
|
90
|
+
return this.transport;
|
|
91
|
+
}
|
|
92
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment configuration.
|
|
3
|
+
*
|
|
4
|
+
* "single shop" is NOT a config concept here. The shop/branch scope is derived
|
|
5
|
+
* from the API key server-side — the client never sends scope. One key == one
|
|
6
|
+
* shop. To act as a different shop, use a different key.
|
|
7
|
+
*/
|
|
8
|
+
export type Environment = "sandbox" | "production";
|
|
9
|
+
|
|
10
|
+
/** Base URLs per environment, taken verbatim from the validated golden client. */
|
|
11
|
+
export const BASE_URLS: Record<Environment, string> = {
|
|
12
|
+
sandbox: "https://sandbox-api.doehpos.com",
|
|
13
|
+
production: "https://api.doehpos.com",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/** Default SDK version string surfaced in the (mandatory) User-Agent. */
|
|
17
|
+
export const SDK_VERSION = "0.1.0";
|
|
18
|
+
|
|
19
|
+
export const DEFAULTS = {
|
|
20
|
+
/** Per-request timeout. */
|
|
21
|
+
timeoutMs: 15_000,
|
|
22
|
+
/**
|
|
23
|
+
* Max retry attempts. Retries apply ONLY to transport failures and HTTP 429
|
|
24
|
+
* (the fleet rate limiter is a deliberately tight shared token bucket).
|
|
25
|
+
* No other status is ever retried.
|
|
26
|
+
*/
|
|
27
|
+
maxRetries: 4,
|
|
28
|
+
/** Linear backoff base; attempt N waits roughly backoffBaseMs * N. */
|
|
29
|
+
backoffBaseMs: 1_000,
|
|
30
|
+
} as const;
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed error ABI.
|
|
3
|
+
*
|
|
4
|
+
* The public API's error codes are a stable, append-only contract. Consumers
|
|
5
|
+
* should NEVER parse `code` strings — they catch typed classes instead:
|
|
6
|
+
*
|
|
7
|
+
* try { await client.delivery.create(...) }
|
|
8
|
+
* catch (e) {
|
|
9
|
+
* if (e instanceof InvalidAmountError) { ... }
|
|
10
|
+
* if (e instanceof ApiKeyRevokedError) { ... }
|
|
11
|
+
* }
|
|
12
|
+
*/
|
|
13
|
+
import type { ErrorBody } from "./types.js";
|
|
14
|
+
|
|
15
|
+
/** Base for everything thrown by the SDK. */
|
|
16
|
+
export class DoehError extends Error {
|
|
17
|
+
constructor(message: string) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = new.target.name;
|
|
20
|
+
// Restore prototype chain for transpiled-to-ES5 consumers.
|
|
21
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The request never produced an HTTP response (network down, DNS, TLS, timeout,
|
|
27
|
+
* aborted). These are the failures the SDK retries.
|
|
28
|
+
*/
|
|
29
|
+
export class DoehTransportError extends DoehError {
|
|
30
|
+
readonly cause?: unknown;
|
|
31
|
+
/** True if the failure was a timeout/abort. */
|
|
32
|
+
readonly timeout: boolean;
|
|
33
|
+
constructor(message: string, opts: { cause?: unknown; timeout?: boolean } = {}) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.cause = opts.cause;
|
|
36
|
+
this.timeout = opts.timeout ?? false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Any non-2xx HTTP response, carrying the stable `code` and correlation ids. */
|
|
41
|
+
export class DoehApiError extends DoehError {
|
|
42
|
+
readonly status: number;
|
|
43
|
+
readonly code: string;
|
|
44
|
+
readonly step?: string;
|
|
45
|
+
readonly traceId?: string;
|
|
46
|
+
readonly body: unknown;
|
|
47
|
+
constructor(
|
|
48
|
+
status: number,
|
|
49
|
+
code: string,
|
|
50
|
+
opts: { step?: string; traceId?: string; body?: unknown } = {},
|
|
51
|
+
) {
|
|
52
|
+
super(`HTTP ${status} ${code}`);
|
|
53
|
+
this.status = status;
|
|
54
|
+
this.code = code;
|
|
55
|
+
this.step = opts.step;
|
|
56
|
+
this.traceId = opts.traceId;
|
|
57
|
+
this.body = opts.body;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ── 401 — authentication (API_KEY_* ABI) ─────────────────────────────────────
|
|
62
|
+
export class ApiKeyInvalidError extends DoehApiError {}
|
|
63
|
+
export class ApiKeyExpiredError extends DoehApiError {}
|
|
64
|
+
export class ApiKeyRevokedError extends DoehApiError {}
|
|
65
|
+
export class EnvMismatchError extends DoehApiError {} // API_KEY_ENV_MISMATCH
|
|
66
|
+
|
|
67
|
+
// ── 403 — authenticated but not permitted ────────────────────────────────────
|
|
68
|
+
export class ScopeDeniedError extends DoehApiError {} // API_KEY_SCOPE_DENIED
|
|
69
|
+
export class TransportDisabledError extends DoehApiError {} // EDGE_TRANSPORT_DISABLED
|
|
70
|
+
|
|
71
|
+
// ── 404 / 409 / 422 / 400 ────────────────────────────────────────────────────
|
|
72
|
+
export class OrderNotFoundError extends DoehApiError {} // EDGE_ORDER_NOT_FOUND
|
|
73
|
+
export class ReplayError extends DoehApiError {} // EDGE_REPLAYED
|
|
74
|
+
export class InvalidAmountError extends DoehApiError {} // EDGE_INVALID_AMOUNT
|
|
75
|
+
export class UnsupportedCurrencyError extends DoehApiError {} // EDGE_UNSUPPORTED_CURRENCY
|
|
76
|
+
export class BadBodyError extends DoehApiError {} // EDGE_BAD_BODY
|
|
77
|
+
|
|
78
|
+
// ── 429 — rate limited (retried internally; only surfaced when retries exhaust)
|
|
79
|
+
export class RateLimitedError extends DoehApiError {}
|
|
80
|
+
|
|
81
|
+
/** Map a stable error code to its typed class. Unknown codes -> DoehApiError. */
|
|
82
|
+
const CODE_TO_CLASS: Record<string, typeof DoehApiError> = {
|
|
83
|
+
API_KEY_INVALID: ApiKeyInvalidError,
|
|
84
|
+
API_KEY_EXPIRED: ApiKeyExpiredError,
|
|
85
|
+
API_KEY_REVOKED: ApiKeyRevokedError,
|
|
86
|
+
API_KEY_ENV_MISMATCH: EnvMismatchError,
|
|
87
|
+
API_KEY_SCOPE_DENIED: ScopeDeniedError,
|
|
88
|
+
EDGE_TRANSPORT_DISABLED: TransportDisabledError,
|
|
89
|
+
EDGE_ORDER_NOT_FOUND: OrderNotFoundError,
|
|
90
|
+
EDGE_REPLAYED: ReplayError,
|
|
91
|
+
EDGE_INVALID_AMOUNT: InvalidAmountError,
|
|
92
|
+
EDGE_UNSUPPORTED_CURRENCY: UnsupportedCurrencyError,
|
|
93
|
+
EDGE_BAD_BODY: BadBodyError,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/** Build the right typed error from an HTTP status + parsed body. */
|
|
97
|
+
export function mapApiError(
|
|
98
|
+
status: number,
|
|
99
|
+
body: unknown,
|
|
100
|
+
traceId?: string,
|
|
101
|
+
): DoehApiError {
|
|
102
|
+
const envelope = (body ?? {}) as Partial<ErrorBody>;
|
|
103
|
+
const code = typeof envelope.code === "string" ? envelope.code : `HTTP_${status}`;
|
|
104
|
+
const opts = { step: envelope.step, traceId, body };
|
|
105
|
+
if (status === 429) return new RateLimitedError(status, code, opts);
|
|
106
|
+
const Cls = CODE_TO_CLASS[code] ?? DoehApiError;
|
|
107
|
+
return new Cls(status, code, opts);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The retry predicate, faithful to the golden client: retry ONLY transport
|
|
112
|
+
* failures and HTTP 429. Every other status (incl. 4xx validation and 5xx) is
|
|
113
|
+
* terminal and surfaces immediately.
|
|
114
|
+
*/
|
|
115
|
+
export function isRetryable(err: unknown): boolean {
|
|
116
|
+
if (err instanceof DoehTransportError) return true;
|
|
117
|
+
if (err instanceof RateLimitedError) return true;
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Idempotency keys.
|
|
3
|
+
*
|
|
4
|
+
* The single most important mobile invariant: a create's idempotency key is
|
|
5
|
+
* minted ONCE — when the mutation is first created — and reused on every
|
|
6
|
+
* attempt, across process restarts and network boundaries. Regenerating a key
|
|
7
|
+
* per attempt produces duplicate orders the instant a retry happens. The
|
|
8
|
+
* OfflineQueue enforces this; callers doing manual retries must do the same.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** A UUID source. Defaults to the platform crypto; injectable for RN/tests. */
|
|
12
|
+
export type UuidFn = () => string;
|
|
13
|
+
|
|
14
|
+
function platformUuid(): string {
|
|
15
|
+
const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;
|
|
16
|
+
if (c?.randomUUID) return c.randomUUID();
|
|
17
|
+
// Last-resort fallback (e.g. older RN without a crypto polyfill). Not
|
|
18
|
+
// cryptographically strong, but collision-safe enough for an idempotency key.
|
|
19
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (ch) => {
|
|
20
|
+
const r = (Math.random() * 16) | 0;
|
|
21
|
+
const v = ch === "x" ? r : (r & 0x3) | 0x8;
|
|
22
|
+
return v.toString(16);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Generate an idempotency key. The optional prefix makes server logs readable
|
|
28
|
+
* (e.g. "delivery"). Stays within the 200-char header limit.
|
|
29
|
+
*/
|
|
30
|
+
export function generateIdempotencyKey(prefix?: string, uuid: UuidFn = platformUuid): string {
|
|
31
|
+
const id = uuid();
|
|
32
|
+
return prefix ? `${prefix}-${id}` : id;
|
|
33
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @beyondplusmm/doehpos-sdk — official TypeScript SDK for the Doeh POS public API.
|
|
3
|
+
*
|
|
4
|
+
* A typed port of the validated golden client. The stable surface is `delivery`;
|
|
5
|
+
* kitchen/loyalty/marketplace/rider are @experimental until exercised by the
|
|
6
|
+
* reference app.
|
|
7
|
+
*/
|
|
8
|
+
export { DoehClient } from "./client.js";
|
|
9
|
+
export type { DoehClientOptions } from "./client.js";
|
|
10
|
+
|
|
11
|
+
export { BASE_URLS, SDK_VERSION, type Environment } from "./config.js";
|
|
12
|
+
|
|
13
|
+
export { generateIdempotencyKey, type UuidFn } from "./idempotency.js";
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
OfflineQueue,
|
|
17
|
+
MemoryStorage,
|
|
18
|
+
type QueueStorage,
|
|
19
|
+
type QueuedMutation,
|
|
20
|
+
type DeadLetter,
|
|
21
|
+
type FlushResult,
|
|
22
|
+
} from "./queue.js";
|
|
23
|
+
|
|
24
|
+
export type {
|
|
25
|
+
Currency,
|
|
26
|
+
OrderStatus,
|
|
27
|
+
OrderCreate,
|
|
28
|
+
Order,
|
|
29
|
+
OrderResponse,
|
|
30
|
+
ErrorBody,
|
|
31
|
+
CallOptions,
|
|
32
|
+
} from "./types.js";
|
|
33
|
+
|
|
34
|
+
// Error ABI — consumers catch these classes, never parse `code` strings.
|
|
35
|
+
export {
|
|
36
|
+
DoehError,
|
|
37
|
+
DoehTransportError,
|
|
38
|
+
DoehApiError,
|
|
39
|
+
ApiKeyInvalidError,
|
|
40
|
+
ApiKeyExpiredError,
|
|
41
|
+
ApiKeyRevokedError,
|
|
42
|
+
EnvMismatchError,
|
|
43
|
+
ScopeDeniedError,
|
|
44
|
+
TransportDisabledError,
|
|
45
|
+
OrderNotFoundError,
|
|
46
|
+
ReplayError,
|
|
47
|
+
InvalidAmountError,
|
|
48
|
+
UnsupportedCurrencyError,
|
|
49
|
+
BadBodyError,
|
|
50
|
+
RateLimitedError,
|
|
51
|
+
isRetryable,
|
|
52
|
+
mapApiError,
|
|
53
|
+
} from "./errors.js";
|
|
54
|
+
|
|
55
|
+
// Module classes + their types (handy for typing app code).
|
|
56
|
+
export { DeliveryModule } from "./modules/delivery.js";
|
|
57
|
+
export { KitchenModule, type TicketCreate, type TicketResponse } from "./modules/experimental/kitchen.js";
|
|
58
|
+
export { LoyaltyModule, type EarnInput, type AccountResponse } from "./modules/experimental/loyalty.js";
|
|
59
|
+
export {
|
|
60
|
+
MarketplaceModule,
|
|
61
|
+
type ListingCreate,
|
|
62
|
+
type ListingResponse,
|
|
63
|
+
} from "./modules/experimental/marketplace.js";
|
|
64
|
+
export { RiderModule, type JobCreate, type JobResponse } from "./modules/experimental/rider.js";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delivery — the stable, reference-app-exercised vertical.
|
|
3
|
+
*
|
|
4
|
+
* POST /v1/delivery/orders create (idempotent with an Idempotency-Key)
|
|
5
|
+
* GET /v1/delivery/orders/{id} read back
|
|
6
|
+
*
|
|
7
|
+
* Scope is derived from the key server-side; there is no shop/branch argument.
|
|
8
|
+
*/
|
|
9
|
+
import type { Transport } from "../transport.js";
|
|
10
|
+
import type { CallOptions, OrderCreate, OrderResponse } from "../types.js";
|
|
11
|
+
import { generateIdempotencyKey } from "../idempotency.js";
|
|
12
|
+
|
|
13
|
+
/** Path ids must match this server-side pattern; we fail fast client-side. */
|
|
14
|
+
const PATH_ID = /^[A-Za-z0-9_]+$/;
|
|
15
|
+
|
|
16
|
+
export class DeliveryModule {
|
|
17
|
+
constructor(private readonly transport: Transport) {}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create a delivery order. Returns 201 on first write, or 200 with
|
|
21
|
+
* `idempotent: true` when an Idempotency-Key replays an existing order.
|
|
22
|
+
*
|
|
23
|
+
* If you do not pass `idempotencyKey`, the SDK mints one for THIS call only —
|
|
24
|
+
* which is not retry-safe across a process restart. For offline-safe creates,
|
|
25
|
+
* mint and persist the key yourself (or use OfflineQueue).
|
|
26
|
+
*/
|
|
27
|
+
async create(input: OrderCreate, opts: CallOptions = {}): Promise<OrderResponse> {
|
|
28
|
+
if (!Number.isInteger(input.amount_minor) || input.amount_minor < 1) {
|
|
29
|
+
// Mirror the server's EDGE_INVALID_AMOUNT rule so obvious mistakes fail
|
|
30
|
+
// locally without burning a rate-limited request. Server remains the SoT.
|
|
31
|
+
throw new RangeError("amount_minor must be an integer >= 1 (minor units)");
|
|
32
|
+
}
|
|
33
|
+
const { body } = await this.transport.request<OrderResponse>({
|
|
34
|
+
method: "POST",
|
|
35
|
+
path: "/v1/delivery/orders",
|
|
36
|
+
body: input,
|
|
37
|
+
idempotencyKey: opts.idempotencyKey ?? generateIdempotencyKey("delivery"),
|
|
38
|
+
traceId: opts.traceId,
|
|
39
|
+
signal: opts.signal,
|
|
40
|
+
});
|
|
41
|
+
return body;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Read a delivery order by id. */
|
|
45
|
+
async get(id: string, opts: CallOptions = {}): Promise<OrderResponse> {
|
|
46
|
+
if (!PATH_ID.test(id)) {
|
|
47
|
+
throw new RangeError(`invalid order id ${JSON.stringify(id)} (must match ${PATH_ID})`);
|
|
48
|
+
}
|
|
49
|
+
const { body } = await this.transport.request<OrderResponse>({
|
|
50
|
+
method: "GET",
|
|
51
|
+
path: `/v1/delivery/orders/${id}`,
|
|
52
|
+
traceId: opts.traceId,
|
|
53
|
+
signal: opts.signal,
|
|
54
|
+
});
|
|
55
|
+
return body;
|
|
56
|
+
}
|
|
57
|
+
}
|