@xpert-ai/plugin-sdk 3.18.4 → 3.18.6

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.
Files changed (34) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +44 -0
  3. package/data-workbench.cjs.default.js +1 -0
  4. package/data-workbench.cjs.js +518 -0
  5. package/data-workbench.cjs.mjs +2 -0
  6. package/data-workbench.d.ts +1 -0
  7. package/data-workbench.esm.js +512 -0
  8. package/index.cjs.js +3118 -33
  9. package/index.esm.js +3109 -39
  10. package/model-provider.cjs.js +9 -0
  11. package/model-provider.esm.js +8 -2
  12. package/package.json +33 -27
  13. package/src/data-workbench.d.ts +3 -0
  14. package/src/lib/ai-model/utils/tokenizer.d.ts +6 -0
  15. package/src/lib/core/runtime-capability.d.ts +4 -0
  16. package/src/lib/data/datasource/index.d.ts +3 -0
  17. package/src/lib/data/datasource/types.d.ts +2 -0
  18. package/src/lib/data/datasource/workbench-adapter.d.ts +39 -0
  19. package/src/lib/data/datasource/workbench-sql.d.ts +12 -0
  20. package/src/lib/data/datasource/workbench.d.ts +133 -0
  21. package/src/lib/mcp/resource-template.d.ts +2 -3
  22. package/src/lib/mcp/resource.d.ts +5 -1
  23. package/src/lib/rag/textsplitter/strategy.interface.d.ts +15 -4
  24. package/src/lib/rag/transformer/strategy.interface.d.ts +3 -0
  25. package/src/lib/runtime/capabilities/index.d.ts +1 -0
  26. package/src/lib/runtime/capabilities/knowledge-graph.d.ts +59 -0
  27. package/src/lib/runtime/capabilities/workspace-files.d.ts +2 -0
  28. package/src/lib/tool-provider/component.d.ts +2 -0
  29. package/src/lib/tool-provider/decorators.d.ts +1 -1
  30. package/src/lib/tool-provider/index.d.ts +3 -0
  31. package/src/lib/tool-provider/mcp-methods.d.ts +39 -0
  32. package/src/lib/tool-provider/provider-methods.d.ts +5 -0
  33. package/src/lib/tool-provider/tool-result.d.ts +1 -1
  34. package/src/lib/tool-provider/types.d.ts +12 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,56 @@
1
1
  # @xpert-ai/plugin-sdk
2
2
 
3
+ ## 3.18.6
4
+
5
+ ### Patch Changes
6
+
7
+ - a8a251d: Support non-tool MCP capabilities, task policies and transport-specific input validation on decorated tool providers. Pass execution-scoped workspace files and Agent feature configuration to middleware business methods and allow existing MCP result envelopes without requiring a new structured output schema.
8
+
9
+ Share runtime component fingerprinting across host entry points and include transport input schemas, task policies, resources, resource templates and prompts in update detection.
10
+
11
+ Add resource, resource-template and prompt method decorators with inherited method discovery, shared runtime/fingerprint definitions and legacy extension compatibility.
12
+
13
+ - 983b730: Prepare the Xpert 3.18.6 patch release, including plugin-sdk.
14
+
15
+ Extend decorated MCP capabilities and structured knowledge graph runtime contracts.
16
+ Improve document parsing and scanned-page understanding, semantic FAQ exclusion,
17
+ manual tags during document import, and per-knowledgebase vector storage selection.
18
+ Include schema-driven sliders and the scoped database workbench adapter.
19
+
20
+ The target platform version is 3.18.6. Verify contracts, plugin-sdk, and xpert-ui
21
+ all resolve to this version in the release version PR, including changelogs,
22
+ internal dependency references, and lockfile entries. Publish the separately
23
+ versioned Runtime Suite source images before creating platform-version aliases.
24
+
25
+ - Updated dependencies [9a2a2a0]
26
+ - Updated dependencies [089e3ff]
27
+ - Updated dependencies [983b730]
28
+ - @xpert-ai/contracts@3.18.6
29
+
30
+ ## 3.18.5
31
+
32
+ ### Patch Changes
33
+
34
+ - 6a51c17: Add a shared knowledge document language hint and bounded, model-free language detection for natural text chunk boundaries. Preserve Auto strategy routing, explicit separators, token limits, and parent-child behavior, and expose the requested and detected languages in chunk previews.
35
+ - 8402f72: Prepare the Xpert 3.18.5 patch release, including plugin-sdk.
36
+
37
+ Add knowledge document chunk token limits, question generation, automatic and
38
+ structure-aware chunking, spreadsheet table metadata, and format-specific scoped
39
+ parsers with diagnostics. Preserve OCR chunk limits and table context.
40
+
41
+ Extend MCP resource reads with scoped workspace files and the requested resource
42
+ URI, preserve confirmation sessions, and reauthorize queued execution. Add PPTX
43
+ preview and editing with binary workspace saves and edited downloads.
44
+
45
+ The target platform version is 3.18.5. Verify contracts, plugin-sdk, and xpert-ui
46
+ all resolve to this version in the release version PR, including changelogs,
47
+ internal dependency references, and lockfile entries.
48
+
49
+ - Updated dependencies [6a51c17]
50
+ - Updated dependencies [95ec638]
51
+ - Updated dependencies [8402f72]
52
+ - @xpert-ai/contracts@3.18.5
53
+
3
54
  ## 3.18.4
