@oomol-lab/open-connector 1.6.3 → 1.6.5
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/assets/open-connector/catalog/apps/aisa.json +568 -0
- package/assets/open-connector/catalog/apps/bilibili.json +2586 -0
- package/assets/open-connector/catalog/apps/bitwarden.json +1423 -0
- package/assets/open-connector/catalog/apps/cronfree.json +303 -0
- package/assets/open-connector/catalog/apps/cufinder.json +368 -0
- package/assets/open-connector/catalog/apps/feishu_app_bot.json +2 -2
- package/assets/open-connector/catalog/apps/gmail.json +73 -0
- package/assets/open-connector/catalog/apps/investoday_mcp.json +22228 -197
- package/assets/open-connector/catalog/apps/ixspy.json +2274 -0
- package/assets/open-connector/catalog/apps/jin10.json +1 -1
- package/assets/open-connector/catalog/apps/osv.json +663 -0
- package/assets/open-connector/catalog/apps/pdf_vector.json +329 -0
- package/assets/open-connector/catalog/apps/pulsetic.json +406 -0
- package/assets/open-connector/catalog/apps/shopee.json +1785 -0
- package/assets/open-connector/catalog/apps/slack.json +81 -0
- package/assets/open-connector/catalog/apps/slackbot.json +81 -0
- package/assets/open-connector/catalog/apps/speak_ai.json +360 -0
- package/assets/open-connector/catalog/apps/tongdaxin.json +11 -440
- package/assets/open-connector/catalog/apps/zyhub.json +1585 -0
- package/assets/open-connector/catalog/apps-index.json +1 -1
- package/package.json +1 -1
- package/src/core/json-schema.d.ts +5 -1
- package/src/core/json-schema.js +14 -2
- package/src/core/types.d.ts +9 -0
- package/src/oauth/oauth-credential-refresh-service.js +3 -1
- package/src/oauth/oauth-flow-service.js +17 -2
- package/src/oauth/oauth-token.d.ts +13 -0
- package/src/providers/action-contracts.generated.d.ts +16 -4
- package/src/providers/aisa/executors.d.ts +4 -0
- package/src/providers/aisa/executors.js +176 -0
- package/src/providers/bilibili/articles.d.ts +3 -0
- package/src/providers/bilibili/articles.js +369 -0
- package/src/providers/bilibili/executors.d.ts +8 -0
- package/src/providers/bilibili/executors.js +262 -0
- package/src/providers/bilibili/runtime.d.ts +95 -0
- package/src/providers/bilibili/runtime.js +238 -0
- package/src/providers/bilibili/upload.d.ts +14 -0
- package/src/providers/bilibili/upload.js +105 -0
- package/src/providers/bitwarden/executors.d.ts +3 -0
- package/src/providers/bitwarden/executors.js +146 -0
- package/src/providers/cronfree/executors.d.ts +4 -0
- package/src/providers/cronfree/executors.js +89 -0
- package/src/providers/cufinder/executors.d.ts +4 -0
- package/src/providers/cufinder/executors.js +105 -0
- package/src/providers/dropbox/executors.js +21 -3
- package/src/providers/elevenreader/runtime.js +6 -5
- package/src/providers/feishu_app_bot/scopes.js +1 -1
- package/src/providers/gmail/attachment-stream.d.ts +2 -0
- package/src/providers/gmail/attachment-stream.js +162 -0
- package/src/providers/gmail/executors.d.ts +3 -1
- package/src/providers/gmail/executors.js +53 -25
- package/src/providers/investoday_mcp/official-actions.js +19 -372
- package/src/providers/investoday_mcp/official-tool-types.d.ts +10 -0
- package/src/providers/investoday_mcp/official-tool-types.js +4 -0
- package/src/providers/investoday_mcp/official-tools/charts.d.ts +2 -0
- package/src/providers/investoday_mcp/official-tools/charts.js +32 -0
- package/src/providers/investoday_mcp/official-tools/fixed-income.d.ts +2 -0
- package/src/providers/investoday_mcp/official-tools/fixed-income.js +326 -0
- package/src/providers/investoday_mcp/official-tools/funds.d.ts +2 -0
- package/src/providers/investoday_mcp/official-tools/funds.js +1085 -0
- package/src/providers/investoday_mcp/official-tools/hong-kong.d.ts +2 -0
- package/src/providers/investoday_mcp/official-tools/hong-kong.js +275 -0
- package/src/providers/investoday_mcp/official-tools/indices-industries.d.ts +2 -0
- package/src/providers/investoday_mcp/official-tools/indices-industries.js +642 -0
- package/src/providers/investoday_mcp/official-tools/macro-calendar.d.ts +2 -0
- package/src/providers/investoday_mcp/official-tools/macro-calendar.js +398 -0
- package/src/providers/investoday_mcp/official-tools/other.d.ts +2 -0
- package/src/providers/investoday_mcp/official-tools/other.js +131 -0
- package/src/providers/investoday_mcp/official-tools/research-news.d.ts +2 -0
- package/src/providers/investoday_mcp/official-tools/research-news.js +302 -0
- package/src/providers/investoday_mcp/official-tools/stocks.d.ts +2 -0
- package/src/providers/investoday_mcp/official-tools/stocks.js +2829 -0
- package/src/providers/ixspy/executors.d.ts +3 -0
- package/src/providers/ixspy/executors.js +220 -0
- package/src/providers/mcp-client.d.ts +1 -0
- package/src/providers/mcp-client.js +41 -27
- package/src/providers/osv/executors.d.ts +2 -0
- package/src/providers/osv/executors.js +11 -0
- package/src/providers/osv/runtime.d.ts +9 -0
- package/src/providers/osv/runtime.js +66 -0
- package/src/providers/pdf_vector/executors.d.ts +4 -0
- package/src/providers/pdf_vector/executors.js +108 -0
- package/src/providers/provider-runtime.d.ts +3 -0
- package/src/providers/provider-runtime.js +4 -1
- package/src/providers/pulsetic/executors.d.ts +4 -0
- package/src/providers/pulsetic/executors.js +126 -0
- package/src/providers/registry.generated.js +12 -0
- package/src/providers/sellersprite_mcp/executors.js +2 -1
- package/src/providers/shopee/actions.d.ts +2 -0
- package/src/providers/shopee/actions.js +474 -0
- package/src/providers/shopee/executors.d.ts +3 -0
- package/src/providers/shopee/executors.js +85 -0
- package/src/providers/shopee/runtime.d.ts +79 -0
- package/src/providers/shopee/runtime.js +693 -0
- package/src/providers/slack/actions.js +17 -0
- package/src/providers/slack/runtime.js +62 -4
- package/src/providers/speak_ai/executors.d.ts +3 -0
- package/src/providers/speak_ai/executors.js +187 -0
- package/src/providers/tongdaxin/executors.js +5 -4
- package/src/providers/tongdaxin/named-action-output.js +3 -9
- package/src/providers/tongdaxin/named-action-routing.js +1 -1
- package/src/providers/tongdaxin/named-actions.js +1 -6
- package/src/providers/tushare/runtime.js +22 -9
- package/src/providers/twenty_crm/runtime.js +4 -2
- package/src/providers/zyhub/executors.d.ts +3 -0
- package/src/providers/zyhub/executors.js +240 -0
- package/src/server/files/transit-files.d.ts +3 -2
- package/src/server/files/transit-files.js +31 -35
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { looseArray, optionalBoolean, optionalInteger, optionalRecord, optionalString } from "../../core/cast.js";
|
|
2
|
+
import { readBoundedResponseBytes } from "../../core/request.js";
|
|
3
|
+
import { defaultProviderJsonMaxResponseBytes, defineApiKeyProviderExecutors, ProviderRequestError, providerUserAgent, readProviderTextBody, runProviderRequest, } from "../provider-runtime.js";
|
|
4
|
+
const service = "zyhub";
|
|
5
|
+
const baseUrl = "https://mcp.finance.sina.com.cn/api-call/";
|
|
6
|
+
const operations = {
|
|
7
|
+
search_symbols: {
|
|
8
|
+
code: "globalStockSearchSymbols",
|
|
9
|
+
query: (i) => ({ type: i.marketTypes, key: i.query, format: "text", num: 4 }),
|
|
10
|
+
},
|
|
11
|
+
get_stock_quote: { code: "globalStockQuoteRealtime", query: (i) => pick(i, { market: "market", symbol: "symbol" }) },
|
|
12
|
+
get_daily_kline: {
|
|
13
|
+
code: "globalStockKlineDaily",
|
|
14
|
+
query: (i) => ({
|
|
15
|
+
market: i.market,
|
|
16
|
+
symbol: i.symbol,
|
|
17
|
+
fq_type: i.adjustment,
|
|
18
|
+
compare: i.comparison,
|
|
19
|
+
in_date: i.date,
|
|
20
|
+
num: i.limit,
|
|
21
|
+
}),
|
|
22
|
+
},
|
|
23
|
+
get_company_profile: { code: "cnCompanyBasicInfo", query: symbol },
|
|
24
|
+
get_forex_quote: { code: "forexQuoteLatest", query: symbol },
|
|
25
|
+
search_news: { code: "newsSearch", query: (i) => ({ keyword: join(i.keywords), page: i.page, num: i.pageSize }) },
|
|
26
|
+
get_intraday_kline: {
|
|
27
|
+
code: "cnStockKLine",
|
|
28
|
+
query: (i) => ({ symbol: i.symbol, scale: i.intervalMinutes, datalen: i.limit }),
|
|
29
|
+
},
|
|
30
|
+
list_financial_report_dates: { code: "cnFinanceReportDateList", query: (i) => ({ paperCode: i.symbol }) },
|
|
31
|
+
get_financial_report: {
|
|
32
|
+
code: "cnFinanceReportsFull",
|
|
33
|
+
query: (i) => ({
|
|
34
|
+
paperCode: i.symbol,
|
|
35
|
+
source: map(i.reportType, {
|
|
36
|
+
income_statement: "lrb",
|
|
37
|
+
balance_sheet: "fzb",
|
|
38
|
+
cash_flow: "llb",
|
|
39
|
+
key_indicators: "gjzb",
|
|
40
|
+
special_indicators: "zxzb",
|
|
41
|
+
}),
|
|
42
|
+
rDate: i.reportDate,
|
|
43
|
+
}),
|
|
44
|
+
},
|
|
45
|
+
get_revenue_composition: {
|
|
46
|
+
code: "cnFinanceRevenueComposition",
|
|
47
|
+
query: (i) => ({ paperCode: i.symbol, frDate: i.reportDate }),
|
|
48
|
+
},
|
|
49
|
+
get_valuation_history: {
|
|
50
|
+
code: "cnStockValuationDetail",
|
|
51
|
+
query: (i) => ({
|
|
52
|
+
symbol: i.symbol,
|
|
53
|
+
rank: map(i.period, { "1y": "y1", "3y": "y3", "5y": "y5", "10y": "y10", all: "all" }),
|
|
54
|
+
type: map(i.metric, { pe_ttm: "syl", pb: "sjl", pcf: "sxl", dividend_yield: "gxl", market_cap: "zsz" }),
|
|
55
|
+
}),
|
|
56
|
+
},
|
|
57
|
+
get_major_events: {
|
|
58
|
+
code: "globalStockMajorEvents",
|
|
59
|
+
query: (i) => ({ market: map(i.market, { cn: "0", hk: "1", us: "2" }), symbols: i.symbol, pageSize: i.limit }),
|
|
60
|
+
},
|
|
61
|
+
get_margin_trading: { code: "cnStockTradingMarginList", query: pageSymbol },
|
|
62
|
+
get_block_trades: { code: "cnTradingBlockList", query: pageSymbol },
|
|
63
|
+
list_stock_connect_holdings: {
|
|
64
|
+
code: "cnStockConnectHoldings",
|
|
65
|
+
query: (i) => ({ type: i.channel, sort: i.sortBy, asc: direction(i.sortDirection), page: i.page, num: i.pageSize }),
|
|
66
|
+
},
|
|
67
|
+
get_stock_industry: { code: "swSymbolList", query: symbol },
|
|
68
|
+
list_sector_rankings: {
|
|
69
|
+
code: "cnVirtualSectorRanking",
|
|
70
|
+
query: (i) => ({
|
|
71
|
+
index_type: map(i.sectorType, {
|
|
72
|
+
all: "",
|
|
73
|
+
shenwan_level_1: "hy1",
|
|
74
|
+
shenwan_level_2: "hy",
|
|
75
|
+
shenwan_level_3: "hy3",
|
|
76
|
+
concept: "gn",
|
|
77
|
+
region: "dy",
|
|
78
|
+
}),
|
|
79
|
+
sort: i.sortBy,
|
|
80
|
+
asc: direction(i.sortDirection),
|
|
81
|
+
page: i.page,
|
|
82
|
+
num: i.pageSize,
|
|
83
|
+
}),
|
|
84
|
+
},
|
|
85
|
+
get_sector_constituents: {
|
|
86
|
+
code: "cnSectorComponentsRanking",
|
|
87
|
+
query: (i) => ({
|
|
88
|
+
node: i.sectorSymbol,
|
|
89
|
+
ret: join(i.fields),
|
|
90
|
+
sort: i.sortBy,
|
|
91
|
+
asc: direction(i.sortDirection),
|
|
92
|
+
page: i.page,
|
|
93
|
+
num: i.pageSize,
|
|
94
|
+
hnew: flag(i.includeNewStocks),
|
|
95
|
+
hcnew: flag(i.includeRecentStocks),
|
|
96
|
+
}),
|
|
97
|
+
},
|
|
98
|
+
list_strong_sectors: {
|
|
99
|
+
code: "cnMarketStrongSectors",
|
|
100
|
+
query: (i) => ({
|
|
101
|
+
type: map(i.boardGroup, { all: "all", growth: "ck", other: "other" }),
|
|
102
|
+
bk: map(i.sectorType, { concept: "gn", industry: "hy", region: "dy" }),
|
|
103
|
+
isNotSt: flag(i.excludeSt),
|
|
104
|
+
}),
|
|
105
|
+
},
|
|
106
|
+
list_limit_up_stocks: { code: "cnMarketLimitUpPool" },
|
|
107
|
+
list_consecutive_limit_up_stocks: { code: "cnStockLianBC" },
|
|
108
|
+
list_hot_stocks: {
|
|
109
|
+
code: "globalStockHotBoard",
|
|
110
|
+
query: (i) => ({ type: map(i.period, { day: "d", hour: "h" }), market: i.market, page: i.page, num: i.pageSize }),
|
|
111
|
+
},
|
|
112
|
+
get_batch_forex_quotes: {
|
|
113
|
+
code: "forexQuotesBatch",
|
|
114
|
+
query: (i) => ({ from: i.baseCurrency, to: join(i.targetCurrencies) }),
|
|
115
|
+
},
|
|
116
|
+
get_futures_quote: {
|
|
117
|
+
code: "future_quotes",
|
|
118
|
+
query: (i) => ({ market: map(i.market, { domestic: "gn", global: "global", financial: "cff" }), symbol: i.symbol }),
|
|
119
|
+
},
|
|
120
|
+
search_stock_news: {
|
|
121
|
+
code: "stockNewsSearch",
|
|
122
|
+
query: (i) => ({ market: i.market, symbol: i.symbol, page: i.page, num: i.pageSize }),
|
|
123
|
+
},
|
|
124
|
+
search_flash_news: {
|
|
125
|
+
code: "qNewsSearch",
|
|
126
|
+
query: (i) => ({ keyword: join(i.keywords), page: i.page, num: i.pageSize }),
|
|
127
|
+
},
|
|
128
|
+
list_flash_news: {
|
|
129
|
+
code: "newsFlashList",
|
|
130
|
+
query: (i) => ({
|
|
131
|
+
id: i.documentId,
|
|
132
|
+
type: map(i.direction, { after: "0", before: "1" }),
|
|
133
|
+
page: i.page,
|
|
134
|
+
num: i.pageSize,
|
|
135
|
+
}),
|
|
136
|
+
},
|
|
137
|
+
get_news_article: { code: "newsArticleDetail", query: (i) => ({ docid: i.documentId }) },
|
|
138
|
+
get_fund_profile: { code: "fund_info", query: symbol },
|
|
139
|
+
get_fund_net_values: { code: "fund_networth", query: symbol },
|
|
140
|
+
get_fund_returns: { code: "fund_cum_return", query: symbol },
|
|
141
|
+
get_fund_metrics: { code: "fund_core_metrics", query: symbol },
|
|
142
|
+
get_fund_stock_holdings: { code: "fund_heavy_stock", query: pageSymbol },
|
|
143
|
+
get_fund_industry_allocation: { code: "fund_industry_alloc", query: symbol },
|
|
144
|
+
get_fund_asset_allocation: { code: "fund_asset_alloc", query: symbol },
|
|
145
|
+
get_fund_manager: { code: "fund_manager_detail", query: (i) => ({ pscode: i.managerCode }) },
|
|
146
|
+
};
|
|
147
|
+
const handlers = Object.fromEntries(Object.entries(operations).map(([name, operation]) => [
|
|
148
|
+
name,
|
|
149
|
+
(input, context) => execute(operation, input, context),
|
|
150
|
+
]));
|
|
151
|
+
export const executors = defineApiKeyProviderExecutors(service, handlers, {
|
|
152
|
+
skipDnsValidation: true,
|
|
153
|
+
});
|
|
154
|
+
export const credentialValidators = {
|
|
155
|
+
async apiKey(input, context) {
|
|
156
|
+
await request("cnMarketUpdownDistribution", {}, input.apiKey, context.fetcher, context.signal, true);
|
|
157
|
+
return {
|
|
158
|
+
profile: { accountId: "zyhub-api-key", displayName: "Sina Finance ZYHub API Key" },
|
|
159
|
+
grantedScopes: [],
|
|
160
|
+
metadata: { apiBaseUrl: baseUrl, validationEndpoint: "cnMarketUpdownDistribution" },
|
|
161
|
+
};
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
async function execute(operation, input, context) {
|
|
165
|
+
return {
|
|
166
|
+
response: await request(operation.code, operation.query?.(input) ?? {}, context.apiKey, context.fetcher, context.signal, false),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
async function request(code, query, apiKey, fetcher, parentSignal, validating) {
|
|
170
|
+
return runProviderRequest({ signal: parentSignal, label: "Sina Finance ZYHub" }, async (signal) => {
|
|
171
|
+
const url = new URL(code, baseUrl);
|
|
172
|
+
for (const [key, value] of Object.entries(query))
|
|
173
|
+
if (value != null)
|
|
174
|
+
url.searchParams.set(key, String(value));
|
|
175
|
+
const response = await fetcher(url, {
|
|
176
|
+
headers: { accept: "application/json", "user-agent": providerUserAgent, "X-Auth-Token": apiKey },
|
|
177
|
+
signal,
|
|
178
|
+
});
|
|
179
|
+
const text = code === "globalStockSearchSymbols" && response.ok
|
|
180
|
+
? new TextDecoder("gb18030").decode(await readBoundedResponseBytes(response, {
|
|
181
|
+
maxBytes: defaultProviderJsonMaxResponseBytes,
|
|
182
|
+
fieldName: "ZYHub response",
|
|
183
|
+
createError: (message) => new ProviderRequestError(413, message),
|
|
184
|
+
}))
|
|
185
|
+
: await readProviderTextBody(response, "ZYHub response");
|
|
186
|
+
const payload = parse(text);
|
|
187
|
+
if (!response.ok) {
|
|
188
|
+
const message = errorMessage(payload) ?? `ZYHub request failed with status ${response.status}`;
|
|
189
|
+
if (response.status === 429)
|
|
190
|
+
throw new ProviderRequestError(429, message, payload);
|
|
191
|
+
if (validating && response.status === 401)
|
|
192
|
+
throw new ProviderRequestError(400, message, payload);
|
|
193
|
+
throw new ProviderRequestError(response.status, message, payload);
|
|
194
|
+
}
|
|
195
|
+
return payload;
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
function parse(text) {
|
|
199
|
+
if (!text.trim())
|
|
200
|
+
return null;
|
|
201
|
+
try {
|
|
202
|
+
return JSON.parse(text);
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
return text;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function errorMessage(value) {
|
|
209
|
+
if (typeof value === "string" && value.trim())
|
|
210
|
+
return value.trim();
|
|
211
|
+
const body = optionalRecord(value);
|
|
212
|
+
return optionalString(body?.message) ?? optionalString(body?.msg) ?? optionalString(body?.error);
|
|
213
|
+
}
|
|
214
|
+
function map(value, values) {
|
|
215
|
+
const key = optionalString(value);
|
|
216
|
+
return key == null ? undefined : values[key];
|
|
217
|
+
}
|
|
218
|
+
function direction(value) {
|
|
219
|
+
return map(value, { desc: "0", asc: "1" });
|
|
220
|
+
}
|
|
221
|
+
function flag(value) {
|
|
222
|
+
const result = optionalBoolean(value);
|
|
223
|
+
return result == null ? undefined : result ? 1 : 0;
|
|
224
|
+
}
|
|
225
|
+
function join(value) {
|
|
226
|
+
return value == null ? undefined : looseArray(value).join(",");
|
|
227
|
+
}
|
|
228
|
+
function symbol(input) {
|
|
229
|
+
return { symbol: optionalString(input.symbol) };
|
|
230
|
+
}
|
|
231
|
+
function pageSymbol(input) {
|
|
232
|
+
return {
|
|
233
|
+
symbol: optionalString(input.symbol),
|
|
234
|
+
page: optionalInteger(input.page),
|
|
235
|
+
num: optionalInteger(input.pageSize),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function pick(input, fields) {
|
|
239
|
+
return Object.fromEntries(Object.entries(fields).map(([to, from]) => [to, input[from]]));
|
|
240
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TransitFileRead, TransitFileUpload } from "../../core/types.ts";
|
|
1
|
+
import type { TransitFileRead, TransitFileStream, TransitFileUpload } from "../../core/types.ts";
|
|
2
2
|
import type { IStagedTransitFileService, StagedTransitFile } from "./transit-file-store.ts";
|
|
3
3
|
export interface TransitFileOptions {
|
|
4
4
|
rootDir: string;
|
|
@@ -13,6 +13,7 @@ export declare class TransitFileService implements IStagedTransitFileService {
|
|
|
13
13
|
readonly maxBytes: number;
|
|
14
14
|
constructor(options: TransitFileOptions);
|
|
15
15
|
create(file: File): Promise<TransitFileUpload>;
|
|
16
|
+
createFromStream(file: TransitFileStream): Promise<TransitFileUpload>;
|
|
16
17
|
createFromPath(file: StagedTransitFile): Promise<TransitFileUpload>;
|
|
17
18
|
read(fileId: string): Promise<TransitFileRead>;
|
|
18
19
|
response(fileId: string): Promise<Response>;
|
|
@@ -21,5 +22,5 @@ export declare class TransitFileService implements IStagedTransitFileService {
|
|
|
21
22
|
/** Resolve a live, unexpired file on disk together with its side-car metadata, or report it as missing. */
|
|
22
23
|
private locate;
|
|
23
24
|
private readMetadata;
|
|
24
|
-
private
|
|
25
|
+
private writeStream;
|
|
25
26
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { once } from "node:events";
|
|
2
1
|
import { createReadStream, createWriteStream } from "node:fs";
|
|
3
2
|
import { mkdir, readFile, readdir, rename, stat, unlink, writeFile } from "node:fs/promises";
|
|
4
3
|
import { join } from "node:path";
|
|
5
4
|
import { Readable } from "node:stream";
|
|
6
|
-
import {
|
|
5
|
+
import { pipeline } from "node:stream/promises";
|
|
7
6
|
import { assertFileSize, assertSafeFileId, contentTypeFromFileId, isSafeFileId, normalizeDescriptor, randomHex, safeExtension, TransitFileError, transitFileRead, transitFileResponse, uploadResult, } from "./transit-file-store.js";
|
|
8
7
|
export class TransitFileService {
|
|
9
8
|
rootDir;
|
|
@@ -18,19 +17,33 @@ export class TransitFileService {
|
|
|
18
17
|
}
|
|
19
18
|
async create(file) {
|
|
20
19
|
assertFileSize(file.size, this.maxBytes);
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
return this.createFromStream({ body: file.stream(), name: file.name, mimeType: file.type });
|
|
21
|
+
}
|
|
22
|
+
async createFromStream(file) {
|
|
23
23
|
const fileId = `${randomHex(16)}${safeExtension(file.name)}`;
|
|
24
24
|
const path = join(this.rootDir, fileId);
|
|
25
25
|
const tempPath = `${path}.tmp`;
|
|
26
|
-
const sizeBytes = await this.writeFile(file, tempPath);
|
|
27
|
-
await rename(tempPath, path);
|
|
28
26
|
const metadata = normalizeDescriptor({
|
|
29
27
|
name: file.name || fileId,
|
|
30
|
-
mimeType: file.
|
|
28
|
+
mimeType: file.mimeType || contentTypeFromFileId(fileId),
|
|
31
29
|
});
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
try {
|
|
31
|
+
file.signal?.throwIfAborted();
|
|
32
|
+
await this.cleanupExpired();
|
|
33
|
+
const sizeBytes = await this.writeStream(file, tempPath);
|
|
34
|
+
file.signal?.throwIfAborted();
|
|
35
|
+
await writeFile(metadataPath(path), JSON.stringify(metadata), { flag: "wx", signal: file.signal });
|
|
36
|
+
await rename(tempPath, path);
|
|
37
|
+
file.signal?.throwIfAborted();
|
|
38
|
+
return uploadResult(this.publicOrigin, fileId, { ...metadata, sizeBytes });
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
if (!file.body.locked) {
|
|
42
|
+
await file.body.cancel(error).catch(() => undefined);
|
|
43
|
+
}
|
|
44
|
+
await Promise.all([tempPath, path, metadataPath(path)].map((filePath) => unlink(filePath).catch(() => undefined)));
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
34
47
|
}
|
|
35
48
|
async createFromPath(file) {
|
|
36
49
|
assertFileSize(file.sizeBytes, this.maxBytes);
|
|
@@ -114,34 +127,17 @@ export class TransitFileService {
|
|
|
114
127
|
return fallback;
|
|
115
128
|
}
|
|
116
129
|
}
|
|
117
|
-
async
|
|
118
|
-
const writer = createWriteStream(tempPath, { flags: "wx" });
|
|
119
|
-
const reader = file.stream().getReader();
|
|
130
|
+
async writeStream(file, tempPath) {
|
|
120
131
|
let sizeBytes = 0;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
sizeBytes += value.byteLength;
|
|
128
|
-
assertFileSize(sizeBytes, this.maxBytes);
|
|
129
|
-
if (!writer.write(value)) {
|
|
130
|
-
await once(writer, "drain");
|
|
131
|
-
}
|
|
132
|
+
const maxBytes = this.maxBytes;
|
|
133
|
+
await pipeline(Readable.fromWeb(file.body), async function* (source) {
|
|
134
|
+
for await (const chunk of source) {
|
|
135
|
+
sizeBytes += chunk.byteLength;
|
|
136
|
+
assertFileSize(sizeBytes, maxBytes);
|
|
137
|
+
yield chunk;
|
|
132
138
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
return sizeBytes;
|
|
136
|
-
}
|
|
137
|
-
catch (error) {
|
|
138
|
-
writer.destroy();
|
|
139
|
-
await unlink(tempPath).catch(() => undefined);
|
|
140
|
-
throw error;
|
|
141
|
-
}
|
|
142
|
-
finally {
|
|
143
|
-
reader.releaseLock();
|
|
144
|
-
}
|
|
139
|
+
}, createWriteStream(tempPath, { flags: "wx" }), { signal: file.signal });
|
|
140
|
+
return sizeBytes;
|
|
145
141
|
}
|
|
146
142
|
}
|
|
147
143
|
function isManagedFileName(fileName) {
|