@nocobase/plugin-data-source-manager 2.1.0-alpha.4 → 2.1.0-alpha.45

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 (85) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +79 -10
  3. package/client-v2.d.ts +2 -0
  4. package/client-v2.js +1 -0
  5. package/dist/ai/ai-employees/orin/ignored.d.ts +10 -0
  6. package/dist/ai/ai-employees/orin/ignored.js +59 -0
  7. package/dist/ai/ai-employees/orin/skills/data-modeling/SKILLS.md +245 -0
  8. package/dist/ai/{tools → ai-employees/orin/skills/data-modeling/tools}/defineCollections.js +47 -1
  9. package/dist/ai/{tools → ai-employees/orin/skills/data-modeling/tools}/intentRouter.js +1 -1
  10. package/dist/ai/{tools/data-query → common}/common.js +63 -11
  11. package/dist/ai/{tools/data-query → common}/utils.d.ts +1 -0
  12. package/dist/ai/{tools/data-query → common}/utils.js +15 -0
  13. package/dist/ai/skills/data-metadata/SKILLS.md +108 -0
  14. package/dist/ai/{tools → skills/data-metadata/tools}/getCollectionMetadata.js +2 -2
  15. package/dist/ai/{tools → skills/data-metadata/tools}/getCollectionNames.js +2 -2
  16. package/dist/ai/{tools → skills/data-metadata/tools}/getDataSources.js +2 -2
  17. package/dist/ai/{tools → skills/data-metadata/tools}/searchFieldMetadata.js +2 -2
  18. package/dist/ai/skills/data-query/SKILLS.md +246 -0
  19. package/dist/ai/skills/data-query/tools/dataQuery.js +282 -0
  20. package/dist/ai/{tools/data-query → skills/data-query/tools}/dataSourceCounting.js +12 -4
  21. package/dist/ai/skills/data-query/tools/dataSourceQuery.d.ts +10 -0
  22. package/dist/ai/{tools/data-query → skills/data-query/tools}/dataSourceQuery.js +12 -5
  23. package/dist/client/271.b3c0013cf976adf0.js +10 -0
  24. package/dist/client/369.6025f2112454fab1.js +10 -0
  25. package/dist/client/397.c8c6659cf3f7ac1c.js +10 -0
  26. package/dist/client/416.197712b8b93033c5.js +10 -0
  27. package/dist/client/420.0c3e3c9888e0ba31.js +10 -0
  28. package/dist/client/603.d6022bceb934f5b0.js +10 -0
  29. package/dist/client/644.b98e4b39058e9c32.js +10 -0
  30. package/dist/client/936.d5ab7aecb4eb6004.js +10 -0
  31. package/dist/client/index.js +1 -1
  32. package/dist/client-v2/167.c72713810e2d0526.js +10 -0
  33. package/dist/client-v2/426.3cd9f994a3145829.js +10 -0
  34. package/dist/client-v2/components/DataSourceForm.d.ts +17 -0
  35. package/dist/client-v2/field-interfaces/index.d.ts +13 -0
  36. package/dist/client-v2/index.d.ts +12 -0
  37. package/dist/client-v2/index.js +10 -0
  38. package/dist/client-v2/locale.d.ts +11 -0
  39. package/dist/client-v2/pages/DataSourceCollectionsPage.d.ts +10 -0
  40. package/dist/client-v2/pages/DataSourcesPage.d.ts +10 -0
  41. package/dist/client-v2/pages/components/CollectionsPage.d.ts +15 -0
  42. package/dist/client-v2/pages/components/FieldForm.d.ts +19 -0
  43. package/dist/client-v2/pages/components/FieldsPage.d.ts +15 -0
  44. package/dist/client-v2/pages/components/SqlCollectionConfigure.d.ts +62 -0
  45. package/dist/client-v2/pages/components/ViewCollectionConfigure.d.ts +27 -0
  46. package/dist/client-v2/pages/components/collectionFieldApi.d.ts +11 -0
  47. package/dist/client-v2/pages/components/collectionTemplateFieldInterfaces.d.ts +18 -0
  48. package/dist/client-v2/plugin.d.ts +188 -0
  49. package/dist/client-v2/runtime.d.ts +12 -0
  50. package/dist/client-v2/utils/compileLegacyTemplate.d.ts +13 -0
  51. package/dist/externalVersion.js +17 -13
  52. package/dist/locale/en-US.json +270 -3
  53. package/dist/locale/zh-CN.json +270 -3
  54. package/dist/node_modules/zod/index.cjs +1 -1
  55. package/dist/node_modules/zod/package.json +1 -1
  56. package/dist/server/mcp-post-processors.d.ts +24 -0
  57. package/dist/server/mcp-post-processors.js +158 -0
  58. package/dist/server/models/data-source.js +3 -13
  59. package/dist/server/plugin.js +2 -0
  60. package/dist/server/resourcers/data-sources-collections-fields.d.ts +1 -0
  61. package/dist/server/resourcers/data-sources-collections-fields.js +9 -0
  62. package/dist/server/services/external-field-apply.d.ts +23 -0
  63. package/dist/server/services/external-field-apply.js +208 -0
  64. package/dist/server/utils.js +6 -1
  65. package/dist/swagger/index.d.ts +326 -0
  66. package/dist/swagger/index.js +385 -0
  67. package/package.json +5 -2
  68. package/LICENSE.txt +0 -172
  69. package/dist/client/1a8cce8035a89dd9.js +0 -10
  70. package/dist/client/276bcf3de74bebd1.js +0 -10
  71. package/dist/client/2f7a418e7935984d.js +0 -10
  72. package/dist/client/689d3bec33ad2ec3.js +0 -10
  73. package/dist/client/d33eb40009bd7ec6.js +0 -10
  74. package/dist/client/dbf5ddf434ab29c0.js +0 -10
  75. package/dist/client/f528b165e77e3608.js +0 -10
  76. package/dist/client/fdae18574fda07af.js +0 -10
  77. /package/dist/ai/{tools → ai-employees/orin/skills/data-modeling/tools}/defineCollections.d.ts +0 -0
  78. /package/dist/ai/{tools → ai-employees/orin/skills/data-modeling/tools}/intentRouter.d.ts +0 -0
  79. /package/dist/ai/{tools/data-query → common}/common.d.ts +0 -0
  80. /package/dist/ai/{tools → skills/data-metadata/tools}/getCollectionMetadata.d.ts +0 -0
  81. /package/dist/ai/{tools → skills/data-metadata/tools}/getCollectionNames.d.ts +0 -0
  82. /package/dist/ai/{tools → skills/data-metadata/tools}/getDataSources.d.ts +0 -0
  83. /package/dist/ai/{tools → skills/data-metadata/tools}/searchFieldMetadata.d.ts +0 -0
  84. /package/dist/ai/{tools/data-query/dataSourceCounting.d.ts → skills/data-query/tools/dataQuery.d.ts} +0 -0
  85. /package/dist/ai/{tools/data-query/dataSourceQuery.d.ts → skills/data-query/tools/dataSourceCounting.d.ts} +0 -0
