ts类型检查

This commit is contained in:
yml
2026-05-04 09:43:38 +08:00
parent a914b7b828
commit 66261e56d1
23 changed files with 223 additions and 76 deletions
+3 -3
View File
@@ -1,13 +1,13 @@
{ {
"syncFromCreatedAt": "2026-05-02T10:53:28.550Z", "syncFromCreatedAt": "2026-05-02T10:53:28.550Z",
"lastRunStartedAt": "2026-05-03T17:50:39.717Z", "lastRunStartedAt": "2026-05-04T01:42:53.462Z",
"lastRunFinishedAt": "2026-05-03T17:50:40.412Z", "lastRunFinishedAt": "2026-05-04T01:42:54.077Z",
"lastRunStatus": "success", "lastRunStatus": "success",
"lastErrorMessage": "", "lastErrorMessage": "",
"fetchedCount": 40, "fetchedCount": 40,
"syncedCount": 0, "syncedCount": 0,
"ignoredCount": 40, "ignoredCount": 40,
"lastOrderCreatedAt": "2026-05-03T17:47:36.000Z", "lastOrderCreatedAt": "2026-05-04T01:42:50.000Z",
"watchMode": "normal", "watchMode": "normal",
"watchOrders": [] "watchOrders": []
} }
+4 -3
View File
@@ -2,8 +2,9 @@
import { query } from '../db/client.js' import { query } from '../db/client.js'
/** @typedef {import('../types/repository-inputs.js').OrderUpsertInput} OrderUpsertInput */ /** @typedef {import('../types/repository-inputs.js').OrderCreateInput} OrderCreateInput */
/** @typedef {import('../types/repository-inputs.js').OrderListQueryInput} OrderListQueryInput */ /** @typedef {import('../types/repository-inputs.js').OrderListQueryInput} OrderListQueryInput */
/** @typedef {import('../types/repository-inputs.js').OrderUpdateInput} OrderUpdateInput */
/** @typedef {import('../types/repository-rows.js').OrderListQueryResult} OrderListQueryResult */ /** @typedef {import('../types/repository-rows.js').OrderListQueryResult} OrderListQueryResult */
/** @typedef {import('../types/repository-rows.js').OrderListRow} OrderListRow */ /** @typedef {import('../types/repository-rows.js').OrderListRow} OrderListRow */
/** @typedef {import('../types/repository-rows.js').OrderRow} OrderRow */ /** @typedef {import('../types/repository-rows.js').OrderRow} OrderRow */
@@ -53,7 +54,7 @@ export async function findOrderByPlatformOrderIdCandidates({
} }
/** @returns {Promise<OrderRow | null>} */ /** @returns {Promise<OrderRow | null>} */
/** @param {OrderUpsertInput} input */ /** @param {OrderCreateInput} input */
export async function createOrder(input) { export async function createOrder(input) {
const result = await query( const result = await query(
` `
@@ -101,7 +102,7 @@ export async function createOrder(input) {
} }
/** @returns {Promise<OrderRow | null>} */ /** @returns {Promise<OrderRow | null>} */
/** @param {OrderUpsertInput} input */ /** @param {OrderUpdateInput} input */
export async function updateOrder(orderId, input) { export async function updateOrder(orderId, input) {
const result = await query( const result = await query(
` `
+52 -25
View File
@@ -10,6 +10,18 @@ import {
} from '../../services/admin/admin-auth-service.js' } from '../../services/admin/admin-auth-service.js'
import { createJsonHandler, requireAdminRoles } from './shared.js' import { createJsonHandler, requireAdminRoles } from './shared.js'
/**
* @typedef {{
* user: {
* userId: number
* username: string
* role: string
* status?: string
* inventoryGroupCodes?: string[]
* }
* }} AdminUserMutationResult
*/
const router = Router() const router = Router()
router.use('/users', requireAdminRoles(['admin'])) router.use('/users', requireAdminRoles(['admin']))
@@ -29,16 +41,19 @@ router.post('/users', createJsonHandler(
successMessage: '后台用户已创建', successMessage: '后台用户已创建',
errorMessage: '创建后台用户失败', errorMessage: '创建后台用户失败',
scope: '[admin/users:create]', scope: '[admin/users:create]',
audit: (_req, data) => ({ audit: (_req, data) => {
const result = /** @type {AdminUserMutationResult} */ (data)
return {
action: 'admin_user_created', action: 'admin_user_created',
targetType: 'admin_user', targetType: 'admin_user',
targetId: String(data.user.userId), targetId: String(result.user.userId),
data: { data: {
username: data.user.username, username: result.user.username,
role: data.user.role, role: result.user.role,
status: data.user.status, status: result.user.status,
}, },
}), }
},
}, },
)) ))
@@ -48,15 +63,18 @@ router.post('/users/:userId/role', createJsonHandler(
successMessage: '用户角色已更新', successMessage: '用户角色已更新',
errorMessage: '更新用户角色失败', errorMessage: '更新用户角色失败',
scope: '[admin/users/:userId/role]', scope: '[admin/users/:userId/role]',
audit: (_req, data) => ({ audit: (_req, data) => {
const result = /** @type {AdminUserMutationResult} */ (data)
return {
action: 'admin_user_role_updated', action: 'admin_user_role_updated',
targetType: 'admin_user', targetType: 'admin_user',
targetId: String(data.user.userId), targetId: String(result.user.userId),
data: { data: {
username: data.user.username, username: result.user.username,
role: data.user.role, role: result.user.role,
}, },
}), }
},
}, },
)) ))
@@ -66,15 +84,18 @@ router.post('/users/:userId/status', createJsonHandler(
successMessage: '用户状态已更新', successMessage: '用户状态已更新',
errorMessage: '更新用户状态失败', errorMessage: '更新用户状态失败',
scope: '[admin/users/:userId/status]', scope: '[admin/users/:userId/status]',
audit: (_req, data) => ({ audit: (_req, data) => {
const result = /** @type {AdminUserMutationResult} */ (data)
return {
action: 'admin_user_status_updated', action: 'admin_user_status_updated',
targetType: 'admin_user', targetType: 'admin_user',
targetId: String(data.user.userId), targetId: String(result.user.userId),
data: { data: {
username: data.user.username, username: result.user.username,
status: data.user.status, status: result.user.status,
}, },
}), }
},
}, },
)) ))
@@ -84,15 +105,18 @@ router.post('/users/:userId/inventory-groups', createJsonHandler(
successMessage: '用户库存组已更新', successMessage: '用户库存组已更新',
errorMessage: '更新用户库存组失败', errorMessage: '更新用户库存组失败',
scope: '[admin/users/:userId/inventory-groups]', scope: '[admin/users/:userId/inventory-groups]',
audit: (_req, data) => ({ audit: (_req, data) => {
const result = /** @type {AdminUserMutationResult} */ (data)
return {
action: 'admin_user_inventory_groups_updated', action: 'admin_user_inventory_groups_updated',
targetType: 'admin_user', targetType: 'admin_user',
targetId: String(data.user.userId), targetId: String(result.user.userId),
data: { data: {
username: data.user.username, username: result.user.username,
inventoryGroupCodes: data.user.inventoryGroupCodes, inventoryGroupCodes: result.user.inventoryGroupCodes,
}, },
}), }
},
}, },
)) ))
@@ -102,14 +126,17 @@ router.post('/users/:userId/reset-password', createJsonHandler(
successMessage: '用户密码已重置', successMessage: '用户密码已重置',
errorMessage: '重置用户密码失败', errorMessage: '重置用户密码失败',
scope: '[admin/users/:userId/reset-password]', scope: '[admin/users/:userId/reset-password]',
audit: (_req, data) => ({ audit: (_req, data) => {
const result = /** @type {AdminUserMutationResult} */ (data)
return {
action: 'admin_user_password_reset', action: 'admin_user_password_reset',
targetType: 'admin_user', targetType: 'admin_user',
targetId: String(data.user.userId), targetId: String(result.user.userId),
data: { data: {
username: data.user.username, username: result.user.username,
}, },
}), }
},
}, },
)) ))
@@ -32,6 +32,29 @@ const CLAIM_TERMINAL_STATUSES = new Set(['expired', 'closed'])
const REDEEM_REPLACEMENT_LIMIT = 10 const REDEEM_REPLACEMENT_LIMIT = 10
const KUAISHOU_CLOUD_CLAIM_GUIDE_BASE_PATH = '/kuaishou-cloud-guide' const KUAISHOU_CLOUD_CLAIM_GUIDE_BASE_PATH = '/kuaishou-cloud-guide'
/**
* @typedef {{
* taskStatus: string
* inventoryStatus: string
* deliveryStatus: string
* lastError: string
* attempts: unknown[]
* classification: { retCode?: unknown } | null
* }} RedeemTaskState
*/
/**
* @typedef {{
* statusCode?: number
* errorCode?: string
* taskStatus?: string
* inventoryStatus?: string
* deliveryStatus?: string
* attempts?: unknown[]
* classification?: { retCode?: unknown } | null
* }} RedeemTaskStatePayload
*/
export async function getClaimDetail(token, { includeQrImage = true } = {}) { export async function getClaimDetail(token, { includeQrImage = true } = {}) {
const context = await getClaimContext(token) const context = await getClaimContext(token)
let { task, session } = await loadTaskSession(context.task, { includeQrImage }) let { task, session } = await loadTaskSession(context.task, { includeQrImage })
@@ -531,7 +554,11 @@ async function finalizeClaimTaskRedeem(context) {
} catch (error) { } catch (error) {
const now = nowIso() const now = nowIso()
const nextRetryCount = Number(context.task.attempt_count || 0) + 1 const nextRetryCount = Number(context.task.attempt_count || 0) + 1
const failureState = error?.redeemTaskState || { const failureState = (
error && typeof error === 'object'
? /** @type {{ redeemTaskState?: RedeemTaskState }} */ (error).redeemTaskState
: null
) || {
taskStatus: 'retry_pending', taskStatus: 'retry_pending',
inventoryStatus: inventoryItem ? 'reserved' : 'pending', inventoryStatus: inventoryItem ? 'reserved' : 'pending',
deliveryStatus: 'pending', deliveryStatus: 'pending',
@@ -1102,11 +1129,15 @@ export async function redeemClaimTaskWithInventoryFallbackWithDeps(
}) })
} }
/**
* @param {string} message
* @param {RedeemTaskStatePayload} [payload]
*/
function createRedeemTaskStateError(message, payload = {}) { function createRedeemTaskStateError(message, payload = {}) {
const error = createHttpError(message, { const error = /** @type {Error & import('../../utils/http.js').HttpErrorLike & { redeemTaskState: RedeemTaskState }} */ (createHttpError(message, {
statusCode: payload.statusCode || 409, statusCode: payload.statusCode || 409,
errorCode: payload.errorCode || 'claim_redeem_failed', errorCode: payload.errorCode || 'claim_redeem_failed',
}) }))
error.redeemTaskState = { error.redeemTaskState = {
taskStatus: payload.taskStatus || 'retry_pending', taskStatus: payload.taskStatus || 'retry_pending',
@@ -13,6 +13,7 @@ import {
import { queryKuaishouEticketConsumeDetail } from '../platforms/kuaishou-eticket/consume-service.js' import { queryKuaishouEticketConsumeDetail } from '../platforms/kuaishou-eticket/consume-service.js'
import { import {
dispatchKuaishouCloudFulfillmentTask, dispatchKuaishouCloudFulfillmentTask,
maskCode,
normalizeKuaishouCloudFlow, normalizeKuaishouCloudFlow,
prepareKuaishouCloudFulfillmentTask, prepareKuaishouCloudFulfillmentTask,
refreshKuaishouCloudTaskRoleInfo, refreshKuaishouCloudTaskRoleInfo,
@@ -745,7 +745,7 @@ function resolveKuaishouCloudBindingResources(flow, { skuItems = [], knapsackIte
} }
} }
function resolveKuaishouCloudVnKeyCandidates() { function resolveKuaishouCloudVnKeyCandidates(_input = {}) {
return [KUAISHOU_CLOUD_FIXED_VN_KEY] return [KUAISHOU_CLOUD_FIXED_VN_KEY]
} }
@@ -66,7 +66,7 @@ export async function syncDeliveryTasksForOrder(order, orderItems) {
executorKey: String(profile.executor_key || 'manual_dispatch'), executorKey: String(profile.executor_key || 'manual_dispatch'),
taskStatus: initialStatus, taskStatus: initialStatus,
inventoryStatus: profile.requires_claim ? 'pending' : 'not_required', inventoryStatus: profile.requires_claim ? 'pending' : 'not_required',
deliveryStatus: initialStatus === 'redeemed' ? 'delivered' : 'pending', deliveryStatus: 'pending',
resultCode: '', resultCode: '',
resultMessage: '', resultMessage: '',
claimToken: '', claimToken: '',
@@ -1,11 +1,34 @@
import { resolveFulfillmentBinding } from '../../repositories/fulfillment-profile-repo.js' import { resolveFulfillmentBinding } from '../../repositories/fulfillment-profile-repo.js'
import { resolveProductMatchRule } from '../../repositories/product-match-rule-repo.js' import { resolveProductMatchRule } from '../../repositories/product-match-rule-repo.js'
/**
* @typedef {{
* itemId?: string
* externalItemId?: string
* skuCode?: string
* skuName?: string
* externalSkuCode?: string
* externalSkuName?: string
* quantity?: number
* spec?: Record<string, unknown>
* snapshot?: Record<string, unknown>
* [key: string]: unknown
* }} FulfillmentItem
*/
/**
* @param {{
* provider?: string
* platform?: string
* shopId?: string
* item?: FulfillmentItem
* }} params
*/
export async function resolveOrderItemForFulfillment({ export async function resolveOrderItemForFulfillment({
provider = '', provider = '',
platform = '', platform = '',
shopId = '', shopId = '',
item = {}, item = /** @type {FulfillmentItem} */ ({}),
}) { }) {
const candidate = await resolveConfiguredItemCandidate({ const candidate = await resolveConfiguredItemCandidate({
provider, provider,
@@ -61,6 +84,14 @@ export async function resolveOrderItemForFulfillment({
} }
} }
/**
* @param {{
* provider?: string
* platform?: string
* shopId?: string
* items?: FulfillmentItem[]
* }} params
*/
export async function hasConfiguredOrderItems({ export async function hasConfiguredOrderItems({
provider = '', provider = '',
platform = '', platform = '',
@@ -104,7 +135,7 @@ async function resolveConfiguredItemCandidate({
provider = '', provider = '',
platform = '', platform = '',
shopId = '', shopId = '',
item = {}, item = /** @type {FulfillmentItem} */ ({}),
}) { }) {
const externalItemId = pickFirstNonEmpty([ const externalItemId = pickFirstNonEmpty([
item.externalItemId, item.externalItemId,
@@ -166,7 +197,8 @@ function readConfigValue(rawValue, key) {
return '' return ''
} }
return String(parsed[key] || '').trim() const config = /** @type {Record<string, unknown>} */ (parsed)
return String(config[key] || '').trim()
} }
function safeParseJson(rawValue) { function safeParseJson(rawValue) {
@@ -177,6 +209,10 @@ function safeParseJson(rawValue) {
} }
} }
/**
* @param {unknown} value
* @returns {value is Record<string, unknown>}
*/
function isPlainObject(value) { function isPlainObject(value) {
return Object.prototype.toString.call(value) === '[object Object]' return Object.prototype.toString.call(value) === '[object Object]'
} }
@@ -1,3 +1,5 @@
// @ts-nocheck
import fs from 'node:fs' import fs from 'node:fs'
import path from 'node:path' import path from 'node:path'
@@ -1,3 +1,5 @@
// @ts-nocheck
import crypto from 'node:crypto' import crypto from 'node:crypto'
import { runtimeConfig } from '../../../../config/runtime.js' import { runtimeConfig } from '../../../../config/runtime.js'
@@ -1,3 +1,5 @@
// @ts-nocheck
import crypto from 'node:crypto' import crypto from 'node:crypto'
import { runtimeConfig } from '../../../../config/runtime.js' import { runtimeConfig } from '../../../../config/runtime.js'
@@ -1,3 +1,5 @@
// @ts-nocheck
import crypto from 'node:crypto' import crypto from 'node:crypto'
import { runtimeConfig } from '../../../../config/runtime.js' import { runtimeConfig } from '../../../../config/runtime.js'
+2
View File
@@ -1,3 +1,5 @@
// @ts-nocheck
import { spawn } from 'node:child_process' import { spawn } from 'node:child_process'
import fs from 'node:fs' import fs from 'node:fs'
import path from 'node:path' import path from 'node:path'
@@ -1,3 +1,5 @@
// @ts-nocheck
import { runtimeConfig } from '../../config/runtime.js' import { runtimeConfig } from '../../config/runtime.js'
import { logDebug } from '../../utils/logger.js' import { logDebug } from '../../utils/logger.js'
@@ -1,3 +1,5 @@
// @ts-nocheck
import fs from 'node:fs/promises' import fs from 'node:fs/promises'
import path from 'node:path' import path from 'node:path'
@@ -1,3 +1,5 @@
// @ts-nocheck
import fs from 'node:fs/promises' import fs from 'node:fs/promises'
import { downloadRemoteQrImage, logBrowserSessionDebug, waitForFrame } from './session-login-shared.js' import { downloadRemoteQrImage, logBrowserSessionDebug, waitForFrame } from './session-login-shared.js'
@@ -1,3 +1,5 @@
// @ts-nocheck
import fs from 'node:fs/promises' import fs from 'node:fs/promises'
export async function runTencentBrowserRedeem({ export async function runTencentBrowserRedeem({
@@ -1,3 +1,5 @@
// @ts-nocheck
import fs from 'node:fs/promises' import fs from 'node:fs/promises'
import { downloadRemoteQrImage, logBrowserSessionDebug, waitForFrame } from './session-login-shared.js' import { downloadRemoteQrImage, logBrowserSessionDebug, waitForFrame } from './session-login-shared.js'
@@ -1,3 +1,5 @@
// @ts-nocheck
import crypto from 'node:crypto' import crypto from 'node:crypto'
import fs from 'node:fs/promises' import fs from 'node:fs/promises'
import path from 'node:path' import path from 'node:path'
+14 -2
View File
@@ -30,9 +30,21 @@ export {}
* currency: string * currency: string
* rawPayloadJson?: string | Record<string, unknown> * rawPayloadJson?: string | Record<string, unknown>
* paidAt?: string | null * paidAt?: string | null
* }} OrderWriteInputBase
*/
/**
* @typedef {OrderWriteInputBase & {
* createdAt: string * createdAt: string
* updatedAt: string * updatedAt: string
* }} OrderUpsertInput * }} OrderCreateInput
*/
/**
* @typedef {OrderWriteInputBase & {
* updatedAt: string
* createdAt?: string
* }} OrderUpdateInput
*/ */
/** /**
@@ -87,7 +99,7 @@ export {}
* shopId?: string * shopId?: string
* shopName?: string * shopName?: string
* platformOrderId: string * platformOrderId: string
* profileId: string * profileId: number | string
* executorKey: string * executorKey: string
* taskStatus?: string * taskStatus?: string
* inventoryStatus?: string * inventoryStatus?: string
+41 -4
View File
@@ -1,7 +1,28 @@
// @ts-check
import { logError, logWarn } from './logger.js' import { logError, logWarn } from './logger.js'
const RESPONSE_TIME_ZONE = 'Asia/Shanghai' const RESPONSE_TIME_ZONE = 'Asia/Shanghai'
/**
* @typedef {{
* statusCode?: number
* errorCode?: string
* cause?: unknown
* context?: unknown
* [key: string]: unknown
* }} HttpErrorLike
*/
/**
* @typedef {{
* statusCode?: number
* errorCode?: string
* cause?: unknown
* context?: unknown
* }} CreateHttpErrorOptions
*/
export function buildSuccessPayload(data, msg = 'ok') { export function buildSuccessPayload(data, msg = 'ok') {
return { return {
code: 0, code: 0,
@@ -43,17 +64,33 @@ export function buildNotFoundPayload(req) {
} }
} }
export function createHttpError(message, { statusCode = 500, errorCode = '' } = {}) { /**
const error = new Error(message) * @param {string} message
* @param {CreateHttpErrorOptions} [options]
* @returns {Error & HttpErrorLike}
*/
export function createHttpError(message, options = {}) {
const { statusCode = 500, errorCode = '', cause, context } = options
const error = /** @type {Error & HttpErrorLike} */ (new Error(message))
error.statusCode = statusCode error.statusCode = statusCode
error.errorCode = errorCode || defaultErrorCodeForStatus(statusCode) error.errorCode = errorCode || defaultErrorCodeForStatus(statusCode)
if (typeof cause !== 'undefined') {
error.cause = cause
}
if (typeof context !== 'undefined') {
error.context = context
}
return error return error
} }
function classifyRouteError(error) { function classifyRouteError(error) {
if (error && typeof error === 'object') { if (error && typeof error === 'object') {
const statusCode = Number(error.statusCode) const currentError = /** @type {HttpErrorLike} */ (error)
const errorCode = String(error.errorCode || '').trim() const statusCode = Number(currentError.statusCode)
const errorCode = String(currentError.errorCode || '').trim()
if (Number.isInteger(statusCode) && statusCode >= 400 && statusCode < 600) { if (Number.isInteger(statusCode) && statusCode >= 400 && statusCode < 600) {
return { return {
+5 -2
View File
@@ -206,12 +206,15 @@ function normalizeLogValue(value) {
return JSON.parse( return JSON.parse(
JSON.stringify(value, (_key, current) => { JSON.stringify(value, (_key, current) => {
if (current instanceof Error) { if (current instanceof Error) {
const currentError = /** @type {import('./http.js').HttpErrorLike} */ (
/** @type {unknown} */ (current)
)
return { return {
name: current.name, name: current.name,
message: current.message, message: current.message,
stack: current.stack, stack: current.stack,
statusCode: current.statusCode, statusCode: currentError.statusCode,
errorCode: current.errorCode, errorCode: currentError.errorCode,
} }
} }
+6 -29
View File
@@ -4,7 +4,7 @@
"module": "NodeNext", "module": "NodeNext",
"moduleResolution": "NodeNext", "moduleResolution": "NodeNext",
"allowJs": true, "allowJs": true,
"checkJs": false, "checkJs": true,
"noEmit": true, "noEmit": true,
"strict": false, "strict": false,
"skipLibCheck": true, "skipLibCheck": true,
@@ -12,36 +12,13 @@
"lib": ["ES2022"] "lib": ["ES2022"]
}, },
"include": [ "include": [
"src/config/runtime.js", "src/**/*.js"
"src/repositories/inventory-repo.js",
"src/repositories/order-item-repo.js",
"src/repositories/order-repo.js",
"src/repositories/task-inventory-binding-repo.js",
"src/repositories/task-repo.js",
"src/repositories/webhook-event-repo.js",
"src/services/admin/admin-inventory-read-helpers.js",
"src/services/admin/admin-order-read-helpers.js",
"src/services/admin/admin-read-shared-helpers.js",
"src/services/admin/admin-task-read-helpers.js",
"src/services/admin/admin-webhook-read-helpers.js",
"src/services/admin/admin-dashboard-service.js",
"src/services/admin/admin-message-delivery-service.js",
"src/services/admin/admin-platform-config-service.js",
"src/services/admin/admin-read-service.js",
"src/services/admin/admin-service.js",
"src/services/admin/admin-write-service.js",
"src/routes/admin/dashboard.js",
"src/routes/admin/inventory.js",
"src/routes/admin/message-deliveries.js",
"src/routes/admin/orders.js",
"src/routes/admin/platform-config.js",
"src/routes/admin/shared.js",
"src/routes/admin/tasks.js",
"src/routes/admin/webhook-events.js",
"src/types/**/*.js"
], ],
"exclude": [ "exclude": [
"node_modules", "node_modules",
"data" "data",
"src/**/*.test.js",
"src/services/session/**",
"src/services/platforms/agiso/**"
] ]
} }