@rdlabo/workers-hono-kit 0.10.4 → 0.10.6
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/dist/cache/kv-cache.js +8 -9
- package/dist/db/database.js +2 -2
- package/dist/db/retry.js +9 -9
- package/dist/middleware/auth.js +9 -10
- package/dist/middleware/perf-log.js +6 -5
- package/dist/offline/index.d.ts +2 -0
- package/dist/offline/index.js +1 -0
- package/dist/offline/wire-compatibility.d.ts +75 -0
- package/dist/offline/wire-compatibility.js +94 -0
- package/dist/queue/consumer.js +19 -17
- package/dist/realtime/retry.js +8 -8
- package/dist/stripe/failure.js +15 -17
- package/dist/testing/db.js +5 -6
- package/dist/testing/workers-bindings.js +7 -11
- package/package.json +13 -1
package/dist/cache/kv-cache.js
CHANGED
|
@@ -121,14 +121,11 @@ export class KVCache {
|
|
|
121
121
|
if (!key) {
|
|
122
122
|
return undefined;
|
|
123
123
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
data = await this.#kv.get(key);
|
|
127
|
-
}
|
|
128
|
-
catch (error) {
|
|
124
|
+
const read = async () => this.#kv.get(key);
|
|
125
|
+
const data = await read().catch((error) => {
|
|
129
126
|
this.#reportError(error, { operation: 'read', table });
|
|
130
|
-
return
|
|
131
|
-
}
|
|
127
|
+
return null;
|
|
128
|
+
});
|
|
132
129
|
if (!data) {
|
|
133
130
|
return undefined;
|
|
134
131
|
}
|
|
@@ -179,7 +176,8 @@ export class KVCache {
|
|
|
179
176
|
return;
|
|
180
177
|
}
|
|
181
178
|
const ttl = Math.max(this.#minTtl, lifetime ?? this.#defaultLifetime);
|
|
182
|
-
|
|
179
|
+
const write = async () => this.#kv.put(key, payload, { expirationTtl: ttl });
|
|
180
|
+
await write().catch((error) => {
|
|
183
181
|
this.#reportError(error, { operation: 'write', table });
|
|
184
182
|
});
|
|
185
183
|
}
|
|
@@ -243,7 +241,8 @@ export class KVCache {
|
|
|
243
241
|
if (!key) {
|
|
244
242
|
return;
|
|
245
243
|
}
|
|
246
|
-
|
|
244
|
+
const remove = async () => this.#kv.delete(key);
|
|
245
|
+
await remove().catch((error) => {
|
|
247
246
|
this.#reportError(error, { operation: 'delete', table });
|
|
248
247
|
});
|
|
249
248
|
}
|
package/dist/db/database.js
CHANGED
|
@@ -70,8 +70,8 @@ export function createHyperdriveDatabase(options) {
|
|
|
70
70
|
return retryWhenDeadlock(() => dz.transaction(fn));
|
|
71
71
|
},
|
|
72
72
|
/** @deprecated Workers cleans up invocation-scoped connections automatically. */
|
|
73
|
-
dispose() {
|
|
74
|
-
return
|
|
73
|
+
async dispose() {
|
|
74
|
+
return;
|
|
75
75
|
},
|
|
76
76
|
};
|
|
77
77
|
}
|
package/dist/db/retry.js
CHANGED
|
@@ -27,17 +27,17 @@
|
|
|
27
27
|
*/
|
|
28
28
|
export async function retryWhenDeadlock(fn, retries = 3, delay = 100) {
|
|
29
29
|
for (let attempt = 0; attempt < retries; attempt++) {
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
const invoke = async () => fn();
|
|
31
|
+
const outcome = await invoke().then((value) => ({ ok: true, value }), (error) => ({ ok: false, error }));
|
|
32
|
+
if (outcome.ok) {
|
|
33
|
+
return outcome.value;
|
|
32
34
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
throw error;
|
|
35
|
+
const code = outcome.error.code;
|
|
36
|
+
if (code === 'ER_LOCK_DEADLOCK' && attempt < retries - 1) {
|
|
37
|
+
await new Promise((resolve) => setTimeout(resolve, delay * (attempt + 1)));
|
|
38
|
+
continue;
|
|
40
39
|
}
|
|
40
|
+
throw outcome.error;
|
|
41
41
|
}
|
|
42
42
|
// Unreachable: the loop returns on success and throws on the final failed attempt.
|
|
43
43
|
throw new Error('retryWhenDeadlock: exhausted retries');
|
package/dist/middleware/auth.js
CHANGED
|
@@ -43,7 +43,7 @@ export function createAuthMiddleware(options) {
|
|
|
43
43
|
return async (c, next) => {
|
|
44
44
|
let stage = 'token';
|
|
45
45
|
let tokenPresent = false;
|
|
46
|
-
|
|
46
|
+
const authenticate = async () => {
|
|
47
47
|
const token = c.req.header(tokenHeader) ?? '';
|
|
48
48
|
tokenPresent = token.trim().length > 0;
|
|
49
49
|
if (!tokenPresent && rejectMissingToken) {
|
|
@@ -57,24 +57,23 @@ export function createAuthMiddleware(options) {
|
|
|
57
57
|
const userId = resolveUserId ? await resolveUserId(verified, c, appInfo) : undefined;
|
|
58
58
|
stage = 'setContext';
|
|
59
59
|
setContext(c, { verified, appInfo, userId });
|
|
60
|
-
}
|
|
61
|
-
|
|
60
|
+
};
|
|
61
|
+
const outcome = await authenticate().then(() => ({ ok: true }), (error) => ({ ok: false, error }));
|
|
62
|
+
if (!outcome.ok) {
|
|
62
63
|
const details = { stage, tokenPresent };
|
|
63
64
|
if (reportFailure) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
catch (reportingError) {
|
|
65
|
+
const report = async () => reportFailure(outcome.error, c, details);
|
|
66
|
+
await report().catch((reportingError) => {
|
|
68
67
|
// Observability must never alter the authentication response.
|
|
69
68
|
console.error(reportingError);
|
|
70
|
-
}
|
|
69
|
+
});
|
|
71
70
|
}
|
|
72
71
|
else {
|
|
73
72
|
// Preserve the historical default for consumers that have not adopted classified reporting.
|
|
74
|
-
console.error(
|
|
73
|
+
console.error(outcome.error);
|
|
75
74
|
}
|
|
76
75
|
if (onFailure) {
|
|
77
|
-
return onFailure(
|
|
76
|
+
return onFailure(outcome.error, c, details);
|
|
78
77
|
}
|
|
79
78
|
throw new HTTPException(failureStatus, { message: failureMessage });
|
|
80
79
|
}
|
|
@@ -98,12 +98,13 @@ export function perfLog(options = {}) {
|
|
|
98
98
|
// In-code sampling thins Analytics Engine writes only; Workers Logs volume is controlled separately
|
|
99
99
|
// by the observability `head_sampling_rate`. Low-traffic Workers should leave `sampleRate` at 1.
|
|
100
100
|
if (sink && (rate >= 1 || Math.random() < rate)) {
|
|
101
|
+
const point = {
|
|
102
|
+
doubles: [tApp, cold ? 1 : 0, status],
|
|
103
|
+
blobs: [path, colo, method],
|
|
104
|
+
indexes: [analyticsIndex(path)],
|
|
105
|
+
};
|
|
101
106
|
try {
|
|
102
|
-
sink.writeDataPoint(
|
|
103
|
-
doubles: [tApp, cold ? 1 : 0, status],
|
|
104
|
-
blobs: [path, colo, method],
|
|
105
|
-
indexes: [analyticsIndex(path)],
|
|
106
|
-
});
|
|
107
|
+
sink.writeDataPoint(point);
|
|
107
108
|
}
|
|
108
109
|
catch (error) {
|
|
109
110
|
// Telemetry must never replace an otherwise successful application response with a 500.
|
package/dist/offline/index.d.ts
CHANGED
|
@@ -16,3 +16,5 @@ export { assertOfflineJournalCursorRetained, compactOfflineJournal, OfflineJourn
|
|
|
16
16
|
export type { CompactOfflineJournalOptions, OfflineJournalRetentionCandidate, OfflineJournalRetentionStore, OfflineJournalRetentionTransaction, } from './journal-retention.js';
|
|
17
17
|
export { assertOfflineJournalCoverage, runOfflineJournalMutation } from './journal-mutation.js';
|
|
18
18
|
export type { OfflineJournalMutationChange, OfflineJournalMutationStore, OfflineJournalMutationTransaction, } from './journal-mutation.js';
|
|
19
|
+
export { defineOfflineWireCompatibility, resolveOfflineWireCompatibility } from './wire-compatibility.js';
|
|
20
|
+
export type { OfflineWireAcceptedFingerprint, OfflineWireCompatibility, OfflineWireCompatibilityResolution, OfflineWireFingerprint, } from './wire-compatibility.js';
|
package/dist/offline/index.js
CHANGED
|
@@ -12,3 +12,4 @@ export { defineRestDbMethodConverter } from './rest-db-method-converter.js';
|
|
|
12
12
|
export { decodeOfflineSnapshotCursor, encodeOfflineSnapshotCursor } from './snapshot-cursor.js';
|
|
13
13
|
export { assertOfflineJournalCursorRetained, compactOfflineJournal, OfflineJournalRebaselineRequiredError, } from './journal-retention.js';
|
|
14
14
|
export { assertOfflineJournalCoverage, runOfflineJournalMutation } from './journal-mutation.js';
|
|
15
|
+
export { defineOfflineWireCompatibility, resolveOfflineWireCompatibility } from './wire-compatibility.js';
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exact offline wire protocol identity: integer version plus content hash.
|
|
3
|
+
*
|
|
4
|
+
* Products own how the hash is computed; this kit never materializes schema
|
|
5
|
+
* projections or storage. Resolution always requires an exact `{version,hash}` pair.
|
|
6
|
+
*/
|
|
7
|
+
export interface OfflineWireFingerprint {
|
|
8
|
+
/** Monotonic published protocol version. */
|
|
9
|
+
readonly version: number;
|
|
10
|
+
/** Content fingerprint for that published version. */
|
|
11
|
+
readonly hash: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A previously published fingerprint that remains accepted until an explicit expiry.
|
|
15
|
+
*
|
|
16
|
+
* Non-current entries must name the product adapter/projection that serves that wire
|
|
17
|
+
* shape. Silently allowlisting a hash alone is forbidden.
|
|
18
|
+
*/
|
|
19
|
+
export interface OfflineWireAcceptedFingerprint extends OfflineWireFingerprint {
|
|
20
|
+
/** Instant at which acceptance ends (exclusive); later clocks reject the fingerprint. */
|
|
21
|
+
readonly expiresAt: Date;
|
|
22
|
+
/** Product-owned adapter or projection identifier for this prior wire shape. */
|
|
23
|
+
readonly adapterId: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Validated current fingerprint plus optional accepted prior fingerprints.
|
|
27
|
+
*
|
|
28
|
+
* Produced only by {@link defineOfflineWireCompatibility}.
|
|
29
|
+
*/
|
|
30
|
+
export interface OfflineWireCompatibility {
|
|
31
|
+
/** The currently published protocol fingerprint. */
|
|
32
|
+
readonly current: OfflineWireFingerprint;
|
|
33
|
+
/** Prior fingerprints accepted until their explicit expiry. */
|
|
34
|
+
readonly accepted: readonly OfflineWireAcceptedFingerprint[];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Successful resolution of an incoming fingerprint against a compatibility table.
|
|
38
|
+
*
|
|
39
|
+
* Unmatched, expired, or inexact fingerprints resolve to `undefined` so products can map
|
|
40
|
+
* the miss to their own conflict response (for example HTTP 409).
|
|
41
|
+
*/
|
|
42
|
+
export type OfflineWireCompatibilityResolution = {
|
|
43
|
+
readonly kind: 'current';
|
|
44
|
+
readonly fingerprint: OfflineWireFingerprint;
|
|
45
|
+
} | {
|
|
46
|
+
readonly kind: 'accepted';
|
|
47
|
+
readonly fingerprint: OfflineWireFingerprint;
|
|
48
|
+
readonly adapterId: string;
|
|
49
|
+
readonly expiresAt: Date;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Validates a current fingerprint plus optional accepted prior fingerprints.
|
|
53
|
+
*
|
|
54
|
+
* Rejects duplicate versions, duplicate hashes, invalid fingerprints, invalid expiry
|
|
55
|
+
* instants, and accepted entries that omit a product adapter/projection identifier.
|
|
56
|
+
*
|
|
57
|
+
* @param options - Current fingerprint and optional accepted prior fingerprints.
|
|
58
|
+
* @returns A validated compatibility table safe to pass to {@link resolveOfflineWireCompatibility}.
|
|
59
|
+
*/
|
|
60
|
+
export declare function defineOfflineWireCompatibility(options: {
|
|
61
|
+
readonly current: OfflineWireFingerprint;
|
|
62
|
+
readonly accepted?: readonly OfflineWireAcceptedFingerprint[];
|
|
63
|
+
}): OfflineWireCompatibility;
|
|
64
|
+
/**
|
|
65
|
+
* Resolves an incoming `{version,hash}` against a validated compatibility table.
|
|
66
|
+
*
|
|
67
|
+
* Matches only exact fingerprints. Current always wins; accepted prior fingerprints match
|
|
68
|
+
* only while the injectable clock is strictly before their `expiresAt`.
|
|
69
|
+
*
|
|
70
|
+
* @param compatibility - Table from {@link defineOfflineWireCompatibility}.
|
|
71
|
+
* @param fingerprint - Incoming client fingerprint.
|
|
72
|
+
* @param clock - Injectable wall clock; defaults to the system wall clock.
|
|
73
|
+
* @returns The match, or `undefined` when the fingerprint is unknown, inexact, or expired.
|
|
74
|
+
*/
|
|
75
|
+
export declare function resolveOfflineWireCompatibility(compatibility: OfflineWireCompatibility, fingerprint: OfflineWireFingerprint, clock?: () => Date): OfflineWireCompatibilityResolution | undefined;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates a current fingerprint plus optional accepted prior fingerprints.
|
|
3
|
+
*
|
|
4
|
+
* Rejects duplicate versions, duplicate hashes, invalid fingerprints, invalid expiry
|
|
5
|
+
* instants, and accepted entries that omit a product adapter/projection identifier.
|
|
6
|
+
*
|
|
7
|
+
* @param options - Current fingerprint and optional accepted prior fingerprints.
|
|
8
|
+
* @returns A validated compatibility table safe to pass to {@link resolveOfflineWireCompatibility}.
|
|
9
|
+
*/
|
|
10
|
+
export function defineOfflineWireCompatibility(options) {
|
|
11
|
+
const current = normalizeFingerprint(options.current, 'current');
|
|
12
|
+
const acceptedInput = options.accepted ?? [];
|
|
13
|
+
const versions = new Set([current.version]);
|
|
14
|
+
const hashes = new Set([current.hash]);
|
|
15
|
+
const accepted = [];
|
|
16
|
+
for (const [index, entry] of acceptedInput.entries()) {
|
|
17
|
+
const label = `accepted[${index}]`;
|
|
18
|
+
const fingerprint = normalizeFingerprint(entry, label);
|
|
19
|
+
if (versions.has(fingerprint.version)) {
|
|
20
|
+
throw new Error(`Offline wire compatibility rejects duplicate version ${fingerprint.version}.`);
|
|
21
|
+
}
|
|
22
|
+
if (hashes.has(fingerprint.hash)) {
|
|
23
|
+
throw new Error(`Offline wire compatibility rejects duplicate hash '${fingerprint.hash}'.`);
|
|
24
|
+
}
|
|
25
|
+
if (!(entry.expiresAt instanceof Date) || Number.isNaN(entry.expiresAt.getTime())) {
|
|
26
|
+
throw new RangeError(`Offline wire compatibility ${label}.expiresAt must be a valid Date.`);
|
|
27
|
+
}
|
|
28
|
+
const adapterId = entry.adapterId;
|
|
29
|
+
if (typeof adapterId !== 'string' || adapterId.trim().length === 0) {
|
|
30
|
+
throw new Error(`Offline wire compatibility ${label} requires a non-empty product adapter/projection identifier.`);
|
|
31
|
+
}
|
|
32
|
+
versions.add(fingerprint.version);
|
|
33
|
+
hashes.add(fingerprint.hash);
|
|
34
|
+
accepted.push({
|
|
35
|
+
version: fingerprint.version,
|
|
36
|
+
hash: fingerprint.hash,
|
|
37
|
+
expiresAt: entry.expiresAt,
|
|
38
|
+
adapterId,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return { current, accepted };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Resolves an incoming `{version,hash}` against a validated compatibility table.
|
|
45
|
+
*
|
|
46
|
+
* Matches only exact fingerprints. Current always wins; accepted prior fingerprints match
|
|
47
|
+
* only while the injectable clock is strictly before their `expiresAt`.
|
|
48
|
+
*
|
|
49
|
+
* @param compatibility - Table from {@link defineOfflineWireCompatibility}.
|
|
50
|
+
* @param fingerprint - Incoming client fingerprint.
|
|
51
|
+
* @param clock - Injectable wall clock; defaults to the system wall clock.
|
|
52
|
+
* @returns The match, or `undefined` when the fingerprint is unknown, inexact, or expired.
|
|
53
|
+
*/
|
|
54
|
+
export function resolveOfflineWireCompatibility(compatibility, fingerprint, clock = () => new Date()) {
|
|
55
|
+
const incoming = normalizeFingerprint(fingerprint, 'incoming');
|
|
56
|
+
if (incoming.version === compatibility.current.version) {
|
|
57
|
+
if (incoming.hash !== compatibility.current.hash) {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
return { kind: 'current', fingerprint: compatibility.current };
|
|
61
|
+
}
|
|
62
|
+
const now = clock();
|
|
63
|
+
if (Number.isNaN(now.getTime())) {
|
|
64
|
+
throw new RangeError('Offline wire compatibility clock must return a valid Date.');
|
|
65
|
+
}
|
|
66
|
+
const nowMs = now.getTime();
|
|
67
|
+
for (const entry of compatibility.accepted) {
|
|
68
|
+
if (entry.version !== incoming.version) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (entry.hash !== incoming.hash) {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
if (nowMs >= entry.expiresAt.getTime()) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
kind: 'accepted',
|
|
79
|
+
fingerprint: { version: entry.version, hash: entry.hash },
|
|
80
|
+
adapterId: entry.adapterId,
|
|
81
|
+
expiresAt: entry.expiresAt,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
function normalizeFingerprint(fingerprint, label) {
|
|
87
|
+
if (!Number.isSafeInteger(fingerprint.version) || fingerprint.version < 0) {
|
|
88
|
+
throw new RangeError(`Offline wire compatibility ${label}.version must be a non-negative safe integer.`);
|
|
89
|
+
}
|
|
90
|
+
if (typeof fingerprint.hash !== 'string' || fingerprint.hash.trim().length === 0) {
|
|
91
|
+
throw new Error(`Offline wire compatibility ${label}.hash must be a non-empty string.`);
|
|
92
|
+
}
|
|
93
|
+
return { version: fingerprint.version, hash: fingerprint.hash };
|
|
94
|
+
}
|
package/dist/queue/consumer.js
CHANGED
|
@@ -69,28 +69,30 @@ export async function processBatch(batch, handler, options) {
|
|
|
69
69
|
let discarded = 0;
|
|
70
70
|
let failed = 0;
|
|
71
71
|
for (const message of batch.messages) {
|
|
72
|
-
|
|
72
|
+
const processMessage = async () => {
|
|
73
73
|
await handler(message.body, message);
|
|
74
74
|
message.ack();
|
|
75
|
+
};
|
|
76
|
+
const outcome = await processMessage().then(() => ({ ok: true }), (error) => ({ ok: false, error }));
|
|
77
|
+
if (outcome.ok) {
|
|
75
78
|
processed++;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
onError(outcome.error, message);
|
|
76
83
|
}
|
|
77
|
-
catch (
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (isNonRetryableQueueError(error)) {
|
|
87
|
-
message.ack();
|
|
88
|
-
discarded++;
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
message.retry(retryOptions);
|
|
92
|
-
failed++;
|
|
84
|
+
catch (reportingError) {
|
|
85
|
+
// Reporting is best-effort. Preserve the domain error's disposition, but never let a broken
|
|
86
|
+
// custom reporter make a permanent failure disappear without any local trace.
|
|
87
|
+
console.error(`[queue:${batch.queue}] onError failed for message ${message.id}`, reportingError, 'original error:', outcome.error);
|
|
88
|
+
}
|
|
89
|
+
if (isNonRetryableQueueError(outcome.error)) {
|
|
90
|
+
message.ack();
|
|
91
|
+
discarded++;
|
|
92
|
+
continue;
|
|
93
93
|
}
|
|
94
|
+
message.retry(retryOptions);
|
|
95
|
+
failed++;
|
|
94
96
|
}
|
|
95
97
|
return { processed, discarded, failed };
|
|
96
98
|
}
|
package/dist/realtime/retry.js
CHANGED
|
@@ -27,16 +27,16 @@ export async function retryDurableObjectOperation(operation, options = {}) {
|
|
|
27
27
|
const random = options.random ?? Math.random;
|
|
28
28
|
const wait = options.wait ?? defaultWait;
|
|
29
29
|
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
const invoke = async () => operation(attempt);
|
|
31
|
+
const outcome = await invoke().then((value) => ({ ok: true, value }), (error) => ({ ok: false, error }));
|
|
32
|
+
if (outcome.ok) {
|
|
33
|
+
return outcome.value;
|
|
32
34
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
throw error;
|
|
36
|
-
}
|
|
37
|
-
const delayMs = Math.min(maxDelayMs, baseDelayMs * 2 ** attempt * random());
|
|
38
|
-
await wait(delayMs);
|
|
35
|
+
if (!isRetryableDurableObjectError(outcome.error) || attempt + 1 >= maxAttempts) {
|
|
36
|
+
throw outcome.error;
|
|
39
37
|
}
|
|
38
|
+
const delayMs = Math.min(maxDelayMs, baseDelayMs * 2 ** attempt * random());
|
|
39
|
+
await wait(delayMs);
|
|
40
40
|
}
|
|
41
41
|
throw new Error('Durable Object retry exhausted');
|
|
42
42
|
}
|
package/dist/stripe/failure.js
CHANGED
|
@@ -162,28 +162,26 @@ export function parsePaymentFailure(receipt) {
|
|
|
162
162
|
if (!receipt) {
|
|
163
163
|
return null;
|
|
164
164
|
}
|
|
165
|
+
let parsed;
|
|
165
166
|
try {
|
|
166
|
-
|
|
167
|
-
const r = asRecord(parsed);
|
|
168
|
-
if (!r) {
|
|
169
|
-
return null;
|
|
170
|
-
}
|
|
171
|
-
if (asRecord(r.reason)) {
|
|
172
|
-
if ((r.source !== undefined && typeof r.source !== 'string') ||
|
|
173
|
-
(r.occurredAt !== undefined && typeof r.occurredAt !== 'string')) {
|
|
174
|
-
return null;
|
|
175
|
-
}
|
|
176
|
-
return parsed;
|
|
177
|
-
}
|
|
178
|
-
// IAP rows store the reason itself. `code` is required so arbitrary JSON is not accepted as a reason.
|
|
179
|
-
if (typeof r.code === 'string') {
|
|
180
|
-
return { reason: parsed };
|
|
181
|
-
}
|
|
182
|
-
return null;
|
|
167
|
+
parsed = JSON.parse(receipt);
|
|
183
168
|
}
|
|
184
169
|
catch {
|
|
185
170
|
return null;
|
|
186
171
|
}
|
|
172
|
+
const r = asRecord(parsed);
|
|
173
|
+
if (!r) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
if (asRecord(r.reason)) {
|
|
177
|
+
if ((r.source !== undefined && typeof r.source !== 'string') ||
|
|
178
|
+
(r.occurredAt !== undefined && typeof r.occurredAt !== 'string')) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
return parsed;
|
|
182
|
+
}
|
|
183
|
+
// IAP rows store the reason itself. `code` is required so arbitrary JSON is not accepted as a reason.
|
|
184
|
+
return typeof r.code === 'string' ? { reason: parsed } : null;
|
|
187
185
|
}
|
|
188
186
|
/**
|
|
189
187
|
* HTTP error for a synchronous card decline, carrying a user-facing Japanese message.
|
package/dist/testing/db.js
CHANGED
|
@@ -83,14 +83,13 @@ export function createTestDb(options) {
|
|
|
83
83
|
await pool.query(`INSERT INTO \`${table}\` (${columnList}) VALUES (${placeholders})`, Object.values(row));
|
|
84
84
|
},
|
|
85
85
|
async mysqlReachable() {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return true;
|
|
90
|
-
}
|
|
91
|
-
catch {
|
|
86
|
+
const connect = async () => createConnection({ ...connection });
|
|
87
|
+
const c = await connect().catch(() => undefined);
|
|
88
|
+
if (!c) {
|
|
92
89
|
return false;
|
|
93
90
|
}
|
|
91
|
+
const close = async () => c.end();
|
|
92
|
+
return close().then(() => true, () => false);
|
|
94
93
|
},
|
|
95
94
|
};
|
|
96
95
|
}
|
|
@@ -21,16 +21,14 @@ export function fakeQueue() {
|
|
|
21
21
|
get batchCount() {
|
|
22
22
|
return batchCount;
|
|
23
23
|
},
|
|
24
|
-
send(body) {
|
|
24
|
+
async send(body) {
|
|
25
25
|
sent.push(body);
|
|
26
|
-
return Promise.resolve();
|
|
27
26
|
},
|
|
28
|
-
sendBatch(messages) {
|
|
27
|
+
async sendBatch(messages) {
|
|
29
28
|
batchCount++;
|
|
30
29
|
for (const m of messages) {
|
|
31
30
|
sent.push(m.body);
|
|
32
31
|
}
|
|
33
|
-
return Promise.resolve();
|
|
34
32
|
},
|
|
35
33
|
};
|
|
36
34
|
}
|
|
@@ -47,16 +45,14 @@ export function fakeQueue() {
|
|
|
47
45
|
export function fakeKv() {
|
|
48
46
|
const store = new Map();
|
|
49
47
|
return {
|
|
50
|
-
get: (key) =>
|
|
51
|
-
put: (key, value) => {
|
|
48
|
+
get: async (key) => store.get(key) ?? null,
|
|
49
|
+
put: async (key, value) => {
|
|
52
50
|
store.set(key, value);
|
|
53
|
-
return Promise.resolve();
|
|
54
51
|
},
|
|
55
|
-
delete: (key) => {
|
|
52
|
+
delete: async (key) => {
|
|
56
53
|
store.delete(key);
|
|
57
|
-
return Promise.resolve();
|
|
58
54
|
},
|
|
59
|
-
list: () =>
|
|
60
|
-
getWithMetadata: () =>
|
|
55
|
+
list: async () => ({ keys: [], list_complete: true, cacheStatus: null }),
|
|
56
|
+
getWithMetadata: async () => ({ value: null, metadata: null, cacheStatus: null }),
|
|
61
57
|
};
|
|
62
58
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rdlabo/workers-hono-kit",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -33,6 +33,13 @@
|
|
|
33
33
|
"engines": {
|
|
34
34
|
"node": ">=20.0.0"
|
|
35
35
|
},
|
|
36
|
+
"devEngines": {
|
|
37
|
+
"runtime": {
|
|
38
|
+
"name": "node",
|
|
39
|
+
"version": "^20.19.0 || ^22.13.0 || >=24.0.0",
|
|
40
|
+
"onFail": "error"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
36
43
|
"files": [
|
|
37
44
|
"dist",
|
|
38
45
|
"scripts",
|
|
@@ -120,8 +127,13 @@
|
|
|
120
127
|
"devDependencies": {
|
|
121
128
|
"@ai-sdk/anthropic": "^3.0.84",
|
|
122
129
|
"@ai-sdk/openai": "^3.0.71",
|
|
130
|
+
"@angular-eslint/template-parser": "^21.4.0",
|
|
131
|
+
"@angular/core": "^21.2.20",
|
|
132
|
+
"@angular/forms": "^21.2.20",
|
|
133
|
+
"@angular/router": "^21.2.20",
|
|
123
134
|
"@hono/eslint-config": "^2.1.0",
|
|
124
135
|
"@hono/zod-validator": "^0.8.0",
|
|
136
|
+
"@rdlabo/eslint-plugin-rules": "^21.2.6",
|
|
125
137
|
"@types/node": "^22.19.21",
|
|
126
138
|
"ai": "^6.0.204",
|
|
127
139
|
"ai-gateway-provider": "^3.1.3",
|