diff --git a/CHANGELOG.md b/CHANGELOG.md index e6b424597..0f4825d0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added + +- **All SDKs:** `OrderStatus` gains a `DelayedNotReported` variant (wire value `DelayedNotReported`) — "monitoring", the state a time-conditional order sits in before it is reported to the exchange. Previously this status deserialized to `OrderStatus::Unknown`, so time-conditional orders being monitored were indistinguishable from a genuinely unrecognized status in `today_orders` / `history_orders` / `order_detail` and the order-changed push. The variant is appended **after** `PartialWithdrawal` rather than inserted next to the other `*NotReported` values on purpose: the C/C++/Node.js enums use implicit discriminants, and inserting mid-list would renumber every following value (an ABI break for the C/C++ bindings, and a silent mismatch for TypeScript code that inlined the old `const enum` values) + ## [5.0.0] - 2026-09-14 ### Changed diff --git a/c/csrc/include/longbridge.h b/c/csrc/include/longbridge.h index e3b78b554..2e501c36e 100644 --- a/c/csrc/include/longbridge.h +++ b/c/csrc/include/longbridge.h @@ -1261,6 +1261,10 @@ typedef enum lb_order_status_t { * Partial Withdrawal */ OrderStatusPartialWithdrawal, + /** + * Monitoring (Time-Conditional Order) + */ + OrderStatusDelayedNotReported, } lb_order_status_t; /** diff --git a/c/src/trade_context/enum_types.rs b/c/src/trade_context/enum_types.rs index dc7260af3..e13a2ef13 100644 --- a/c/src/trade_context/enum_types.rs +++ b/c/src/trade_context/enum_types.rs @@ -138,6 +138,9 @@ pub enum COrderStatus { /// Partial Withdrawal #[c(remote = "PartialWithdrawal")] OrderStatusPartialWithdrawal, + /// Monitoring (Time-Conditional Order) + #[c(remote = "DelayedNotReported")] + OrderStatusDelayedNotReported, } /// Order tag diff --git a/cpp/include/types.hpp b/cpp/include/types.hpp index 33ae6ffb7..a83fcdae1 100644 --- a/cpp/include/types.hpp +++ b/cpp/include/types.hpp @@ -1480,6 +1480,8 @@ enum class OrderStatus Expired, /// Partial Withdrawal PartialWithdrawal, + /// Monitoring (Time-Conditional Order) + DelayedNotReported, }; /// Order type diff --git a/cpp/src/convert.hpp b/cpp/src/convert.hpp index f18c05ff9..482080ba3 100644 --- a/cpp/src/convert.hpp +++ b/cpp/src/convert.hpp @@ -1051,6 +1051,8 @@ convert(lb_order_status_t status) return OrderStatus::Expired; case OrderStatusPartialWithdrawal: return OrderStatus::PartialWithdrawal; + case OrderStatusDelayedNotReported: + return OrderStatus::DelayedNotReported; default: throw std::invalid_argument("unreachable"); } @@ -1096,6 +1098,8 @@ convert(OrderStatus status) return OrderStatusExpired; case OrderStatus::PartialWithdrawal: return OrderStatusPartialWithdrawal; + case OrderStatus::DelayedNotReported: + return OrderStatusDelayedNotReported; default: throw std::invalid_argument("unreachable"); } diff --git a/java/javasrc/src/main/java/com/longbridge/trade/OrderStatus.java b/java/javasrc/src/main/java/com/longbridge/trade/OrderStatus.java index 8813cc4ae..7fbeae1f6 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/OrderStatus.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/OrderStatus.java @@ -40,4 +40,6 @@ public enum OrderStatus { Expired, /** Partial withdrawal */ PartialWithdrawal, + /** Monitoring (time-conditional order) */ + DelayedNotReported, } diff --git a/java/src/types/enum_types.rs b/java/src/types/enum_types.rs index 38ceb4fca..1d16d9428 100644 --- a/java/src/types/enum_types.rs +++ b/java/src/types/enum_types.rs @@ -381,6 +381,7 @@ impl_java_enum!( Canceled, Expired, PartialWithdrawal, + DelayedNotReported, ] ); diff --git a/nodejs/index.d.ts b/nodejs/index.d.ts index df10f667e..5425b6bf1 100644 --- a/nodejs/index.d.ts +++ b/nodejs/index.d.ts @@ -6227,7 +6227,9 @@ export declare const enum OrderStatus { /** Expired */ Expired = 16, /** Partial Withdrawal */ - PartialWithdrawal = 17 + PartialWithdrawal = 17, + /** Monitoring (Time-Conditional Order) */ + DelayedNotReported = 18 } /** Order tag */ diff --git a/nodejs/index.js b/nodejs/index.js index 88789ebfb..ed780f473 100644 --- a/nodejs/index.js +++ b/nodejs/index.js @@ -3,7 +3,7 @@ // @ts-nocheck /* auto-generated by NAPI-RS */ -const { readFileSync } = require('node:fs') +const { readFileSync } = require('fs') let nativeBinding = null const loadErrors = [] @@ -33,7 +33,7 @@ const isMuslFromFilesystem = () => { const isMuslFromReport = () => { let report = null - if (typeof process.report?.getReport === 'function') { + if (process.report && typeof process.report.getReport === 'function') { process.report.excludeNetwork = true report = process.report.getReport() } @@ -105,7 +105,7 @@ function requireNative() { } } else if (process.platform === 'win32') { if (process.arch === 'x64') { - if (process.config?.variables?.shlib_suffix === 'dll.a' || process.config?.variables?.node_target_type === 'shared_library') { + if ((process.config && process.config.variables && process.config.variables.shlib_suffix === 'dll.a') || (process.config && process.config.variables && process.config.variables.node_target_type === 'shared_library')) { try { return require('./longbridge.win32-x64-gnu.node') } catch (e) { @@ -523,54 +523,178 @@ function requireNative() { } } -nativeBinding = requireNative() +function createLoadErrorChain(errors) { + return errors.reduce((previous, current) => { + let message + try { + message = + current && typeof current.message === 'string' + ? current.message + : String(current) + } catch { + message = 'Unknown error' + } + const error = new Error(message) + error.cause = previous + return error + }, null) +} + +// NAPI_RS_FORCE_WASI is a tri-state flag: +// unset / any other value → native binding preferred, WASI is only a fallback +// 'true' → prefer WASI, but retain native as a lazy fallback +// 'error' → require WASI without initializing a native fallback +// Treating any non-empty string as truthy (the historical behavior) meant +// NAPI_RS_FORCE_WASI=false, NAPI_RS_FORCE_WASI=0, etc. inadvertently triggered +// the WASI path, causing ENOENT for packages shipped without a .wasi.cjs file. +// +// NAPI_RS_WASI_FLAVOR selects one exact generated flavor and implies strict +// WASI loading. It never crosses into another flavor or falls back to native. +const __napiWasiFlavors = ["wasm32-wasi"] +const __napiWasiFlavor = process.env.NAPI_RS_WASI_FLAVOR +const __napiWasiFlavorRequested = + typeof __napiWasiFlavor === 'string' && __napiWasiFlavor.length > 0 +if ( + __napiWasiFlavorRequested && + __napiWasiFlavors.indexOf(__napiWasiFlavor) === -1 +) { + throw new Error( + 'Unsupported WASI flavor "' + + __napiWasiFlavor + + '". Available flavors: ' + + __napiWasiFlavors.join(', '), + ) +} +const forceWasiError = process.env.NAPI_RS_FORCE_WASI === 'error' +const forceWasi = + process.env.NAPI_RS_FORCE_WASI === 'true' || + forceWasiError || + __napiWasiFlavorRequested + +if (!forceWasi) { + nativeBinding = requireNative() +} -if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) { +if (!nativeBinding || forceWasi) { let wasiBinding = null - let wasiBindingError = null - try { - wasiBinding = require('./longbridge.wasi.cjs') - nativeBinding = wasiBinding - } catch (err) { - if (process.env.NAPI_RS_FORCE_WASI) { - wasiBindingError = err + let wasiBindingLoaded = false + const wasiBindingErrors = [] + const __napiWasiResolveCandidate = (specifier, isPackage, localArtifacts) => { + try { + require.resolve(specifier) + } catch (resolveError) { + if (!resolveError || resolveError.code !== 'MODULE_NOT_FOUND') { + throw resolveError + } + if (isPackage) { + try { + require.resolve(specifier + '/package.json') + } catch (packageError) { + if (packageError && packageError.code === 'MODULE_NOT_FOUND') { + return resolveError + } + // An exports restriction proves the package exists even when its + // package.json is not public. Preserve the root resolution failure. + throw resolveError + } + // The package exists but its main/export target is broken. + throw resolveError + } + return resolveError } + if (localArtifacts) { + let artifactError = null + for (let i = 0; i < localArtifacts.length; i++) { + try { + require.resolve(localArtifacts[i]) + return null + } catch (resolveError) { + if (!resolveError || resolveError.code !== 'MODULE_NOT_FOUND') { + throw resolveError + } + artifactError = resolveError + } + } + return artifactError + } + return null } - if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) { + if (!wasiBindingLoaded && (!__napiWasiFlavorRequested || __napiWasiFlavor === "wasm32-wasi")) { + let candidateError = null + let candidateFailed = false try { - wasiBinding = require('longbridge-wasm32-wasi') - nativeBinding = wasiBinding + candidateError = __napiWasiResolveCandidate('./longbridge.wasi.cjs', false, ["./longbridge.wasm32-wasi.debug.wasm","./longbridge.wasm32-wasi.wasm"]) + candidateFailed = candidateError !== null + if (!candidateFailed) { + wasiBinding = require('./longbridge.wasi.cjs') + nativeBinding = wasiBinding + wasiBindingLoaded = true + } } catch (err) { - if (process.env.NAPI_RS_FORCE_WASI) { - if (!wasiBindingError) { - wasiBindingError = err - } else { - wasiBindingError.cause = err + candidateError = err + candidateFailed = true + } + if (candidateFailed) { + wasiBindingErrors.push(candidateError) + loadErrors.push(candidateError) + } + } + if (!wasiBindingLoaded && (!__napiWasiFlavorRequested || __napiWasiFlavor === "wasm32-wasi")) { + let candidateError = null + let candidateFailed = false + try { + candidateError = __napiWasiResolveCandidate('longbridge-wasm32-wasi', true, undefined) + candidateFailed = candidateError !== null + if (!candidateFailed) { + if (process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + const bindingPackageVersion = require('longbridge-wasm32-wasi/package.json').version + if (bindingPackageVersion !== '0.0.0') { + throw new Error(`WASI binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } } - loadErrors.push(err) + wasiBinding = require('longbridge-wasm32-wasi') + nativeBinding = wasiBinding + wasiBindingLoaded = true } + } catch (err) { + candidateError = err + candidateFailed = true + } + if (candidateFailed) { + wasiBindingErrors.push(candidateError) + loadErrors.push(candidateError) } } - if (process.env.NAPI_RS_FORCE_WASI === 'error' && !wasiBinding) { - const error = new Error('WASI binding not found and NAPI_RS_FORCE_WASI is set to error') - error.cause = wasiBindingError + if ( + !wasiBindingLoaded && + forceWasi && + !forceWasiError && + !__napiWasiFlavorRequested + ) { + nativeBinding = requireNative() + } + if ((forceWasiError || __napiWasiFlavorRequested) && !wasiBindingLoaded) { + const error = new Error( + __napiWasiFlavorRequested + ? 'WASI binding for flavor "' + __napiWasiFlavor + '" not found' + : 'WASI binding not found and NAPI_RS_FORCE_WASI is set to error', + ) + error.cause = createLoadErrorChain(wasiBindingErrors) throw error } } if (!nativeBinding) { if (loadErrors.length > 0) { - throw new Error( + const error = new Error( `Cannot find native binding. ` + `npm has a bug related to optional dependencies (https://github.com/npm/cli/issues/4828). ` + 'Please try `npm i` again after removing both package-lock.json and node_modules directory.', - { - cause: loadErrors.reduce((err, cur) => { - cur.cause = err - return cur - }), - }, ) + // assign instead of the `new Error(message, { cause })` options form, + // which Node < 16.9 silently ignores + error.cause = createLoadErrorChain(loadErrors) + throw error } throw new Error(`Failed to load native binding`) } diff --git a/nodejs/src/trade/types.rs b/nodejs/src/trade/types.rs index b251bc9c7..859a156b5 100644 --- a/nodejs/src/trade/types.rs +++ b/nodejs/src/trade/types.rs @@ -74,6 +74,8 @@ pub enum OrderStatus { Expired, /// Partial Withdrawal PartialWithdrawal, + /// Monitoring (Time-Conditional Order) + DelayedNotReported, } #[napi_derive::napi] diff --git a/python/pysrc/longbridge/openapi.pyi b/python/pysrc/longbridge/openapi.pyi index cdfb56adb..407e3e156 100644 --- a/python/pysrc/longbridge/openapi.pyi +++ b/python/pysrc/longbridge/openapi.pyi @@ -5704,6 +5704,11 @@ class OrderStatus: PartialWithdrawal """ + class DelayedNotReported(OrderStatus): + """ + Monitoring (Time-Conditional Order) + """ + class OrderTag: """ Order tag diff --git a/python/src/trade/types.rs b/python/src/trade/types.rs index 0baefd345..cd281e2be 100644 --- a/python/src/trade/types.rs +++ b/python/src/trade/types.rs @@ -90,6 +90,8 @@ pub(crate) enum OrderStatus { Expired, /// Partial Withdrawal PartialWithdrawal, + /// Monitoring (Time-Conditional Order) + DelayedNotReported, } #[pyclass(eq, eq_int, from_py_object)] diff --git a/rust/src/trade/types.rs b/rust/src/trade/types.rs index 46f079c17..7ea71b56a 100644 --- a/rust/src/trade/types.rs +++ b/rust/src/trade/types.rs @@ -109,6 +109,9 @@ pub enum OrderStatus { /// Partial Withdrawal #[strum(serialize = "PartialWithdrawal")] PartialWithdrawal, + /// Monitoring (Time-Conditional Order) + #[strum(serialize = "DelayedNotReported")] + DelayedNotReported, } /// Execution