@hedeqiang/dsh-nl2sql 0.10.0 → 0.11.1
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/CHANGELOG.md +22 -2
- package/README.en.md +2 -1
- package/README.md +2 -2
- package/channels/dingtalk.js +574 -0
- package/channels/http.js +63 -0
- package/channels/index.js +138 -0
- package/channels/lark.js +1180 -0
- package/channels/message.js +267 -0
- package/channels/telegram.js +663 -0
- package/channels/wecom.js +585 -0
- package/cordis.patch.yml +3 -3
- package/docs/CHANNELS.md +184 -0
- package/im.js +6 -509
- package/index.js +25 -17
- package/lark-bot.js +7 -604
- package/lib/client.js +1097 -185
- package/package.json +14 -9
- package/service/access.js +116 -22
- package/service/admin.js +29 -13
- package/service/bots.js +665 -0
- package/service/channels.js +363 -0
- package/service/datasources.js +31 -8
- package/service/push.js +5 -156
- package/service/shared.js +1 -1
- package/service/lark-actions.js +0 -223
- package/service/lark.js +0 -250
package/channels/http.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared HTTP helpers for channel adapters.
|
|
3
|
+
*
|
|
4
|
+
* Adapters throw `ChannelError(code, message)`: a stable code such as
|
|
5
|
+
* `channel/auth`, `channel/config`, `channel/network`, `channel/rate-limit`,
|
|
6
|
+
* `channel/rejected` (or a platform-specific `channel/<platform>-<code>`) and
|
|
7
|
+
* a Chinese user-facing message.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export class ChannelError extends Error {
|
|
11
|
+
/** @param {{ field?: string }} [opts] - `field`: the form field key the error belongs to. */
|
|
12
|
+
constructor(code, message, { field } = {}) {
|
|
13
|
+
super(message)
|
|
14
|
+
this.name = 'ChannelError'
|
|
15
|
+
this.code = code
|
|
16
|
+
if (field) this.field = field
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function parseBody(text) {
|
|
21
|
+
try { return JSON.parse(text) } catch { return { raw: String(text ?? '').slice(0, 300) } }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* fetch with a timeout. Returns { status, json, headers } — json is the parsed
|
|
26
|
+
* body, or `{ raw }` (first 300 chars) when the body is not JSON.
|
|
27
|
+
* Network failures and timeouts throw ChannelError('channel/network').
|
|
28
|
+
* @param {string} url
|
|
29
|
+
* @param {{ method?: string, headers?: object, body?: any, json?: any, timeoutMs?: number, label?: string, fetch?: Function }} [opts]
|
|
30
|
+
* `json` is serialized as the request body with a JSON content type;
|
|
31
|
+
* `label` names the platform in error messages (e.g. '飞书').
|
|
32
|
+
*/
|
|
33
|
+
export async function request(url, { method, headers = {}, body, json, timeoutMs = 15000, label = '', fetch: fetchImpl = globalThis.fetch } = {}) {
|
|
34
|
+
const controller = new AbortController()
|
|
35
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs)
|
|
36
|
+
const init = { method: method ?? (json !== undefined || body !== undefined ? 'POST' : 'GET'), headers: { ...headers }, signal: controller.signal }
|
|
37
|
+
if (json !== undefined) {
|
|
38
|
+
init.headers['content-type'] ??= 'application/json; charset=utf-8'
|
|
39
|
+
init.body = JSON.stringify(json)
|
|
40
|
+
} else if (body !== undefined) {
|
|
41
|
+
init.body = body
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const res = await fetchImpl(url, init)
|
|
45
|
+
const text = await res.text()
|
|
46
|
+
return { status: res.status, json: parseBody(text), headers: res.headers }
|
|
47
|
+
} catch (error) {
|
|
48
|
+
const who = label ? `${label}接口` : '接口'
|
|
49
|
+
throw new ChannelError('channel/network', error?.name === 'AbortError' ? `请求${who}超时` : `无法访问${who}:${error?.cause?.code ?? error?.message ?? error}`)
|
|
50
|
+
} finally {
|
|
51
|
+
clearTimeout(timer)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** POST a JSON body. Returns { status, json }. */
|
|
56
|
+
export function postJson(url, body, headers = {}, opts = {}) {
|
|
57
|
+
return request(url, { ...opts, method: 'POST', headers, json: body })
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** GET a URL. Returns { status, json }. */
|
|
61
|
+
export function getJson(url, headers = {}, opts = {}) {
|
|
62
|
+
return request(url, { ...opts, method: 'GET', headers })
|
|
63
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Channel adapter registry (see docs/CHANNELS.md).
|
|
3
|
+
*
|
|
4
|
+
* Built-in adapters: lark (static), telegram / wecom / dingtalk (loaded with
|
|
5
|
+
* dynamic import so a missing or broken module never breaks the registry).
|
|
6
|
+
* Adapters must NOT import this module (import ./http.js and ./message.js
|
|
7
|
+
* instead) — the registry imports them.
|
|
8
|
+
*
|
|
9
|
+
* Call `await loadAdapters()` once before using the synchronous lookups; the
|
|
10
|
+
* service core does this on start and in every channel RPC.
|
|
11
|
+
*/
|
|
12
|
+
import lark from './lark.js'
|
|
13
|
+
import { ChannelError } from './http.js'
|
|
14
|
+
|
|
15
|
+
const adapters = new Map()
|
|
16
|
+
const kinds = new Map()
|
|
17
|
+
|
|
18
|
+
/** Register (or replace) an adapter. Its kinds become valid channel types. */
|
|
19
|
+
export function registerAdapter(adapter) {
|
|
20
|
+
if (!adapter?.id || !Array.isArray(adapter.kinds)) throw new Error('adapter needs id and kinds')
|
|
21
|
+
const previous = adapters.get(adapter.id)
|
|
22
|
+
if (previous) for (const k of previous.kinds) kinds.delete(k.id)
|
|
23
|
+
adapters.set(adapter.id, adapter)
|
|
24
|
+
for (const kind of adapter.kinds) kinds.set(kind.id, { adapter, kind: { platform: adapter.id, ...kind } })
|
|
25
|
+
return adapter
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Remove an adapter (tests). */
|
|
29
|
+
export function unregisterAdapter(id) {
|
|
30
|
+
const adapter = adapters.get(id)
|
|
31
|
+
if (!adapter) return false
|
|
32
|
+
for (const k of adapter.kinds) kinds.delete(k.id)
|
|
33
|
+
adapters.delete(id)
|
|
34
|
+
return true
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
registerAdapter(lark)
|
|
38
|
+
|
|
39
|
+
const OPTIONAL = ['telegram', 'wecom', 'dingtalk']
|
|
40
|
+
const loadErrors = {}
|
|
41
|
+
let loading
|
|
42
|
+
|
|
43
|
+
/** Load the optional built-in adapters (idempotent). Resolves to the list of loaded ids. */
|
|
44
|
+
export function loadAdapters() {
|
|
45
|
+
loading ??= Promise.all(OPTIONAL.map(async (id) => {
|
|
46
|
+
try {
|
|
47
|
+
const mod = await import(`./${id}.js`)
|
|
48
|
+
const adapter = mod.default ?? mod.adapter
|
|
49
|
+
// A test may have registered a replacement already: keep it.
|
|
50
|
+
if (adapter && !adapters.has(adapter.id)) registerAdapter(adapter)
|
|
51
|
+
return adapter?.id
|
|
52
|
+
} catch (error) {
|
|
53
|
+
loadErrors[id] = error?.message ?? String(error)
|
|
54
|
+
return undefined
|
|
55
|
+
}
|
|
56
|
+
})).then(ids => ids.filter(Boolean))
|
|
57
|
+
return loading
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Errors from optional adapters that failed to load: { [platform]: message }. */
|
|
61
|
+
export function adapterLoadErrors() {
|
|
62
|
+
return { ...loadErrors }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function getAdapter(platform) {
|
|
66
|
+
return adapters.get(platform)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function listAdapters() {
|
|
70
|
+
return [...adapters.values()]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Resolve a channel type to { adapter, kind }, or undefined. */
|
|
74
|
+
export function kindOf(type) {
|
|
75
|
+
return kinds.get(type)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Like kindOf, but throws ChannelError('channel/config') for unknown types. */
|
|
79
|
+
export function requireKind(type) {
|
|
80
|
+
const found = kinds.get(type)
|
|
81
|
+
if (!found) throw new ChannelError('channel/config', `不支持的渠道类型:${type}`)
|
|
82
|
+
return found
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Pick zh/en text from a { zh, en } object (or return a plain string). */
|
|
86
|
+
export function localized(text, locale = 'zh') {
|
|
87
|
+
if (text === null || text === undefined) return text
|
|
88
|
+
if (typeof text !== 'object' || Array.isArray(text)) return text
|
|
89
|
+
return text[locale] ?? text.zh ?? text.en
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** A JSON-safe description of a kind for the UI: no functions, never a value. */
|
|
93
|
+
export function describeKind(kind) {
|
|
94
|
+
return {
|
|
95
|
+
id: kind.id,
|
|
96
|
+
platform: kind.platform,
|
|
97
|
+
label: kind.label,
|
|
98
|
+
description: kind.description,
|
|
99
|
+
fields: (kind.fields ?? []).map(f => {
|
|
100
|
+
const secret = Boolean(f.secret || f.type === 'secret')
|
|
101
|
+
const out = { ...f, secret }
|
|
102
|
+
if (secret) delete out.default // never any secret value
|
|
103
|
+
return JSON.parse(JSON.stringify(out))
|
|
104
|
+
}),
|
|
105
|
+
capabilities: { ...kind.capabilities },
|
|
106
|
+
setupSteps: kind.setupSteps ?? { zh: [], en: [] },
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The catalog for the page: [{ platform, name, kinds:[{ id, label, description, fields, capabilities, setupSteps }] }].
|
|
112
|
+
* With a locale, `name` / `label` / `description` / `setupSteps` / field labels are also given as `*Text` strings.
|
|
113
|
+
*/
|
|
114
|
+
export function listCatalog(locale) {
|
|
115
|
+
return listAdapters().map(adapter => ({
|
|
116
|
+
platform: adapter.id,
|
|
117
|
+
name: adapter.name,
|
|
118
|
+
...(locale ? { nameText: localized(adapter.name, locale) } : {}),
|
|
119
|
+
kinds: adapter.kinds.map(k => {
|
|
120
|
+
const d = describeKind({ platform: adapter.id, ...k })
|
|
121
|
+
if (!locale) return d
|
|
122
|
+
return {
|
|
123
|
+
...d,
|
|
124
|
+
labelText: localized(d.label, locale),
|
|
125
|
+
descriptionText: localized(d.description, locale),
|
|
126
|
+
setupStepsText: localized(d.setupSteps, locale) ?? [],
|
|
127
|
+
fields: d.fields.map(f => ({ ...f, labelText: localized(f.label, locale), helpText: localized(f.help, locale) })),
|
|
128
|
+
}
|
|
129
|
+
}),
|
|
130
|
+
}))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Secret field keys of a kind. */
|
|
134
|
+
export function secretKeys(kind) {
|
|
135
|
+
return (kind.fields ?? []).filter(f => f.secret || f.type === 'secret').map(f => f.key)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export { ChannelError }
|