db-mcp 1.0.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.
Files changed (208) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +860 -0
  3. package/dist/adapters/DatabaseAdapter.d.ts +141 -0
  4. package/dist/adapters/DatabaseAdapter.d.ts.map +1 -0
  5. package/dist/adapters/DatabaseAdapter.js +131 -0
  6. package/dist/adapters/DatabaseAdapter.js.map +1 -0
  7. package/dist/adapters/sqlite/SchemaManager.d.ts +58 -0
  8. package/dist/adapters/sqlite/SchemaManager.d.ts.map +1 -0
  9. package/dist/adapters/sqlite/SchemaManager.js +187 -0
  10. package/dist/adapters/sqlite/SchemaManager.js.map +1 -0
  11. package/dist/adapters/sqlite/SqliteAdapter.d.ts +161 -0
  12. package/dist/adapters/sqlite/SqliteAdapter.d.ts.map +1 -0
  13. package/dist/adapters/sqlite/SqliteAdapter.js +741 -0
  14. package/dist/adapters/sqlite/SqliteAdapter.js.map +1 -0
  15. package/dist/adapters/sqlite/index.d.ts +9 -0
  16. package/dist/adapters/sqlite/index.d.ts.map +1 -0
  17. package/dist/adapters/sqlite/index.js +8 -0
  18. package/dist/adapters/sqlite/index.js.map +1 -0
  19. package/dist/adapters/sqlite/json-utils.d.ts +100 -0
  20. package/dist/adapters/sqlite/json-utils.d.ts.map +1 -0
  21. package/dist/adapters/sqlite/json-utils.js +274 -0
  22. package/dist/adapters/sqlite/json-utils.js.map +1 -0
  23. package/dist/adapters/sqlite/output-schemas.d.ts +1187 -0
  24. package/dist/adapters/sqlite/output-schemas.d.ts.map +1 -0
  25. package/dist/adapters/sqlite/output-schemas.js +1337 -0
  26. package/dist/adapters/sqlite/output-schemas.js.map +1 -0
  27. package/dist/adapters/sqlite/prompts.d.ts +13 -0
  28. package/dist/adapters/sqlite/prompts.d.ts.map +1 -0
  29. package/dist/adapters/sqlite/prompts.js +605 -0
  30. package/dist/adapters/sqlite/prompts.js.map +1 -0
  31. package/dist/adapters/sqlite/resources.d.ts +13 -0
  32. package/dist/adapters/sqlite/resources.d.ts.map +1 -0
  33. package/dist/adapters/sqlite/resources.js +251 -0
  34. package/dist/adapters/sqlite/resources.js.map +1 -0
  35. package/dist/adapters/sqlite/tools/admin.d.ts +14 -0
  36. package/dist/adapters/sqlite/tools/admin.d.ts.map +1 -0
  37. package/dist/adapters/sqlite/tools/admin.js +788 -0
  38. package/dist/adapters/sqlite/tools/admin.js.map +1 -0
  39. package/dist/adapters/sqlite/tools/core.d.ts +25 -0
  40. package/dist/adapters/sqlite/tools/core.d.ts.map +1 -0
  41. package/dist/adapters/sqlite/tools/core.js +359 -0
  42. package/dist/adapters/sqlite/tools/core.js.map +1 -0
  43. package/dist/adapters/sqlite/tools/fts.d.ts +13 -0
  44. package/dist/adapters/sqlite/tools/fts.d.ts.map +1 -0
  45. package/dist/adapters/sqlite/tools/fts.js +347 -0
  46. package/dist/adapters/sqlite/tools/fts.js.map +1 -0
  47. package/dist/adapters/sqlite/tools/geo.d.ts +14 -0
  48. package/dist/adapters/sqlite/tools/geo.d.ts.map +1 -0
  49. package/dist/adapters/sqlite/tools/geo.js +252 -0
  50. package/dist/adapters/sqlite/tools/geo.js.map +1 -0
  51. package/dist/adapters/sqlite/tools/index.d.ts +30 -0
  52. package/dist/adapters/sqlite/tools/index.d.ts.map +1 -0
  53. package/dist/adapters/sqlite/tools/index.js +61 -0
  54. package/dist/adapters/sqlite/tools/index.js.map +1 -0
  55. package/dist/adapters/sqlite/tools/json-helpers.d.ts +14 -0
  56. package/dist/adapters/sqlite/tools/json-helpers.d.ts.map +1 -0
  57. package/dist/adapters/sqlite/tools/json-helpers.js +477 -0
  58. package/dist/adapters/sqlite/tools/json-helpers.js.map +1 -0
  59. package/dist/adapters/sqlite/tools/json-operations.d.ts +14 -0
  60. package/dist/adapters/sqlite/tools/json-operations.d.ts.map +1 -0
  61. package/dist/adapters/sqlite/tools/json-operations.js +839 -0
  62. package/dist/adapters/sqlite/tools/json-operations.js.map +1 -0
  63. package/dist/adapters/sqlite/tools/stats.d.ts +15 -0
  64. package/dist/adapters/sqlite/tools/stats.d.ts.map +1 -0
  65. package/dist/adapters/sqlite/tools/stats.js +1219 -0
  66. package/dist/adapters/sqlite/tools/stats.js.map +1 -0
  67. package/dist/adapters/sqlite/tools/text.d.ts +14 -0
  68. package/dist/adapters/sqlite/tools/text.d.ts.map +1 -0
  69. package/dist/adapters/sqlite/tools/text.js +1141 -0
  70. package/dist/adapters/sqlite/tools/text.js.map +1 -0
  71. package/dist/adapters/sqlite/tools/vector.d.ts +14 -0
  72. package/dist/adapters/sqlite/tools/vector.d.ts.map +1 -0
  73. package/dist/adapters/sqlite/tools/vector.js +613 -0
  74. package/dist/adapters/sqlite/tools/vector.js.map +1 -0
  75. package/dist/adapters/sqlite/tools/virtual.d.ts +13 -0
  76. package/dist/adapters/sqlite/tools/virtual.d.ts.map +1 -0
  77. package/dist/adapters/sqlite/tools/virtual.js +930 -0
  78. package/dist/adapters/sqlite/tools/virtual.js.map +1 -0
  79. package/dist/adapters/sqlite/types.d.ts +207 -0
  80. package/dist/adapters/sqlite/types.d.ts.map +1 -0
  81. package/dist/adapters/sqlite/types.js +186 -0
  82. package/dist/adapters/sqlite/types.js.map +1 -0
  83. package/dist/adapters/sqlite-native/NativeSqliteAdapter.d.ts +163 -0
  84. package/dist/adapters/sqlite-native/NativeSqliteAdapter.d.ts.map +1 -0
  85. package/dist/adapters/sqlite-native/NativeSqliteAdapter.js +748 -0
  86. package/dist/adapters/sqlite-native/NativeSqliteAdapter.js.map +1 -0
  87. package/dist/adapters/sqlite-native/index.d.ts +11 -0
  88. package/dist/adapters/sqlite-native/index.d.ts.map +1 -0
  89. package/dist/adapters/sqlite-native/index.js +11 -0
  90. package/dist/adapters/sqlite-native/index.js.map +1 -0
  91. package/dist/adapters/sqlite-native/tools/spatialite.d.ts +19 -0
  92. package/dist/adapters/sqlite-native/tools/spatialite.d.ts.map +1 -0
  93. package/dist/adapters/sqlite-native/tools/spatialite.js +628 -0
  94. package/dist/adapters/sqlite-native/tools/spatialite.js.map +1 -0
  95. package/dist/adapters/sqlite-native/tools/transactions.d.ts +12 -0
  96. package/dist/adapters/sqlite-native/tools/transactions.d.ts.map +1 -0
  97. package/dist/adapters/sqlite-native/tools/transactions.js +255 -0
  98. package/dist/adapters/sqlite-native/tools/transactions.js.map +1 -0
  99. package/dist/adapters/sqlite-native/tools/window.d.ts +12 -0
  100. package/dist/adapters/sqlite-native/tools/window.d.ts.map +1 -0
  101. package/dist/adapters/sqlite-native/tools/window.js +370 -0
  102. package/dist/adapters/sqlite-native/tools/window.js.map +1 -0
  103. package/dist/auth/AuthorizationServerDiscovery.d.ts +90 -0
  104. package/dist/auth/AuthorizationServerDiscovery.d.ts.map +1 -0
  105. package/dist/auth/AuthorizationServerDiscovery.js +204 -0
  106. package/dist/auth/AuthorizationServerDiscovery.js.map +1 -0
  107. package/dist/auth/OAuthResourceServer.d.ts +65 -0
  108. package/dist/auth/OAuthResourceServer.d.ts.map +1 -0
  109. package/dist/auth/OAuthResourceServer.js +121 -0
  110. package/dist/auth/OAuthResourceServer.js.map +1 -0
  111. package/dist/auth/TokenValidator.d.ts +60 -0
  112. package/dist/auth/TokenValidator.d.ts.map +1 -0
  113. package/dist/auth/TokenValidator.js +235 -0
  114. package/dist/auth/TokenValidator.js.map +1 -0
  115. package/dist/auth/errors.d.ts +74 -0
  116. package/dist/auth/errors.d.ts.map +1 -0
  117. package/dist/auth/errors.js +133 -0
  118. package/dist/auth/errors.js.map +1 -0
  119. package/dist/auth/index.d.ts +13 -0
  120. package/dist/auth/index.d.ts.map +1 -0
  121. package/dist/auth/index.js +15 -0
  122. package/dist/auth/index.js.map +1 -0
  123. package/dist/auth/middleware.d.ts +81 -0
  124. package/dist/auth/middleware.d.ts.map +1 -0
  125. package/dist/auth/middleware.js +291 -0
  126. package/dist/auth/middleware.js.map +1 -0
  127. package/dist/auth/scopes.d.ts +136 -0
  128. package/dist/auth/scopes.d.ts.map +1 -0
  129. package/dist/auth/scopes.js +349 -0
  130. package/dist/auth/scopes.js.map +1 -0
  131. package/dist/auth/types.d.ts +257 -0
  132. package/dist/auth/types.d.ts.map +1 -0
  133. package/dist/auth/types.js +8 -0
  134. package/dist/auth/types.js.map +1 -0
  135. package/dist/cli.d.ts +8 -0
  136. package/dist/cli.d.ts.map +1 -0
  137. package/dist/cli.js +236 -0
  138. package/dist/cli.js.map +1 -0
  139. package/dist/constants/ServerInstructions.d.ts +45 -0
  140. package/dist/constants/ServerInstructions.d.ts.map +1 -0
  141. package/dist/constants/ServerInstructions.js +356 -0
  142. package/dist/constants/ServerInstructions.js.map +1 -0
  143. package/dist/filtering/ToolConstants.d.ts +34 -0
  144. package/dist/filtering/ToolConstants.d.ts.map +1 -0
  145. package/dist/filtering/ToolConstants.js +174 -0
  146. package/dist/filtering/ToolConstants.js.map +1 -0
  147. package/dist/filtering/ToolFilter.d.ts +82 -0
  148. package/dist/filtering/ToolFilter.d.ts.map +1 -0
  149. package/dist/filtering/ToolFilter.js +296 -0
  150. package/dist/filtering/ToolFilter.js.map +1 -0
  151. package/dist/index.d.ts +13 -0
  152. package/dist/index.d.ts.map +1 -0
  153. package/dist/index.js +17 -0
  154. package/dist/index.js.map +1 -0
  155. package/dist/server/McpServer.d.ts +61 -0
  156. package/dist/server/McpServer.d.ts.map +1 -0
  157. package/dist/server/McpServer.js +270 -0
  158. package/dist/server/McpServer.js.map +1 -0
  159. package/dist/transports/http.d.ts +134 -0
  160. package/dist/transports/http.d.ts.map +1 -0
  161. package/dist/transports/http.js +516 -0
  162. package/dist/transports/http.js.map +1 -0
  163. package/dist/transports/index.d.ts +5 -0
  164. package/dist/transports/index.d.ts.map +1 -0
  165. package/dist/transports/index.js +5 -0
  166. package/dist/transports/index.js.map +1 -0
  167. package/dist/types/index.d.ts +380 -0
  168. package/dist/types/index.d.ts.map +1 -0
  169. package/dist/types/index.js +68 -0
  170. package/dist/types/index.js.map +1 -0
  171. package/dist/utils/annotations.d.ts +44 -0
  172. package/dist/utils/annotations.d.ts.map +1 -0
  173. package/dist/utils/annotations.js +77 -0
  174. package/dist/utils/annotations.js.map +1 -0
  175. package/dist/utils/errors.d.ts +155 -0
  176. package/dist/utils/errors.d.ts.map +1 -0
  177. package/dist/utils/errors.js +329 -0
  178. package/dist/utils/errors.js.map +1 -0
  179. package/dist/utils/identifiers.d.ts +121 -0
  180. package/dist/utils/identifiers.d.ts.map +1 -0
  181. package/dist/utils/identifiers.js +319 -0
  182. package/dist/utils/identifiers.js.map +1 -0
  183. package/dist/utils/index.d.ts +7 -0
  184. package/dist/utils/index.d.ts.map +1 -0
  185. package/dist/utils/index.js +7 -0
  186. package/dist/utils/index.js.map +1 -0
  187. package/dist/utils/insightsManager.d.ts +39 -0
  188. package/dist/utils/insightsManager.d.ts.map +1 -0
  189. package/dist/utils/insightsManager.js +63 -0
  190. package/dist/utils/insightsManager.js.map +1 -0
  191. package/dist/utils/logger.d.ts +189 -0
  192. package/dist/utils/logger.d.ts.map +1 -0
  193. package/dist/utils/logger.js +394 -0
  194. package/dist/utils/logger.js.map +1 -0
  195. package/dist/utils/progress-utils.d.ts +54 -0
  196. package/dist/utils/progress-utils.d.ts.map +1 -0
  197. package/dist/utils/progress-utils.js +74 -0
  198. package/dist/utils/progress-utils.js.map +1 -0
  199. package/dist/utils/resourceAnnotations.d.ts +36 -0
  200. package/dist/utils/resourceAnnotations.d.ts.map +1 -0
  201. package/dist/utils/resourceAnnotations.js +57 -0
  202. package/dist/utils/resourceAnnotations.js.map +1 -0
  203. package/dist/utils/where-clause.d.ts +41 -0
  204. package/dist/utils/where-clause.d.ts.map +1 -0
  205. package/dist/utils/where-clause.js +116 -0
  206. package/dist/utils/where-clause.js.map +1 -0
  207. package/package.json +83 -0
  208. package/server.json +53 -0