4
55
 
5
56
  ### Patch Changes
package/README.md CHANGED
@@ -67,6 +67,50 @@ export class OrderOperationsTools {
67
67
 
68
68
  MCP exposure is explicit. Provide strict Zod input/output schemas and return an allowlisted DTO; the host validates the output and maps it to `structuredContent` and a JSON `TextContent` block so clients that only expose text can still read the result. With `resultFormat: 'tool_result'`, image and other content blocks are preserved and the validated structured metadata also receives a JSON text fallback. Invocation context is constructed for every call and must never be cached on the Provider singleton. `getMiddlewareExtensions()` may add Agent-only hooks such as `wrapToolCall`, but business-critical behavior belongs in the method or shared service.
69
69
 
70
+ Use the same `@XpertToolProvider` class for every capability:
71
+
72
+ | Decorator | Method signature | Result |
73
+ | ------------------------ | --------------------------------------------------------------- | ---------------------------- |
74
+ | `@XpertTool` | `(input, context)` | Tool DTO or content envelope |
75
+ | `@XpertResource` | `(context: ResourceReadContext)` | `McpResourceReadResult` |
76
+ | `@XpertResourceTemplate` | `(args: Record<string, string>, context: ResourceReadContext)` | `McpResourceReadResult` |
77
+ | `@XpertPrompt` | `(args: Record<string, string>, context: ToolExecutionContext)` | `McpPromptResult` |
78
+
79
+ No business base class is required. All four decorators support inherited methods;
80
+ a child override replaces the parent declaration, and an undecorated override
81
+ removes that exposure. The SDK reuses its existing `BuiltinToolset` adapter.
82
+ Resource-only and prompt-only providers are supported. Template and prompt options
83
+ can specify `completionMethod` to bind an instance `McpCompletionHandler`.
84
+
85
+ ```ts
86
+ @XpertResourceTemplate({
87
+ key: 'project', uriTemplate: 'example://projects/{projectId}',
88
+ arguments: { projectId: { required: true } }, requiredContext: ['tenant', 'principal']
89
+ })
90
+ readProject(args: Record<string, string>, context: ResourceReadContext) {
91
+ return this.projects.readResource(args.projectId, context)
92
+ }
93
+ ```
94
+
95
+ Handlers receive the current caller's context and retain their provider `this`.
96
+ Do not cache caller context on a singleton. `getMcpExtensions()` remains a legacy
97
+ compatibility hook; duplicate keys or public URIs/names are rejected when merged
98
+ with decorated declarations. Runtime publication and component fingerprints use
99
+ the same resolved definitions. App bundles remain in provider options.
100
+
101
+ Use `mcp.task` for the existing MCP task policy and `mcp.inputSchema` for stricter
102
+ transport inputs (for example, requiring project identity that native Workbench
103
+ calls may infer). The MCP adapter validates the shared schema first, then the
104
+ transport schema. The transport schema is advertised to MCP clients. Native
105
+ methods also receive execution-scoped workspace files via `context.host.files`
106
+ and the current Agent feature configuration via `context.xpertFeatures`.
107
+
108
+ Existing tools without a structured output contract may explicitly select
109
+ `resultFormat: 'tool_result'` and omit `outputSchema`. The SDK still validates the
110
+ content envelope and media blocks, but does not claim to validate business fields
111
+ in `structuredContent`. DTO-format MCP methods continue to require a strict
112
+ output schema, and supplied output schemas retain their validation.
113
+
70
114
  For completed operations, `prepareToolResult(() => project(result), () => minimalReceipt)` defers output mapping to the adapter. The normal path maps and validates once; projection, validation or JSON serialization failures recover a bounded receipt without executing the business method again. Declare `outputSchema: z.union([normalSchema, receiptSchema])`, with strict objects in every branch. The fallback is validated too. Business exceptions thrown before the method returns remain errors. With `tool_result`, both callbacks return the content envelope and the union describes its structured metadata. The MCP protocol still validates the published JSON Schema; custom Zod refinements remain enforced by the SDK. Do not put side effects inside either callback.
71
115
 
72
116
  For a host-native MCP App, declare each static HTML bundle once in the Provider's `apps` array, then bind a Tool with `mcp.app.resourceKey` and include `app` visibility. The App key must exist in the same Provider and its entry must be a relative `.html` path inside the packaged plugin. The host publishes the bundle as a `ui://` resource; plugins must not create a stdio server or put CSP/credentials in Tool results.
@@ -0,0 +1 @@
1
+ exports._default = require('./data-workbench.cjs.js').default;
@@ -0,0 +1,518 @@
1
+ 'use strict';
2
+
3
+ var _extends = require('./_extends.cjs.js');
4
+
5
+ const DataSourceRuntimeCapability = Object.freeze({
6
+ id: 'platform.datasource.workbench',
7
+ description: 'Scoped database workbench access; credentials remain host-owned.',
8
+ __type: undefined
9
+ });
10
+
11
+ const WRITE = new Set([
12
+ 'INSERT',
13
+ 'UPDATE',
14
+ 'DELETE',
15
+ 'CREATE',
16
+ 'ALTER',
17
+ 'DROP',
18
+ 'TRUNCATE',
19
+ 'RENAME'
20
+ ]);
21
+ const FORBIDDEN = new Set([
22
+ 'INTO',
23
+ 'OUTFILE',
24
+ 'DUMPFILE',
25
+ 'PROCEDURE',
26
+ 'CALL',
27
+ 'COPY',
28
+ 'LOAD',
29
+ 'EXPORT',
30
+ 'IMPORT',
31
+ 'GRANT',
32
+ 'REVOKE',
33
+ 'LOCK',
34
+ 'UNLOCK',
35
+ 'KILL',
36
+ 'SET',
37
+ 'RESET',
38
+ 'ANALYZE',
39
+ 'VACUUM',
40
+ 'OPTIMIZE',
41
+ 'ATTACH',
42
+ 'DETACH'
43
+ ]);
44
+ const SAFE_FUNCTIONS = new Set('ABS ACOS ASIN ATAN ATAN2 AVG CAST CEIL CEILING COALESCE CONCAT CONCAT_WS CONVERT COS COUNT CURDATE CURRENT_DATE CURRENT_TIMESTAMP DATE DATE_ADD DATE_SUB DATE_FORMAT DATEDIFF DAY DAYOFMONTH DAYOFWEEK EXTRACT FLOOR FORMAT GREATEST GROUP_CONCAT IF IFNULL JSON_EXTRACT JSON_LENGTH JSON_UNQUOTE LEAST LEFT LENGTH LN LOG LOWER LPAD LTRIM MAX MD5 MIN MOD MONTH NOW NULLIF POWER RAND REPLACE REVERSE RIGHT ROUND ROW_NUMBER RANK DENSE_RANK LAG LEAD FIRST_VALUE LAST_VALUE LPAD RPAD RTRIM SIGN SIN SQRT STDDEV STDDEV_POP STDDEV_SAMP SUBSTR SUBSTRING SUM TAN TO_CHAR TO_DATE TO_TIMESTAMP TRIM TRUNC UPPER VAR_POP VAR_SAMP VERSION WEEK YEAR DATABASE CURRENT_DATABASE CURRENT_SCHEMA ARRAY_AGG STRING_AGG JSON_AGG JSON_BUILD_OBJECT PG_GET_VIEWDEF'.split(' '));
45
+ const SQL_PARENS = new Set('IN AS EXISTS OVER PARTITION VALUES SELECT WITH AND OR NOT ON USING FILTER GROUPING ROLLUP CUBE DISTINCT EXPLAIN'.split(' '));
46
+ const UNSAFE_FUNCTIONS = /\b(pg_sleep|sleep|benchmark|dblink\w*|lo_\w+|pg_(?:read|write|ls|stat|terminate|cancel|reload|rotate|log|create|drop|promote|switch|backup|replication|advisory)\w*|get_lock|release_lock|load_file|nextval|setval|set_config|http\w*|s3|hdfs|jdbc|mysql|postgresql|file|url)\s*\(/i;
47
+ function splitWorkbenchSql(source) {
48
+ if (!source.trim() || source.length > 100000 || source.includes('\0')) throw new Error('invalid_sql');
49
+ const statements = [];
50
+ let start = 0, index = 0, clean = '';
51
+ const finish = (end)=>{
52
+ const text = clean.trim();
53
+ if (text) {
54
+ var _text_toUpperCase_match;
55
+ const words = (_text_toUpperCase_match = text.toUpperCase().match(/[A-Z_][A-Z_0-9]*/g)) != null ? _text_toUpperCase_match : [];
56
+ const first = words[0];
57
+ let effect = 'unsupported';
58
+ if (WRITE.has(first)) effect = 'write';
59
+ if (first === 'SELECT' || first === 'WITH' || first === 'SHOW' || first === 'DESC' || first === 'DESCRIBE' || first === 'EXPLAIN') {
60
+ effect = 'read';
61
+ if (words.some((word, index)=>WRITE.has(word) && !(first === 'SHOW' && index === 1 && word === 'CREATE') || FORBIDDEN.has(word)) || UNSAFE_FUNCTIONS.test(text) || /\bFOR\s+(UPDATE|SHARE)\b/i.test(text) || /:=|\bEXPLAIN\s+ANALYZE\b|@/i.test(text)) effect = 'unsupported';
62
+ // eslint-disable-next-line no-control-regex -- intentional: reject any non-ASCII/control input
63
+ if (/[^\x00-\x7f]/.test(text) || /\.\s*[A-Z_][A-Z_0-9]*\s*\(/i.test(text)) effect = 'unsupported';
64
+ if ([
65
+ ...text.matchAll(/([A-Z_][A-Z_0-9]*)\s*\(/gi)
66
+ ].some((match)=>!SAFE_FUNCTIONS.has(match[1].toUpperCase()) && !SQL_PARENS.has(match[1].toUpperCase()))) effect = 'unsupported';
67
+ if (first === 'SHOW' && !/^(SHOW\s+(DATABASES|SCHEMAS|CATALOGS|TABLES|FULL\s+TABLES|COLUMNS|FULL\s+COLUMNS|INDEX|INDEXES|KEYS|CREATE\s+TABLE|CREATE\s+VIEW|PARTITIONS|VARIABLES|STATUS|GRANTS))\b/i.test(text)) effect = 'unsupported';
68
+ }
69
+ // CREATE/ALTER routines, external resources and privileged SQL are outside this app.
70
+ if (effect === 'write' && /\b(FUNCTION|PROCEDURE|TRIGGER|USER|ROLE|CATALOG|RESOURCE|SYSTEM|SERVER|EXTENSION|FILE|OUTFILE|DUMPFILE|EXECUTE)\b/i.test(text)) effect = 'unsupported';
71
+ statements.push({
72
+ sql: source.slice(start, end).trim(),
73
+ words,
74
+ effect
75
+ });
76
+ }
77
+ clean = '';
78
+ start = end + 1;
79
+ };
80
+ while(index < source.length){
81
+ const c = source[index], next = source[index + 1];
82
+ if (c === '-' && next === '-' || c === '#') {
83
+ while(index < source.length && source[index] !== '\n')index++;
84
+ clean += ' ';
85
+ continue;
86
+ }
87
+ if (c === '/' && next === '*') {
88
+ if (source[index + 2] === '!') throw new Error('executable_comment_not_supported');
89
+ const end = source.indexOf('*/', index + 2);
90
+ if (end < 0) throw new Error('unterminated_comment');
91
+ index = end + 2;
92
+ clean += ' ';
93
+ continue;
94
+ }
95
+ if (c === "'" || c === '"' || c === '`') {
96
+ const quote = c;
97
+ let closed = false;
98
+ index++;
99
+ while(index < source.length){
100
+ if (source[index] === '\\') throw new Error('use_parameters_for_backslash_literals');
101
+ if (source[index] === quote) {
102
+ if (source[index + 1] === quote) {
103
+ index += 2;
104
+ continue;
105
+ }
106
+ index++;
107
+ closed = true;
108
+ break;
109
+ }
110
+ index++;
111
+ }
112
+ if (!closed) throw new Error('unterminated_literal');
113
+ clean += quote === "'" ? ' ? ' : ' identifier ';
114
+ continue;
115
+ }
116
+ if (c === '$') {
117
+ var _source_slice_match;
118
+ const tag = (_source_slice_match = source.slice(index).match(/^\$(?:[A-Za-z_][A-Za-z_0-9]*)?\$/)) == null ? void 0 : _source_slice_match[0];
119
+ if (tag) {
120
+ const end = source.indexOf(tag, index + tag.length);
121
+ if (end < 0) throw new Error('unterminated_literal');
122
+ index = end + tag.length;
123
+ clean += ' ? ';
124
+ continue;
125
+ }
126
+ }
127
+ if (c === ';') {
128
+ finish(index);
129
+ index++;
130
+ continue;
131
+ }
132
+ clean += c;
133
+ index++;
134
+ }
135
+ finish(source.length);
136
+ if (!statements.length || statements.length > 50) throw new Error('statement_count_exceeded');
137
+ return statements;
138
+ }
139
+ function requireReadStatement(sql) {
140
+ const statements = splitWorkbenchSql(sql);
141
+ if (statements.length !== 1 || statements[0].effect !== 'read') throw new Error('read_only_violation');
142
+ return statements[0].sql;
143
+ }
144
+ function quoteDatabaseIdentifier(name, engine) {
145
+ // eslint-disable-next-line no-control-regex -- intentional: reject control characters in identifiers
146
+ if (!name || name.length > 256 || /[\0-\x1f]/.test(name)) throw new Error('invalid_identifier');
147
+ const quote = engine === 'postgres' ? '"' : '`';
148
+ return quote + name.split(quote).join(quote + quote) + quote;
149
+ }
150
+
151
+ const number = (value)=>Number(value != null ? value : 0);
152
+ const string = (value)=>value == null ? '' : String(value);
153
+ const cell = (result, row, name)=>row[result.columns.findIndex((column)=>column.name.toLowerCase() === name.toLowerCase())];
154
+ /** One adapter per physical connection. A host session must not share it across users. */ class SqlDatabaseWorkbenchAdapter {
155
+ quote(name) {
156
+ return quoteDatabaseIdentifier(name, this.engine);
157
+ }
158
+ ref(object) {
159
+ this.assertLocation(object);
160
+ var _object_schema, _ref, _object_database;
161
+ const namespace = this.engine === 'postgres' ? (_ref = (_object_schema = object.schema) != null ? _object_schema : this.initial.schema) != null ? _ref : 'public' : (_object_database = object.database) != null ? _object_database : this.initial.database;
162
+ if (!namespace) throw new Error('database_required');
163
+ return `${this.quote(namespace)}.${this.quote(object.name)}`;
164
+ }
165
+ assertLocation(location) {
166
+ if (location.engineCatalog && location.engineCatalog !== 'internal') throw new Error('external_catalog_not_supported');
167
+ if (this.engine === 'postgres' && location.database && location.database !== this.initial.database) throw new Error('database_requires_new_connection');
168
+ }
169
+ async raw(sql, parameters = [], maxRows = 1001, signal, timeoutMs = 30000) {
170
+ if (this.closed) throw new Error('session_closed');
171
+ return this.transport.execute(sql, parameters, {
172
+ maxRows,
173
+ timeoutMs,
174
+ signal
175
+ });
176
+ }
177
+ async capabilities() {
178
+ if (!this.version) {
179
+ var _result_rows_;
180
+ const result = await this.raw(this.engine === 'doris' ? 'SELECT @@version_comment AS version' : 'SELECT version() AS version', [], 1);
181
+ this.version = string((_result_rows_ = result.rows[0]) == null ? void 0 : _result_rows_[0]);
182
+ }
183
+ const supported = this.engine !== 'doris' || /(?:^|[^0-9])(?:2\.1|3\.\d+|4\.\d+)(?:[.\s_-]|$)/.test(this.version);
184
+ return {
185
+ engine: this.engine,
186
+ version: this.version,
187
+ query: true,
188
+ explain: true,
189
+ transactions: this.engine !== 'doris',
190
+ cancel: true,
191
+ import: supported,
192
+ writes: supported,
193
+ nativeReadOnly: this.engine !== 'doris',
194
+ objectKinds: [
195
+ 'table',
196
+ 'view'
197
+ ],
198
+ diagnostics: this.engine === 'doris' ? [
199
+ ...supported ? [] : [
200
+ 'doris_version_unverified_writes_disabled'
201
+ ],
202
+ 'doris_permissions_enforce_read_only',
203
+ 'external_catalog_not_supported',
204
+ 'doris_transactions_not_exposed'
205
+ ] : []
206
+ };
207
+ }
208
+ async locations() {
209
+ const result = await this.raw(this.engine === 'postgres' ? "SELECT schema_name AS name FROM information_schema.schemata WHERE schema_name NOT LIKE 'pg_%' AND schema_name <> 'information_schema' ORDER BY schema_name LIMIT 1000" : "SELECT schema_name AS name FROM information_schema.schemata WHERE schema_name NOT IN ('information_schema','performance_schema','mysql','sys') ORDER BY schema_name LIMIT 1000");
210
+ return result.rows.map((row)=>this.engine === 'postgres' ? {
211
+ database: this.initial.database,
212
+ schema: string(row[0])
213
+ } : _extends._extends({
214
+ database: string(row[0])
215
+ }, this.engine === 'doris' ? {
216
+ engineCatalog: 'internal'
217
+ } : {}));
218
+ }
219
+ async objects(input) {
220
+ this.assertLocation(input);
221
+ var _input_page, _input_pageSize;
222
+ const page = Math.max(1, Math.min((_input_page = input.page) != null ? _input_page : 1, 10000)), pageSize = Math.max(1, Math.min((_input_pageSize = input.pageSize) != null ? _input_pageSize : 100, 1000));
223
+ var _input_schema, _ref, _input_database;
224
+ const namespace = this.engine === 'postgres' ? (_ref = (_input_schema = input.schema) != null ? _input_schema : this.initial.schema) != null ? _ref : 'public' : (_input_database = input.database) != null ? _input_database : this.initial.database;
225
+ if (!namespace) return {
226
+ items: [],
227
+ page,
228
+ pageSize,
229
+ hasMore: false
230
+ };
231
+ const placeholder = (i)=>this.engine === 'postgres' ? `$${i}` : '?';
232
+ var _input_search;
233
+ const result = await this.raw(`SELECT table_name, table_type FROM information_schema.tables WHERE table_schema = ${placeholder(1)} AND table_name LIKE ${placeholder(2)} ORDER BY table_name LIMIT ${pageSize + 1} OFFSET ${(page - 1) * pageSize}`, [
234
+ namespace,
235
+ `%${(_input_search = input.search) != null ? _input_search : ''}%`
236
+ ], pageSize + 1);
237
+ return {
238
+ items: result.rows.slice(0, pageSize).map((row)=>({
239
+ database: input.database,
240
+ schema: input.schema,
241
+ engineCatalog: input.engineCatalog,
242
+ name: string(row[0]),
243
+ kind: string(row[1]).includes('VIEW') ? 'view' : 'table'
244
+ })),
245
+ page,
246
+ pageSize,
247
+ hasMore: result.rows.length > pageSize
248
+ };
249
+ }
250
+ async describe(object) {
251
+ const reference = this.ref(object);
252
+ var _object_schema, _ref, _object_database, _ref1;
253
+ const namespace = this.engine === 'postgres' ? (_ref = (_object_schema = object.schema) != null ? _object_schema : this.initial.schema) != null ? _ref : 'public' : (_ref1 = (_object_database = object.database) != null ? _object_database : this.initial.database) != null ? _ref1 : '';
254
+ const p = (i)=>this.engine === 'postgres' ? `$${i}` : '?';
255
+ const metadata = await this.raw(this.engine === 'postgres' ? `SELECT a.attname AS column_name, pg_catalog.format_type(a.atttypid,a.atttypmod) AS data_type, CASE WHEN a.attnotnull THEN 'NO' ELSE 'YES' END AS is_nullable, a.attnum AS ordinal_position, pg_get_expr(d.adbin,d.adrelid) AS column_default FROM pg_catalog.pg_attribute a JOIN pg_catalog.pg_class c ON c.oid=a.attrelid JOIN pg_catalog.pg_namespace n ON n.oid=c.relnamespace LEFT JOIN pg_catalog.pg_attrdef d ON d.adrelid=a.attrelid AND d.adnum=a.attnum WHERE n.nspname=$1 AND c.relname=$2 AND a.attnum>0 AND NOT a.attisdropped ORDER BY a.attnum LIMIT 1000` : `SELECT column_name, column_type AS data_type, is_nullable, ordinal_position, column_default FROM information_schema.columns WHERE table_schema = ${p(1)} AND table_name = ${p(2)} ORDER BY ordinal_position LIMIT 1000`, [
256
+ namespace,
257
+ object.name
258
+ ]);
259
+ const columns = metadata.rows.map((row, index)=>({
260
+ id: `c${index}`,
261
+ name: string(cell(metadata, row, 'column_name')),
262
+ dataType: string(cell(metadata, row, 'data_type')),
263
+ nullable: string(cell(metadata, row, 'is_nullable')) === 'YES',
264
+ ordinal: number(cell(metadata, row, 'ordinal_position')),
265
+ defaultValue: cell(metadata, row, 'column_default') == null ? null : string(cell(metadata, row, 'column_default'))
266
+ }));
267
+ if (!columns.length) throw new Error('object_not_found_or_denied');
268
+ const keys = [], diagnostics = [];
269
+ let definition = '', model;
270
+ if (this.engine === 'postgres') {
271
+ const result = await this.raw(`SELECT tc.constraint_name, tc.constraint_type, kcu.column_name, kcu.ordinal_position, ccu.table_schema AS foreign_schema, ccu.table_name AS foreign_table, ccu.column_name AS foreign_column FROM information_schema.table_constraints tc JOIN information_schema.key_column_usage kcu ON tc.constraint_catalog=kcu.constraint_catalog AND tc.constraint_schema=kcu.constraint_schema AND tc.constraint_name=kcu.constraint_name LEFT JOIN information_schema.referential_constraints rc ON rc.constraint_catalog=tc.constraint_catalog AND rc.constraint_schema=tc.constraint_schema AND rc.constraint_name=tc.constraint_name LEFT JOIN information_schema.key_column_usage ccu ON ccu.constraint_catalog=rc.unique_constraint_catalog AND ccu.constraint_schema=rc.unique_constraint_schema AND ccu.constraint_name=rc.unique_constraint_name AND ccu.ordinal_position=kcu.position_in_unique_constraint WHERE tc.table_schema=$1 AND tc.table_name=$2 ORDER BY tc.constraint_name,kcu.ordinal_position LIMIT 1000`, [
272
+ namespace,
273
+ object.name
274
+ ]);
275
+ for (const row of result.rows){
276
+ const name = string(cell(result, row, 'constraint_name')), kind = string(cell(result, row, 'constraint_type'));
277
+ let key = keys.find((item)=>item.name === name);
278
+ if (!key) {
279
+ key = {
280
+ name,
281
+ kind: kind === 'PRIMARY KEY' ? 'primary' : kind === 'FOREIGN KEY' ? 'foreign' : 'unique',
282
+ columns: []
283
+ };
284
+ keys.push(key);
285
+ }
286
+ key.columns.push(string(cell(result, row, 'column_name')));
287
+ if (key.kind === 'foreign') {
288
+ var _key;
289
+ key.referencedTable = {
290
+ schema: string(cell(result, row, 'foreign_schema')),
291
+ name: string(cell(result, row, 'foreign_table')),
292
+ kind: 'table'
293
+ };
294
+ var _referencedColumns;
295
+ ((_referencedColumns = (_key = key).referencedColumns) != null ? _referencedColumns : _key.referencedColumns = []).push(string(cell(result, row, 'foreign_column')));
296
+ }
297
+ }
298
+ if (object.kind === 'view') {
299
+ var _source_rows_;
300
+ const source = await this.raw('SELECT pg_get_viewdef($1::regclass, true) AS definition', [
301
+ reference
302
+ ], 1);
303
+ definition = `CREATE VIEW ${reference} AS\n${string((_source_rows_ = source.rows[0]) == null ? void 0 : _source_rows_[0])}`;
304
+ } else {
305
+ definition = `CREATE TABLE ${reference} (\n${columns.map((column)=>` ${this.quote(column.name)} ${column.dataType}${column.nullable ? '' : ' NOT NULL'}${column.defaultValue === null ? '' : ` DEFAULT ${column.defaultValue}`}`).join(',\n')}\n);`;
306
+ diagnostics.push('definition_reconstructed_from_metadata');
307
+ }
308
+ } else {
309
+ var _source_rows_1;
310
+ const source = await this.raw(`SHOW CREATE ${object.kind === 'view' ? 'VIEW' : 'TABLE'} ${reference}`, [], 1);
311
+ definition = string((_source_rows_1 = source.rows[0]) == null ? void 0 : _source_rows_1[1]);
312
+ if (this.engine === 'doris') {
313
+ const match = definition.match(/\b(UNIQUE|DUPLICATE|AGGREGATE)\s+KEY\s*\(([^)]*)\)/i);
314
+ model = (match == null ? void 0 : match[1].toUpperCase()) === 'UNIQUE' ? 'unique' : (match == null ? void 0 : match[1].toUpperCase()) === 'DUPLICATE' ? 'duplicate' : (match == null ? void 0 : match[1].toUpperCase()) === 'AGGREGATE' ? 'aggregate' : undefined;
315
+ if (model === 'unique' && match) keys.push({
316
+ name: 'UNIQUE KEY',
317
+ kind: 'primary',
318
+ columns: [
319
+ ...match[2].matchAll(/`((?:``|[^`])+)`/g)
320
+ ].map((item)=>item[1].split('``').join('`'))
321
+ });
322
+ } else {
323
+ const index = await this.raw(`SHOW INDEX FROM ${reference}`);
324
+ for (const row of index.rows){
325
+ const name = string(cell(index, row, 'Key_name'));
326
+ let key = keys.find((item)=>item.name === name);
327
+ if (!key) {
328
+ key = {
329
+ name,
330
+ kind: name === 'PRIMARY' ? 'primary' : number(cell(index, row, 'Non_unique')) === 0 ? 'unique' : 'index',
331
+ columns: []
332
+ };
333
+ keys.push(key);
334
+ }
335
+ key.columns.push(string(cell(index, row, 'Column_name')));
336
+ }
337
+ const foreign = await this.raw('SELECT constraint_name,column_name,referenced_table_schema,referenced_table_name,referenced_column_name FROM information_schema.key_column_usage WHERE table_schema=? AND table_name=? AND referenced_table_name IS NOT NULL ORDER BY constraint_name,ordinal_position LIMIT 1000', [
338
+ namespace,
339
+ object.name
340
+ ]);
341
+ for (const row of foreign.rows){
342
+ var _key_referencedColumns;
343
+ let key = keys.find((item)=>item.name === string(row[0]));
344
+ if (!key) {
345
+ key = {
346
+ name: string(row[0]),
347
+ kind: 'foreign',
348
+ columns: [],
349
+ referencedTable: {
350
+ database: string(row[2]),
351
+ name: string(row[3]),
352
+ kind: 'table'
353
+ },
354
+ referencedColumns: []
355
+ };
356
+ keys.push(key);
357
+ }
358
+ key.columns.push(string(row[1]));
359
+ (_key_referencedColumns = key.referencedColumns) == null ? void 0 : _key_referencedColumns.push(string(row[4]));
360
+ }
361
+ }
362
+ }
363
+ const editable = (this.engine !== 'doris' || (await this.capabilities()).writes) && object.kind === 'table' && keys.some((key)=>(key.kind === 'primary' || key.kind === 'unique') && key.columns.length > 0 && key.columns.every((name)=>{
364
+ var _columns_find;
365
+ return ((_columns_find = columns.find((column)=>column.name === name)) == null ? void 0 : _columns_find.nullable) === false;
366
+ }));
367
+ return {
368
+ object,
369
+ columns,
370
+ keys,
371
+ definition,
372
+ model,
373
+ editable,
374
+ diagnostics
375
+ };
376
+ }
377
+ async query(input, signal) {
378
+ this.assertLocation(input);
379
+ if (input.database && input.database !== this.initial.database) throw new Error('database_requires_new_connection');
380
+ if (input.schema && input.schema !== this.initial.schema) throw new Error('schema_requires_new_connection');
381
+ if (this.busy) throw new Error('session_busy');
382
+ if (input.mode === 'read') {
383
+ requireReadStatement(input.sql);
384
+ }
385
+ const statements = splitWorkbenchSql(input.sql);
386
+ if (statements.length !== 1 || statements[0].effect === 'unsupported') throw new Error('unsupported_statement');
387
+ var _input_limit, _input_offset;
388
+ const limit = Math.max(1, Math.min((_input_limit = input.limit) != null ? _input_limit : 100, 1000)), offset = Math.max(0, Math.min((_input_offset = input.offset) != null ? _input_offset : 0, 100000));
389
+ if (input.mode === 'write' && !(await this.capabilities()).writes) throw new Error('engine_version_writes_unavailable');
390
+ if (input.mode === 'write' && offset) throw new Error('mutation_cannot_be_paginated');
391
+ let sql = statements[0].sql;
392
+ // Wrap a SELECT instead of appending LIMIT after an existing LIMIT or trailing comment.
393
+ if (input.mode === 'read' && [
394
+ 'SELECT',
395
+ 'WITH'
396
+ ].includes(statements[0].words[0])) sql = `SELECT * FROM (\n${sql}\n) AS ${this.quote('_db_studio_page')} LIMIT ${limit + 1} OFFSET ${offset}`;
397
+ else if (offset) throw new Error('statement_cannot_be_paginated');
398
+ this.busy = true;
399
+ let readTransaction = false;
400
+ try {
401
+ if (input.mode === 'read' && this.engine !== 'doris' && !this.transactionOpen) {
402
+ await this.raw(this.engine === 'postgres' ? 'BEGIN READ ONLY' : 'START TRANSACTION READ ONLY', [], 1);
403
+ readTransaction = true;
404
+ }
405
+ var _input_timeoutMs;
406
+ const result = await this.raw(sql, input.parameters, limit + 1, signal, Math.max(1, Math.min((_input_timeoutMs = input.timeoutMs) != null ? _input_timeoutMs : 30000, 120000)));
407
+ const hasMore = result.rows.length > limit || result.truncated;
408
+ return _extends._extends({}, result, {
409
+ rows: result.rows.slice(0, limit),
410
+ hasMore,
411
+ truncated: result.truncated
412
+ });
413
+ } finally{
414
+ try {
415
+ if (readTransaction) {
416
+ try {
417
+ await this.raw('ROLLBACK', [], 1);
418
+ } catch (e) {
419
+ await this.close();
420
+ }
421
+ }
422
+ } finally{
423
+ this.busy = false;
424
+ }
425
+ }
426
+ }
427
+ async explain(input, signal) {
428
+ const sql = requireReadStatement(input.sql);
429
+ if (!/^(SELECT|WITH)\b/i.test(sql)) throw new Error('explain_requires_select');
430
+ const prefix = this.engine === 'postgres' ? 'EXPLAIN (FORMAT JSON) ' : this.engine === 'mysql' ? 'EXPLAIN FORMAT=JSON ' : 'EXPLAIN ';
431
+ return this.query(_extends._extends({}, input, {
432
+ sql: prefix + sql,
433
+ mode: 'read'
434
+ }), signal);
435
+ }
436
+ async transaction(action) {
437
+ if (this.engine === 'doris') throw new Error('transactions_not_supported');
438
+ if (this.busy) throw new Error('session_busy');
439
+ if (action === 'begin' && this.transactionOpen) throw new Error('transaction_already_open');
440
+ if (action !== 'begin' && !this.transactionOpen) throw new Error('transaction_not_open');
441
+ this.busy = true;
442
+ try {
443
+ await this.raw(action.toUpperCase(), [], 1);
444
+ this.transactionOpen = action === 'begin';
445
+ } finally{
446
+ this.busy = false;
447
+ }
448
+ }
449
+ async importRows(input, signal) {
450
+ this.assertLocation(input);
451
+ if (this.busy) throw new Error('session_busy');
452
+ if (!(await this.capabilities()).import) throw new Error('engine_version_import_unavailable');
453
+ if (new Set(input.columns).size !== input.columns.length) throw new Error('duplicate_import_columns');
454
+ if (!input.rows.length || input.rows.length > 10000 || !input.columns.length || input.columns.length > 500 || input.rows.some((row)=>row.length !== input.columns.length)) throw new Error('invalid_import_shape');
455
+ if (this.transport.importRows) {
456
+ this.busy = true;
457
+ try {
458
+ return await this.transport.importRows(input, signal);
459
+ } finally{
460
+ this.busy = false;
461
+ }
462
+ }
463
+ if (this.engine === 'doris') throw new Error('stream_load_required');
464
+ const reference = this.ref(_extends._extends({}, input, {
465
+ name: input.table,
466
+ kind: 'table'
467
+ }));
468
+ await this.transaction('begin');
469
+ try {
470
+ for(let i = 0; i < input.rows.length; i += 100){
471
+ const batch = input.rows.slice(i, i + 100), values = batch.flat();
472
+ let n = 0;
473
+ const placeholders = batch.map((row)=>`(${row.map(()=>this.engine === 'postgres' ? `$${++n}` : '?').join(',')})`).join(',');
474
+ await this.query({
475
+ sql: `INSERT INTO ${reference} (${input.columns.map((name)=>this.quote(name)).join(',')}) VALUES ${placeholders}`,
476
+ parameters: values,
477
+ mode: 'write'
478
+ }, signal);
479
+ }
480
+ await this.transaction('commit');
481
+ return {
482
+ outcome: 'succeeded',
483
+ loadedRows: input.rows.length,
484
+ filteredRows: 0,
485
+ label: input.operationId,
486
+ diagnostics: []
487
+ };
488
+ } catch (error) {
489
+ try {
490
+ await this.transaction('rollback');
491
+ } catch (e) {
492
+ await this.close();
493
+ }
494
+ throw error;
495
+ }
496
+ }
497
+ async cancel() {
498
+ await this.close();
499
+ }
500
+ async close() {
501
+ this.closed = true;
502
+ await this.transport.close();
503
+ }
504
+ constructor(engine, transport, initial = {}){
505
+ this.transactionOpen = false;
506
+ this.busy = false;
507
+ this.closed = false;
508
+ this.engine = engine;
509
+ this.transport = transport;
510
+ this.initial = initial;
511
+ }
512
+ }
513
+
514
+ exports.DataSourceRuntimeCapability = DataSourceRuntimeCapability;
515
+ exports.SqlDatabaseWorkbenchAdapter = SqlDatabaseWorkbenchAdapter;
516
+ exports.quoteDatabaseIdentifier = quoteDatabaseIdentifier;
517
+ exports.requireReadStatement = requireReadStatement;
518
+ exports.splitWorkbenchSql = splitWorkbenchSql;
@@ -0,0 +1,2 @@
1
+ export * from './data-workbench.cjs.js';
2
+ export { _default as default } from './data-workbench.cjs.default.js';
@@ -0,0 +1 @@
1
+ export * from "./src/data-workbench";