@@ -42,7 +42,7 @@ module.exports = __toCommonJS(defineCollections_exports);
42
42
  var import_ai = require("@nocobase/ai");
43
43
  var import_lodash = __toESM(require("lodash"));
44
44
  var import_zod = require("zod");
45
- var import_package = __toESM(require("../../../package.json"));
45
+ var import_package = __toESM(require("../../../../../../../package.json"));
46
46
  const idField = {
47
47
  name: "id",
48
48
  type: "snowflakeId",
@@ -88,6 +88,44 @@ const updatedAtField = {
88
88
  "x-read-pretty": true
89
89
  }
90
90
  };
91
+ const createdByField = {
92
+ type: "belongsTo",
93
+ name: "createdBy",
94
+ interface: "createdBy",
95
+ target: "users",
96
+ foreignKey: "createdById",
97
+ uiSchema: {
98
+ type: "object",
99
+ title: '{{t("Created by")}}',
100
+ "x-component": "AssociationField",
101
+ "x-component-props": {
102
+ fieldNames: {
103
+ value: "id",
104
+ label: "nickname"
105
+ }
106
+ },
107
+ "x-read-pretty": true
108
+ }
109
+ };
110
+ const updatedByField = {
111
+ type: "belongsTo",
112
+ name: "updatedBy",
113
+ interface: "updatedBy",
114
+ target: "users",
115
+ foreignKey: "updatedById",
116
+ uiSchema: {
117
+ type: "object",
118
+ title: '{{t("Last updated by")}}',
119
+ "x-component": "AssociationField",
120
+ "x-component-props": {
121
+ fieldNames: {
122
+ value: "id",
123
+ label: "nickname"
124
+ }
125
+ },
126
+ "x-read-pretty": true
127
+ }
128
+ };
91
129
  class IntentError extends Error {
92
130
  constructor(message) {
93
131
  super(message);
@@ -161,6 +199,14 @@ var defineCollections_default = (0, import_ai.defineTools)({
161
199
  systemFields.push(updatedAtField);
162
200
  options.updatedAt = true;
163
201
  }
202
+ if (options.createdBy) {
203
+ systemFields.push(createdByField);
204
+ options.createdBy = true;
205
+ }
206
+ if (options.updatedBy) {
207
+ systemFields.push(updatedByField);
208
+ options.updatedBy = true;
209
+ }
164
210
  const baseFields = [...systemFields, ...normalFields];
165
211
  const collection = await repo.findOne({ filter: { name: options.name }, transaction });
166
212
  if (!collection) {
@@ -40,7 +40,7 @@ __export(intentRouter_exports, {
40
40
  });
41
41
  module.exports = __toCommonJS(intentRouter_exports);
42
42
  var import_ai = require("@nocobase/ai");
43
- var import_package = __toESM(require("../../../package.json"));
43
+ var import_package = __toESM(require("../../../../../../../package.json"));
44
44
  const createPrompt = `You are now entering the **New Schema Creation Flow**. Follow these rules:
45
45
 
46
46
  1. **Design Tables and Fields**
@@ -40,7 +40,7 @@ __export(common_exports, {
40
40
  });
41
41
  module.exports = __toCommonJS(common_exports);
42
42
  var import_zod = require("zod");
43
- var import_package = __toESM(require("../../../../package.json"));
43
+ var import_package = __toESM(require("../../../package.json"));
44
44
  const ArgSchema = import_zod.z.object({
45
45
  datasource: import_zod.z.string().describe(`{{t("ai.tools.dataQuery.args.datasource", { ns: "${import_package.default.name}" })}}`),
46
46
  collectionName: import_zod.z.string().describe(`{{t("ai.tools.dataQuery.args.collectionName", { ns: "${import_package.default.name}" })}}`),
@@ -49,34 +49,34 @@ const ArgSchema = import_zod.z.object({
49
49
  filter: import_zod.z.object({}).catchall(import_zod.z.any()).describe(`# Parameters definition
50
50
  \`\`\`
51
51
  export type QueryCondition = {
52
- [field: string]: { // \`field\` key is Field name
53
- [operator: string]: any; // \`operator\` key is Query condition operator.
52
+ [field: string]: {
53
+ [operator: string]: any;
54
54
  };
55
55
  };
56
56
 
57
57
 
58
58
  export type QueryObject =
59
59
  | {
60
- [logic: string]: (QueryCondition | QueryObject)[]; // \`logic\` key is the relationship between query conditions, should be one of '$and', '$or', the value is recursion object array, item in array can be QueryCondition or QueryObject
60
+ [logic: string]: (QueryCondition | QueryObject)[];
61
61
  }
62
- | QueryCondition // QueryCondition definition above;
62
+ | QueryCondition
63
63
  \`\`\`
64
64
 
65
-
66
- // QueryCondition examples
65
+ Field names are collection field names.
66
+ Operator names are query operators such as \`$eq\`, \`$in\`, or \`$dateOn\`.
67
+ Logical keys in \`QueryObject\` should be \`$and\` or \`$or\`.
67
68
 
68
69
  \`\`\`
69
70
  const example1: QueryCondition = {
70
- age: { $gt: 18 }, // age great than 18
71
- name: { $like: '%John%' }, // name include John
71
+ age: { $gt: 18 },
72
+ name: { $like: '%John%' },
72
73
  };
73
74
 
74
75
  const example2: QueryCondition = {
75
- status: { $in: ['active', 'pending'] }, // status is active or pending
76
+ status: { $in: ['active', 'pending'] },
76
77
  };
77
78
  \`\`\`
78
79
 
79
- // QueryObject example
80
80
  \`\`\`
81
81
  const example1: QueryObject = {
82
82
  $and: [
@@ -99,6 +99,58 @@ const example2: QueryObject = {
99
99
 
100
100
  const example3: QueryObject = { age: { $lt: 50 } };
101
101
  \`\`\`
102
+
103
+ Supported scalar operators include:
104
+ \`$eq\`, \`$ne\`, \`$gt\`, \`$gte\`, \`$lt\`, \`$lte\`, \`$like\`, \`$notLike\`, \`$includes\`, \`$notIncludes\`, \`$in\`, \`$notIn\`, \`$dateOn\`, \`$dateNotOn\`, \`$dateBefore\`, \`$dateAfter\`, \`$dateNotBefore\`, \`$dateNotAfter\`, \`$dateBetween\`, \`$empty\`, \`$notEmpty\`
105
+
106
+ Frontend date filter contract:
107
+ - Always follow the same frontend-compatible date filter contract used by NocoBase filters.
108
+ - \`filter\` itself must be a structured object. Do not pass a JSON-encoded string such as \`"{\\"createdAt\\":{\\"$dateOn\\":\\"2025-11\\"}}"\`.
109
+ - For calendar-style date filtering, supported operators are exactly:
110
+ - \`$dateOn\`
111
+ - \`$dateNotOn\`
112
+ - \`$dateBefore\`
113
+ - \`$dateAfter\`
114
+ - \`$dateNotBefore\`
115
+ - \`$dateNotAfter\`
116
+ - \`$dateBetween\`
117
+ - \`$empty\`
118
+ - \`$notEmpty\`
119
+ - Do not generate \`$gte\`, \`$gt\`, \`$lte\`, \`$lt\`, or custom operator names for calendar date ranges.
120
+ - Allowed values:
121
+ - for \`$dateOn\`, \`$dateNotOn\`, \`$dateBefore\`, \`$dateAfter\`, \`$dateNotBefore\`, \`$dateNotAfter\`: \`YYYY-MM-DD\`, \`YYYY-MM\`, \`YYYY\`, a relative period object, or an exact datetime string only when the user explicitly wants timestamp comparison
122
+ - for \`$dateBetween\`: \`["YYYY-MM-DD", "YYYY-MM-DD"]\` or a relative period object
123
+ - for \`$empty\` and \`$notEmpty\`: no value
124
+ - Relative period object \`type\` values are exactly:
125
+ - \`today\`
126
+ - \`yesterday\`
127
+ - \`tomorrow\`
128
+ - \`thisWeek\`
129
+ - \`lastWeek\`
130
+ - \`nextWeek\`
131
+ - \`thisMonth\`
132
+ - \`lastMonth\`
133
+ - \`nextMonth\`
134
+ - \`thisQuarter\`
135
+ - \`lastQuarter\`
136
+ - \`nextQuarter\`
137
+ - \`thisYear\`
138
+ - \`lastYear\`
139
+ - \`nextYear\`
140
+ - \`past\`
141
+ - \`next\`
142
+ - If \`type\` is \`past\` or \`next\`, also provide:
143
+ - \`number\`: positive integer
144
+ - \`unit\`: one of \`day\`, \`week\`, \`month\`, \`quarter\`, \`year\`
145
+ - Prefer frontend-style calendar filters such as:
146
+ - \`{ createdAt: { $dateOn: "2026-04" } }\`
147
+ - \`{ createdAt: { $dateOn: { type: "thisMonth" } } }\`
148
+ - \`{ createdAt: { $dateBetween: ["2026-04-01", "2026-04-30"] } }\`
149
+ - Do not expand calendar queries into UTC month-start or day-start boundary expressions.
150
+ - If the user explicitly asks for exact timestamp comparison instead of a calendar period:
151
+ - timezone-aware datetime fields: use ISO 8601 strings such as \`2026-04-10T12:00:00.000Z\`
152
+ - \`datetimeNoTz\` fields: use local datetime strings such as \`2026-04-10 12:00:00\`
153
+ - \`dateOnly\` fields: use date-only strings without time components
102
154
  `),
103
155
  sort: import_zod.z.array(import_zod.z.string()).describe(`{{t("ai.tools.dataQuery.args.sort", { ns: "${import_package.default.name}" })}}`),
104
156
  offset: import_zod.z.number().optional().describe(`{{t("ai.tools.dataQuery.args.offset", { ns: "${import_package.default.name}" })}}`),
@@ -38,3 +38,4 @@ export declare function buildPagedToolResult<T>(params: {
38
38
  * 递归截断对象中的长字符串,保持 JSON 结构完整
39
39
  */
40
40
  export declare function truncateLongStrings(obj: any, maxLen?: number): any;
41
+ export declare function getStructuredQueryArgError(name: string, value: unknown): string | null;
@@ -29,6 +29,7 @@ __export(utils_exports, {
29
29
  MAX_QUERY_LIMIT: () => MAX_QUERY_LIMIT,
30
30
  MAX_STRING_LENGTH: () => MAX_STRING_LENGTH,
31
31
  buildPagedToolResult: () => buildPagedToolResult,
32
+ getStructuredQueryArgError: () => getStructuredQueryArgError,
32
33
  normalizeLimitOffset: () => normalizeLimitOffset,
33
34
  truncateLongStrings: () => truncateLongStrings
34
35
  });
@@ -77,11 +78,25 @@ function truncateLongStrings(obj, maxLen = MAX_STRING_LENGTH) {
77
78
  }
78
79
  return obj;
79
80
  }
81
+ function getStructuredQueryArgError(name, value) {
82
+ if (value === void 0) {
83
+ return null;
84
+ }
85
+ if (typeof value === "string") {
86
+ const example = name === "having" ? '{ "count": { "$gt": 10 } }' : '{ "createdAt": { "$dateOn": "2025-11" } }';
87
+ return `"${name}" must be an object, not a JSON string. Pass structured JSON like ${example}.`;
88
+ }
89
+ if (value === null || Array.isArray(value) || typeof value !== "object") {
90
+ return `"${name}" must be an object.`;
91
+ }
92
+ return null;
93
+ }
80
94
  // Annotate the CommonJS export names for ESM import in node:
81
95
  0 && (module.exports = {
82
96
  MAX_QUERY_LIMIT,
83
97
  MAX_STRING_LENGTH,
84
98
  buildPagedToolResult,
99
+ getStructuredQueryArgError,
85
100
  normalizeLimitOffset,
86
101
  truncateLongStrings
87
102
  });
@@ -0,0 +1,108 @@
1
+ ---
2
+ scope: GENERAL
3
+ name: data-metadata
4
+ description: helps get collection metadata (data model, like database table definition, RESTful API definition), like collection definition, field metadata
5
+ tools:
6
+ - getDataSources
7
+ - getCollectionNames
8
+ - getCollectionMetadata
9
+ - searchFieldMetadata
10
+ introduction:
11
+ title: '{{t("ai.skills.dataMetadata.title", { ns: "@nocobase/plugin-data-source-manager" })}}'
12
+ about: '{{t("ai.skills.dataMetadata.about", { ns: "@nocobase/plugin-data-source-manager" })}}'
13
+ ---
14
+
15
+ You are a professional data model metadata assistant for NocoBase.
16
+
17
+ You help users explore and understand existing database schemas, including collection definitions, field metadata, and relationships.
18
+
19
+ # Primary Workflows
20
+
21
+ This skill focuses on reading and exploring existing data models, not creating or modifying them.
22
+
23
+ ## Data Source Exploration
24
+
25
+ When users want to understand available data sources:
26
+
27
+ 1. **List Data Sources**
28
+ - Call `getDataSources` to retrieve all available data sources
29
+ - Present the data source list with their display names and database types
30
+
31
+ 2. **Select a Data Source**
32
+ - If the user mentions a specific data source, confirm which one to use
33
+ - Default to "main" if not specified
34
+
35
+ ## Collection Exploration
36
+
37
+ When users want to understand what collections exist in a data source:
38
+
39
+ 1. **List Collections**
40
+ - Call `getCollectionNames` with the appropriate data source to get all collection names and titles
41
+
42
+ 2. **Explore Collection Details**
43
+ - Call `getCollectionMetadata` to retrieve detailed information about specific collections
44
+ - This includes field definitions, field types, interfaces, and options
45
+
46
+ ## Field Search
47
+
48
+ When users want to find specific fields across collections:
49
+
50
+ 1. **Search by Keyword**
51
+ - Call `searchFieldMetadata` with keywords (e.g., "order amount", "user email")
52
+ - Optionally filter by data source, collection, or field type
53
+
54
+ 2. **Interpret Results**
55
+ - Present the search results with field names, titles, collection names, and data source
56
+ - If no exact results, explain suggested results
57
+
58
+ # Available Tools
59
+
60
+ - `getDataSources`: Lists all available data sources with their display names and database types.
61
+ - `getCollectionNames`: Lists all collections in a specified data source with their names and titles. Use this to disambiguate user references.
62
+ - `getCollectionMetadata`: Returns detailed field definitions and metadata for specified collections, including field types, interfaces, and options.
63
+ - `searchFieldMetadata`: Searches for fields across data models by keyword. Returns either exact results or suggested results. Supports filtering by data source, collection, and field type.
64
+
65
+ # Common Use Cases
66
+
67
+ ## Explore All Collections
68
+ ```
69
+ User: "Show me all tables in the database"
70
+ Action: Call getCollectionNames with dataSource="main"
71
+ ```
72
+
73
+ ## Get Collection Schema
74
+ ```
75
+ User: "What fields does the users collection have?"
76
+ Action: Call getCollectionMetadata with collectionNames=["users"]
77
+ ```
78
+
79
+ ## Search for Specific Fields
80
+ ```
81
+ User: "Find fields related to email"
82
+ Action: Call searchFieldMetadata with query="email"
83
+ ```
84
+
85
+ ## Understand Data Sources
86
+ ```
87
+ User: "What databases are available?"
88
+ Action: Call getDataSources
89
+ ```
90
+
91
+ # Field Metadata Structure
92
+
93
+ When displaying field metadata, present information clearly:
94
+
95
+ | Property | Description |
96
+ | ----------- | --------------------------------------------------------------- |
97
+ | `name` | Field internal name |
98
+ | `title` | Field display name |
99
+ | `type` | Field data type (string, integer, boolean, etc.) |
100
+ | `interface` | Field interface type (input, select, m2o, etc.) |
101
+ | `options` | Additional field options (enum values, default, required, etc.) |
102
+
103
+ # Notes
104
+
105
+ - This skill is read-only - it does not modify any data or schema
106
+ - Always confirm the data source before querying collections
107
+ - When searching fields, provide context about which collection each field belongs to
108
+ - Use clear formatting when presenting metadata to help users understand the schema
@@ -40,9 +40,9 @@ __export(getCollectionMetadata_exports, {
40
40
  });
41
41
  module.exports = __toCommonJS(getCollectionMetadata_exports);
42
42
  var import_ai = require("@nocobase/ai");
43
- var import_package = __toESM(require("../../../package.json"));
43
+ var import_package = __toESM(require("../../../../../package.json"));
44
44
  var getCollectionMetadata_default = (0, import_ai.defineTools)({
45
- scope: "GENERAL",
45
+ scope: "SPECIFIED",
46
46
  defaultPermission: "ALLOW",
47
47
  introduction: {
48
48
  title: `{{t("ai.tools.getCollectionMetadata.title", { ns: "${import_package.default.name}" })}}`,
@@ -40,9 +40,9 @@ __export(getCollectionNames_exports, {
40
40
  });
41
41
  module.exports = __toCommonJS(getCollectionNames_exports);
42
42
  var import_ai = require("@nocobase/ai");
43
- var import_package = __toESM(require("../../../package.json"));
43
+ var import_package = __toESM(require("../../../../../package.json"));
44
44
  var getCollectionNames_default = (0, import_ai.defineTools)({
45
- scope: "GENERAL",
45
+ scope: "SPECIFIED",
46
46
  defaultPermission: "ALLOW",
47
47
  introduction: {
48
48
  title: `{{t("ai.tools.getCollectionNames.title", { ns: "${import_package.default.name}" })}}`,
@@ -40,9 +40,9 @@ __export(getDataSources_exports, {
40
40
  });
41
41
  module.exports = __toCommonJS(getDataSources_exports);
42
42
  var import_ai = require("@nocobase/ai");
43
- var import_package = __toESM(require("../../../package.json"));
43
+ var import_package = __toESM(require("../../../../../package.json"));
44
44
  var getDataSources_default = (0, import_ai.defineTools)({
45
- scope: "GENERAL",
45
+ scope: "SPECIFIED",
46
46
  defaultPermission: "ALLOW",
47
47
  introduction: {
48
48
  title: `{{t("ai.tools.getDataSources.title", { ns: "${import_package.default.name}" })}}`,
@@ -40,9 +40,9 @@ __export(searchFieldMetadata_exports, {
40
40
  });
41
41
  module.exports = __toCommonJS(searchFieldMetadata_exports);
42
42
  var import_ai = require("@nocobase/ai");
43
- var import_package = __toESM(require("../../../package.json"));
43
+ var import_package = __toESM(require("../../../../../package.json"));
44
44
  var searchFieldMetadata_default = (0, import_ai.defineTools)({
45
- scope: "GENERAL",
45
+ scope: "SPECIFIED",
46
46
  defaultPermission: "ALLOW",
47
47
  introduction: {
48
48
  title: `{{t("ai.tools.searchFieldMetadata.title", { ns: "${import_package.default.name}" })}}`,
@@ -0,0 +1,246 @@
1
+ ---
2
+ scope: GENERAL
3
+ name: data-query
4
+ description: Inspect schemas, retrieve records, and run aggregate queries on specified datasources
5
+ tools:
6
+ - getSkill
7
+ - dataQuery
8
+ - dataSourceQuery
9
+ - dataSourceCounting
10
+ introduction:
11
+ title: '{{t("ai.skills.dataQuery.title", { ns: "@nocobase/plugin-data-source-manager" })}}'
12
+ about: '{{t("ai.skills.dataQuery.about", { ns: "@nocobase/plugin-data-source-manager" })}}'
13
+ ---
14
+ You are a professional data query assistant for NocoBase.
15
+
16
+ You help users inspect schemas, retrieve records, and run aggregate queries on NocoBase collections.
17
+
18
+ # Primary Workflows
19
+
20
+ This skill focuses on safe read-only data access.
21
+
22
+ ## Schema-first Querying
23
+
24
+ When the user does not provide an exact collection or field name, or when this is the first query against a collection in the current conversation:
25
+
26
+ 1. If schema, field, relation path, or datasource choice is not already explicit and reliable, your first tool call must be `getSkill` with `skillName="data-metadata"`.
27
+ 2. Do not guess collection names, field names, relation paths, or data types before `data-metadata` has been loaded.
28
+ 3. Call `getDataSources` from the loaded `data-metadata` workflow if the target data source is unclear.
29
+ 4. If multiple data sources may contain relevant data, inspect each candidate before choosing the query scope.
30
+ 5. Do not silently default to `main` when other relevant data sources are available.
31
+ 6. If you intentionally limit the query to one data source, explain why that data source was chosen and why others were not used.
32
+ 7. Call `getCollectionNames` from the loaded `data-metadata` workflow to find the right collection.
33
+ 8. Call `getCollectionMetadata` or `searchFieldMetadata` from the loaded `data-metadata` workflow to confirm field names, relation paths, and data types.
34
+ 9. Only then run a data tool.
35
+ 10. Even if the user already mentions a collection name such as `date_boundary_cases` or a common field such as `createdAt`, verify them with the loaded `data-metadata` workflow before the first real query when the collection has not yet been confirmed in the current conversation.
36
+
37
+ Do not guess collection names, measure aliases, or dotted relation paths.
38
+
39
+ ## Raw Record Query
40
+
41
+ Use `dataSourceQuery` when the user wants actual records rather than grouped statistics.
42
+
43
+ Typical cases:
44
+
45
+ - list rows
46
+ - inspect recent records
47
+ - fetch selected fields
48
+ - browse data with filter, sort, and pagination
49
+
50
+ ## Aggregate Query
51
+
52
+ Use `dataQuery` when the user wants:
53
+
54
+ - counts, sums, averages, min, max
55
+ - grouped statistics
56
+ - rankings
57
+ - trend buckets
58
+ - post-aggregation filtering with `having`
59
+
60
+ Prefer `dataQuery` over `dataSourceCounting` whenever the request can be expressed as a measure query, because it is closer to the repository `query` capability used by charts, actions, ACL, and MCP.
61
+
62
+ ### Aggregate Query Failure Handling
63
+
64
+ If `dataQuery` fails, do not immediately switch to `dataSourceQuery` and manually sum, count, group, or rank records.
65
+
66
+ Before falling back to raw records, inspect the tool error and retry `dataQuery` with corrected parameters. Date filters are the most common source of aggregate query failures, so check them first. Common fixes include:
67
+
68
+ - rebuild date ranges with the frontend date filter contract below, such as `$dateOn`, `$dateBetween`, or relative period objects
69
+ - replace unsupported calendar operators such as `$gte`, `$gt`, `$lte`, `$lt`, or custom date operator names
70
+ - avoid UTC boundary expansions like `2026-04-01T00:00:00.000Z` to `2026-05-01T00:00:00.000Z` unless the user explicitly asks for exact timestamp comparison
71
+ - verify field names, relation paths, and data types with metadata
72
+ - correct `measures`, `dimensions`, aliases, and `orders`
73
+ - fix `filter` versus `having` placement
74
+ - simplify grouping, relation paths, or post-aggregation filters
75
+ - confirm the intended `dataSource` and `collectionName`
76
+
77
+ For aggregation/statistics/rankings/trends, raw record fetching plus manual calculation is a last resort only. Use `dataSourceQuery` as a fallback only when:
78
+
79
+ - the user explicitly asks to inspect raw records
80
+ - the requested computation cannot be expressed with `dataQuery`
81
+ - boundary-sensitive verification requires a small sample of source records
82
+ - at least two corrected `dataQuery` attempts have failed and the error has been analyzed
83
+
84
+ When a raw-record fallback is unavoidable, explain why `dataQuery` could not be used, keep the fetched record set small, and do not fetch large datasets just to manually aggregate them.
85
+
86
+ ## Count Records
87
+
88
+ Use `dataSourceCounting` only for a simple total when grouped output is unnecessary.
89
+
90
+ ## Query Construction Rules
91
+
92
+ 1. `filter` is applied before aggregation.
93
+ 2. `having` is applied after aggregation and should reference selected aliases or selected field paths.
94
+ 3. For grouped results, put grouping fields in `dimensions`.
95
+ 4. For metrics, put aggregate definitions in `measures`.
96
+ 5. Use aliases when the user clearly needs stable output keys.
97
+ 6. For dotted relation fields, prefer the exact field path confirmed from metadata, such as `createdBy.nickname`.
98
+ 7. Default row limit is 50 and the tool caps the limit at 100.
99
+ 8. Always follow the same frontend date filter contract used by NocoBase filters.
100
+ 8.1. `filter` and `having` must be structured objects, not JSON-encoded strings.
101
+ 9. Supported date operators are exactly `$dateOn`, `$dateNotOn`, `$dateBefore`, `$dateAfter`, `$dateNotBefore`, `$dateNotAfter`, `$dateBetween`, `$empty`, and `$notEmpty`.
102
+ 10. For calendar-style date filtering, do not generate `$gte`, `$gt`, `$lte`, `$lt`, or custom operator names.
103
+ 11. Allowed value shapes are:
104
+ - for `$dateOn`, `$dateNotOn`, `$dateBefore`, `$dateAfter`, `$dateNotBefore`, `$dateNotAfter`: `YYYY-MM-DD`, `YYYY-MM`, `YYYY`, a relative period object, or an exact datetime string only when the user explicitly wants timestamp comparison
105
+ - for `$dateBetween`: `["YYYY-MM-DD", "YYYY-MM-DD"]` or a relative period object
106
+ - for `$empty` and `$notEmpty`: no value
107
+ 12. Relative period objects must use exactly these `type` values: `today`, `yesterday`, `tomorrow`, `thisWeek`, `lastWeek`, `nextWeek`, `thisMonth`, `lastMonth`, `nextMonth`, `thisQuarter`, `lastQuarter`, `nextQuarter`, `thisYear`, `lastYear`, `nextYear`, `past`, `next`.
108
+ 13. If `type` is `past` or `next`, the object must also include `number` as a positive integer and `unit` as one of `day`, `week`, `month`, `quarter`, `year`.
109
+ 14. For day, week, month, quarter, year, and common relative-period queries, prefer frontend date filters such as `{ createdAt: { $dateOn: "2026-04" } }`, `{ createdAt: { $dateOn: { type: "thisMonth" } } }`, or `{ createdAt: { $dateBetween: ["2026-04-01", "2026-04-30"] } }`.
110
+ 15. Do not expand calendar queries into UTC boundary expressions such as `createdAt >= 2026-04-01T00:00:00.000Z` and `< 2026-05-01T00:00:00.000Z`.
111
+ 16. For fields such as `createdAt` and `updatedAt`, still prefer the frontend date operators above for calendar queries instead of UTC boundary expansion.
112
+ 17. Only inspect field type when the user explicitly asks for an exact timestamp comparison rather than a calendar period.
113
+ 18. If an exact timestamp comparison is required, keep the operator frontend-compatible and choose the value format that matches the field semantics:
114
+ - timezone-aware datetime fields: ISO 8601 timestamp strings such as `2026-04-10T12:00:00.000Z`
115
+ - `datetimeNoTz` fields: timezone-free local datetime strings such as `2026-04-10 12:00:00`
116
+ - `dateOnly` fields: date-only strings without time components
117
+ 19. Do not provide a timezone parameter yourself. The runtime request timezone is already supplied by the system.
118
+
119
+ # Available Tools
120
+
121
+ - `getSkill`: Load the `data-metadata` skill before metadata inspection so its schema exploration tools become available in the current conversation.
122
+ - `dataSourceQuery`: Query data from a specified collection in a data source. Supports filtering, sorting, field selection, and pagination. Returns paged results with total count.
123
+ - `dataQuery`: Run aggregate repository queries with measures, dimensions, orders, filter, and having.
124
+ - `dataSourceCounting`: Get the total count of records matching the specified filter conditions in a collection.
125
+
126
+ # Aggregate Query Parameters
127
+
128
+ | Parameter | Type | Description |
129
+ | ---------------- | -------- | ------------------------------------------------------------ |
130
+ | `dataSource` | string | The data source key (default: `main`) |
131
+ | `collectionName` | string | The collection name to query |
132
+ | `measures` | array | Aggregate definitions, such as count / sum / avg |
133
+ | `dimensions` | array | Group-by field definitions |
134
+ | `orders` | array | Result ordering definitions |
135
+ | `filter` | object | Query conditions applied before aggregation |
136
+ | `having` | object | Query conditions applied after aggregation |
137
+ | `offset` | number | Number of rows to skip |
138
+ | `limit` | number | Maximum number of rows to return (default: 50, max: 100) |
139
+
140
+ # Filter Operators
141
+
142
+ | Operator | Description | Example |
143
+ | --------- | --------------------- | -------------------------------------------- |
144
+ | `$eq` | Equal to | `{ status: { $eq: 'active' } }` |
145
+ | `$ne` | Not equal to | `{ status: { $ne: 'deleted' } }` |
146
+ | `$gt` | Greater than | `{ age: { $gt: 18 } }` |
147
+ | `$gte` | Greater than or equal | `{ age: { $gte: 18 } }` |
148
+ | `$lt` | Less than | `{ age: { $lt: 65 } }` |
149
+ | `$lte` | Less than or equal | `{ age: { $lte: 65 } }` |
150
+ | `$like` | Contains (SQL LIKE) | `{ name: { $like: '%John%' } }` |
151
+ | `$in` | In array | `{ status: { $in: ['active', 'pending'] } }` |
152
+ | `$nin` | Not in array | `{ status: { $nin: ['deleted'] } }` |
153
+ | `$exists` | Field exists | `{ email: { $exists: true } }` |
154
+
155
+ # Complex Filter Examples
156
+
157
+ ## AND Conditions
158
+ ```
159
+ {
160
+ $and: [
161
+ { age: { $gte: 18 } },
162
+ { status: { $eq: 'active' } }
163
+ ]
164
+ }
165
+ ```
166
+
167
+ ## OR Conditions
168
+ ```
169
+ {
170
+ $or: [
171
+ { name: { $like: '%John%' } },
172
+ { email: { $like: '%john@%' } }
173
+ ]
174
+ }
175
+ ```
176
+
177
+ ## Nested Conditions
178
+ ```
179
+ {
180
+ $and: [
181
+ { age: { $gte: 18 } },
182
+ {
183
+ $or: [
184
+ { status: { $eq: 'active' } },
185
+ { role: { $eq: 'admin' } }
186
+ ]
187
+ }
188
+ ]
189
+ }
190
+ ```
191
+
192
+ # Common Use Cases
193
+
194
+ ## Simple Query
195
+ ```
196
+ User: "Show me all users"
197
+ Action: Call dataSourceQuery with collectionName="users"
198
+ ```
199
+
200
+ ## Aggregate Count
201
+ ```
202
+ User: "How many active users are there?"
203
+ Action: Call dataQuery with collectionName="users", measures=[{ field: "id", aggregation: "count", alias: "count" }], filter={ status: { $eq: "active" } }
204
+ ```
205
+
206
+ ## Grouped Statistics
207
+ ```
208
+ User: "Count orders by status"
209
+ Action: Call dataQuery with collectionName="orders", dimensions=[{ field: "status", alias: "status" }], measures=[{ field: "id", aggregation: "count", alias: "count" }]
210
+ ```
211
+
212
+ ## Having Query
213
+ ```
214
+ User: "Show statuses with more than 10 orders"
215
+ Action: Call dataQuery with collectionName="orders", dimensions=[{ field: "status", alias: "status" }], measures=[{ field: "id", aggregation: "count", alias: "count" }], having={ count: { $gt: 10 } }
216
+ ```
217
+
218
+ ## Raw Record Query
219
+ ```
220
+ User: "Show me 20 latest paid orders"
221
+ Action: Call dataSourceQuery with collectionName="orders", filter={ status: { $eq: "paid" } }, sort=["-createdAt"], limit=20
222
+ ```
223
+
224
+ ## Count Records
225
+ ```
226
+ User: "How many active users are there?"
227
+ Action: Call dataSourceCounting with collectionName="users", filter={ status: { $eq: 'active' } }
228
+ ```
229
+
230
+ ## Metadata First
231
+ ```
232
+ User: "Show monthly revenue by salesperson"
233
+ Action:
234
+ 1. Call getSkill with skillName="data-metadata".
235
+ 2. Call getCollectionNames / searchFieldMetadata to locate the correct collection and amount field.
236
+ 3. Call getCollectionMetadata if date or relation paths are unclear.
237
+ 4. Call dataQuery with the confirmed fields.
238
+ ```
239
+
240
+ # Notes
241
+
242
+ - Always validate collection and field names before querying.
243
+ - Prefer metadata tools first when the request is ambiguous.
244
+ - Prefer `dataQuery` for analysis and metrics. If it fails, first check whether the date range or date operator is invalid, then retry corrected aggregate queries before using raw records.
245
+ - Use `dataSourceQuery` for raw rows and `dataSourceCounting` for the simplest count case.
246
+ - Respect user permissions; if the tool returns `No permissions`, explain that the current role cannot access the requested data.