@@ -0,0 +1,839 @@
1
+ /**
2
+ * SQLite JSON Operation Tools
3
+ *
4
+ * Low-level JSON functions wrapping SQLite's JSON1 extension:
5
+ * validate, extract, set, remove, type, array/object operations, etc.
6
+ * 12 tools total.
7
+ */
8
+ import { z } from "zod";
9
+ import { readOnly, write } from "../../../utils/annotations.js";
10
+ import { sanitizeIdentifier } from "../../../utils/index.js";
11
+ import { ValidateJsonSchema, JsonExtractSchema, JsonSetSchema, JsonRemoveSchema, } from "../types.js";
12
+ import { JsonValidOutputSchema, JsonExtractOutputSchema, JsonSetOutputSchema, JsonRemoveOutputSchema, JsonTypeOutputSchema, JsonArrayLengthOutputSchema, JsonKeysOutputSchema, JsonEachOutputSchema, JsonGroupArrayOutputSchema, JsonGroupObjectOutputSchema, JsonPrettyOutputSchema, JsonbConvertOutputSchema, JsonStorageInfoOutputSchema, JsonNormalizeColumnOutputSchema, } from "../output-schemas.js";
13
+ import { normalizeJson, isJsonbSupported, detectJsonStorageFormat, } from "../json-utils.js";
14
+ // Additional schemas for JSON operations
15
+ const JsonTypeSchema = z.object({
16
+ table: z.string().describe("Table name"),
17
+ column: z.string().describe("JSON column name"),
18
+ path: z.string().optional().describe("JSON path (defaults to $)"),
19
+ whereClause: z.string().optional(),
20
+ });
21
+ const JsonArrayLengthSchema = z.object({
22
+ table: z.string().describe("Table name"),
23
+ column: z.string().describe("JSON column name"),
24
+ path: z.string().optional().describe("Path to array (defaults to $)"),
25
+ whereClause: z.string().optional(),
26
+ });
27
+ const JsonArrayAppendSchema = z.object({
28
+ table: z.string().describe("Table name"),
29
+ column: z.string().describe("JSON column name"),
30
+ path: z.string().describe("Path to array"),
31
+ value: z.unknown().describe("Value to append"),
32
+ whereClause: z.string().describe("WHERE clause"),
33
+ });
34
+ const JsonKeysSchema = z.object({
35
+ table: z.string().describe("Table name"),
36
+ column: z.string().describe("JSON column name"),
37
+ path: z.string().optional().describe("Path to object (defaults to $)"),
38
+ whereClause: z.string().optional(),
39
+ });
40
+ const JsonEachSchema = z.object({
41
+ table: z.string().describe("Table name"),
42
+ column: z.string().describe("JSON column name"),
43
+ path: z.string().optional().describe("Path to expand (defaults to $)"),
44
+ whereClause: z.string().optional(),
45
+ limit: z.number().optional().default(100),
46
+ });
47
+ const JsonGroupArraySchema = z.object({
48
+ table: z.string().describe("Table name"),
49
+ valueColumn: z
50
+ .string()
51
+ .describe("Column to aggregate (or SQL expression if allowExpressions is true)"),
52
+ groupByColumn: z
53
+ .string()
54
+ .optional()
55
+ .describe("Column to group by. For JSON collection tables, use allowExpressions with json_extract(data, '$.field') instead."),
56
+ whereClause: z.string().optional(),
57
+ allowExpressions: z
58
+ .boolean()
59
+ .optional()
60
+ .describe("Allow SQL expressions like json_extract() instead of plain column names"),
61
+ });
62
+ const JsonGroupObjectSchema = z.object({
63
+ table: z.string().describe("Table name"),
64
+ keyColumn: z
65
+ .string()
66
+ .describe("Column for object keys (or SQL expression if allowExpressions is true)"),
67
+ valueColumn: z
68
+ .string()
69
+ .optional()
70
+ .describe("Column for object values (or SQL expression if allowExpressions is true). For aggregates like COUNT(*), use aggregateFunction instead."),
71
+ groupByColumn: z
72
+ .string()
73
+ .optional()
74
+ .describe("Column to group by. For JSON collection tables, use allowExpressions with json_extract(data, '$.field') instead."),
75
+ whereClause: z.string().optional(),
76
+ allowExpressions: z
77
+ .boolean()
78
+ .optional()
79
+ .describe("Allow SQL expressions like json_extract() instead of plain column names. NOTE: Does NOT support aggregate functions - use aggregateFunction parameter instead."),
80
+ aggregateFunction: z
81
+ .string()
82
+ .optional()
83
+ .describe("Aggregate function to use for values (e.g., 'COUNT(*)', 'SUM(amount)', 'AVG(price)'). When provided, builds object from pre-aggregated subquery results."),
84
+ });
85
+ const JsonPrettySchema = z.object({
86
+ json: z.string().describe("JSON string to pretty print"),
87
+ });
88
+ /**
89
+ * Get all JSON operation tools
90
+ */
91
+ export function getJsonOperationTools(adapter) {
92
+ return [
93
+ createValidateJsonTool(),
94
+ createJsonExtractTool(adapter),
95
+ createJsonSetTool(adapter),
96
+ createJsonRemoveTool(adapter),
97
+ createJsonTypeTool(adapter),
98
+ createJsonArrayLengthTool(adapter),
99
+ createJsonArrayAppendTool(adapter),
100
+ createJsonKeysTool(adapter),
101
+ createJsonEachTool(adapter),
102
+ createJsonGroupArrayTool(adapter),
103
+ createJsonGroupObjectTool(adapter),
104
+ createJsonPrettyTool(),
105
+ // JSONB tools
106
+ createJsonbConvertTool(adapter),
107
+ createJsonStorageInfoTool(adapter),
108
+ createJsonNormalizeColumnTool(adapter),
109
+ ];
110
+ }
111
+ /**
112
+ * Validate JSON string
113
+ */
114
+ function createValidateJsonTool() {
115
+ return {
116
+ name: "sqlite_json_valid",
117
+ description: "Check if a string is valid JSON.",
118
+ group: "json",
119
+ inputSchema: ValidateJsonSchema,
120
+ outputSchema: JsonValidOutputSchema,
121
+ requiredScopes: ["read"],
122
+ annotations: readOnly("Validate JSON"),
123
+ handler: (params, _context) => {
124
+ const input = ValidateJsonSchema.parse(params);
125
+ try {
126
+ JSON.parse(input.json);
127
+ return Promise.resolve({
128
+ success: true,
129
+ valid: true,
130
+ message: "Valid JSON",
131
+ });
132
+ }
133
+ catch (error) {
134
+ return Promise.resolve({
135
+ success: true,
136
+ valid: false,
137
+ message: error instanceof Error ? error.message : "Invalid JSON",
138
+ });
139
+ }
140
+ },
141
+ };
142
+ }
143
+ /**
144
+ * Extract value from JSON
145
+ */
146
+ function createJsonExtractTool(adapter) {
147
+ return {
148
+ name: "sqlite_json_extract",
149
+ description: "Extract a value from a JSON column at the specified path using json_extract().",
150
+ group: "json",
151
+ inputSchema: JsonExtractSchema,
152
+ outputSchema: JsonExtractOutputSchema,
153
+ requiredScopes: ["read"],
154
+ annotations: readOnly("JSON Extract"),
155
+ handler: async (params, _context) => {
156
+ const input = JsonExtractSchema.parse(params);
157
+ // Validate and quote identifiers
158
+ const table = sanitizeIdentifier(input.table);
159
+ const column = sanitizeIdentifier(input.column);
160
+ if (!input.path.startsWith("$")) {
161
+ throw new Error("JSON path must start with $");
162
+ }
163
+ let sql = `SELECT json_extract(${column}, '${input.path}') as value FROM ${table}`;
164
+ if (input.whereClause) {
165
+ sql += ` WHERE ${input.whereClause}`;
166
+ }
167
+ const result = await adapter.executeReadQuery(sql);
168
+ return {
169
+ success: true,
170
+ rowCount: result.rows?.length ?? 0,
171
+ values: result.rows?.map((r) => r["value"]),
172
+ };
173
+ },
174
+ };
175
+ }
176
+ /**
177
+ * Set value in JSON
178
+ */
179
+ function createJsonSetTool(adapter) {
180
+ return {
181
+ name: "sqlite_json_set",
182
+ description: "Set a value at a JSON path using json_set(). Creates path if it does not exist.",
183
+ group: "json",
184
+ inputSchema: JsonSetSchema,
185
+ outputSchema: JsonSetOutputSchema,
186
+ requiredScopes: ["write"],
187
+ annotations: write("JSON Set"),
188
+ handler: async (params, _context) => {
189
+ const input = JsonSetSchema.parse(params);
190
+ // Validate and quote identifiers
191
+ const table = sanitizeIdentifier(input.table);
192
+ const column = sanitizeIdentifier(input.column);
193
+ if (!input.path.startsWith("$")) {
194
+ throw new Error("JSON path must start with $");
195
+ }
196
+ const valueJson = JSON.stringify(input.value);
197
+ const sql = `UPDATE ${table} SET ${column} = json_set(${column}, '${input.path}', json('${valueJson.replace(/'/g, "''")}')) WHERE ${input.whereClause}`;
198
+ const result = await adapter.executeWriteQuery(sql);
199
+ return {
200
+ success: true,
201
+ rowsAffected: result.rowsAffected,
202
+ };
203
+ },
204
+ };
205
+ }
206
+ /**
207
+ * Remove value from JSON
208
+ */
209
+ function createJsonRemoveTool(adapter) {
210
+ return {
211
+ name: "sqlite_json_remove",
212
+ description: "Remove a value at a JSON path using json_remove().",
213
+ group: "json",
214
+ inputSchema: JsonRemoveSchema,
215
+ outputSchema: JsonRemoveOutputSchema,
216
+ requiredScopes: ["write"],
217
+ annotations: write("JSON Remove"),
218
+ handler: async (params, _context) => {
219
+ const input = JsonRemoveSchema.parse(params);
220
+ // Validate and quote identifiers
221
+ const table = sanitizeIdentifier(input.table);
222
+ const column = sanitizeIdentifier(input.column);
223
+ if (!input.path.startsWith("$")) {
224
+ throw new Error("JSON path must start with $");
225
+ }
226
+ const sql = `UPDATE ${table} SET ${column} = json_remove(${column}, '${input.path}') WHERE ${input.whereClause}`;
227
+ const result = await adapter.executeWriteQuery(sql);
228
+ return {
229
+ success: true,
230
+ rowsAffected: result.rowsAffected,
231
+ };
232
+ },
233
+ };
234
+ }
235
+ /**
236
+ * Get JSON value type
237
+ */
238
+ function createJsonTypeTool(adapter) {
239
+ return {
240
+ name: "sqlite_json_type",
241
+ description: "Get the JSON type (null, true, false, integer, real, text, array, object) at a path.",
242
+ group: "json",
243
+ inputSchema: JsonTypeSchema,
244
+ outputSchema: JsonTypeOutputSchema,
245
+ requiredScopes: ["read"],
246
+ annotations: readOnly("JSON Type"),
247
+ handler: async (params, _context) => {
248
+ const input = JsonTypeSchema.parse(params);
249
+ // Validate and quote identifiers
250
+ const table = sanitizeIdentifier(input.table);
251
+ const column = sanitizeIdentifier(input.column);
252
+ const path = input.path ?? "$";
253
+ if (!path.startsWith("$")) {
254
+ throw new Error("JSON path must start with $");
255
+ }
256
+ let sql = `SELECT json_type(${column}, '${path}') as type FROM ${table}`;
257
+ if (input.whereClause) {
258
+ sql += ` WHERE ${input.whereClause}`;
259
+ }
260
+ const result = await adapter.executeReadQuery(sql);
261
+ return {
262
+ success: true,
263
+ rowCount: result.rows?.length ?? 0,
264
+ types: result.rows?.map((r) => r["type"]),
265
+ };
266
+ },
267
+ };
268
+ }
269
+ /**
270
+ * Get JSON array length
271
+ */
272
+ function createJsonArrayLengthTool(adapter) {
273
+ return {
274
+ name: "sqlite_json_array_length",
275
+ description: "Get the length of a JSON array at the specified path.",
276
+ group: "json",
277
+ inputSchema: JsonArrayLengthSchema,
278
+ outputSchema: JsonArrayLengthOutputSchema,
279
+ requiredScopes: ["read"],
280
+ annotations: readOnly("Array Length"),
281
+ handler: async (params, _context) => {
282
+ const input = JsonArrayLengthSchema.parse(params);
283
+ // Validate and quote identifiers
284
+ const table = sanitizeIdentifier(input.table);
285
+ const column = sanitizeIdentifier(input.column);
286
+ const path = input.path ?? "$";
287
+ if (!path.startsWith("$")) {
288
+ throw new Error("JSON path must start with $");
289
+ }
290
+ let sql = `SELECT json_array_length(${column}, '${path}') as length FROM ${table}`;
291
+ if (input.whereClause) {
292
+ sql += ` WHERE ${input.whereClause}`;
293
+ }
294
+ const result = await adapter.executeReadQuery(sql);
295
+ return {
296
+ success: true,
297
+ rowCount: result.rows?.length ?? 0,
298
+ lengths: result.rows?.map((r) => r["length"]),
299
+ };
300
+ },
301
+ };
302
+ }
303
+ /**
304
+ * Append to JSON array
305
+ */
306
+ function createJsonArrayAppendTool(adapter) {
307
+ return {
308
+ name: "sqlite_json_array_append",
309
+ description: "Append a value to a JSON array using json_insert().",
310
+ group: "json",
311
+ inputSchema: JsonArrayAppendSchema,
312
+ outputSchema: JsonSetOutputSchema,
313
+ requiredScopes: ["write"],
314
+ annotations: write("Array Append"),
315
+ handler: async (params, _context) => {
316
+ const input = JsonArrayAppendSchema.parse(params);
317
+ // Validate and quote identifiers
318
+ const table = sanitizeIdentifier(input.table);
319
+ const column = sanitizeIdentifier(input.column);
320
+ if (!input.path.startsWith("$")) {
321
+ throw new Error("JSON path must start with $");
322
+ }
323
+ const valueJson = JSON.stringify(input.value);
324
+ // Append by using [#] which means "end of array"
325
+ const appendPath = input.path.endsWith("]")
326
+ ? input.path.replace(/\]$/, "#]")
327
+ : `${input.path}[#]`;
328
+ const sql = `UPDATE ${table} SET ${column} = json_insert(${column}, '${appendPath}', json('${valueJson.replace(/'/g, "''")}')) WHERE ${input.whereClause}`;
329
+ const result = await adapter.executeWriteQuery(sql);
330
+ return {
331
+ success: true,
332
+ rowsAffected: result.rowsAffected,
333
+ };
334
+ },
335
+ };
336
+ }
337
+ /**
338
+ * Get JSON object keys
339
+ */
340
+ function createJsonKeysTool(adapter) {
341
+ return {
342
+ name: "sqlite_json_keys",
343
+ description: "Get the distinct keys of JSON objects at the specified path (returns unique keys across all matching rows).",
344
+ group: "json",
345
+ inputSchema: JsonKeysSchema,
346
+ outputSchema: JsonKeysOutputSchema,
347
+ requiredScopes: ["read"],
348
+ annotations: readOnly("JSON Keys"),
349
+ handler: async (params, _context) => {
350
+ const input = JsonKeysSchema.parse(params);
351
+ // Validate and quote identifiers
352
+ const table = sanitizeIdentifier(input.table);
353
+ const column = sanitizeIdentifier(input.column);
354
+ const path = input.path ?? "$";
355
+ if (!path.startsWith("$")) {
356
+ throw new Error("JSON path must start with $");
357
+ }
358
+ // Use subquery to avoid ambiguous column when table has a 'key' or 'id' column
359
+ // json_each returns: key, value, type, atom, id, parent, fullkey, path
360
+ let sql;
361
+ if (input.whereClause) {
362
+ // With WHERE clause, use subquery to isolate table columns from json_each columns
363
+ // This avoids ambiguity between e.g. table.id and json_each.id
364
+ sql = `SELECT DISTINCT json_each.key
365
+ FROM json_each(
366
+ (SELECT ${column} FROM ${table} WHERE ${input.whereClause} LIMIT 1),
367
+ '${path}'
368
+ )`;
369
+ }
370
+ else {
371
+ // Without WHERE, simpler subquery avoids 'key' column ambiguity
372
+ sql = `SELECT DISTINCT json_each.key FROM ${table} AS t, json_each(t.${column}, '${path}')`;
373
+ }
374
+ const result = await adapter.executeReadQuery(sql);
375
+ const keys = result.rows?.map((r) => r["key"]) ?? [];
376
+ return {
377
+ success: true,
378
+ rowCount: keys.length,
379
+ keys: keys,
380
+ };
381
+ },
382
+ };
383
+ }
384
+ /**
385
+ * Expand JSON to rows
386
+ */
387
+ function createJsonEachTool(adapter) {
388
+ return {
389
+ name: "sqlite_json_each",
390
+ description: "Expand a JSON array or object into rows using json_each().",
391
+ group: "json",
392
+ inputSchema: JsonEachSchema,
393
+ outputSchema: JsonEachOutputSchema,
394
+ requiredScopes: ["read"],
395
+ annotations: readOnly("JSON Each"),
396
+ handler: async (params, _context) => {
397
+ const input = JsonEachSchema.parse(params);
398
+ // Validate and quote identifiers
399
+ const table = sanitizeIdentifier(input.table);
400
+ const column = sanitizeIdentifier(input.column);
401
+ const path = input.path ?? "$";
402
+ if (!path.startsWith("$")) {
403
+ throw new Error("JSON path must start with $");
404
+ }
405
+ // Use table alias and CROSS JOIN to avoid ambiguity with json_each() output columns
406
+ // json_each() returns: key, value, type, atom, id, parent, fullkey, path
407
+ // If the source table has any of these columns (e.g., 'id'), they must be qualified
408
+ let sql = `SELECT t.rowid as row_id, je.key, je.value, je.type FROM ${table} AS t CROSS JOIN json_each(t.${column}, '${path}') AS je`;
409
+ if (input.whereClause) {
410
+ // Qualify unqualified 'id' column references with table alias 't.'
411
+ // This handles: id = X, id IN (...), id BETWEEN, id IS NULL, etc.
412
+ // Won't match already-qualified refs like 't.id' or 'je.id'
413
+ const qualifiedWhere = input.whereClause.replace(/(?<![.\w])id(?=\s*[=<>!]|\s+(?:IN|BETWEEN|IS|LIKE)\b)/gi, "t.id");
414
+ sql += ` WHERE ${qualifiedWhere}`;
415
+ }
416
+ sql += ` LIMIT ${input.limit}`;
417
+ const result = await adapter.executeReadQuery(sql);
418
+ return {
419
+ success: true,
420
+ rowCount: result.rows?.length ?? 0,
421
+ elements: result.rows,
422
+ };
423
+ },
424
+ };
425
+ }
426
+ /**
427
+ * Aggregate values into JSON array
428
+ */
429
+ function createJsonGroupArrayTool(adapter) {
430
+ return {
431
+ name: "sqlite_json_group_array",
432
+ description: "Aggregate column values into a JSON array using json_group_array().",
433
+ group: "json",
434
+ inputSchema: JsonGroupArraySchema,
435
+ outputSchema: JsonGroupArrayOutputSchema,
436
+ requiredScopes: ["read"],
437
+ annotations: readOnly("Group Array"),
438
+ handler: async (params, _context) => {
439
+ const input = JsonGroupArraySchema.parse(params);
440
+ // Validate table name (always required)
441
+ const table = sanitizeIdentifier(input.table);
442
+ // Allow raw SQL expressions when allowExpressions is true
443
+ // This enables use cases like: json_extract(data, '$.name')
444
+ let valueColumn;
445
+ if (input.allowExpressions) {
446
+ // Use expression directly (user takes responsibility for SQL safety)
447
+ valueColumn = input.valueColumn;
448
+ }
449
+ else {
450
+ // Validate as identifier (default, safe behavior)
451
+ valueColumn = sanitizeIdentifier(input.valueColumn);
452
+ }
453
+ let selectClause = `json_group_array(${valueColumn}) as array_result`;
454
+ let groupByClause = "";
455
+ if (input.groupByColumn) {
456
+ // Apply allowExpressions to groupByColumn as well
457
+ const groupByCol = input.allowExpressions
458
+ ? input.groupByColumn
459
+ : sanitizeIdentifier(input.groupByColumn);
460
+ // Use alias for clean output; for expressions use 'group_key' alias
461
+ const groupAlias = input.allowExpressions
462
+ ? "group_key"
463
+ : input.groupByColumn;
464
+ selectClause = `${groupByCol} AS ${groupAlias}, ${selectClause}`;
465
+ groupByClause = ` GROUP BY ${groupByCol}`;
466
+ }
467
+ let sql = `SELECT ${selectClause} FROM ${table}`;
468
+ if (input.whereClause) {
469
+ sql += ` WHERE ${input.whereClause}`;
470
+ }
471
+ sql += groupByClause;
472
+ const result = await adapter.executeReadQuery(sql);
473
+ return {
474
+ success: true,
475
+ rowCount: result.rows?.length ?? 0,
476
+ rows: result.rows ?? [],
477
+ };
478
+ },
479
+ };
480
+ }
481
+ /**
482
+ * Aggregate key-value pairs into JSON object
483
+ */
484
+ function createJsonGroupObjectTool(adapter) {
485
+ return {
486
+ name: "sqlite_json_group_object",
487
+ description: "Aggregate key-value pairs into a JSON object using json_group_object().",
488
+ group: "json",
489
+ inputSchema: JsonGroupObjectSchema,
490
+ outputSchema: JsonGroupObjectOutputSchema,
491
+ requiredScopes: ["read"],
492
+ annotations: readOnly("Group Object"),
493
+ handler: async (params, _context) => {
494
+ const input = JsonGroupObjectSchema.parse(params);
495
+ // Validate table name (always required)
496
+ const table = sanitizeIdentifier(input.table);
497
+ // Handle aggregate function mode - uses subquery pattern
498
+ // This enables COUNT(*), SUM(x), AVG(x), etc. as values
499
+ if (input.aggregateFunction) {
500
+ // Build the key column expression
501
+ const keyCol = input.allowExpressions
502
+ ? input.keyColumn
503
+ : sanitizeIdentifier(input.keyColumn);
504
+ // Build subquery that computes the aggregate grouped by key
505
+ let subquery = `SELECT ${keyCol} as agg_key, ${input.aggregateFunction} as agg_value FROM ${table}`;
506
+ if (input.whereClause) {
507
+ subquery += ` WHERE ${input.whereClause}`;
508
+ }
509
+ subquery += ` GROUP BY ${keyCol}`;
510
+ // Outer query wraps the aggregates into a JSON object
511
+ const outerSelect = `json_group_object(agg_key, agg_value) as object_result`;
512
+ const outerGroupBy = "";
513
+ if (input.groupByColumn) {
514
+ // For nested grouping, we need a more complex approach with window functions or correlated subqueries
515
+ // For now, outer grouping with aggregates is not supported - return error with guidance
516
+ return {
517
+ success: false,
518
+ error: "groupByColumn is not supported when using aggregateFunction. Use a separate query for each group.",
519
+ rowCount: 0,
520
+ rows: [],
521
+ };
522
+ }
523
+ const sql = `SELECT ${outerSelect} FROM (${subquery})${outerGroupBy}`;
524
+ const result = await adapter.executeReadQuery(sql);
525
+ return {
526
+ success: true,
527
+ rowCount: result.rows?.length ?? 0,
528
+ rows: result.rows ?? [],
529
+ };
530
+ }
531
+ // Standard mode: valueColumn is required when not using aggregateFunction
532
+ if (!input.valueColumn) {
533
+ return {
534
+ success: false,
535
+ error: "valueColumn is required unless using aggregateFunction parameter",
536
+ rowCount: 0,
537
+ rows: [],
538
+ };
539
+ }
540
+ // Warn when allowExpressions is used without groupByColumn - can produce duplicate keys
541
+ // Each row creates a key-value pair; if multiple rows have the same key, duplicates result
542
+ const duplicateKeyWarning = input.allowExpressions && !input.groupByColumn
543
+ ? "Warning: Using allowExpressions without groupByColumn may produce duplicate keys if key values aren't unique. Consider using groupByColumn, aggregateFunction, or ensuring key uniqueness."
544
+ : undefined;
545
+ // Allow raw SQL expressions when allowExpressions is true
546
+ // This enables use cases like: json_extract(data, '$.name')
547
+ let keyColumn;
548
+ let valueColumn;
549
+ if (input.allowExpressions) {
550
+ // Use expressions directly (user takes responsibility for SQL safety)
551
+ keyColumn = input.keyColumn;
552
+ valueColumn = input.valueColumn;
553
+ }
554
+ else {
555
+ // Validate as identifiers (default, safe behavior)
556
+ keyColumn = sanitizeIdentifier(input.keyColumn);
557
+ valueColumn = sanitizeIdentifier(input.valueColumn);
558
+ }
559
+ let selectClause = `json_group_object(${keyColumn}, ${valueColumn}) as object_result`;
560
+ let groupByClause = "";
561
+ if (input.groupByColumn) {
562
+ // Apply allowExpressions to groupByColumn as well
563
+ const groupByCol = input.allowExpressions
564
+ ? input.groupByColumn
565
+ : sanitizeIdentifier(input.groupByColumn);
566
+ // Use alias for clean output; for expressions use 'group_key' alias
567
+ const groupAlias = input.allowExpressions
568
+ ? "group_key"
569
+ : input.groupByColumn;
570
+ selectClause = `${groupByCol} AS ${groupAlias}, ${selectClause}`;
571
+ groupByClause = ` GROUP BY ${groupByCol}`;
572
+ }
573
+ let sql = `SELECT ${selectClause} FROM ${table}`;
574
+ if (input.whereClause) {
575
+ sql += ` WHERE ${input.whereClause}`;
576
+ }
577
+ sql += groupByClause;
578
+ const result = await adapter.executeReadQuery(sql);
579
+ return {
580
+ success: true,
581
+ rowCount: result.rows?.length ?? 0,
582
+ rows: result.rows ?? [],
583
+ ...(duplicateKeyWarning && { hint: duplicateKeyWarning }),
584
+ };
585
+ },
586
+ };
587
+ }
588
+ /**
589
+ * Pretty print and compact JSON
590
+ */
591
+ function createJsonPrettyTool() {
592
+ return {
593
+ name: "sqlite_json_pretty",
594
+ description: "Format JSON string with indentation for readability.",
595
+ group: "json",
596
+ inputSchema: JsonPrettySchema,
597
+ outputSchema: JsonPrettyOutputSchema,
598
+ requiredScopes: ["read"],
599
+ annotations: readOnly("JSON Pretty"),
600
+ handler: (params, _context) => {
601
+ const input = JsonPrettySchema.parse(params);
602
+ try {
603
+ const parsed = JSON.parse(input.json);
604
+ const pretty = JSON.stringify(parsed, null, 2);
605
+ return Promise.resolve({
606
+ success: true,
607
+ formatted: pretty,
608
+ });
609
+ }
610
+ catch (error) {
611
+ return Promise.resolve({
612
+ success: false,
613
+ error: error instanceof Error ? error.message : "Invalid JSON",
614
+ });
615
+ }
616
+ },
617
+ };
618
+ }
619
+ // =============================================================================
620
+ // JSONB Tools
621
+ // =============================================================================
622
+ // Schema for JSONB convert tool
623
+ const JsonbConvertSchema = z.object({
624
+ table: z.string().describe("Table name"),
625
+ column: z.string().describe("JSON column to convert"),
626
+ whereClause: z.string().optional().describe("Optional WHERE clause"),
627
+ });
628
+ // Schema for storage info tool
629
+ const JsonStorageInfoSchema = z.object({
630
+ table: z.string().describe("Table name"),
631
+ column: z.string().describe("JSON column to analyze"),
632
+ sampleSize: z
633
+ .number()
634
+ .optional()
635
+ .default(100)
636
+ .describe("Number of rows to sample"),
637
+ });
638
+ // Schema for normalize column tool
639
+ const JsonNormalizeColumnSchema = z.object({
640
+ table: z.string().describe("Table name"),
641
+ column: z.string().describe("JSON column to normalize"),
642
+ whereClause: z.string().optional().describe("Optional WHERE clause"),
643
+ outputFormat: z
644
+ .enum(["text", "jsonb", "preserve"])
645
+ .optional()
646
+ .default("preserve")
647
+ .describe("Output format: 'preserve' original format (default), 'text', or 'jsonb'"),
648
+ });
649
+ /**
650
+ * Convert text JSON column to JSONB format
651
+ */
652
+ function createJsonbConvertTool(adapter) {
653
+ return {
654
+ name: "sqlite_jsonb_convert",
655
+ description: "Convert a text JSON column to JSONB binary format for faster processing. Requires SQLite 3.45+.",
656
+ group: "json",
657
+ inputSchema: JsonbConvertSchema,
658
+ outputSchema: JsonbConvertOutputSchema,
659
+ requiredScopes: ["write"],
660
+ annotations: write("JSONB Convert"),
661
+ handler: async (params, _context) => {
662
+ const input = JsonbConvertSchema.parse(params);
663
+ // Validate and quote identifiers
664
+ const table = sanitizeIdentifier(input.table);
665
+ const column = sanitizeIdentifier(input.column);
666
+ // Check JSONB support
667
+ if (!isJsonbSupported()) {
668
+ return {
669
+ success: false,
670
+ error: "JSONB not supported (requires SQLite 3.45+)",
671
+ hint: "Current SQLite version does not support JSONB. Data remains as text JSON.",
672
+ };
673
+ }
674
+ let sql = `UPDATE ${table} SET ${column} = jsonb(${column})`;
675
+ if (input.whereClause) {
676
+ sql += ` WHERE ${input.whereClause}`;
677
+ }
678
+ const result = await adapter.executeWriteQuery(sql);
679
+ return {
680
+ success: true,
681
+ message: `Converted ${result.rowsAffected} rows to JSONB format`,
682
+ rowsAffected: result.rowsAffected,
683
+ };
684
+ },
685
+ };
686
+ }
687
+ /**
688
+ * Get storage format info for a JSON column
689
+ */
690
+ function createJsonStorageInfoTool(adapter) {
691
+ return {
692
+ name: "sqlite_json_storage_info",
693
+ description: "Analyze storage format of a JSON column (text vs JSONB) and report statistics.",
694
+ group: "json",
695
+ inputSchema: JsonStorageInfoSchema,
696
+ outputSchema: JsonStorageInfoOutputSchema,
697
+ requiredScopes: ["read"],
698
+ annotations: readOnly("JSON Storage Info"),
699
+ handler: async (params, _context) => {
700
+ const input = JsonStorageInfoSchema.parse(params);
701
+ // Validate identifiers
702
+ const table = sanitizeIdentifier(input.table);
703
+ // Sample rows to detect format
704
+ const sql = `SELECT ${sanitizeIdentifier(input.column)} FROM ${table} LIMIT ${input.sampleSize}`;
705
+ const result = await adapter.executeReadQuery(sql);
706
+ let textCount = 0;
707
+ let jsonbCount = 0;
708
+ let nullCount = 0;
709
+ let unknownCount = 0;
710
+ for (const row of result.rows ?? []) {
711
+ const value = row[input.column];
712
+ if (value === null || value === undefined) {
713
+ nullCount++;
714
+ }
715
+ else {
716
+ const format = detectJsonStorageFormat(value);
717
+ if (format === "text")
718
+ textCount++;
719
+ else if (format === "jsonb")
720
+ jsonbCount++;
721
+ else
722
+ unknownCount++;
723
+ }
724
+ }
725
+ const total = result.rows?.length ?? 0;
726
+ return {
727
+ success: true,
728
+ jsonbSupported: isJsonbSupported(),
729
+ sampleSize: total,
730
+ formats: {
731
+ text: textCount,
732
+ jsonb: jsonbCount,
733
+ null: nullCount,
734
+ unknown: unknownCount,
735
+ },
736
+ recommendation:
737
+ // Mixed format: both text and JSONB rows exist
738
+ textCount > 0 && jsonbCount > 0
739
+ ? `Column has mixed formats (${textCount} text, ${jsonbCount} JSONB). Run sqlite_jsonb_convert to unify.`
740
+ : // All text, JSONB supported: recommend conversion
741
+ jsonbCount === 0 && textCount > 0 && isJsonbSupported()
742
+ ? "Column uses text JSON. Consider converting to JSONB for better performance."
743
+ : // All JSONB: already optimal
744
+ jsonbCount > 0
745
+ ? "Column already uses JSONB format."
746
+ : // No JSON data found
747
+ "No JSON data found in sample.",
748
+ };
749
+ },
750
+ };
751
+ }
752
+ /**
753
+ * Normalize JSON data in a column for consistent storage
754
+ *
755
+ * Handles both text JSON and JSONB binary format by using SQL's json()
756
+ * function to read the data as text before JavaScript processing.
757
+ */
758
+ function createJsonNormalizeColumnTool(adapter) {
759
+ return {
760
+ name: "sqlite_json_normalize_column",
761
+ description: "Normalize JSON data in a column (sort keys, compact format) for consistent storage and comparison.",
762
+ group: "json",
763
+ inputSchema: JsonNormalizeColumnSchema,
764
+ outputSchema: JsonNormalizeColumnOutputSchema,
765
+ requiredScopes: ["write"],
766
+ annotations: write("Normalize JSON Column"),
767
+ handler: async (params, _context) => {
768
+ const input = JsonNormalizeColumnSchema.parse(params);
769
+ // Validate and quote identifiers
770
+ const table = sanitizeIdentifier(input.table);
771
+ const column = sanitizeIdentifier(input.column);
772
+ // Select both the raw column value (to detect JSONB format) and the text
773
+ // representation via json(). This allows us to:
774
+ // 1. Detect if original storage is JSONB (binary blob)
775
+ // 2. Get text JSON for normalization processing
776
+ let selectSql = `SELECT rowid, ${column} as raw_data, json(${column}) as json_data FROM ${table}`;
777
+ if (input.whereClause) {
778
+ selectSql += ` WHERE ${input.whereClause}`;
779
+ }
780
+ const selectResult = await adapter.executeReadQuery(selectSql);
781
+ let normalizedCount = 0;
782
+ let unchangedCount = 0;
783
+ let errorCount = 0;
784
+ // Normalize each row
785
+ for (const row of selectResult.rows ?? []) {
786
+ const rowid = row["rowid"];
787
+ const rawData = row["raw_data"];
788
+ const jsonData = row["json_data"];
789
+ if (jsonData === null || jsonData === undefined) {
790
+ unchangedCount++;
791
+ continue;
792
+ }
793
+ try {
794
+ const { normalized, wasModified } = normalizeJson(jsonData);
795
+ // Detect if original was stored as JSONB (binary blob)
796
+ // If rawData is not a string, it's likely JSONB binary data
797
+ // better-sqlite3 returns JSONB blobs as Buffer objects
798
+ const wasJsonb = rawData !== null &&
799
+ rawData !== undefined &&
800
+ typeof rawData !== "string";
801
+ // Determine target format based on outputFormat parameter
802
+ const targetFormat = input.outputFormat ?? "preserve";
803
+ const shouldOutputJsonb = targetFormat === "jsonb" ||
804
+ (targetFormat === "preserve" && wasJsonb);
805
+ // Determine if update is needed:
806
+ // - Content was modified (keys reordered, normalized)
807
+ // - Converting from JSONB to text (when outputFormat is 'text')
808
+ // - Converting from text to JSONB (when outputFormat is 'jsonb')
809
+ const needsFormatChange = (wasJsonb && targetFormat === "text") ||
810
+ (!wasJsonb && targetFormat === "jsonb");
811
+ if (wasModified || needsFormatChange) {
812
+ // Use jsonb() wrapper if target is JSONB, otherwise plain text
813
+ const updateSql = shouldOutputJsonb
814
+ ? `UPDATE ${table} SET ${column} = jsonb(?) WHERE rowid = ?`
815
+ : `UPDATE ${table} SET ${column} = ? WHERE rowid = ?`;
816
+ await adapter.executeWriteQuery(updateSql, [normalized, rowid]);
817
+ normalizedCount++;
818
+ }
819
+ else {
820
+ unchangedCount++;
821
+ }
822
+ }
823
+ catch {
824
+ errorCount++;
825
+ }
826
+ }
827
+ return {
828
+ success: true,
829
+ message: `Normalized ${normalizedCount} rows`,
830
+ normalized: normalizedCount,
831
+ unchanged: unchangedCount,
832
+ errors: errorCount,
833
+ total: selectResult.rows?.length ?? 0,
834
+ outputFormat: input.outputFormat ?? "preserve",
835
+ };
836
+ },
837
+ };
838
+ }
839
+ //# sourceMappingURL=json-operations.js.map