@laranex/myanmar-payments 0.0.0-stage → 4.0.0-dev.1

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 (110) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/LICENSE.md +21 -0
  3. package/README.md +10 -2
  4. package/dist/cjs/aya-pay/aya-pay.d.ts +122 -0
  5. package/dist/cjs/aya-pay/aya-pay.js +301 -0
  6. package/dist/cjs/aya-pay/index.d.ts +1 -0
  7. package/dist/cjs/aya-pay/index.js +8 -0
  8. package/dist/cjs/core/amount.d.ts +57 -0
  9. package/dist/cjs/core/amount.js +107 -0
  10. package/dist/cjs/core/cache.d.ts +26 -0
  11. package/dist/cjs/core/cache.js +35 -0
  12. package/dist/cjs/core/callback.d.ts +180 -0
  13. package/dist/cjs/core/callback.js +304 -0
  14. package/dist/cjs/core/crypto.d.ts +19 -0
  15. package/dist/cjs/core/crypto.js +50 -0
  16. package/dist/cjs/core/env.d.ts +21 -0
  17. package/dist/cjs/core/env.js +54 -0
  18. package/dist/cjs/core/errors.d.ts +56 -0
  19. package/dist/cjs/core/errors.js +78 -0
  20. package/dist/cjs/core/http.d.ts +81 -0
  21. package/dist/cjs/core/http.js +128 -0
  22. package/dist/cjs/core/index.d.ts +8 -0
  23. package/dist/cjs/core/index.js +30 -0
  24. package/dist/cjs/core/json.d.ts +44 -0
  25. package/dist/cjs/core/json.js +254 -0
  26. package/dist/cjs/core/results.d.ts +122 -0
  27. package/dist/cjs/core/results.js +147 -0
  28. package/dist/cjs/core/status.d.ts +50 -0
  29. package/dist/cjs/core/status.js +60 -0
  30. package/dist/cjs/core/validate.d.ts +46 -0
  31. package/dist/cjs/core/validate.js +148 -0
  32. package/dist/cjs/core/values.d.ts +16 -0
  33. package/dist/cjs/core/values.js +45 -0
  34. package/dist/cjs/cyber-source/cyber-source.d.ts +95 -0
  35. package/dist/cjs/cyber-source/cyber-source.js +188 -0
  36. package/dist/cjs/cyber-source/index.d.ts +1 -0
  37. package/dist/cjs/cyber-source/index.js +7 -0
  38. package/dist/cjs/index.d.ts +7 -0
  39. package/dist/cjs/index.js +25 -0
  40. package/dist/cjs/kbz-pay/index.d.ts +2 -0
  41. package/dist/cjs/kbz-pay/index.js +8 -0
  42. package/dist/cjs/kbz-pay/kbz-pay.d.ts +102 -0
  43. package/dist/cjs/kbz-pay/kbz-pay.js +266 -0
  44. package/dist/cjs/kbz-pay/signer.d.ts +15 -0
  45. package/dist/cjs/kbz-pay/signer.js +36 -0
  46. package/dist/cjs/myanmar-payments.d.ts +50 -0
  47. package/dist/cjs/myanmar-payments.js +73 -0
  48. package/dist/cjs/package.json +3 -0
  49. package/dist/cjs/wave-money/index.d.ts +1 -0
  50. package/dist/cjs/wave-money/index.js +6 -0
  51. package/dist/cjs/wave-money/wave-money.d.ts +111 -0
  52. package/dist/cjs/wave-money/wave-money.js +251 -0
  53. package/dist/cjs/yoma-mmqr/index.d.ts +1 -0
  54. package/dist/cjs/yoma-mmqr/index.js +6 -0
  55. package/dist/cjs/yoma-mmqr/yoma-mmqr.d.ts +105 -0
  56. package/dist/cjs/yoma-mmqr/yoma-mmqr.js +233 -0
  57. package/dist/esm/aya-pay/aya-pay.d.ts +122 -0
  58. package/dist/esm/aya-pay/aya-pay.js +296 -0
  59. package/dist/esm/aya-pay/index.d.ts +1 -0
  60. package/dist/esm/aya-pay/index.js +1 -0
  61. package/dist/esm/core/amount.d.ts +57 -0
  62. package/dist/esm/core/amount.js +103 -0
  63. package/dist/esm/core/cache.d.ts +26 -0
  64. package/dist/esm/core/cache.js +31 -0
  65. package/dist/esm/core/callback.d.ts +180 -0
  66. package/dist/esm/core/callback.js +294 -0
  67. package/dist/esm/core/crypto.d.ts +19 -0
  68. package/dist/esm/core/crypto.js +41 -0
  69. package/dist/esm/core/env.d.ts +21 -0
  70. package/dist/esm/core/env.js +45 -0
  71. package/dist/esm/core/errors.d.ts +56 -0
  72. package/dist/esm/core/errors.js +70 -0
  73. package/dist/esm/core/http.d.ts +81 -0
  74. package/dist/esm/core/http.js +121 -0
  75. package/dist/esm/core/index.d.ts +8 -0
  76. package/dist/esm/core/index.js +7 -0
  77. package/dist/esm/core/json.d.ts +44 -0
  78. package/dist/esm/core/json.js +244 -0
  79. package/dist/esm/core/results.d.ts +122 -0
  80. package/dist/esm/core/results.js +139 -0
  81. package/dist/esm/core/status.d.ts +50 -0
  82. package/dist/esm/core/status.js +56 -0
  83. package/dist/esm/core/validate.d.ts +46 -0
  84. package/dist/esm/core/validate.js +142 -0
  85. package/dist/esm/core/values.d.ts +16 -0
  86. package/dist/esm/core/values.js +38 -0
  87. package/dist/esm/cyber-source/cyber-source.d.ts +95 -0
  88. package/dist/esm/cyber-source/cyber-source.js +184 -0
  89. package/dist/esm/cyber-source/index.d.ts +1 -0
  90. package/dist/esm/cyber-source/index.js +1 -0
  91. package/dist/esm/index.d.ts +7 -0
  92. package/dist/esm/index.js +7 -0
  93. package/dist/esm/kbz-pay/index.d.ts +2 -0
  94. package/dist/esm/kbz-pay/index.js +2 -0
  95. package/dist/esm/kbz-pay/kbz-pay.d.ts +102 -0
  96. package/dist/esm/kbz-pay/kbz-pay.js +262 -0
  97. package/dist/esm/kbz-pay/signer.d.ts +15 -0
  98. package/dist/esm/kbz-pay/signer.js +32 -0
  99. package/dist/esm/myanmar-payments.d.ts +50 -0
  100. package/dist/esm/myanmar-payments.js +69 -0
  101. package/dist/esm/wave-money/index.d.ts +1 -0
  102. package/dist/esm/wave-money/index.js +1 -0
  103. package/dist/esm/wave-money/wave-money.d.ts +111 -0
  104. package/dist/esm/wave-money/wave-money.js +246 -0
  105. package/dist/esm/yoma-mmqr/index.d.ts +1 -0
  106. package/dist/esm/yoma-mmqr/index.js +1 -0
  107. package/dist/esm/yoma-mmqr/yoma-mmqr.d.ts +105 -0
  108. package/dist/esm/yoma-mmqr/yoma-mmqr.js +229 -0
  109. package/package.json +151 -4
  110. package/skills/node-myanmar-payments/SKILL.md +109 -0
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Base class of every error thrown by this package.
3
+ */
4
+ export class PaymentError extends Error {
5
+ constructor(message, options) {
6
+ super(message, options);
7
+ this.name = new.target.name;
8
+ }
9
+ }
10
+ /**
11
+ * Thrown when payment data has values the gateway would reject. Thrown before any request is sent.
12
+ */
13
+ export class InvalidPaymentDataError extends PaymentError {
14
+ /** Field name to error message, e.g. `{ amount: 'The amount field is required.' }`. */
15
+ errors;
16
+ constructor(errors) {
17
+ const messages = Object.keys(errors)
18
+ .sort()
19
+ .map((field) => errors[field]);
20
+ super(`Invalid payment data: ${messages.join(' ')}`);
21
+ this.errors = Object.freeze({ ...errors });
22
+ }
23
+ }
24
+ /**
25
+ * Thrown when a gateway rejects a request or answers with an error, including errors sent with
26
+ * HTTP 200, or when it cannot be reached (the network error is the `cause`).
27
+ */
28
+ export class ApiError extends PaymentError {
29
+ /** The gateway's own error code, e.g. `ORDER_ID_USED` or `09`. */
30
+ gatewayCode;
31
+ /** The gateway's own error message. */
32
+ gatewayMessage;
33
+ /** The HTTP status of the response, or `0` when no response was received. */
34
+ httpStatus;
35
+ /** The decoded response body. */
36
+ raw;
37
+ constructor(message, details = {}) {
38
+ super(message, details.cause === undefined ? undefined : { cause: details.cause });
39
+ this.gatewayCode = details.gatewayCode;
40
+ this.gatewayMessage = details.gatewayMessage;
41
+ this.httpStatus = details.httpStatus ?? 0;
42
+ this.raw = details.raw ?? {};
43
+ }
44
+ }
45
+ /**
46
+ * Thrown when a callback, return redirect or gateway response fails signature verification.
47
+ * Never act on its payload.
48
+ */
49
+ export class SignatureVerificationError extends PaymentError {
50
+ /** The unverified payload, for logging only. */
51
+ raw;
52
+ constructor(message, raw = {}) {
53
+ super(message);
54
+ this.raw = raw;
55
+ }
56
+ }
57
+ /**
58
+ * Thrown when a gateway is missing a credential or setting it needs.
59
+ */
60
+ export class ConfigurationError extends PaymentError {
61
+ /** The gateway, e.g. `kbz_pay`. */
62
+ gateway;
63
+ /** The missing setting, e.g. `app_key`. */
64
+ key;
65
+ constructor(gateway, key) {
66
+ super(`The ${gateway} configuration is missing [${key}].`);
67
+ this.gateway = gateway;
68
+ this.key = key;
69
+ }
70
+ }
@@ -0,0 +1,81 @@
1
+ import { type LosslessObject } from './json.js';
2
+ /** A request a gateway sends. Gateways only send `POST`s. */
3
+ export interface HttpRequest {
4
+ method: 'POST';
5
+ url: string;
6
+ headers: Record<string, string>;
7
+ body: string;
8
+ /** Aborts the request, e.g. when the caller's own request is canceled. */
9
+ signal?: AbortSignal | undefined;
10
+ }
11
+ /** A gateway's response: its status and raw body text. */
12
+ export interface HttpResponse {
13
+ status: number;
14
+ body: string;
15
+ }
16
+ /**
17
+ * Sends the gateway's HTTP requests. Implement it to add proxies, tracing, retries or a test
18
+ * double; the default is {@link FetchHttpClient}. Throw on network failures; any non-2xx status
19
+ * is a normal response.
20
+ */
21
+ export interface HttpClient {
22
+ send(request: HttpRequest): Promise<HttpResponse>;
23
+ }
24
+ /** The `fetch` function {@link FetchHttpClient} uses, e.g. `globalThis.fetch` or `undici.fetch`. */
25
+ export type FetchFunction = (input: string, init: RequestInit) => Promise<Response>;
26
+ /** Options of {@link FetchHttpClient}. */
27
+ export interface FetchHttpClientOptions {
28
+ /** The `fetch` to call; defaults to the global `fetch`. */
29
+ fetch?: FetchFunction | undefined;
30
+ /** Milliseconds before a request is aborted; defaults to 30000. `0` disables the timeout. */
31
+ timeoutMs?: number | undefined;
32
+ }
33
+ /** The default request timeout: 30 seconds. */
34
+ export declare const DEFAULT_TIMEOUT_MS = 30000;
35
+ /**
36
+ * The default {@link HttpClient}: the global `fetch` (or the one you pass) with a 30 second
37
+ * timeout.
38
+ */
39
+ export declare class FetchHttpClient implements HttpClient {
40
+ private readonly fetchFunction;
41
+ private readonly timeoutMs;
42
+ constructor(options?: FetchHttpClientOptions);
43
+ send(request: HttpRequest): Promise<HttpResponse>;
44
+ }
45
+ /** Options every gateway that calls an API accepts. */
46
+ export interface GatewayOptions {
47
+ /** Sends the requests. Takes precedence over `fetch` and `timeoutMs`. */
48
+ httpClient?: HttpClient | undefined;
49
+ /** The `fetch` the default client calls, e.g. a mocked one in tests. */
50
+ fetch?: FetchFunction | undefined;
51
+ /** Milliseconds before the default client aborts a request; defaults to 30000. */
52
+ timeoutMs?: number | undefined;
53
+ }
54
+ /** Per-call options of every network method. */
55
+ export interface RequestOptions {
56
+ /** Aborts the gateway call, e.g. with `AbortSignal.timeout(5000)` or the incoming request's signal. */
57
+ signal?: AbortSignal | undefined;
58
+ }
59
+ /** @internal */
60
+ export declare function httpClientFrom(options?: GatewayOptions): HttpClient;
61
+ /** A decoded gateway response. @internal */
62
+ export declare class GatewayResponse {
63
+ readonly status: number;
64
+ readonly body: string;
65
+ constructor(status: number, body: string);
66
+ successful(): boolean;
67
+ /** The body as a JSON object with exact numbers, or `{}` when it is not one. */
68
+ json(): LosslessObject;
69
+ }
70
+ /**
71
+ * Posts the JSON and form requests the gateways use.
72
+ *
73
+ * @internal
74
+ */
75
+ export declare class Transport {
76
+ private readonly client;
77
+ constructor(client: HttpClient);
78
+ postJson(url: string, data: unknown, headers?: Record<string, string>, signal?: AbortSignal): Promise<GatewayResponse>;
79
+ postForm(url: string, data: Record<string, string>, headers?: Record<string, string>, signal?: AbortSignal): Promise<GatewayResponse>;
80
+ private post;
81
+ }
@@ -0,0 +1,121 @@
1
+ import { ApiError, PaymentError } from './errors.js';
2
+ import { parseJsonObject } from './json.js';
3
+ /** The default request timeout: 30 seconds. */
4
+ export const DEFAULT_TIMEOUT_MS = 30_000;
5
+ /**
6
+ * The default {@link HttpClient}: the global `fetch` (or the one you pass) with a 30 second
7
+ * timeout.
8
+ */
9
+ export class FetchHttpClient {
10
+ fetchFunction;
11
+ timeoutMs;
12
+ constructor(options = {}) {
13
+ this.fetchFunction = options.fetch;
14
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
15
+ }
16
+ async send(request) {
17
+ const fetchFunction = this.fetchFunction ?? globalThis.fetch;
18
+ if (typeof fetchFunction !== 'function') {
19
+ throw new TypeError('No fetch implementation is available; pass one with the fetch option.');
20
+ }
21
+ const response = await fetchFunction(request.url, {
22
+ method: request.method,
23
+ headers: request.headers,
24
+ body: request.body,
25
+ signal: combineSignals(request.signal, this.timeoutMs) ?? null,
26
+ });
27
+ let body;
28
+ try {
29
+ body = await response.text();
30
+ }
31
+ catch (error) {
32
+ throw new ApiError(`Could not read response from ${request.url}: ${describe(error)}`, {
33
+ httpStatus: response.status,
34
+ cause: error,
35
+ });
36
+ }
37
+ return { status: response.status, body };
38
+ }
39
+ }
40
+ /** @internal */
41
+ export function httpClientFrom(options = {}) {
42
+ return (options.httpClient ??
43
+ new FetchHttpClient({ fetch: options.fetch, timeoutMs: options.timeoutMs }));
44
+ }
45
+ /** A decoded gateway response. @internal */
46
+ export class GatewayResponse {
47
+ status;
48
+ body;
49
+ constructor(status, body) {
50
+ this.status = status;
51
+ this.body = body;
52
+ }
53
+ successful() {
54
+ return this.status >= 200 && this.status < 300;
55
+ }
56
+ /** The body as a JSON object with exact numbers, or `{}` when it is not one. */
57
+ json() {
58
+ return parseJsonObject(this.body) ?? {};
59
+ }
60
+ }
61
+ /**
62
+ * Posts the JSON and form requests the gateways use.
63
+ *
64
+ * @internal
65
+ */
66
+ export class Transport {
67
+ client;
68
+ constructor(client) {
69
+ this.client = client;
70
+ }
71
+ postJson(url, data, headers = {}, signal) {
72
+ return this.post(url, JSON.stringify(data), { 'Content-Type': 'application/json', Accept: 'application/json', ...headers }, signal);
73
+ }
74
+ postForm(url, data, headers = {}, signal) {
75
+ return this.post(url, new URLSearchParams(data).toString(), {
76
+ 'Content-Type': 'application/x-www-form-urlencoded',
77
+ Accept: 'application/json',
78
+ ...headers,
79
+ }, signal);
80
+ }
81
+ async post(url, body, headers, signal) {
82
+ let response;
83
+ try {
84
+ response = await this.client.send({ method: 'POST', url, headers, body, signal });
85
+ }
86
+ catch (error) {
87
+ if (error instanceof PaymentError) {
88
+ throw error;
89
+ }
90
+ throw new ApiError(`Could not reach ${url}: ${describe(error)}`, { cause: error });
91
+ }
92
+ return new GatewayResponse(response.status, response.body);
93
+ }
94
+ }
95
+ function describe(error) {
96
+ return error instanceof Error ? error.message : String(error);
97
+ }
98
+ function combineSignals(signal, timeoutMs) {
99
+ const signals = [];
100
+ if (signal !== undefined) {
101
+ signals.push(signal);
102
+ }
103
+ if (timeoutMs > 0) {
104
+ signals.push(AbortSignal.timeout(timeoutMs));
105
+ }
106
+ if (signals.length <= 1) {
107
+ return signals[0];
108
+ }
109
+ // AbortSignal.any() needs Node 20.3; this works on every supported version.
110
+ const controller = new AbortController();
111
+ for (const source of signals) {
112
+ if (source.aborted) {
113
+ controller.abort(source.reason);
114
+ break;
115
+ }
116
+ source.addEventListener('abort', () => {
117
+ controller.abort(source.reason);
118
+ }, { once: true });
119
+ }
120
+ return controller.signal;
121
+ }
@@ -0,0 +1,8 @@
1
+ export { Amount, type AmountInput } from './amount.js';
2
+ export { MemoryTokenCache, type TokenCache } from './cache.js';
3
+ export { Acknowledgement, CallbackRequest, PaymentCallback, PaymentStatusResult, type AcknowledgementInit, type BodyInput, type CallbackRequestInit, type HeadersInput, type NodeRequestLike, type PaymentCallbackInit, type PaymentStatusResultInit, type QueryInput, type ServerResponseLike, type WebRequestLike, } from './callback.js';
4
+ export type { EnvSource } from './env.js';
5
+ export { ApiError, ConfigurationError, InvalidPaymentDataError, PaymentError, SignatureVerificationError, } from './errors.js';
6
+ export { DEFAULT_TIMEOUT_MS, FetchHttpClient, type FetchFunction, type FetchHttpClientOptions, type GatewayOptions, type HttpClient, type HttpRequest, type HttpResponse, type RequestOptions, } from './http.js';
7
+ export { AppPayment, FormPayment, QrPayment, RedirectPayment, type FormField, type PaymentResult, } from './results.js';
8
+ export { PaymentFlow, PaymentStatus, resolveStatus } from './status.js';
@@ -0,0 +1,7 @@
1
+ export { Amount } from './amount.js';
2
+ export { MemoryTokenCache } from './cache.js';
3
+ export { Acknowledgement, CallbackRequest, PaymentCallback, PaymentStatusResult, } from './callback.js';
4
+ export { ApiError, ConfigurationError, InvalidPaymentDataError, PaymentError, SignatureVerificationError, } from './errors.js';
5
+ export { DEFAULT_TIMEOUT_MS, FetchHttpClient, } from './http.js';
6
+ export { AppPayment, FormPayment, QrPayment, RedirectPayment, } from './results.js';
7
+ export { PaymentFlow, PaymentStatus, resolveStatus } from './status.js';
@@ -0,0 +1,44 @@
1
+ /**
2
+ * A JSON number kept as the exact text the gateway sent, so `1000.50` stays `1000.50` and large
3
+ * integers keep every digit. Only used internally while verifying signatures and reading values.
4
+ *
5
+ * @internal
6
+ */
7
+ export declare class JsonNumber {
8
+ readonly text: string;
9
+ constructor(text: string);
10
+ }
11
+ /** A value decoded by {@link parseJson}: numbers are {@link JsonNumber}s. @internal */
12
+ export type LosslessValue = string | boolean | null | JsonNumber | LosslessValue[] | {
13
+ [key: string]: LosslessValue;
14
+ };
15
+ /** A decoded JSON object whose numbers are {@link JsonNumber}s. @internal */
16
+ export type LosslessObject = {
17
+ [key: string]: LosslessValue;
18
+ };
19
+ /**
20
+ * Parses strict JSON (RFC 8259) without passing numbers through a float. Returns `undefined` when
21
+ * the text is not valid JSON.
22
+ *
23
+ * @internal
24
+ */
25
+ export declare function parseJson(text: string): LosslessValue | undefined;
26
+ /**
27
+ * Parses JSON and returns it only when it is an object (not an array, string or null).
28
+ *
29
+ * @internal
30
+ */
31
+ export declare function parseJsonObject(text: string): LosslessObject | undefined;
32
+ /** @internal */
33
+ export declare function isLosslessObject(value: unknown): value is LosslessObject;
34
+ /**
35
+ * Converts a lossless value to plain JavaScript values (numbers become `number`), for the `raw`
36
+ * fields of results.
37
+ *
38
+ * @internal
39
+ */
40
+ export declare function toPlain(value: LosslessValue): unknown;
41
+ /** @internal */
42
+ export declare function toPlainObject(value: LosslessObject): Record<string, unknown>;
43
+ /** Sets a key without letting `__proto__` change the object's prototype. @internal */
44
+ export declare function setKey(target: Record<string, unknown>, key: string, value: unknown): void;
@@ -0,0 +1,244 @@
1
+ /**
2
+ * A JSON number kept as the exact text the gateway sent, so `1000.50` stays `1000.50` and large
3
+ * integers keep every digit. Only used internally while verifying signatures and reading values.
4
+ *
5
+ * @internal
6
+ */
7
+ export class JsonNumber {
8
+ text;
9
+ constructor(text) {
10
+ this.text = text;
11
+ }
12
+ }
13
+ const NUMBER = /-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/y;
14
+ /**
15
+ * Parses strict JSON (RFC 8259) without passing numbers through a float. Returns `undefined` when
16
+ * the text is not valid JSON.
17
+ *
18
+ * @internal
19
+ */
20
+ export function parseJson(text) {
21
+ const parser = new Parser(text);
22
+ try {
23
+ parser.skipWhitespace();
24
+ const value = parser.value();
25
+ parser.skipWhitespace();
26
+ return parser.done() ? value : undefined;
27
+ }
28
+ catch {
29
+ return undefined;
30
+ }
31
+ }
32
+ /**
33
+ * Parses JSON and returns it only when it is an object (not an array, string or null).
34
+ *
35
+ * @internal
36
+ */
37
+ export function parseJsonObject(text) {
38
+ const value = parseJson(text);
39
+ return isLosslessObject(value) ? value : undefined;
40
+ }
41
+ /** @internal */
42
+ export function isLosslessObject(value) {
43
+ return (typeof value === 'object' &&
44
+ value !== null &&
45
+ !Array.isArray(value) &&
46
+ !(value instanceof JsonNumber));
47
+ }
48
+ /**
49
+ * Converts a lossless value to plain JavaScript values (numbers become `number`), for the `raw`
50
+ * fields of results.
51
+ *
52
+ * @internal
53
+ */
54
+ export function toPlain(value) {
55
+ if (value instanceof JsonNumber) {
56
+ return Number(value.text);
57
+ }
58
+ if (Array.isArray(value)) {
59
+ return value.map(toPlain);
60
+ }
61
+ if (value !== null && typeof value === 'object') {
62
+ return toPlainObject(value);
63
+ }
64
+ return value;
65
+ }
66
+ /** @internal */
67
+ export function toPlainObject(value) {
68
+ const result = {};
69
+ for (const key of Object.keys(value)) {
70
+ setKey(result, key, toPlain(value[key]));
71
+ }
72
+ return result;
73
+ }
74
+ /** Sets a key without letting `__proto__` change the object's prototype. @internal */
75
+ export function setKey(target, key, value) {
76
+ Object.defineProperty(target, key, {
77
+ value,
78
+ enumerable: true,
79
+ writable: true,
80
+ configurable: true,
81
+ });
82
+ }
83
+ class Parser {
84
+ text;
85
+ index = 0;
86
+ constructor(text) {
87
+ this.text = text;
88
+ }
89
+ done() {
90
+ return this.index === this.text.length;
91
+ }
92
+ skipWhitespace() {
93
+ while (this.index < this.text.length) {
94
+ const char = this.text[this.index];
95
+ if (char !== ' ' && char !== '\t' && char !== '\n' && char !== '\r') {
96
+ return;
97
+ }
98
+ this.index++;
99
+ }
100
+ }
101
+ value() {
102
+ const char = this.text[this.index];
103
+ switch (char) {
104
+ case '{':
105
+ return this.object();
106
+ case '[':
107
+ return this.array();
108
+ case '"':
109
+ return this.string();
110
+ case 't':
111
+ return this.literal('true', true);
112
+ case 'f':
113
+ return this.literal('false', false);
114
+ case 'n':
115
+ return this.literal('null', null);
116
+ default:
117
+ return this.number();
118
+ }
119
+ }
120
+ object() {
121
+ this.index++;
122
+ const result = {};
123
+ this.skipWhitespace();
124
+ if (this.text[this.index] === '}') {
125
+ this.index++;
126
+ return result;
127
+ }
128
+ for (;;) {
129
+ this.skipWhitespace();
130
+ if (this.text[this.index] !== '"') {
131
+ throw new SyntaxError('Expected a string key');
132
+ }
133
+ const key = this.string();
134
+ this.skipWhitespace();
135
+ this.expect(':');
136
+ this.skipWhitespace();
137
+ setKey(result, key, this.value());
138
+ this.skipWhitespace();
139
+ if (this.text[this.index] === ',') {
140
+ this.index++;
141
+ continue;
142
+ }
143
+ this.expect('}');
144
+ return result;
145
+ }
146
+ }
147
+ array() {
148
+ this.index++;
149
+ const result = [];
150
+ this.skipWhitespace();
151
+ if (this.text[this.index] === ']') {
152
+ this.index++;
153
+ return result;
154
+ }
155
+ for (;;) {
156
+ this.skipWhitespace();
157
+ result.push(this.value());
158
+ this.skipWhitespace();
159
+ if (this.text[this.index] === ',') {
160
+ this.index++;
161
+ continue;
162
+ }
163
+ this.expect(']');
164
+ return result;
165
+ }
166
+ }
167
+ string() {
168
+ this.index++;
169
+ let result = '';
170
+ for (;;) {
171
+ if (this.index >= this.text.length) {
172
+ throw new SyntaxError('Unterminated string');
173
+ }
174
+ const char = this.text[this.index];
175
+ if (char === '"') {
176
+ this.index++;
177
+ return result;
178
+ }
179
+ if (char === '\\') {
180
+ result += this.escape();
181
+ continue;
182
+ }
183
+ if (char < ' ') {
184
+ throw new SyntaxError('Control character in string');
185
+ }
186
+ result += char;
187
+ this.index++;
188
+ }
189
+ }
190
+ escape() {
191
+ const char = this.text[this.index + 1];
192
+ this.index += 2;
193
+ switch (char) {
194
+ case '"':
195
+ return '"';
196
+ case '\\':
197
+ return '\\';
198
+ case '/':
199
+ return '/';
200
+ case 'b':
201
+ return '\b';
202
+ case 'f':
203
+ return '\f';
204
+ case 'n':
205
+ return '\n';
206
+ case 'r':
207
+ return '\r';
208
+ case 't':
209
+ return '\t';
210
+ case 'u': {
211
+ const hex = this.text.slice(this.index, this.index + 4);
212
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
213
+ throw new SyntaxError('Invalid unicode escape');
214
+ }
215
+ this.index += 4;
216
+ return String.fromCharCode(parseInt(hex, 16));
217
+ }
218
+ default:
219
+ throw new SyntaxError('Invalid escape');
220
+ }
221
+ }
222
+ literal(word, value) {
223
+ if (!this.text.startsWith(word, this.index)) {
224
+ throw new SyntaxError(`Expected ${word}`);
225
+ }
226
+ this.index += word.length;
227
+ return value;
228
+ }
229
+ number() {
230
+ NUMBER.lastIndex = this.index;
231
+ const match = NUMBER.exec(this.text);
232
+ if (match === null) {
233
+ throw new SyntaxError('Unexpected token');
234
+ }
235
+ this.index += match[0].length;
236
+ return new JsonNumber(match[0]);
237
+ }
238
+ expect(char) {
239
+ if (this.text[this.index] !== char) {
240
+ throw new SyntaxError(`Expected ${char}`);
241
+ }
242
+ this.index++;
243
+ }
244
+ }