From a343f9bad983e1d47ce006977d9764ef6f753a51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A2=81=E7=AB=A0=E6=B4=AA?= Date: Wed, 23 Sep 2026 09:56:38 +0800 Subject: [PATCH] fix(calendar): expose finance_calendar pagination parameters The finance_calendar method issued a single request with no page-size control, so the server's default page cap (~10 events) made even a one-day query look truncated at 10 results. Expose the endpoint's pagination controls instead of hiding them behind an internal loop: add optional `count`, `offset` and `next` (a new CalendarPageDirection enum) parameters and return the response's `next_date` cursor as-is, so callers can page through the full window themselves. Propagated across all layers: Rust core + blocking, C (cbindgen), C++, Java (JNI + FinanceCalendarOptions), Node.js (napi), Python (PyO3). Reported as longbridge/developers#1265 --- CHANGELOG.md | 6 + c/cbindgen.toml | 3 +- c/csrc/include/longbridge.h | 20 ++ c/src/calendar_context/context.rs | 13 +- c/src/calendar_context/enum_types.rs | 14 ++ cpp/include/calendar_context.hpp | 18 +- cpp/src/calendar_context.cpp | 11 +- .../calendar/CalendarPageDirection.java | 9 + .../calendar/FinanceCalendarOptions.java | 6 + java/src/calendar_context.rs | 11 +- java/src/init.rs | 1 + java/src/types/enum_types.rs | 5 + nodejs/index.d.ts | 20 +- nodejs/index.js | 187 +++--------------- nodejs/src/calendar/context.rs | 23 ++- nodejs/src/calendar/types.rs | 18 ++ python/pysrc/longbridge/openapi.pyi | 31 +++ python/src/calendar/context.rs | 20 +- python/src/calendar/context_async.rs | 19 +- python/src/calendar/mod.rs | 1 + python/src/calendar/types.rs | 19 ++ rust/src/blocking/calendar.rs | 11 +- rust/src/calendar/context.rs | 30 ++- rust/src/calendar/types.rs | 14 ++ 24 files changed, 334 insertions(+), 176 deletions(-) create mode 100644 java/javasrc/src/main/java/com/longbridge/calendar/CalendarPageDirection.java diff --git a/CHANGELOG.md b/CHANGELOG.md index b584f9c2a..fca045f87 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] + +### Changed + +- **All SDKs:** `CalendarContext.finance_calendar` now exposes the endpoint's pagination controls so callers can page through results themselves. Previously the method issued a single request with no page-size control, so the server's default page cap (historically ~10 events) made even a one-day query look truncated at 10 results. Three optional parameters were added — `count` (max events per page), `offset` (events to skip), and `next` (a new `CalendarPageDirection` enum: `Later` / `Earlier`) — and the response's `next_date` cursor is returned as-is. To retrieve a full window, request a larger `count`, or re-call with the returned `next_date` as `start` until it comes back empty. This is a signature change across every layer (Rust core + blocking, C, C++, Java, Node.js, Python); the new arguments are optional in the languages that support defaults. Reported as longbridge/developers#1265 + ## [5.1.0] - 2026-09-20 ### Breaking changes diff --git a/c/cbindgen.toml b/c/cbindgen.toml index f8df46c97..f8a0f0df0 100644 --- a/c/cbindgen.toml +++ b/c/cbindgen.toml @@ -254,6 +254,7 @@ cpp_compat = true "CDCAStatus" = "lb_dca_status_t" # CalendarContext enums "CCalendarCategory" = "lb_calendar_category_t" +"CCalendarPageDirection" = "lb_calendar_page_direction_t" # FundamentalContext enums "CFinancialReportKind" = "lb_financial_report_kind_t" "CIndustryRankIndicator" = "lb_industry_rank_indicator_t" @@ -507,7 +508,7 @@ include = [ "CDcaCreateResult", "CDcaCalcDateResult", "CDcaHistoryRecord", "CDcaHistoryResponse", # CalendarContext enums - "CCalendarCategory", + "CCalendarCategory", "CCalendarPageDirection", # FundamentalContext enums "CFinancialReportKind", "CFinancialReportPeriod", "CInstitutionRecommend", "CIndustryRankIndicator", "CIndustryRankSortType", diff --git a/c/csrc/include/longbridge.h b/c/csrc/include/longbridge.h index 94866ae6e..7b8fddbd4 100644 --- a/c/csrc/include/longbridge.h +++ b/c/csrc/include/longbridge.h @@ -271,6 +271,20 @@ typedef enum lb_calendar_category_t { CalendarCategoryMerge, } lb_calendar_category_t; +/** + * Pagination direction for finance_calendar (the `next` query parameter) + */ +typedef enum lb_calendar_page_direction_t { + /** + * Page towards later dates + */ + CalendarPageDirectionLater, + /** + * Page towards earlier dates + */ + CalendarPageDirectionEarlier, +} lb_calendar_page_direction_t; + /** * Language identifer */ @@ -12115,12 +12129,18 @@ void lb_calendar_context_release(const struct lb_calendar_context_t *ctx); /** * Get financial calendar events. + * + * `count`, `offset` and `next` are optional pagination controls; pass `NULL` + * to omit any of them and use the server default. */ void lb_calendar_context_finance_calendar(const struct lb_calendar_context_t *ctx, enum lb_calendar_category_t category, const char *start, const char *end, const char *market, + const int32_t *count, + const int32_t *offset, + const enum lb_calendar_page_direction_t *next, lb_async_callback_t callback, void *userdata); diff --git a/c/src/calendar_context/context.rs b/c/src/calendar_context/context.rs index 3b809364d..cf61d9090 100644 --- a/c/src/calendar_context/context.rs +++ b/c/src/calendar_context/context.rs @@ -32,6 +32,9 @@ pub unsafe extern "C" fn lb_calendar_context_release(ctx: *const CCalendarContex } /// Get financial calendar events. +/// +/// `count`, `offset` and `next` are optional pagination controls; pass `NULL` +/// to omit any of them and use the server default. #[unsafe(no_mangle)] pub unsafe extern "C" fn lb_calendar_context_finance_calendar( ctx: *const CCalendarContext, @@ -39,6 +42,9 @@ pub unsafe extern "C" fn lb_calendar_context_finance_calendar( start: *const c_char, end: *const c_char, market: *const c_char, + count: *const i32, + offset: *const i32, + next: *const CCalendarPageDirection, callback: CAsyncCallback, userdata: *mut c_void, ) { @@ -51,10 +57,15 @@ pub unsafe extern "C" fn lb_calendar_context_finance_calendar( } else { Some(cstr_to_rust(market)) }; + let count = count.as_ref().copied(); + let offset = offset.as_ref().copied(); + let next = next.as_ref().map(|d| (*d).into()); execute_async(callback, ctx, userdata, async move { let resp: CCow = CCow::new(CCalendarEventsResponseOwned::from( - ctx_inner.finance_calendar(cat, start, end, mkt).await?, + ctx_inner + .finance_calendar(cat, start, end, mkt, count, offset, next) + .await?, )); Ok(resp) }); diff --git a/c/src/calendar_context/enum_types.rs b/c/src/calendar_context/enum_types.rs index 61d125396..703b46662 100644 --- a/c/src/calendar_context/enum_types.rs +++ b/c/src/calendar_context/enum_types.rs @@ -31,3 +31,17 @@ pub enum CCalendarCategory { #[c(remote = "Merge")] CalendarCategoryMerge, } + +/// Pagination direction for finance_calendar (the `next` query parameter) +#[derive(Debug, Copy, Clone, Eq, PartialEq, CEnum)] +#[c(remote = "longbridge::calendar::types::CalendarPageDirection")] +#[allow(clippy::enum_variant_names)] +#[repr(C)] +pub enum CCalendarPageDirection { + /// Page towards later dates + #[c(remote = "Later")] + CalendarPageDirectionLater, + /// Page towards earlier dates + #[c(remote = "Earlier")] + CalendarPageDirectionEarlier, +} diff --git a/cpp/include/calendar_context.hpp b/cpp/include/calendar_context.hpp index b42e28e53..1ce82e3de 100644 --- a/cpp/include/calendar_context.hpp +++ b/cpp/include/calendar_context.hpp @@ -1,4 +1,6 @@ #pragma once +#include + #include "async_result.hpp" #include "callback.hpp" #include "config.hpp" @@ -19,6 +21,13 @@ enum class CalendarCategory Closed = 5, }; +/// Pagination direction for finance_calendar (the `next` query parameter). +enum class CalendarPageDirection +{ + Later = 0, + Earlier = 1, +}; + /// Key-value metadata entry attached to a calendar event. struct CalendarDataKv { std::string key; std::string value; std::string value_type; std::string value_raw; }; /// A single financial calendar event (earnings report, dividend, IPO, etc.). @@ -37,7 +46,14 @@ private: const lb_calendar_context_t* ctx_; /// Create a CalendarContext from a Config. static CalendarContext create(const Config& config); /// Get financial calendar events for the given date range. - void finance_calendar(CalendarCategory category, const std::string& start, const std::string& end, const std::string& market, AsyncCallback callback) const; + /// + /// The endpoint paginates: the server caps each response (historically 10 + /// events per page unless a larger `count` is requested) and returns a + /// `next_date` cursor. To page through the full window, request a larger + /// `count`, or re-call with the returned `next_date` as `start`. `count`, + /// `offset` and `next` are optional (pass `std::nullopt` to use the server + /// default). + void finance_calendar(CalendarCategory category, const std::string& start, const std::string& end, const std::string& market, std::optional count, std::optional offset, std::optional next, AsyncCallback callback) const; }; } // namespace calendar diff --git a/cpp/src/calendar_context.cpp b/cpp/src/calendar_context.cpp index a4e48a29e..e0263e75a 100644 --- a/cpp/src/calendar_context.cpp +++ b/cpp/src/calendar_context.cpp @@ -5,7 +5,7 @@ extern "C" { const lb_calendar_context_t* lb_calendar_context_new(const lb_config_t* config); void lb_calendar_context_retain(const lb_calendar_context_t* ctx); void lb_calendar_context_release(const lb_calendar_context_t* ctx); -void lb_calendar_context_finance_calendar(const lb_calendar_context_t*, lb_calendar_category_t, const char*, const char*, const char*, lb_async_callback_t, void*); +void lb_calendar_context_finance_calendar(const lb_calendar_context_t*, lb_calendar_category_t, const char*, const char*, const char*, const int32_t*, const int32_t*, const lb_calendar_page_direction_t*, lb_async_callback_t, void*); } namespace longbridge { @@ -19,8 +19,13 @@ CalendarContext::~CalendarContext() { if (ctx_) lb_calendar_context_release(ctx_ CalendarContext& CalendarContext::operator=(const CalendarContext& ctx) { ctx_ = ctx.ctx_; if (ctx_) lb_calendar_context_retain(ctx_); return *this; } CalendarContext CalendarContext::create(const Config& config) { auto* ptr = lb_calendar_context_new(config); CalendarContext ctx(ptr); if (ptr) lb_calendar_context_release(ptr); return ctx; } -void CalendarContext::finance_calendar(CalendarCategory category, const std::string& start, const std::string& end, const std::string& market, AsyncCallback callback) const { - lb_calendar_context_finance_calendar(ctx_, (lb_calendar_category_t)category, start.c_str(), end.c_str(), market.empty() ? nullptr : market.c_str(), +void CalendarContext::finance_calendar(CalendarCategory category, const std::string& start, const std::string& end, const std::string& market, std::optional count, std::optional offset, std::optional next, AsyncCallback callback) const { + int32_t count_val = 0, offset_val = 0; + lb_calendar_page_direction_t next_val{}; + const int32_t* count_ptr = count.has_value() ? (count_val = count.value(), &count_val) : nullptr; + const int32_t* offset_ptr = offset.has_value() ? (offset_val = offset.value(), &offset_val) : nullptr; + const lb_calendar_page_direction_t* next_ptr = next.has_value() ? (next_val = (lb_calendar_page_direction_t)next.value(), &next_val) : nullptr; + lb_calendar_context_finance_calendar(ctx_, (lb_calendar_category_t)category, start.c_str(), end.c_str(), market.empty() ? nullptr : market.c_str(), count_ptr, offset_ptr, next_ptr, [](auto res) { auto cb = callback::get_async_callback(res->userdata); CalendarContext fctx((const lb_calendar_context_t*)res->ctx); diff --git a/java/javasrc/src/main/java/com/longbridge/calendar/CalendarPageDirection.java b/java/javasrc/src/main/java/com/longbridge/calendar/CalendarPageDirection.java new file mode 100644 index 000000000..13f5a319c --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/calendar/CalendarPageDirection.java @@ -0,0 +1,9 @@ +package com.longbridge.calendar; + +/** Pagination direction for {@link CalendarContext#getFinanceCalendar} (the {@code next} parameter). */ +public enum CalendarPageDirection { + /** Page towards later dates */ + Later, + /** Page towards earlier dates */ + Earlier, +} diff --git a/java/javasrc/src/main/java/com/longbridge/calendar/FinanceCalendarOptions.java b/java/javasrc/src/main/java/com/longbridge/calendar/FinanceCalendarOptions.java index 59d498f13..32d2d7180 100644 --- a/java/javasrc/src/main/java/com/longbridge/calendar/FinanceCalendarOptions.java +++ b/java/javasrc/src/main/java/com/longbridge/calendar/FinanceCalendarOptions.java @@ -10,4 +10,10 @@ public class FinanceCalendarOptions { public String end; /** Market filter, e.g. {@code "HK"} or {@code "US"} (optional). */ public String market; + /** Maximum number of events per page; server default when {@code null} (optional). */ + public Integer count; + /** Number of events to skip from the start of the window (optional). */ + public Integer offset; + /** Direction to page from the {@code nextDate} cursor (optional). */ + public CalendarPageDirection next; } diff --git a/java/src/calendar_context.rs b/java/src/calendar_context.rs index 05e3df4e9..9f9e76b57 100644 --- a/java/src/calendar_context.rs +++ b/java/src/calendar_context.rs @@ -6,7 +6,11 @@ use jni::{ }; use longbridge::{CalendarContext, Config, calendar::types::*}; -use crate::{async_util, error::jni_result, types::get_field}; +use crate::{ + async_util, + error::jni_result, + types::{JavaInteger, get_field}, +}; struct ContextObj { ctx: CalendarContext, @@ -51,9 +55,12 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_calendarContextFinan let start: String = get_field(env, &opts, "start")?; let end: String = get_field(env, &opts, "end")?; let market: Option = get_field(env, &opts, "market")?; + let count = get_field::<_, _, Option>(env, &opts, "count")?.map(i32::from); + let offset = get_field::<_, _, Option>(env, &opts, "offset")?.map(i32::from); + let next: Option = get_field(env, &opts, "next")?; async_util::execute(env, callback, async move { Ok(__owned_ctx - .finance_calendar(category, start, end, market) + .finance_calendar(category, start, end, market, count, offset, next) .await?) })?; Ok(()) diff --git a/java/src/init.rs b/java/src/init.rs index 7a969fbc1..832c27625 100644 --- a/java/src/init.rs +++ b/java/src/init.rs @@ -141,6 +141,7 @@ pub extern "system" fn Java_com_longbridge_SdkNative_init<'a>( longbridge::alert::types::AlertCondition, longbridge::alert::types::AlertFrequency, longbridge::calendar::types::CalendarCategory, + longbridge::calendar::types::CalendarPageDirection, longbridge::agent::ConversationStatus, longbridge::grid::TriggerPriceType, longbridge::grid::GridLimitEvent, diff --git a/java/src/types/enum_types.rs b/java/src/types/enum_types.rs index 0b3bb2443..cdc682da2 100644 --- a/java/src/types/enum_types.rs +++ b/java/src/types/enum_types.rs @@ -527,6 +527,11 @@ impl_java_enum!( Report, Dividend, Split, Ipo, MacroData, Closed, Meeting, Merge ] ); +impl_java_enum!( + "com/longbridge/calendar/CalendarPageDirection", + longbridge::calendar::types::CalendarPageDirection, + [Later, Earlier] +); impl_java_enum!( "com/longbridge/fundamental/FinancialReportKind", longbridge::fundamental::types::FinancialReportKind, diff --git a/nodejs/index.d.ts b/nodejs/index.d.ts index 9b1815da5..9dbf27e98 100644 --- a/nodejs/index.d.ts +++ b/nodejs/index.d.ts @@ -250,8 +250,18 @@ export declare class CalendarContext { * * `start` and `end` are date strings in `YYYY-MM-DD` format. * `market` is an optional market filter (e.g. `"HK"` or `"US"`). + * + * The endpoint paginates: the server caps each response (historically 10 + * events per page unless a larger `count` is requested) and returns a + * `nextDate` cursor. To page through the full window, request a larger + * `count`, or re-call with the returned `nextDate` as `start`. + * + * - `count` — maximum number of events per page (server default when + * omitted). + * - `offset` — number of events to skip from the start of the window. + * - `next` — direction to page from the cursor. */ - financeCalendar(category: CalendarCategory, start: string, end: string, market?: string | undefined | null): Promise + financeCalendar(category: CalendarCategory, start: string, end: string, market?: string | undefined | null, count?: number | undefined | null, offset?: number | undefined | null, next?: CalendarPageDirection | undefined | null): Promise } /** Candlestick */ @@ -4366,6 +4376,14 @@ export interface CalendarEventsResponse { list: Array } +/** Pagination direction for `financeCalendar` (the `next` query parameter). */ +export declare const enum CalendarPageDirection { + /** Page towards later dates */ + Later = 0, + /** Page towards earlier dates */ + Earlier = 1 +} + export declare const enum CashFlowDirection { /** Unknown */ Unknown = 0, diff --git a/nodejs/index.js b/nodejs/index.js index a8f040323..7b13527fa 100644 --- a/nodejs/index.js +++ b/nodejs/index.js @@ -3,7 +3,7 @@ // @ts-nocheck /* auto-generated by NAPI-RS */ -const { readFileSync } = require('fs') +const { readFileSync } = require('node:fs') let nativeBinding = null const loadErrors = [] @@ -33,7 +33,7 @@ const isMuslFromFilesystem = () => { const isMuslFromReport = () => { let report = null - if (process.report && typeof process.report.getReport === 'function') { + if (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 && process.config.variables && process.config.variables.shlib_suffix === 'dll.a') || (process.config && process.config.variables && process.config.variables.node_target_type === 'shared_library')) { + if (process.config?.variables?.shlib_suffix === 'dll.a' || process.config?.variables?.node_target_type === 'shared_library') { try { return require('./longbridge.win32-x64-gnu.node') } catch (e) { @@ -523,178 +523,54 @@ function 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() -} +nativeBinding = requireNative() -if (!nativeBinding || forceWasi) { +if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) { let wasiBinding = null - 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 + let wasiBindingError = null + try { + wasiBinding = require('./longbridge.wasi.cjs') + nativeBinding = wasiBinding + } catch (err) { + if (process.env.NAPI_RS_FORCE_WASI) { + wasiBindingError = err } - return null } - if (!wasiBindingLoaded && (!__napiWasiFlavorRequested || __napiWasiFlavor === "wasm32-wasi")) { - let candidateError = null - let candidateFailed = false + if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) { try { - 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 - } + wasiBinding = require('longbridge-wasm32-wasi') + nativeBinding = wasiBinding } catch (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.`) - } + if (process.env.NAPI_RS_FORCE_WASI) { + if (!wasiBindingError) { + wasiBindingError = err + } else { + wasiBindingError.cause = err } - wasiBinding = require('longbridge-wasm32-wasi') - nativeBinding = wasiBinding - wasiBindingLoaded = true + loadErrors.push(err) } - } catch (err) { - candidateError = err - candidateFailed = true - } - if (candidateFailed) { - wasiBindingErrors.push(candidateError) - loadErrors.push(candidateError) } } - 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) + 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 throw error } } if (!nativeBinding) { if (loadErrors.length > 0) { - const error = new Error( + throw 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`) } @@ -818,6 +694,7 @@ module.exports.BalanceType = nativeBinding.BalanceType module.exports.BrokerHoldingPeriod = nativeBinding.BrokerHoldingPeriod module.exports.CalcIndex = nativeBinding.CalcIndex module.exports.CalendarCategory = nativeBinding.CalendarCategory +module.exports.CalendarPageDirection = nativeBinding.CalendarPageDirection module.exports.CashFlowDirection = nativeBinding.CashFlowDirection module.exports.ChargeCategoryCode = nativeBinding.ChargeCategoryCode module.exports.CommissionFreeStatus = nativeBinding.CommissionFreeStatus diff --git a/nodejs/src/calendar/context.rs b/nodejs/src/calendar/context.rs index a359460dc..c9ae7b100 100644 --- a/nodejs/src/calendar/context.rs +++ b/nodejs/src/calendar/context.rs @@ -25,6 +25,16 @@ impl CalendarContext { /// /// `start` and `end` are date strings in `YYYY-MM-DD` format. /// `market` is an optional market filter (e.g. `"HK"` or `"US"`). + /// + /// The endpoint paginates: the server caps each response (historically 10 + /// events per page unless a larger `count` is requested) and returns a + /// `nextDate` cursor. To page through the full window, request a larger + /// `count`, or re-call with the returned `nextDate` as `start`. + /// + /// - `count` — maximum number of events per page (server default when + /// omitted). + /// - `offset` — number of events to skip from the start of the window. + /// - `next` — direction to page from the cursor. #[napi] pub async fn finance_calendar( &self, @@ -32,10 +42,21 @@ impl CalendarContext { start: String, end: String, market: Option, + count: Option, + offset: Option, + next: Option, ) -> Result { Ok(self .ctx - .finance_calendar(category.into(), start, end, market) + .finance_calendar( + category.into(), + start, + end, + market, + count, + offset, + next.map(Into::into), + ) .await .map_err(ErrorNewType)? .into()) diff --git a/nodejs/src/calendar/types.rs b/nodejs/src/calendar/types.rs index b579a0236..645d1634f 100644 --- a/nodejs/src/calendar/types.rs +++ b/nodejs/src/calendar/types.rs @@ -158,3 +158,21 @@ impl From for lb::CalendarCategory { } } } + +/// Pagination direction for `financeCalendar` (the `next` query parameter). +#[napi_derive::napi] +#[derive(Debug, Clone, Copy)] +pub enum CalendarPageDirection { + /// Page towards later dates + Later, + /// Page towards earlier dates + Earlier, +} +impl From for lb::CalendarPageDirection { + fn from(v: CalendarPageDirection) -> Self { + match v { + CalendarPageDirection::Later => lb::CalendarPageDirection::Later, + CalendarPageDirection::Earlier => lb::CalendarPageDirection::Earlier, + } + } +} diff --git a/python/pysrc/longbridge/openapi.pyi b/python/pysrc/longbridge/openapi.pyi index 94f5bf151..90721a89a 100644 --- a/python/pysrc/longbridge/openapi.pyi +++ b/python/pysrc/longbridge/openapi.pyi @@ -13666,6 +13666,15 @@ class CalendarCategory: """Stock consolidations / mergers""" +class CalendarPageDirection: + """Pagination direction for ``finance_calendar`` (the ``next`` parameter).""" + + class Later(CalendarPageDirection): ... + """Page towards later dates""" + class Earlier(CalendarPageDirection): ... + """Page towards earlier dates""" + + class CalendarContext: """ Financial calendar context. @@ -13694,15 +13703,26 @@ class CalendarContext: start: str, end: str, market: str | None = None, + count: int | None = None, + offset: int | None = None, + next: "CalendarPageDirection | None" = None, ) -> "CalendarEventsResponse": """ Get financial calendar events. + The endpoint paginates: the server caps each response (historically 10 + events per page unless a larger ``count`` is requested) and returns a + ``next_date`` cursor. To page through the full window, request a larger + ``count``, or re-call with the returned ``next_date`` as ``start``. + Args: category: Event category start: Start date in ``YYYY-MM-DD`` format end: End date in ``YYYY-MM-DD`` format market: Optional market filter, e.g. ``"HK"`` + count: Maximum number of events per page (server default when omitted) + offset: Number of events to skip from the start of the window + next: Direction to page from the cursor """ ... @@ -13724,15 +13744,26 @@ class AsyncCalendarContext: start: str, end: str, market: str | None = None, + count: int | None = None, + offset: int | None = None, + next: "CalendarPageDirection | None" = None, ) -> "CalendarEventsResponse": """ Get financial calendar events. + The endpoint paginates: the server caps each response (historically 10 + events per page unless a larger ``count`` is requested) and returns a + ``next_date`` cursor. To page through the full window, request a larger + ``count``, or re-call with the returned ``next_date`` as ``start``. + Args: category: Event category start: Start date in ``YYYY-MM-DD`` format end: End date in ``YYYY-MM-DD`` format market: Optional market filter, e.g. ``"HK"`` + count: Maximum number of events per page (server default when omitted) + offset: Number of events to skip from the start of the window + next: Direction to page from the cursor """ ... diff --git a/python/src/calendar/context.rs b/python/src/calendar/context.rs index 91a91d405..01038fa83 100644 --- a/python/src/calendar/context.rs +++ b/python/src/calendar/context.rs @@ -21,17 +21,33 @@ impl CalendarContext { } /// Get financial calendar events. - #[pyo3(signature = (category, start, end, market = None))] + /// + /// The endpoint paginates: the server caps each response (historically 10 + /// events per page unless a larger ``count`` is requested) and returns a + /// ``next_date`` cursor. To page through the full window, request a larger + /// ``count``, or re-call with the returned ``next_date`` as ``start``. + #[pyo3(signature = (category, start, end, market = None, count = None, offset = None, next = None))] fn finance_calendar( &self, category: CalendarCategory, start: String, end: String, market: Option, + count: Option, + offset: Option, + next: Option, ) -> PyResult { Ok(self .ctx - .finance_calendar(category.into(), start, end, market) + .finance_calendar( + category.into(), + start, + end, + market, + count, + offset, + next.map(Into::into), + ) .map_err(ErrorNewType)? .into()) } diff --git a/python/src/calendar/context_async.rs b/python/src/calendar/context_async.rs index c384c0f24..4f41d3133 100644 --- a/python/src/calendar/context_async.rs +++ b/python/src/calendar/context_async.rs @@ -20,7 +20,7 @@ impl AsyncCalendarContext { } } - #[pyo3(signature = (category, start, end, market = None))] + #[pyo3(signature = (category, start, end, market = None, count = None, offset = None, next = None))] fn finance_calendar( &self, py: Python<'_>, @@ -28,13 +28,24 @@ impl AsyncCalendarContext { start: String, end: String, market: Option, + count: Option, + offset: Option, + next: Option, ) -> PyResult> { let ctx = self.ctx.clone(); pyo3_async_runtimes::tokio::future_into_py(py, async move { Ok(CalendarEventsResponse::from( - ctx.finance_calendar(category.into(), start, end, market) - .await - .map_err(ErrorNewType)?, + ctx.finance_calendar( + category.into(), + start, + end, + market, + count, + offset, + next.map(Into::into), + ) + .await + .map_err(ErrorNewType)?, )) }) .map(|b| b.unbind()) diff --git a/python/src/calendar/mod.rs b/python/src/calendar/mod.rs index b2639893f..f739c2497 100644 --- a/python/src/calendar/mod.rs +++ b/python/src/calendar/mod.rs @@ -9,6 +9,7 @@ pub(crate) fn register_types(parent: &Bound) -> PyResult<()> { parent.add_class::()?; parent.add_class::()?; parent.add_class::()?; + parent.add_class::()?; parent.add_class::()?; parent.add_class::()?; Ok(()) diff --git a/python/src/calendar/types.rs b/python/src/calendar/types.rs index 70205cbe3..03bd2d013 100644 --- a/python/src/calendar/types.rs +++ b/python/src/calendar/types.rs @@ -184,3 +184,22 @@ impl From for lb::CalendarCategory { } } } + +/// Pagination direction for `finance_calendar` (the `next` query parameter). +#[pyclass(eq, eq_int, from_py_object)] +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] +pub(crate) enum CalendarPageDirection { + /// Page towards later dates + Later = 0, + /// Page towards earlier dates + Earlier = 1, +} + +impl From for lb::CalendarPageDirection { + fn from(v: CalendarPageDirection) -> Self { + match v { + CalendarPageDirection::Later => lb::CalendarPageDirection::Later, + CalendarPageDirection::Earlier => lb::CalendarPageDirection::Earlier, + } + } +} diff --git a/rust/src/blocking/calendar.rs b/rust/src/blocking/calendar.rs index 31a102b5c..ad19203d8 100644 --- a/rust/src/blocking/calendar.rs +++ b/rust/src/blocking/calendar.rs @@ -29,15 +29,20 @@ impl CalendarContextSync { } /// Get financial calendar events + #[allow(clippy::too_many_arguments)] pub fn finance_calendar( &self, category: CalendarCategory, start: impl Into + Send + 'static, end: impl Into + Send + 'static, market: Option, + count: Option, + offset: Option, + next: Option, ) -> Result { - self.rt.call( - move |ctx| async move { ctx.finance_calendar(category, start, end, market).await }, - ) + self.rt.call(move |ctx| async move { + ctx.finance_calendar(category, start, end, market, count, offset, next) + .await + }) } } diff --git a/rust/src/calendar/context.rs b/rust/src/calendar/context.rs index 5cbf6d90f..e025e1679 100644 --- a/rust/src/calendar/context.rs +++ b/rust/src/calendar/context.rs @@ -48,8 +48,18 @@ impl CalendarContext { /// Get financial calendar events. /// - /// The endpoint is paginated via `next_date`. When the returned - /// `next_date` is non-empty, pass it as `start` to fetch the next page. + /// The endpoint is paginated. The server caps each response (it + /// historically returns at most 10 events per page unless a larger + /// `count` is requested) and reports a `next_date` cursor. To page + /// through the full window, either request a larger `count`, or re-call + /// this method passing the returned `next_date` as `start` until + /// `next_date` comes back empty. + /// + /// - `count` — maximum number of events per page (server default when + /// `None`). + /// - `offset` — number of events to skip from the start of the window. + /// - `next` — direction to page from the cursor (see + /// [`CalendarPageDirection`]). /// /// Path: `GET /v1/quote/finance_calendar` pub async fn finance_calendar( @@ -58,6 +68,9 @@ impl CalendarContext { start: impl Into, end: impl Into, market: Option, + count: Option, + offset: Option, + next: Option, ) -> Result { let cat_str = match category { CalendarCategory::Report => "report", @@ -69,6 +82,10 @@ impl CalendarContext { CalendarCategory::Meeting => "meeting", CalendarCategory::Merge => "merge", }; + let next_str = next.map(|d| match d { + CalendarPageDirection::Later => "later", + CalendarPageDirection::Earlier => "earlier", + }); #[derive(Serialize)] struct Query { date: String, @@ -77,6 +94,12 @@ impl CalendarContext { types: &'static str, #[serde(rename = "markets[]", skip_serializing_if = "Option::is_none")] markets: Option, + #[serde(skip_serializing_if = "Option::is_none")] + count: Option, + #[serde(skip_serializing_if = "Option::is_none")] + offset: Option, + #[serde(skip_serializing_if = "Option::is_none")] + next: Option<&'static str>, } Ok(self .0 @@ -87,6 +110,9 @@ impl CalendarContext { date_end: end.into(), types: cat_str, markets: market, + count, + offset, + next: next_str, }) .response::>() .send() diff --git a/rust/src/calendar/types.rs b/rust/src/calendar/types.rs index 54914cba1..7a4514f65 100644 --- a/rust/src/calendar/types.rs +++ b/rust/src/calendar/types.rs @@ -116,3 +116,17 @@ pub enum CalendarCategory { #[serde(rename = "merge")] Merge, } + +/// Pagination direction for [`crate::CalendarContext::finance_calendar`]. +/// +/// Passed as the `next` query parameter to control which side of the cursor +/// the next page is taken from. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum CalendarPageDirection { + /// Page towards later dates (`next=later`) + #[serde(rename = "later")] + Later, + /// Page towards earlier dates (`next=earlier`) + #[serde(rename = "earlier")] + Earlier, +}