@hedeqiang/dsh-nl2sql 0.0.0-stage → 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.
@@ -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 }