@happyvertical/smrt-core 0.36.5 → 0.36.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/collection.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/consumer-plugin/index.d.ts.map +1 -1
- package/dist/consumer-plugin/index.js +7 -3
- package/dist/consumer-plugin/index.js.map +1 -1
- package/dist/database.d.ts +3 -3
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js.map +1 -1
- package/dist/embeddings/provider.d.ts +14 -2
- package/dist/embeddings/provider.d.ts.map +1 -1
- package/dist/embeddings/provider.js +3 -3
- package/dist/embeddings/provider.js.map +1 -1
- package/dist/embeddings/storage.js.map +1 -1
- package/dist/errors.d.ts +22 -22
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +5 -4
- package/dist/errors.js.map +1 -1
- package/dist/generators/cli.d.ts +4 -22
- package/dist/generators/cli.d.ts.map +1 -1
- package/dist/generators/cli.js +9 -5
- package/dist/generators/cli.js.map +1 -1
- package/dist/generators/mcp-runtime-template.d.ts +1 -1
- package/dist/generators/mcp-runtime-template.d.ts.map +1 -1
- package/dist/generators/mcp-runtime-template.js.map +1 -1
- package/dist/generators/mcp.d.ts +16 -4
- package/dist/generators/mcp.d.ts.map +1 -1
- package/dist/generators/mcp.js +25 -9
- package/dist/generators/mcp.js.map +1 -1
- package/dist/generators/rest.d.ts +6 -5
- package/dist/generators/rest.d.ts.map +1 -1
- package/dist/generators/rest.js +8 -5
- package/dist/generators/rest.js.map +1 -1
- package/dist/generators/swagger.d.ts +12 -2
- package/dist/generators/swagger.d.ts.map +1 -1
- package/dist/generators/swagger.js +6 -3
- package/dist/generators/swagger.js.map +1 -1
- package/dist/knowledge.d.ts +12 -1
- package/dist/knowledge.d.ts.map +1 -1
- package/dist/knowledge.js.map +1 -1
- package/dist/lazy-config.d.ts.map +1 -1
- package/dist/lazy-config.js.map +1 -1
- package/dist/manifest/generator.d.ts.map +1 -1
- package/dist/manifest/generator.js +14 -12
- package/dist/manifest/generator.js.map +1 -1
- package/dist/manifest/manager.d.ts +3 -3
- package/dist/manifest/manager.d.ts.map +1 -1
- package/dist/manifest/manager.js.map +1 -1
- package/dist/manifest/manifest-loader.d.ts +3 -2
- package/dist/manifest/manifest-loader.d.ts.map +1 -1
- package/dist/manifest/manifest-loader.js +3 -2
- package/dist/manifest/manifest-loader.js.map +1 -1
- package/dist/manifest/static-manifest.js +2 -2
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +2 -2
- package/dist/manifest/test-manifest-stub.js +2 -2
- package/dist/manifest/test-manifest-stub.js.map +1 -1
- package/dist/manifest.json +2 -2
- package/dist/mcp-advisor/index.d.ts +1 -1
- package/dist/mcp-advisor/index.d.ts.map +1 -1
- package/dist/mcp-advisor/tools/get-object-config.d.ts.map +1 -1
- package/dist/mcp-advisor/types.d.ts +5 -5
- package/dist/mcp-advisor/types.d.ts.map +1 -1
- package/dist/migrations/differ.js.map +1 -1
- package/dist/scanner/manifest-generator.d.ts +11 -3
- package/dist/scanner/manifest-generator.d.ts.map +1 -1
- package/dist/scanner/manifest-generator.js +19 -15
- package/dist/scanner/manifest-generator.js.map +1 -1
- package/dist/scanner/types.d.ts +60 -6
- package/dist/scanner/types.d.ts.map +1 -1
- package/dist/schema/code-generator.d.ts.map +1 -1
- package/dist/schema/ddl/base-strategy.d.ts +2 -2
- package/dist/schema/ddl/base-strategy.d.ts.map +1 -1
- package/dist/schema/ddl/base-strategy.js.map +1 -1
- package/dist/schema/ddl/sqlite-strategy.d.ts +1 -1
- package/dist/schema/ddl/sqlite-strategy.d.ts.map +1 -1
- package/dist/schema/ddl/sqlite-strategy.js.map +1 -1
- package/dist/schema/ddl/types.d.ts +1 -1
- package/dist/schema/ddl/types.d.ts.map +1 -1
- package/dist/schema/generator.d.ts +27 -4
- package/dist/schema/generator.d.ts.map +1 -1
- package/dist/schema/generator.js +3 -2
- package/dist/schema/generator.js.map +1 -1
- package/dist/schema/override-system.d.ts +3 -3
- package/dist/schema/override-system.d.ts.map +1 -1
- package/dist/schema/schema-aggregator.d.ts.map +1 -1
- package/dist/schema/schema-manager.d.ts.map +1 -1
- package/dist/schema/schema-manager.js +12 -4
- package/dist/schema/schema-manager.js.map +1 -1
- package/dist/schema/types.d.ts +1 -1
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/utils.d.ts +6 -1
- package/dist/schema/utils.d.ts.map +1 -1
- package/dist/schema/utils.js +2 -1
- package/dist/schema/utils.js.map +1 -1
- package/dist/signals/sanitizer.d.ts +1 -1
- package/dist/signals/sanitizer.d.ts.map +1 -1
- package/dist/signals/sanitizer.js +12 -2
- package/dist/signals/sanitizer.js.map +1 -1
- package/dist/smrt-knowledge.json +4 -4
- package/dist/system/types.d.ts +2 -2
- package/dist/system/types.d.ts.map +1 -1
- package/dist/system-fields.d.ts +5 -43
- package/dist/system-fields.d.ts.map +1 -1
- package/dist/system-fields.js +2 -1
- package/dist/system-fields.js.map +1 -1
- package/dist/test-utils.d.ts +39 -13
- package/dist/test-utils.d.ts.map +1 -1
- package/dist/testing/database.d.ts.map +1 -1
- package/dist/testing/database.js.map +1 -1
- package/dist/tools/tool-executor.d.ts +19 -5
- package/dist/tools/tool-executor.d.ts.map +1 -1
- package/dist/tools/tool-executor.js +4 -2
- package/dist/tools/tool-executor.js.map +1 -1
- package/dist/tools/tool-generator.d.ts +8 -1
- package/dist/tools/tool-generator.d.ts.map +1 -1
- package/dist/tools/tool-generator.js +10 -11
- package/dist/tools/tool-generator.js.map +1 -1
- package/dist/utils/json.js.map +1 -1
- package/dist/utils.d.ts +16 -8
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js.map +1 -1
- package/dist/vite-plugin/index.d.ts.map +1 -1
- package/dist/vite-plugin/index.js +9 -7
- package/dist/vite-plugin/index.js.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.js +4 -3
- package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
- package/dist/vite-plugin/templates/default-ui.ts +20 -6
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-executor.js","sources":["../../src/tools/tool-executor.ts"],"sourcesContent":["/**\n * Tool Call Execution for AI Function Calling\n *\n * This module handles runtime execution of AI tool calls on SMRT object instances.\n */\n\nimport { createLogger } from '@happyvertical/logger';\nimport type { Signal } from '@happyvertical/smrt-types';\nimport { RuntimeError, ValidationError } from '../errors.js';\nimport type { SignalBus } from '../signals/bus.js';\n\nconst logger = createLogger({ level: 'info' });\n\n/**\n * Tool call structure from AI response\n */\nexport interface ToolCall {\n /**\n * Unique identifier for this tool call\n */\n id: string;\n\n /**\n * Type of tool (always 'function' for now)\n */\n type: 'function';\n\n /**\n * Function details\n */\n function: {\n /**\n * Name of the method to call\n */\n name: string;\n\n /**\n * JSON string of arguments to pass to the method\n */\n arguments: string;\n };\n}\n\n/**\n * Result of executing a tool call\n */\nexport interface ToolCallResult {\n /**\n * Tool call ID for correlation\n */\n id: string;\n\n /**\n * Method name that was called\n */\n methodName: string;\n\n /**\n * Parsed arguments that were used\n */\n arguments: Record<string, any>;\n\n /**\n * Result returned from the method\n */\n result: any;\n\n /**\n * Whether the call succeeded\n */\n success: boolean;\n\n /**\n * Error message if call failed\n */\n error?: string;\n\n /**\n * Execution time in milliseconds\n */\n duration?: number;\n}\n\n/**\n * Validates tool call arguments against method parameters\n *\n * @param methodName - Name of the method being called\n * @param args - Parsed arguments from tool call\n * @param allowedMethods - List of methods AI is allowed to call\n * @throws ValidationError if method not allowed or arguments invalid\n */\nexport function validateToolCall(\n methodName: string,\n args: Record<string, any>,\n allowedMethods: string[],\n): void {\n // Check if method is allowed\n if (!allowedMethods.includes(methodName)) {\n throw ValidationError.invalidValue(\n 'methodName',\n methodName,\n `Method must be one of: ${allowedMethods.join(', ')}`,\n );\n }\n\n // Basic argument validation (could be enhanced)\n if (typeof args !== 'object' || args === null) {\n throw ValidationError.invalidValue(\n 'arguments',\n args,\n 'Arguments must be a valid object',\n );\n }\n}\n\n/**\n * Executes a tool call on an object instance\n *\n * @param instance - Object instance to call method on\n * @param toolCall - Tool call from AI\n * @param allowedMethods - List of methods AI is allowed to call\n * @param signalBus - Optional signal bus for emitting execution events\n * @returns Result of the tool call execution\n */\nexport async function executeToolCall(\n instance: any,\n toolCall: ToolCall,\n allowedMethods: string[],\n signalBus?: SignalBus,\n): Promise<ToolCallResult> {\n const startTime = Date.now();\n const methodName = toolCall.function.name;\n const executionId = signalBus?.generateExecutionId() ?? toolCall.id;\n\n // Declare args outside try blocks so it's accessible in catch block\n let args: Record<string, any> | undefined;\n\n try {\n // Parse arguments\n try {\n args = JSON.parse(toolCall.function.arguments);\n } catch (_parseError) {\n throw ValidationError.invalidValue(\n 'arguments',\n toolCall.function.arguments,\n 'Arguments must be valid JSON',\n );\n }\n\n // Type guard - args is always defined after successful parsing\n if (!args) {\n throw ValidationError.invalidValue(\n 'arguments',\n toolCall.function.arguments,\n 'Arguments must be a valid object',\n );\n }\n\n // Validate tool call\n validateToolCall(methodName, args, allowedMethods);\n\n // Check method exists\n if (typeof instance[methodName] !== 'function') {\n throw RuntimeError.operationFailed(\n `Method '${methodName}' not found on object`,\n );\n }\n\n // Emit start signal\n if (signalBus) {\n const startSignal: Signal = {\n id: executionId,\n objectId: instance.id ?? 'unknown',\n className: instance.constructor?.name ?? 'Unknown',\n method: methodName,\n type: 'start',\n args: [args], // Wrap in array for consistency\n timestamp: new Date(),\n };\n await signalBus.emit(startSignal);\n }\n\n // Execute method\n const result = await instance[methodName](args);\n\n // Emit end signal\n if (signalBus) {\n const endSignal: Signal = {\n id: executionId,\n objectId: instance.id ?? 'unknown',\n className: instance.constructor?.name ?? 'Unknown',\n method: methodName,\n type: 'end',\n args: [args],\n result,\n duration: Date.now() - startTime,\n timestamp: new Date(),\n };\n await signalBus.emit(endSignal);\n }\n\n return {\n id: toolCall.id,\n methodName,\n arguments: args,\n result,\n success: true,\n duration: Date.now() - startTime,\n };\n } catch (error) {\n // Emit error signal\n if (signalBus) {\n const errorSignal: Signal = {\n id: executionId,\n objectId: instance.id ?? 'unknown',\n className: instance.constructor?.name ?? 'Unknown',\n method: methodName,\n type: 'error',\n // Preserve actual args if parsed, otherwise include raw arguments for debugging\n args: [\n typeof args !== 'undefined' ? args : toolCall.function.arguments,\n ],\n error: error instanceof Error ? error : new Error(String(error)),\n duration: Date.now() - startTime,\n timestamp: new Date(),\n };\n await signalBus.emit(errorSignal);\n }\n\n return {\n id: toolCall.id,\n methodName,\n arguments: {},\n result: null,\n success: false,\n error: error instanceof Error ? error.message : String(error),\n duration: Date.now() - startTime,\n };\n }\n}\n\n/**\n * Executes multiple tool calls in sequence\n *\n * @param instance - Object instance to call methods on\n * @param toolCalls - Array of tool calls from AI\n * @param allowedMethods - List of methods AI is allowed to call\n * @param signalBus - Optional signal bus for emitting execution events\n * @returns Array of tool call results\n */\nexport async function executeToolCalls(\n instance: any,\n toolCalls: ToolCall[],\n allowedMethods: string[],\n signalBus?: SignalBus,\n): Promise<ToolCallResult[]> {\n const results: ToolCallResult[] = [];\n\n for (const toolCall of toolCalls) {\n const result = await executeToolCall(\n instance,\n toolCall,\n allowedMethods,\n signalBus,\n );\n results.push(result);\n\n // Stop on first error if needed\n if (!result.success) {\n logger.warn(`Tool call failed for ${result.methodName}: ${result.error}`);\n // Continue executing other tools (don't break)\n }\n }\n\n return results;\n}\n\n/**\n * Formats tool call results into messages for AI\n *\n * @param results - Tool call execution results\n * @returns Array of tool response messages\n */\nexport function formatToolResults(\n results: ToolCallResult[],\n): Array<{ role: 'tool'; tool_call_id: string; content: string }> {\n return results.map((result) => ({\n role: 'tool' as const,\n tool_call_id: result.id,\n content: result.success\n ? JSON.stringify(result.result)\n : `Error: ${result.error}`,\n }));\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tool-executor.js","sources":["../../src/tools/tool-executor.ts"],"sourcesContent":["/**\n * Tool Call Execution for AI Function Calling\n *\n * This module handles runtime execution of AI tool calls on SMRT object instances.\n */\n\nimport { createLogger } from '@happyvertical/logger';\nimport type { Signal } from '@happyvertical/smrt-types';\nimport { RuntimeError, ValidationError } from '../errors.js';\nimport type { SignalBus } from '../signals/bus.js';\n\nconst logger = createLogger({ level: 'info' });\n\n/**\n * A dynamically-dispatched tool target (typically a SmrtObject instance, but\n * typed structurally so any object whose method names are resolved at runtime\n * from the AI tool call is accepted). No index signature is declared so plain\n * class instances like `SmrtObject` remain assignable; dynamic method lookup\n * goes through a `Record<string, unknown>` cast at the call boundary.\n */\ninterface ToolCallTarget {\n id?: string | null;\n constructor?: { name?: string };\n}\n\n/**\n * A callable resolved from a {@link ToolCallTarget} by method name. The method\n * is actually invoked with the parsed (untyped JSON) tool-call arguments, so\n * the parameter list is `unknown[]` rather than `never[]`.\n */\ntype InstanceMethod = (...args: unknown[]) => unknown;\n\n/**\n * Tool call structure from AI response\n */\nexport interface ToolCall {\n /**\n * Unique identifier for this tool call\n */\n id: string;\n\n /**\n * Type of tool (always 'function' for now)\n */\n type: 'function';\n\n /**\n * Function details\n */\n function: {\n /**\n * Name of the method to call\n */\n name: string;\n\n /**\n * JSON string of arguments to pass to the method\n */\n arguments: string;\n };\n}\n\n/**\n * Result of executing a tool call\n */\nexport interface ToolCallResult {\n /**\n * Tool call ID for correlation\n */\n id: string;\n\n /**\n * Method name that was called\n */\n methodName: string;\n\n /**\n * Parsed arguments that were used\n */\n arguments: Record<string, unknown>;\n\n /**\n * Result returned from the method\n */\n result: unknown;\n\n /**\n * Whether the call succeeded\n */\n success: boolean;\n\n /**\n * Error message if call failed\n */\n error?: string;\n\n /**\n * Execution time in milliseconds\n */\n duration?: number;\n}\n\n/**\n * Validates tool call arguments against method parameters\n *\n * @param methodName - Name of the method being called\n * @param args - Parsed arguments from tool call\n * @param allowedMethods - List of methods AI is allowed to call\n * @throws ValidationError if method not allowed or arguments invalid\n */\nexport function validateToolCall(\n methodName: string,\n args: Record<string, unknown>,\n allowedMethods: string[],\n): void {\n // Check if method is allowed\n if (!allowedMethods.includes(methodName)) {\n throw ValidationError.invalidValue(\n 'methodName',\n methodName,\n `Method must be one of: ${allowedMethods.join(', ')}`,\n );\n }\n\n // Basic argument validation (could be enhanced)\n if (typeof args !== 'object' || args === null) {\n throw ValidationError.invalidValue(\n 'arguments',\n args,\n 'Arguments must be a valid object',\n );\n }\n}\n\n/**\n * Executes a tool call on an object instance\n *\n * @param instance - Object instance to call method on\n * @param toolCall - Tool call from AI\n * @param allowedMethods - List of methods AI is allowed to call\n * @param signalBus - Optional signal bus for emitting execution events\n * @returns Result of the tool call execution\n */\nexport async function executeToolCall(\n instance: ToolCallTarget,\n toolCall: ToolCall,\n allowedMethods: string[],\n signalBus?: SignalBus,\n): Promise<ToolCallResult> {\n const startTime = Date.now();\n const methodName = toolCall.function.name;\n const executionId = signalBus?.generateExecutionId() ?? toolCall.id;\n\n // Declare args outside try blocks so it's accessible in catch block\n let args: Record<string, unknown> | undefined;\n\n try {\n // Parse arguments\n try {\n args = JSON.parse(toolCall.function.arguments);\n } catch (_parseError) {\n throw ValidationError.invalidValue(\n 'arguments',\n toolCall.function.arguments,\n 'Arguments must be valid JSON',\n );\n }\n\n // Type guard - args is always defined after successful parsing\n if (!args) {\n throw ValidationError.invalidValue(\n 'arguments',\n toolCall.function.arguments,\n 'Arguments must be a valid object',\n );\n }\n\n // Validate tool call\n validateToolCall(methodName, args, allowedMethods);\n\n // Check method exists. The target is dynamically dispatched, so the method\n // is resolved by string index (cast to a record) and the result is\n // `unknown`; narrow it to a callable at this boundary after the runtime\n // `typeof === 'function'` guard.\n const method = (instance as Record<string, unknown>)[methodName];\n if (typeof method !== 'function') {\n throw RuntimeError.operationFailed(\n `Method '${methodName}' not found on object`,\n );\n }\n const invokeMethod = method as InstanceMethod;\n\n // Emit start signal\n if (signalBus) {\n const startSignal: Signal = {\n id: executionId,\n objectId: instance.id ?? 'unknown',\n className: instance.constructor?.name ?? 'Unknown',\n method: methodName,\n type: 'start',\n args: [args], // Wrap in array for consistency\n timestamp: new Date(),\n };\n await signalBus.emit(startSignal);\n }\n\n // Execute method. `.call(instance, …)` preserves the receiver binding of\n // the original member call (`instance[methodName](args)`) — AI-callable\n // methods routinely read/write `this`.\n const result = await invokeMethod.call(instance, args);\n\n // Emit end signal\n if (signalBus) {\n const endSignal: Signal = {\n id: executionId,\n objectId: instance.id ?? 'unknown',\n className: instance.constructor?.name ?? 'Unknown',\n method: methodName,\n type: 'end',\n args: [args],\n result,\n duration: Date.now() - startTime,\n timestamp: new Date(),\n };\n await signalBus.emit(endSignal);\n }\n\n return {\n id: toolCall.id,\n methodName,\n arguments: args,\n result,\n success: true,\n duration: Date.now() - startTime,\n };\n } catch (error) {\n // Emit error signal\n if (signalBus) {\n const errorSignal: Signal = {\n id: executionId,\n objectId: instance.id ?? 'unknown',\n className: instance.constructor?.name ?? 'Unknown',\n method: methodName,\n type: 'error',\n // Preserve actual args if parsed, otherwise include raw arguments for debugging\n args: [\n typeof args !== 'undefined' ? args : toolCall.function.arguments,\n ],\n error: error instanceof Error ? error : new Error(String(error)),\n duration: Date.now() - startTime,\n timestamp: new Date(),\n };\n await signalBus.emit(errorSignal);\n }\n\n return {\n id: toolCall.id,\n methodName,\n arguments: {},\n result: null,\n success: false,\n error: error instanceof Error ? error.message : String(error),\n duration: Date.now() - startTime,\n };\n }\n}\n\n/**\n * Executes multiple tool calls in sequence\n *\n * @param instance - Object instance to call methods on\n * @param toolCalls - Array of tool calls from AI\n * @param allowedMethods - List of methods AI is allowed to call\n * @param signalBus - Optional signal bus for emitting execution events\n * @returns Array of tool call results\n */\nexport async function executeToolCalls(\n instance: ToolCallTarget,\n toolCalls: ToolCall[],\n allowedMethods: string[],\n signalBus?: SignalBus,\n): Promise<ToolCallResult[]> {\n const results: ToolCallResult[] = [];\n\n for (const toolCall of toolCalls) {\n const result = await executeToolCall(\n instance,\n toolCall,\n allowedMethods,\n signalBus,\n );\n results.push(result);\n\n // Stop on first error if needed\n if (!result.success) {\n logger.warn(`Tool call failed for ${result.methodName}: ${result.error}`);\n // Continue executing other tools (don't break)\n }\n }\n\n return results;\n}\n\n/**\n * Formats tool call results into messages for AI\n *\n * @param results - Tool call execution results\n * @returns Array of tool response messages\n */\nexport function formatToolResults(\n results: ToolCallResult[],\n): Array<{ role: 'tool'; tool_call_id: string; content: string }> {\n return results.map((result) => ({\n role: 'tool' as const,\n tool_call_id: result.id,\n content: result.success\n ? JSON.stringify(result.result)\n : `Error: ${result.error}`,\n }));\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { AITool } from '@happyvertical/ai';
|
|
2
2
|
import { MethodDefinition } from '../scanner/types.js';
|
|
3
|
+
/**
|
|
4
|
+
* A JSON Schema fragment. The shape is open-ended (different keywords appear
|
|
5
|
+
* for primitives, arrays, unions, etc.), so values are `unknown`; callers
|
|
6
|
+
* narrow specific keys (e.g. `.default`) at the point of use.
|
|
7
|
+
*/
|
|
8
|
+
type JsonSchema = Record<string, unknown>;
|
|
3
9
|
/**
|
|
4
10
|
* Configuration for AI-callable methods
|
|
5
11
|
*/
|
|
@@ -26,7 +32,7 @@ export interface AiConfig {
|
|
|
26
32
|
* @param tsType - TypeScript type string (e.g., 'string', 'number', '{ foo: string }')
|
|
27
33
|
* @returns JSON Schema representation
|
|
28
34
|
*/
|
|
29
|
-
export declare function convertTypeToJsonSchema(tsType: string):
|
|
35
|
+
export declare function convertTypeToJsonSchema(tsType: string): JsonSchema;
|
|
30
36
|
/**
|
|
31
37
|
* Determines if a method should be included as an AI-callable tool
|
|
32
38
|
*
|
|
@@ -51,4 +57,5 @@ export declare function generateToolFromMethod(method: MethodDefinition, config?
|
|
|
51
57
|
* @returns Array of AITool definitions for LLM function calling
|
|
52
58
|
*/
|
|
53
59
|
export declare function generateToolManifest(methods: MethodDefinition[], config?: AiConfig): AITool[];
|
|
60
|
+
export {};
|
|
54
61
|
//# sourceMappingURL=tool-generator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-generator.d.ts","sourceRoot":"","sources":["../../src/tools/tool-generator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,cAAc,GAAG,KAAK,CAAC;IAE7C;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAEnB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACvC;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,
|
|
1
|
+
{"version":3,"file":"tool-generator.d.ts","sourceRoot":"","sources":["../../src/tools/tool-generator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D;;;;GAIG;AACH,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAgB1C;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,cAAc,GAAG,KAAK,CAAC;IAE7C;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAEnB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACvC;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAqElE;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,gBAAgB,EACxB,MAAM,CAAC,EAAE,QAAQ,GAChB,OAAO,CAqCT;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,gBAAgB,EACxB,MAAM,CAAC,EAAE,QAAQ,GAChB,MAAM,CA0CR;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,gBAAgB,EAAE,EAC3B,MAAM,CAAC,EAAE,QAAQ,GAChB,MAAM,EAAE,CAWV"}
|
|
@@ -75,23 +75,22 @@ function shouldIncludeMethod(method, config) {
|
|
|
75
75
|
return false;
|
|
76
76
|
}
|
|
77
77
|
function generateToolFromMethod(method, config) {
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
properties: {},
|
|
81
|
-
required: []
|
|
82
|
-
};
|
|
78
|
+
const properties = {};
|
|
79
|
+
const required = [];
|
|
83
80
|
for (const param of method.parameters) {
|
|
84
|
-
|
|
81
|
+
properties[param.name] = convertTypeToJsonSchema(param.type);
|
|
85
82
|
if (!param.optional) {
|
|
86
|
-
|
|
83
|
+
required.push(param.name);
|
|
87
84
|
}
|
|
88
85
|
if (param.default !== void 0) {
|
|
89
|
-
|
|
86
|
+
properties[param.name].default = param.default;
|
|
90
87
|
}
|
|
91
88
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
89
|
+
const parameters = {
|
|
90
|
+
type: "object",
|
|
91
|
+
properties,
|
|
92
|
+
...required.length > 0 ? { required } : {}
|
|
93
|
+
};
|
|
95
94
|
const description = config?.descriptions?.[method.name] || method.description || `Call the ${method.name} method`;
|
|
96
95
|
return {
|
|
97
96
|
type: "function",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-generator.js","sources":["../../src/tools/tool-generator.ts"],"sourcesContent":["/**\n * Tool Manifest Generation for AI Function Calling\n *\n * This module converts TypeScript method definitions from the AST scanner\n * into AI tool format at build time for use with LLM function calling.\n */\n\nimport type { AITool } from '@happyvertical/ai';\nimport type { MethodDefinition } from '../scanner/types.js';\n\n/**\n * Configuration for AI-callable methods\n */\nexport interface AiConfig {\n /**\n * Methods that AI can call\n * - Array of method names, e.g., ['analyze', 'validate']\n * - 'public-async' to auto-include all public async methods\n * - 'all' to include all methods (not recommended)\n */\n callable?: string[] | 'public-async' | 'all';\n\n /**\n * Methods to exclude from AI calling (higher priority than callable)\n */\n exclude?: string[];\n\n /**\n * Additional tool descriptions to override method JSDoc\n */\n descriptions?: Record<string, string>;\n}\n\n/**\n * Converts a TypeScript type string to JSON Schema format\n *\n * @param tsType - TypeScript type string (e.g., 'string', 'number', '{ foo: string }')\n * @returns JSON Schema representation\n */\nexport function convertTypeToJsonSchema(tsType: string):
|
|
1
|
+
{"version":3,"file":"tool-generator.js","sources":["../../src/tools/tool-generator.ts"],"sourcesContent":["/**\n * Tool Manifest Generation for AI Function Calling\n *\n * This module converts TypeScript method definitions from the AST scanner\n * into AI tool format at build time for use with LLM function calling.\n */\n\nimport type { AITool } from '@happyvertical/ai';\nimport type { MethodDefinition } from '../scanner/types.js';\n\n/**\n * A JSON Schema fragment. The shape is open-ended (different keywords appear\n * for primitives, arrays, unions, etc.), so values are `unknown`; callers\n * narrow specific keys (e.g. `.default`) at the point of use.\n */\ntype JsonSchema = Record<string, unknown>;\n\n/**\n * The `parameters` object of a generated tool: a JSON Schema `object` with a\n * `properties` bag (each entry a nested schema) and a `required` name list.\n */\ninterface ToolParametersSchema {\n type: 'object';\n properties: Record<string, JsonSchema>;\n /**\n * Optional so the empty array can be `delete`d (omitting the key from the\n * emitted JSON Schema). It is always present while parameters are collected.\n */\n required?: string[];\n}\n\n/**\n * Configuration for AI-callable methods\n */\nexport interface AiConfig {\n /**\n * Methods that AI can call\n * - Array of method names, e.g., ['analyze', 'validate']\n * - 'public-async' to auto-include all public async methods\n * - 'all' to include all methods (not recommended)\n */\n callable?: string[] | 'public-async' | 'all';\n\n /**\n * Methods to exclude from AI calling (higher priority than callable)\n */\n exclude?: string[];\n\n /**\n * Additional tool descriptions to override method JSDoc\n */\n descriptions?: Record<string, string>;\n}\n\n/**\n * Converts a TypeScript type string to JSON Schema format\n *\n * @param tsType - TypeScript type string (e.g., 'string', 'number', '{ foo: string }')\n * @returns JSON Schema representation\n */\nexport function convertTypeToJsonSchema(tsType: string): JsonSchema {\n // Remove whitespace\n const cleanType = tsType.trim();\n\n // Primitive types\n if (cleanType === 'string') {\n return { type: 'string' };\n }\n if (cleanType === 'number') {\n return { type: 'number' };\n }\n if (cleanType === 'boolean') {\n return { type: 'boolean' };\n }\n if (cleanType === 'null') {\n return { type: 'null' };\n }\n if (cleanType === 'any' || cleanType === 'unknown') {\n return {}; // No type constraint\n }\n\n // Array types\n if (cleanType.endsWith('[]')) {\n const itemType = cleanType.slice(0, -2);\n return {\n type: 'array',\n items: convertTypeToJsonSchema(itemType),\n };\n }\n\n // Array<T> syntax\n const arrayMatch = cleanType.match(/^Array<(.+)>$/);\n if (arrayMatch) {\n return {\n type: 'array',\n items: convertTypeToJsonSchema(arrayMatch[1]),\n };\n }\n\n // Union types with literal values (e.g., 'shallow' | 'deep')\n if (cleanType.includes('|')) {\n const options = cleanType.split('|').map((s) => s.trim());\n\n // Check if all options are string literals\n if (options.every((opt) => opt.startsWith(\"'\") && opt.endsWith(\"'\"))) {\n return {\n type: 'string',\n enum: options.map((opt) => opt.slice(1, -1)), // Remove quotes\n };\n }\n\n // Mixed union - use oneOf\n return {\n oneOf: options.map(convertTypeToJsonSchema),\n };\n }\n\n // Object types - basic support\n if (cleanType.startsWith('{') && cleanType.endsWith('}')) {\n return { type: 'object' };\n }\n\n // Record<string, any> and similar\n if (cleanType.startsWith('Record<')) {\n return { type: 'object' };\n }\n\n // Default fallback\n return { type: 'string', description: `TypeScript type: ${cleanType}` };\n}\n\n/**\n * Determines if a method should be included as an AI-callable tool\n *\n * @param method - Method definition from AST scanner\n * @param config - AI configuration from @smrt decorator\n * @returns True if method should be callable by AI\n */\nexport function shouldIncludeMethod(\n method: MethodDefinition,\n config?: AiConfig,\n): boolean {\n // Skip if no AI config\n if (!config || !config.callable) {\n return false;\n }\n\n // Check exclusions first (higher priority)\n if (config.exclude?.includes(method.name)) {\n return false;\n }\n\n // Skip private methods always\n if (!method.isPublic) {\n return false;\n }\n\n // Skip static methods (tools operate on instances)\n if (method.isStatic) {\n return false;\n }\n\n // Handle 'all' mode\n if (config.callable === 'all') {\n return true;\n }\n\n // Handle 'public-async' mode\n if (config.callable === 'public-async') {\n return method.async;\n }\n\n // Handle explicit array of method names\n if (Array.isArray(config.callable)) {\n return config.callable.includes(method.name);\n }\n\n return false;\n}\n\n/**\n * Generates an AITool definition from a method definition\n *\n * @param method - Method definition from AST scanner\n * @param config - AI configuration for custom descriptions\n * @returns AITool definition for LLM function calling\n */\nexport function generateToolFromMethod(\n method: MethodDefinition,\n config?: AiConfig,\n): AITool {\n // Build parameters JSON Schema. `required` is a guaranteed-present local\n // array while collecting, then attached only when non-empty — avoids both\n // optional-chaining on an invariant and a post-hoc `delete`.\n const properties: ToolParametersSchema['properties'] = {};\n const required: string[] = [];\n\n for (const param of method.parameters) {\n // Convert parameter type to JSON Schema\n properties[param.name] = convertTypeToJsonSchema(param.type);\n\n // Add to required if not optional\n if (!param.optional) {\n required.push(param.name);\n }\n\n // Add default value if present\n if (param.default !== undefined) {\n properties[param.name].default = param.default;\n }\n }\n\n const parameters: ToolParametersSchema = {\n type: 'object',\n properties,\n ...(required.length > 0 ? { required } : {}),\n };\n\n // Get description (custom override or from JSDoc)\n const description =\n config?.descriptions?.[method.name] ||\n method.description ||\n `Call the ${method.name} method`;\n\n return {\n type: 'function',\n function: {\n name: method.name,\n description,\n parameters,\n },\n };\n}\n\n/**\n * Generates tool manifest from method definitions\n *\n * @param methods - Array of method definitions from AST scanner\n * @param config - AI configuration from @smrt decorator\n * @returns Array of AITool definitions for LLM function calling\n */\nexport function generateToolManifest(\n methods: MethodDefinition[],\n config?: AiConfig,\n): AITool[] {\n const tools: AITool[] = [];\n\n for (const method of methods) {\n if (shouldIncludeMethod(method, config)) {\n const tool = generateToolFromMethod(method, config);\n tools.push(tool);\n }\n }\n\n return tools;\n}\n"],"names":[],"mappings":"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"}
|
package/dist/utils/json.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json.js","sources":["../../src/utils/json.ts"],"sourcesContent":["/**\n * JSON utilities with optional SIMD acceleration\n *\n * Uses @happyvertical/json for faster parsing when available,\n * with automatic fallback to native JSON.\n *\n * Performance: 2-3x faster parsing on large JSON files (680KB manifests)\n */\n\nimport {\n type JSONAdapter,\n JSONFactory,\n type ParseError,\n type Result,\n type StringifyError,\n} from '@happyvertical/json';\n\n// Singleton adapter instance\nlet adapter: JSONAdapter | null = null;\n\n/**\n * Get or create the JSON adapter instance\n */\nfunction getAdapter(): JSONAdapter {\n if (!adapter) {\n adapter = JSONFactory.create({\n adapter: 'auto', // Use SIMD when available, fallback to native\n fallback: true,\n });\n }\n return adapter;\n}\n\n/**\n * Parse JSON string with optional SIMD acceleration\n *\n * @example\n * ```typescript\n * import { parse } from '@happyvertical/smrt-core/utils/json';\n *\n * const data = parse<MyType>('{\"key\": \"value\"}');\n * ```\n */\nexport function parse<T = unknown>(text: string): T {\n return getAdapter().parse<T>(text);\n}\n\n/**\n * Stringify value to JSON with optional SIMD acceleration\n *\n * @example\n * ```typescript\n * import { stringify } from '@happyvertical/smrt-core/utils/json';\n *\n * const json = stringify({ key: 'value' });\n * const pretty = stringify({ key: 'value' }, null, 2);\n * ```\n */\nexport function stringify(\n value: unknown,\n replacer?:\n | ((key: string, value: unknown) => unknown)\n | (string | number)[]\n | null,\n space?: number | string,\n): string {\n return getAdapter().stringify(value, replacer
|
|
1
|
+
{"version":3,"file":"json.js","sources":["../../src/utils/json.ts"],"sourcesContent":["/**\n * JSON utilities with optional SIMD acceleration\n *\n * Uses @happyvertical/json for faster parsing when available,\n * with automatic fallback to native JSON.\n *\n * Performance: 2-3x faster parsing on large JSON files (680KB manifests)\n */\n\nimport {\n type JSONAdapter,\n JSONFactory,\n type ParseError,\n type Result,\n type StringifyError,\n} from '@happyvertical/json';\n\n// Singleton adapter instance\nlet adapter: JSONAdapter | null = null;\n\n/**\n * Get or create the JSON adapter instance\n */\nfunction getAdapter(): JSONAdapter {\n if (!adapter) {\n adapter = JSONFactory.create({\n adapter: 'auto', // Use SIMD when available, fallback to native\n fallback: true,\n });\n }\n return adapter;\n}\n\n/**\n * Parse JSON string with optional SIMD acceleration\n *\n * @example\n * ```typescript\n * import { parse } from '@happyvertical/smrt-core/utils/json';\n *\n * const data = parse<MyType>('{\"key\": \"value\"}');\n * ```\n */\nexport function parse<T = unknown>(text: string): T {\n return getAdapter().parse<T>(text);\n}\n\n/**\n * Stringify value to JSON with optional SIMD acceleration\n *\n * @example\n * ```typescript\n * import { stringify } from '@happyvertical/smrt-core/utils/json';\n *\n * const json = stringify({ key: 'value' });\n * const pretty = stringify({ key: 'value' }, null, 2);\n * ```\n */\nexport function stringify(\n value: unknown,\n replacer?:\n | ((key: string, value: unknown) => unknown)\n | (string | number)[]\n | null,\n space?: number | string,\n): string {\n return getAdapter().stringify(value, replacer, space);\n}\n\n/**\n * Deep clone via JSON round-trip with optional SIMD acceleration\n *\n * @example\n * ```typescript\n * import { clone } from '@happyvertical/smrt-core/utils/json';\n *\n * const copy = clone(original);\n * ```\n */\nexport function clone<T>(value: T): T {\n return getAdapter().clone(value);\n}\n\n/**\n * Parse JSON without throwing - returns Result type\n *\n * @example\n * ```typescript\n * import { safeParse } from '@happyvertical/smrt-core/utils/json';\n *\n * const result = safeParse<Config>(configJson);\n * if (result.success) {\n * console.log(result.value);\n * } else {\n * console.error(result.error.message);\n * }\n * ```\n */\nexport function safeParse<T = unknown>(text: string): Result<T, ParseError> {\n return getAdapter().safeParse<T>(text);\n}\n\n/**\n * Stringify without throwing - returns Result type\n *\n * @example\n * ```typescript\n * import { safeStringify } from '@happyvertical/smrt-core/utils/json';\n *\n * const result = safeStringify(data);\n * if (result.success) {\n * console.log(result.value);\n * } else {\n * console.error(result.error.message);\n * }\n * ```\n */\nexport function safeStringify(value: unknown): Result<string, StringifyError> {\n return getAdapter().safeStringify(value);\n}\n\n/**\n * Check if a string is valid JSON\n *\n * @example\n * ```typescript\n * import { isValid } from '@happyvertical/smrt-core/utils/json';\n *\n * if (isValid(userInput)) {\n * // Safe to parse\n * }\n * ```\n */\nexport function isValid(text: string): boolean {\n return getAdapter().isValid(text);\n}\n\n/**\n * Get information about the active JSON adapter\n *\n * @example\n * ```typescript\n * import { getAdapterInfo } from '@happyvertical/smrt-core/utils/json';\n *\n * const info = getAdapterInfo();\n * console.log(`Using ${info.name} adapter, SIMD: ${info.simdEnabled}`);\n * ```\n */\nexport function getAdapterInfo(): {\n name: string;\n isNative: boolean;\n version?: string;\n simdEnabled?: boolean;\n} {\n return getAdapter().getInfo();\n}\n\n/**\n * Re-export types for convenience\n */\nexport type { JSONAdapter, ParseError, Result, StringifyError };\n"],"names":[],"mappings":";AAkBA,IAAI,UAA8B;AAKlC,SAAS,aAA0B;AACjC,MAAI,CAAC,SAAS;AACZ,cAAU,YAAY,OAAO;AAAA,MAC3B,SAAS;AAAA;AAAA,MACT,UAAU;AAAA,IAAA,CACX;AAAA,EACH;AACA,SAAO;AACT;AAYO,SAAS,MAAmB,MAAiB;AAClD,SAAO,WAAA,EAAa,MAAS,IAAI;AACnC;AAaO,SAAS,UACd,OACA,UAIA,OACQ;AACR,SAAO,WAAA,EAAa,UAAU,OAAO,UAAU,KAAK;AACtD;AAYO,SAAS,MAAS,OAAa;AACpC,SAAO,WAAA,EAAa,MAAM,KAAK;AACjC;AAiBO,SAAS,UAAuB,MAAqC;AAC1E,SAAO,WAAA,EAAa,UAAa,IAAI;AACvC;AAiBO,SAAS,cAAc,OAAgD;AAC5E,SAAO,WAAA,EAAa,cAAc,KAAK;AACzC;AAcO,SAAS,QAAQ,MAAuB;AAC7C,SAAO,WAAA,EAAa,QAAQ,IAAI;AAClC;AAaO,SAAS,iBAKd;AACA,SAAO,WAAA,EAAa,QAAA;AACtB;"}
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SmrtObject } from './object';
|
|
1
2
|
import { classnameToTablename, pluralize, toSnakeCase } from './utils/naming.js';
|
|
2
3
|
export { classnameToTablename, pluralize, toSnakeCase };
|
|
3
4
|
/**
|
|
@@ -19,14 +20,14 @@ export declare function toCamelCase(str: string): string;
|
|
|
19
20
|
* @param obj - Object with camelCase keys
|
|
20
21
|
* @returns Object with snake_case keys
|
|
21
22
|
*/
|
|
22
|
-
export declare function keysToSnakeCase(obj: Record<string,
|
|
23
|
+
export declare function keysToSnakeCase(obj: Record<string, unknown>): Record<string, unknown>;
|
|
23
24
|
/**
|
|
24
25
|
* Converts all keys in an object from snake_case to camelCase
|
|
25
26
|
*
|
|
26
27
|
* @param obj - Object with snake_case keys
|
|
27
28
|
* @returns Object with camelCase keys
|
|
28
29
|
*/
|
|
29
|
-
export declare function keysToCamelCase(obj: Record<string,
|
|
30
|
+
export declare function keysToCamelCase(obj: Record<string, unknown>): Record<string, unknown>;
|
|
30
31
|
/**
|
|
31
32
|
* Checks if a field name indicates a date field based on naming conventions
|
|
32
33
|
*
|
|
@@ -81,7 +82,7 @@ export declare function dateAsObject(date: Date | string): string;
|
|
|
81
82
|
* console.log(fields.price.type); // 'REAL'
|
|
82
83
|
* ```
|
|
83
84
|
*/
|
|
84
|
-
export declare function fieldsFromClass(ClassType: new (...args:
|
|
85
|
+
export declare function fieldsFromClass(ClassType: new (...args: never[]) => SmrtObject, values?: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
85
86
|
/**
|
|
86
87
|
* Returns the old (incorrect) pluralized table name for migration purposes.
|
|
87
88
|
*
|
|
@@ -146,18 +147,25 @@ export declare function generateTableRenameMigrations(classNames: string[]): str
|
|
|
146
147
|
* tableNameFromClass(Category); // "categories" (derived from runtime name)
|
|
147
148
|
* ```
|
|
148
149
|
*/
|
|
149
|
-
export declare function tableNameFromClass(ClassType: Function | (new (...args:
|
|
150
|
+
export declare function tableNameFromClass(ClassType: Function | (new (...args: never[]) => SmrtObject)): string;
|
|
150
151
|
/**
|
|
151
152
|
* Formats data for JavaScript by converting date strings to Date objects
|
|
152
153
|
* and snake_case column names to camelCase properties
|
|
153
154
|
*
|
|
155
|
+
* Generic over the input row type `T` so callers preserve their (typically
|
|
156
|
+
* loose) row typing across the hydration boundary — this matches the historic
|
|
157
|
+
* behavior where a `Record<string, any>` / `any` row produced an equally loose
|
|
158
|
+
* result. Keys are re-cased and values are type-converted at runtime; the
|
|
159
|
+
* return is the same `Record` shape, so it is reasserted as `T` at this DB
|
|
160
|
+
* boundary rather than widened to an unrelated type.
|
|
161
|
+
*
|
|
154
162
|
* @param data - Object with data to format (snake_case column names from DB)
|
|
155
163
|
* @param fields - Optional field definitions to determine types (from fieldsFromClass)
|
|
156
164
|
* @returns Object with properly typed values and camelCase property names for JavaScript
|
|
157
165
|
*/
|
|
158
|
-
export declare function formatDataJs
|
|
166
|
+
export declare function formatDataJs<T extends Record<string, unknown> = Record<string, unknown>>(data: T, fields?: Record<string, {
|
|
159
167
|
type?: string;
|
|
160
|
-
}>):
|
|
168
|
+
}>): T;
|
|
161
169
|
/**
|
|
162
170
|
* Type guard to check if a value is a Field instance
|
|
163
171
|
*
|
|
@@ -165,7 +173,7 @@ export declare function formatDataJs(data: Record<string, any>, fields?: Record<
|
|
|
165
173
|
* @param value - Value to check
|
|
166
174
|
* @returns Always false (Field class no longer exists)
|
|
167
175
|
*/
|
|
168
|
-
export declare function isFieldInstance(value:
|
|
176
|
+
export declare function isFieldInstance(value: unknown): value is never;
|
|
169
177
|
/**
|
|
170
178
|
* Formats data for SQL by converting Date objects to ISO strings
|
|
171
179
|
* and camelCase property names to snake_case column names
|
|
@@ -173,5 +181,5 @@ export declare function isFieldInstance(value: any): value is never;
|
|
|
173
181
|
* @param data - Object with data to format (camelCase property names from JavaScript)
|
|
174
182
|
* @returns Object with properly formatted values and snake_case column names for SQL
|
|
175
183
|
*/
|
|
176
|
-
export declare function formatDataSql(data: Record<string,
|
|
184
|
+
export declare function formatDataSql(data: Record<string, unknown>): Record<string, unknown>;
|
|
177
185
|
//# sourceMappingURL=utils.d.ts.map
|
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAG3C,OAAO,EACL,oBAAoB,EACpB,SAAS,EACT,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAQ3B,OAAO,EAAE,oBAAoB,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC;AAExD;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC3B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAMzB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC3B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAMzB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,WAItC;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,QAK/C;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,UAK/C;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,UAAU,EAC/C,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,oCAmCjC;AAMD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAMpE;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,6BAA6B,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAa5E;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,kBAAkB,CAEhC,SAAS,EAAE,QAAQ,GAAG,CAAC,KAAK,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,UAAU,CAAC,UAiB7D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAC1B,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC3D,IAAI,EAAE,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,CAAC,CAuHxD;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,KAAK,CAE9D;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,2BAc1D"}
|
package/dist/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sources":["../src/utils.ts"],"sourcesContent":["import { createLogger } from '@happyvertical/logger';\nimport { ObjectRegistry } from './registry';\nimport {\n classnameToTablename,\n pluralize,\n toSnakeCase,\n} from './utils/naming.js';\n\n// formatDataJs' debug traces are gated by DEBUG_STI, so the level must allow\n// debug when it's set (a fixed 'info' would filter them out and break the flag).\nconst logger = createLogger({\n level: process.env.DEBUG_STI ? 'debug' : 'info',\n});\n\nexport { classnameToTablename, pluralize, toSnakeCase };\n\n/**\n * Converts a snake_case string to camelCase\n *\n * @param str - String in snake_case format\n * @returns String in camelCase format\n * @example\n * ```typescript\n * toCamelCase('meetings_url'); // 'meetingsUrl'\n * toCamelCase('created_at'); // 'createdAt'\n * toCamelCase('id'); // 'id'\n * ```\n */\nexport function toCamelCase(str: string): string {\n return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());\n}\n\n/**\n * Converts all keys in an object from camelCase to snake_case\n *\n * @param obj - Object with camelCase keys\n * @returns Object with snake_case keys\n */\nexport function keysToSnakeCase(obj: Record<string, any>): Record<string, any> {\n const result: Record<string, any> = {};\n for (const [key, value] of Object.entries(obj)) {\n result[toSnakeCase(key)] = value;\n }\n return result;\n}\n\n/**\n * Converts all keys in an object from snake_case to camelCase\n *\n * @param obj - Object with snake_case keys\n * @returns Object with camelCase keys\n */\nexport function keysToCamelCase(obj: Record<string, any>): Record<string, any> {\n const result: Record<string, any> = {};\n for (const [key, value] of Object.entries(obj)) {\n result[toCamelCase(key)] = value;\n }\n return result;\n}\n\n/**\n * Checks if a field name indicates a date field based on naming conventions\n *\n * Recognizes common date field patterns like '_at', '_date', and 'date'.\n * Used for automatic type inference during schema generation.\n *\n * @param key - Field name to check\n * @returns Boolean indicating if the field is likely a date field\n * @example\n * ```typescript\n * isDateField('created_at'); // true\n * isDateField('updated_date'); // true\n * isDateField('name'); // false\n * ```\n */\nexport function isDateField(key: string) {\n // Fallback pattern matching when schema is not available\n // Primary date detection should use schema field types\n return key.endsWith('_date') || key.endsWith('_at') || key === 'date';\n}\n\n/**\n * Converts a date string to a Date object\n *\n * @param date - Date as string or Date object\n * @returns Date object\n */\nexport function dateAsString(date: Date | string) {\n if (typeof date === 'string') {\n return new Date(date);\n }\n return date;\n}\n\n/**\n * Converts a Date object to an ISO string\n *\n * @param date - Date as Date object or string\n * @returns ISO date string or the original string\n */\nexport function dateAsObject(date: Date | string) {\n if (date instanceof Date) {\n return date.toISOString();\n }\n return date;\n}\n\n/**\n * Extracts field definitions from a class constructor\n *\n * Uses ObjectRegistry cached fields from AST manifest exclusively.\n * No runtime introspection fallback - classes must be decorated with @smrt()\n * for schema generation to work.\n *\n * @param ClassType - Class constructor to extract fields from\n * @param values - Optional values to set for the fields\n * @returns Object containing field definitions with names, types, and values\n * @throws {Error} If the class is not registered in ObjectRegistry\n * @example\n * ```typescript\n * @smrt()\n * class Product extends SmrtObject {\n * name: string = '';\n * price: number = 0.0;\n * }\n *\n * const fields = await fieldsFromClass(Product);\n * console.log(fields.name.type); // 'TEXT'\n * console.log(fields.price.type); // 'REAL'\n * ```\n */\nexport async function fieldsFromClass(\n ClassType: new (...args: any[]) => any,\n values?: Record<string, any>,\n) {\n const className =\n ObjectRegistry.getClassByConstructor(ClassType as any)?.name ||\n ClassType.name;\n // NEW: Use getAllFields() to include inherited fields from parent classes\n const cachedFields = await ObjectRegistry.getAllFields(className);\n\n // Phase 2: AST manifest only - no runtime introspection fallback\n if (cachedFields.size === 0) {\n // Return empty fields for unregistered classes (for backward compatibility)\n // generateSchema() will throw if it needs field definitions\n return {};\n }\n\n // Use cached field definitions from AST manifest\n const fields: Record<string, any> = {};\n\n // Add/override with fields from cached registry\n for (const [key, field] of cachedFields.entries()) {\n const meta = { ...(field._meta || {}) };\n delete meta.__smrtSystemField;\n\n fields[key] = {\n name: key,\n type: field.type || 'TEXT',\n _meta: meta,\n ...(values && key in values ? { value: values[key] } : {}),\n };\n }\n\n return fields;\n}\n\n// NOTE: generateSchema moved to schema/utils.ts\n// to prevent bundling Node.js-only code (SchemaGenerator with node:crypto) in browser builds.\n// Import from './schema/utils' in Node.js code that needs schema generation.\n\n/**\n * Returns the old (incorrect) pluralized table name for migration purposes.\n *\n * This function replicates the previous buggy behavior where 'y' → 'ies'\n * transformation was applied AFTER adding 's', resulting in incorrect\n * pluralization (e.g., 'currency' → 'currencys' instead of 'currencies').\n *\n * Use this to generate migration SQL that renames old tables to new names.\n *\n * @param className - Name of the class\n * @returns The incorrectly pluralized table name (old behavior)\n * @example\n * ```typescript\n * // Generate migration for a class\n * const oldName = legacyClassnameToTablename('Currency'); // 'currencys'\n * const newName = classnameToTablename('Currency'); // 'currencies'\n * console.log(`ALTER TABLE ${oldName} RENAME TO ${newName};`);\n * ```\n */\nexport function legacyClassnameToTablename(className: string): string {\n return className\n .replace(/([a-z])([A-Z])/g, '$1_$2')\n .toLowerCase()\n .replace(/([^s])$/, '$1s')\n .replace(/y$/, 'ies'); // This runs AFTER 's' is added, so it's a no-op for 'y' words\n}\n\n/**\n * Generates migration SQL for renaming tables from old to new naming convention.\n *\n * Returns an array of SQL statements to rename tables that were affected\n * by the pluralization bug (Issue #839).\n *\n * @param classNames - Array of class names to check for migration\n * @returns Array of SQL RENAME statements (empty if no changes needed)\n * @example\n * ```typescript\n * const migrations = generateTableRenameMigrations([\n * 'Currency',\n * 'JournalEntry',\n * 'Product' // Not affected\n * ]);\n * // Returns:\n * // [\n * // 'ALTER TABLE currencys RENAME TO currencies;',\n * // 'ALTER TABLE journal_entrys RENAME TO journal_entries;'\n * // ]\n * ```\n */\nexport function generateTableRenameMigrations(classNames: string[]): string[] {\n const migrations: string[] = [];\n\n for (const className of classNames) {\n const oldName = legacyClassnameToTablename(className);\n const newName = classnameToTablename(className);\n\n if (oldName !== newName) {\n migrations.push(`ALTER TABLE ${oldName} RENAME TO ${newName};`);\n }\n }\n\n return migrations;\n}\n\n/**\n * Generates a table name from a class constructor\n *\n * Checks for SMRT_TABLE_NAME static property first (set by @smrt() decorator),\n * which survives code minification. Falls back to deriving from ClassType.name\n * for backward compatibility.\n *\n * @param ClassType - Class constructor or function\n * @returns Pluralized snake_case table name\n * @example\n * ```typescript\n * // With @smrt() decorator (recommended)\n * @smrt()\n * class Product extends SmrtObject { }\n * tableNameFromClass(Product); // \"products\" (captured before minification)\n *\n * // Without decorator (fallback)\n * class Category extends SmrtObject { }\n * tableNameFromClass(Category); // \"categories\" (derived from runtime name)\n * ```\n */\nexport function tableNameFromClass(\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n ClassType: Function | (new (...args: any[]) => any),\n) {\n // Check for SMRT_TABLE_NAME property set by @smrt() decorator (survives minification)\n if ('SMRT_TABLE_NAME' in ClassType) {\n return (ClassType as any).SMRT_TABLE_NAME;\n }\n\n // Fallback: derive from class name (breaks with minification)\n const snakeCase = ClassType.name\n // Insert underscore between lower & upper case letters\n .replace(/([a-z])([A-Z])/g, '$1_$2')\n // Convert to lowercase\n .toLowerCase();\n\n return pluralize(snakeCase);\n}\n\n/**\n * Formats data for JavaScript by converting date strings to Date objects\n * and snake_case column names to camelCase properties\n *\n * @param data - Object with data to format (snake_case column names from DB)\n * @param fields - Optional field definitions to determine types (from fieldsFromClass)\n * @returns Object with properly typed values and camelCase property names for JavaScript\n */\nexport function formatDataJs(\n data: Record<string, any>,\n fields?: Record<string, { type?: string }>,\n) {\n const normalizedData: Record<string, any> = {};\n\n if (process.env.DEBUG_STI) {\n logger.debug('[formatDataJs] Input data', {\n hasMetaData: !!data._meta_data,\n metaType: data._meta_type,\n keys: Object.keys(data),\n });\n }\n\n // STI: If _meta_data exists, merge it into a FRESH object first so meta\n // fields are available during formatting. This must never mutate the caller's\n // `data` argument — `formatDataJs` is a public export and external callers may\n // pass shared objects that would otherwise get silent field injection (#1378).\n let mergedData: Record<string, any> = data;\n if (data._meta_data) {\n const metaData =\n typeof data._meta_data === 'string'\n ? JSON.parse(data._meta_data)\n : data._meta_data;\n\n if (process.env.DEBUG_STI) {\n logger.debug('[formatDataJs] Merging _meta_data', {\n metaDataKeys: Object.keys(metaData),\n metaData,\n });\n }\n\n // Merge meta fields into a copy (will be formatted below). Spreading\n // `metaData` last preserves the original precedence of Object.assign.\n mergedData = { ...data, ...metaData };\n\n if (process.env.DEBUG_STI) {\n logger.debug('[formatDataJs] After merge, data keys', {\n keys: Object.keys(mergedData),\n });\n }\n }\n\n for (const [key, value] of Object.entries(mergedData)) {\n // Preserve keys with leading underscore (e.g., _meta_type, _meta_data)\n // These are special STI/framework fields that should not be camelCased\n const camelKey = key.startsWith('_') ? key : toCamelCase(key);\n\n // Determine the actual output key based on what's in fields\n // If the original key (snake_case) is in fields, use it; otherwise use camelCase\n // This supports both conventions (e.g., publish_date vs publishDate)\n let outputKey = camelKey;\n if (fields && key in fields && !(camelKey in fields)) {\n // Original key exists in fields but camelCase doesn't - use original (snake_case)\n outputKey = key;\n }\n\n // Get field type from fields, trying both key variants\n const fieldDef = fields?.[outputKey] ?? fields?.[camelKey] ?? fields?.[key];\n const fieldType = fieldDef?.type?.toLowerCase();\n\n if (value instanceof Date) {\n normalizedData[outputKey] = value;\n } else if (typeof value === 'string') {\n // Use field definitions if available, otherwise fall back to name patterns\n const isDate = fieldType === 'datetime' || isDateField(key);\n\n if (isDate) {\n const parsedDate = value.trim() ? new Date(value) : null;\n normalizedData[outputKey] =\n parsedDate && !Number.isNaN(parsedDate.getTime())\n ? parsedDate\n : value;\n } else if (fieldType === 'json') {\n // Parse JSON strings back to objects\n try {\n normalizedData[outputKey] = JSON.parse(value);\n } catch {\n // Keep as string if parsing fails\n normalizedData[outputKey] = value;\n }\n } else if (fieldType === 'integer') {\n // Convert string numbers to integers for INTEGER fields\n // SQLite may return \"2.0\" as a string in some cases\n const parsed = Number.parseInt(value, 10);\n normalizedData[outputKey] = Number.isNaN(parsed) ? value : parsed;\n } else if (fieldType === 'real' || fieldType === 'decimal') {\n // Convert string numbers to floats for REAL/DECIMAL fields\n const parsed = Number.parseFloat(value);\n normalizedData[outputKey] = Number.isNaN(parsed) ? value : parsed;\n } else {\n normalizedData[outputKey] = value;\n }\n } else if (typeof value === 'number') {\n if (fieldType === 'boolean') {\n // Convert SQLite integers (0/1) to booleans for boolean fields\n normalizedData[outputKey] = value === 1;\n } else {\n // Pass through numeric values as-is\n // Note: In JavaScript, 2.0 === 2 so no conversion needed\n // Non-integers in INTEGER fields (e.g., 2.9) are kept to surface data issues\n normalizedData[outputKey] = value;\n }\n } else {\n normalizedData[outputKey] = value;\n }\n }\n\n if (process.env.DEBUG_STI) {\n logger.debug('[formatDataJs] Output normalizedData', {\n keys: Object.keys(normalizedData),\n metaType: normalizedData._meta_type,\n });\n }\n\n return normalizedData;\n}\n\n/**\n * Type guard to check if a value is a Field instance\n *\n * @deprecated Field helpers have been removed. This function always returns false.\n * @param value - Value to check\n * @returns Always false (Field class no longer exists)\n */\nexport function isFieldInstance(value: any): value is never {\n return false;\n}\n\n/**\n * Formats data for SQL by converting Date objects to ISO strings\n * and camelCase property names to snake_case column names\n *\n * @param data - Object with data to format (camelCase property names from JavaScript)\n * @returns Object with properly formatted values and snake_case column names for SQL\n */\nexport function formatDataSql(data: Record<string, any>) {\n const normalizedData: Record<string, any> = {};\n for (const [key, value] of Object.entries(data)) {\n // Convert camelCase to snake_case for SQL\n const snakeKey = toSnakeCase(key);\n\n // Field helpers removed - no need to extract values (deprecated code removed)\n if (value instanceof Date) {\n normalizedData[snakeKey] = value.toISOString(); // Postgres accepts ISO format with timezone\n } else {\n normalizedData[snakeKey] = value;\n }\n }\n return normalizedData;\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"utils.js","sources":["../src/utils.ts"],"sourcesContent":["import { createLogger } from '@happyvertical/logger';\nimport type { SmrtObject } from './object';\nimport { ObjectRegistry } from './registry';\nimport type { SmrtObjectConstructor } from './registry/types';\nimport {\n classnameToTablename,\n pluralize,\n toSnakeCase,\n} from './utils/naming.js';\n\n// formatDataJs' debug traces are gated by DEBUG_STI, so the level must allow\n// debug when it's set (a fixed 'info' would filter them out and break the flag).\nconst logger = createLogger({\n level: process.env.DEBUG_STI ? 'debug' : 'info',\n});\n\nexport { classnameToTablename, pluralize, toSnakeCase };\n\n/**\n * Converts a snake_case string to camelCase\n *\n * @param str - String in snake_case format\n * @returns String in camelCase format\n * @example\n * ```typescript\n * toCamelCase('meetings_url'); // 'meetingsUrl'\n * toCamelCase('created_at'); // 'createdAt'\n * toCamelCase('id'); // 'id'\n * ```\n */\nexport function toCamelCase(str: string): string {\n return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());\n}\n\n/**\n * Converts all keys in an object from camelCase to snake_case\n *\n * @param obj - Object with camelCase keys\n * @returns Object with snake_case keys\n */\nexport function keysToSnakeCase(\n obj: Record<string, unknown>,\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n result[toSnakeCase(key)] = value;\n }\n return result;\n}\n\n/**\n * Converts all keys in an object from snake_case to camelCase\n *\n * @param obj - Object with snake_case keys\n * @returns Object with camelCase keys\n */\nexport function keysToCamelCase(\n obj: Record<string, unknown>,\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n result[toCamelCase(key)] = value;\n }\n return result;\n}\n\n/**\n * Checks if a field name indicates a date field based on naming conventions\n *\n * Recognizes common date field patterns like '_at', '_date', and 'date'.\n * Used for automatic type inference during schema generation.\n *\n * @param key - Field name to check\n * @returns Boolean indicating if the field is likely a date field\n * @example\n * ```typescript\n * isDateField('created_at'); // true\n * isDateField('updated_date'); // true\n * isDateField('name'); // false\n * ```\n */\nexport function isDateField(key: string) {\n // Fallback pattern matching when schema is not available\n // Primary date detection should use schema field types\n return key.endsWith('_date') || key.endsWith('_at') || key === 'date';\n}\n\n/**\n * Converts a date string to a Date object\n *\n * @param date - Date as string or Date object\n * @returns Date object\n */\nexport function dateAsString(date: Date | string) {\n if (typeof date === 'string') {\n return new Date(date);\n }\n return date;\n}\n\n/**\n * Converts a Date object to an ISO string\n *\n * @param date - Date as Date object or string\n * @returns ISO date string or the original string\n */\nexport function dateAsObject(date: Date | string) {\n if (date instanceof Date) {\n return date.toISOString();\n }\n return date;\n}\n\n/**\n * Extracts field definitions from a class constructor\n *\n * Uses ObjectRegistry cached fields from AST manifest exclusively.\n * No runtime introspection fallback - classes must be decorated with @smrt()\n * for schema generation to work.\n *\n * @param ClassType - Class constructor to extract fields from\n * @param values - Optional values to set for the fields\n * @returns Object containing field definitions with names, types, and values\n * @throws {Error} If the class is not registered in ObjectRegistry\n * @example\n * ```typescript\n * @smrt()\n * class Product extends SmrtObject {\n * name: string = '';\n * price: number = 0.0;\n * }\n *\n * const fields = await fieldsFromClass(Product);\n * console.log(fields.name.type); // 'TEXT'\n * console.log(fields.price.type); // 'REAL'\n * ```\n */\nexport async function fieldsFromClass(\n ClassType: new (...args: never[]) => SmrtObject,\n values?: Record<string, unknown>,\n) {\n // `getClassByConstructor` expects the registry's `SmrtObjectConstructor`\n // (`new (...args: any[]) => SmrtObject`); the narrower `never[]` param type\n // is contravariantly assignable, so this widening cast is purely structural.\n const className =\n ObjectRegistry.getClassByConstructor(ClassType as SmrtObjectConstructor)\n ?.name || ClassType.name;\n // NEW: Use getAllFields() to include inherited fields from parent classes\n const cachedFields = await ObjectRegistry.getAllFields(className);\n\n // Phase 2: AST manifest only - no runtime introspection fallback\n if (cachedFields.size === 0) {\n // Return empty fields for unregistered classes (for backward compatibility)\n // generateSchema() will throw if it needs field definitions\n return {};\n }\n\n // Use cached field definitions from AST manifest\n const fields: Record<string, unknown> = {};\n\n // Add/override with fields from cached registry\n for (const [key, field] of cachedFields.entries()) {\n const meta = { ...(field._meta || {}) };\n delete meta.__smrtSystemField;\n\n fields[key] = {\n name: key,\n type: field.type || 'TEXT',\n _meta: meta,\n ...(values && key in values ? { value: values[key] } : {}),\n };\n }\n\n return fields;\n}\n\n// NOTE: generateSchema moved to schema/utils.ts\n// to prevent bundling Node.js-only code (SchemaGenerator with node:crypto) in browser builds.\n// Import from './schema/utils' in Node.js code that needs schema generation.\n\n/**\n * Returns the old (incorrect) pluralized table name for migration purposes.\n *\n * This function replicates the previous buggy behavior where 'y' → 'ies'\n * transformation was applied AFTER adding 's', resulting in incorrect\n * pluralization (e.g., 'currency' → 'currencys' instead of 'currencies').\n *\n * Use this to generate migration SQL that renames old tables to new names.\n *\n * @param className - Name of the class\n * @returns The incorrectly pluralized table name (old behavior)\n * @example\n * ```typescript\n * // Generate migration for a class\n * const oldName = legacyClassnameToTablename('Currency'); // 'currencys'\n * const newName = classnameToTablename('Currency'); // 'currencies'\n * console.log(`ALTER TABLE ${oldName} RENAME TO ${newName};`);\n * ```\n */\nexport function legacyClassnameToTablename(className: string): string {\n return className\n .replace(/([a-z])([A-Z])/g, '$1_$2')\n .toLowerCase()\n .replace(/([^s])$/, '$1s')\n .replace(/y$/, 'ies'); // This runs AFTER 's' is added, so it's a no-op for 'y' words\n}\n\n/**\n * Generates migration SQL for renaming tables from old to new naming convention.\n *\n * Returns an array of SQL statements to rename tables that were affected\n * by the pluralization bug (Issue #839).\n *\n * @param classNames - Array of class names to check for migration\n * @returns Array of SQL RENAME statements (empty if no changes needed)\n * @example\n * ```typescript\n * const migrations = generateTableRenameMigrations([\n * 'Currency',\n * 'JournalEntry',\n * 'Product' // Not affected\n * ]);\n * // Returns:\n * // [\n * // 'ALTER TABLE currencys RENAME TO currencies;',\n * // 'ALTER TABLE journal_entrys RENAME TO journal_entries;'\n * // ]\n * ```\n */\nexport function generateTableRenameMigrations(classNames: string[]): string[] {\n const migrations: string[] = [];\n\n for (const className of classNames) {\n const oldName = legacyClassnameToTablename(className);\n const newName = classnameToTablename(className);\n\n if (oldName !== newName) {\n migrations.push(`ALTER TABLE ${oldName} RENAME TO ${newName};`);\n }\n }\n\n return migrations;\n}\n\n/**\n * Generates a table name from a class constructor\n *\n * Checks for SMRT_TABLE_NAME static property first (set by @smrt() decorator),\n * which survives code minification. Falls back to deriving from ClassType.name\n * for backward compatibility.\n *\n * @param ClassType - Class constructor or function\n * @returns Pluralized snake_case table name\n * @example\n * ```typescript\n * // With @smrt() decorator (recommended)\n * @smrt()\n * class Product extends SmrtObject { }\n * tableNameFromClass(Product); // \"products\" (captured before minification)\n *\n * // Without decorator (fallback)\n * class Category extends SmrtObject { }\n * tableNameFromClass(Category); // \"categories\" (derived from runtime name)\n * ```\n */\nexport function tableNameFromClass(\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n ClassType: Function | (new (...args: never[]) => SmrtObject),\n) {\n // Check for SMRT_TABLE_NAME property set by @smrt() decorator (survives minification)\n if ('SMRT_TABLE_NAME' in ClassType) {\n // The `in` guard above proves the static decorator property exists; read it\n // through a typed shape rather than `any`.\n return (ClassType as { SMRT_TABLE_NAME: string }).SMRT_TABLE_NAME;\n }\n\n // Fallback: derive from class name (breaks with minification)\n const snakeCase = ClassType.name\n // Insert underscore between lower & upper case letters\n .replace(/([a-z])([A-Z])/g, '$1_$2')\n // Convert to lowercase\n .toLowerCase();\n\n return pluralize(snakeCase);\n}\n\n/**\n * Formats data for JavaScript by converting date strings to Date objects\n * and snake_case column names to camelCase properties\n *\n * Generic over the input row type `T` so callers preserve their (typically\n * loose) row typing across the hydration boundary — this matches the historic\n * behavior where a `Record<string, any>` / `any` row produced an equally loose\n * result. Keys are re-cased and values are type-converted at runtime; the\n * return is the same `Record` shape, so it is reasserted as `T` at this DB\n * boundary rather than widened to an unrelated type.\n *\n * @param data - Object with data to format (snake_case column names from DB)\n * @param fields - Optional field definitions to determine types (from fieldsFromClass)\n * @returns Object with properly typed values and camelCase property names for JavaScript\n */\nexport function formatDataJs<\n T extends Record<string, unknown> = Record<string, unknown>,\n>(data: T, fields?: Record<string, { type?: string }>): T {\n const normalizedData: Record<string, unknown> = {};\n\n if (process.env.DEBUG_STI) {\n logger.debug('[formatDataJs] Input data', {\n hasMetaData: !!data._meta_data,\n metaType: data._meta_type,\n keys: Object.keys(data),\n });\n }\n\n // STI: If _meta_data exists, merge it into a FRESH object first so meta\n // fields are available during formatting. This must never mutate the caller's\n // `data` argument — `formatDataJs` is a public export and external callers may\n // pass shared objects that would otherwise get silent field injection (#1378).\n let mergedData: Record<string, unknown> = data;\n if (data._meta_data) {\n // `_meta_data` is the STI meta column: a JSON string from the DB or an\n // already-parsed object. Either way it is an object of meta fields; type it\n // as such at this boundary so the merge/key-walk below stays sound.\n const metaData: Record<string, unknown> =\n typeof data._meta_data === 'string'\n ? JSON.parse(data._meta_data)\n : (data._meta_data as Record<string, unknown>);\n\n if (process.env.DEBUG_STI) {\n logger.debug('[formatDataJs] Merging _meta_data', {\n metaDataKeys: Object.keys(metaData),\n metaData,\n });\n }\n\n // Merge meta fields into a copy (will be formatted below). Spreading\n // `metaData` last preserves the original precedence of Object.assign.\n mergedData = { ...data, ...metaData };\n\n if (process.env.DEBUG_STI) {\n logger.debug('[formatDataJs] After merge, data keys', {\n keys: Object.keys(mergedData),\n });\n }\n }\n\n for (const [key, value] of Object.entries(mergedData)) {\n // Preserve keys with leading underscore (e.g., _meta_type, _meta_data)\n // These are special STI/framework fields that should not be camelCased\n const camelKey = key.startsWith('_') ? key : toCamelCase(key);\n\n // Determine the actual output key based on what's in fields\n // If the original key (snake_case) is in fields, use it; otherwise use camelCase\n // This supports both conventions (e.g., publish_date vs publishDate)\n let outputKey = camelKey;\n if (fields && key in fields && !(camelKey in fields)) {\n // Original key exists in fields but camelCase doesn't - use original (snake_case)\n outputKey = key;\n }\n\n // Get field type from fields, trying both key variants\n const fieldDef = fields?.[outputKey] ?? fields?.[camelKey] ?? fields?.[key];\n const fieldType = fieldDef?.type?.toLowerCase();\n\n if (value instanceof Date) {\n normalizedData[outputKey] = value;\n } else if (typeof value === 'string') {\n // Use field definitions if available, otherwise fall back to name patterns\n const isDate = fieldType === 'datetime' || isDateField(key);\n\n if (isDate) {\n const parsedDate = value.trim() ? new Date(value) : null;\n normalizedData[outputKey] =\n parsedDate && !Number.isNaN(parsedDate.getTime())\n ? parsedDate\n : value;\n } else if (fieldType === 'json') {\n // Parse JSON strings back to objects\n try {\n normalizedData[outputKey] = JSON.parse(value);\n } catch {\n // Keep as string if parsing fails\n normalizedData[outputKey] = value;\n }\n } else if (fieldType === 'integer') {\n // Convert string numbers to integers for INTEGER fields\n // SQLite may return \"2.0\" as a string in some cases\n const parsed = Number.parseInt(value, 10);\n normalizedData[outputKey] = Number.isNaN(parsed) ? value : parsed;\n } else if (fieldType === 'real' || fieldType === 'decimal') {\n // Convert string numbers to floats for REAL/DECIMAL fields\n const parsed = Number.parseFloat(value);\n normalizedData[outputKey] = Number.isNaN(parsed) ? value : parsed;\n } else {\n normalizedData[outputKey] = value;\n }\n } else if (typeof value === 'number') {\n if (fieldType === 'boolean') {\n // Convert SQLite integers (0/1) to booleans for boolean fields\n normalizedData[outputKey] = value === 1;\n } else {\n // Pass through numeric values as-is\n // Note: In JavaScript, 2.0 === 2 so no conversion needed\n // Non-integers in INTEGER fields (e.g., 2.9) are kept to surface data issues\n normalizedData[outputKey] = value;\n }\n } else {\n normalizedData[outputKey] = value;\n }\n }\n\n if (process.env.DEBUG_STI) {\n logger.debug('[formatDataJs] Output normalizedData', {\n keys: Object.keys(normalizedData),\n metaType: normalizedData._meta_type,\n });\n }\n\n // Reassert the re-cased/type-converted row as the caller's row type `T`.\n // The output is structurally a `Record<string, unknown>`; this boundary cast\n // preserves the historic loose-in/loose-out hydration contract without `any`.\n return normalizedData as T;\n}\n\n/**\n * Type guard to check if a value is a Field instance\n *\n * @deprecated Field helpers have been removed. This function always returns false.\n * @param value - Value to check\n * @returns Always false (Field class no longer exists)\n */\nexport function isFieldInstance(value: unknown): value is never {\n return false;\n}\n\n/**\n * Formats data for SQL by converting Date objects to ISO strings\n * and camelCase property names to snake_case column names\n *\n * @param data - Object with data to format (camelCase property names from JavaScript)\n * @returns Object with properly formatted values and snake_case column names for SQL\n */\nexport function formatDataSql(data: Record<string, unknown>) {\n const normalizedData: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n // Convert camelCase to snake_case for SQL\n const snakeKey = toSnakeCase(key);\n\n // Field helpers removed - no need to extract values (deprecated code removed)\n if (value instanceof Date) {\n normalizedData[snakeKey] = value.toISOString(); // Postgres accepts ISO format with timezone\n } else {\n normalizedData[snakeKey] = value;\n }\n }\n return normalizedData;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vite-plugin/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vite-plugin/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EACV,qBAAqB,EAEtB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,MAAM,EAAiC,MAAM,MAAM,CAAC;AAgBlE,YAAY,EACV,wBAAwB,EACxB,gBAAgB,GACjB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EACL,6BAA6B,EAC7B,uBAAuB,EACvB,iBAAiB,EACjB,mBAAmB,EACnB,4BAA4B,GAC7B,MAAM,0BAA0B,CAAC;AAalC,MAAM,WAAW,iBAAiB;IAChC,0CAA0C;IAC1C,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,0BAA0B;IAC1B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,2CAA2C;IAC3C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,6BAA6B;IAC7B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,qBAAqB;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,sCAAsC;IACtC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,kFAAkF;IAClF,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,4EAA4E;IAC5E,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,wEAAwE;IACxE,IAAI,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;IACpC;;OAEG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,8CAA8C;IAC9C,SAAS,CAAC,EAAE;QACV,yDAAyD;QACzD,OAAO,EAAE,OAAO,CAAC;QACjB,wEAAwE;QACxE,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,qEAAqE;QACrE,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,mEAAmE;QACnE,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,mDAAmD;QACnD,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB;;;;WAIG;QACH,WAAW,CAAC,EAAE,OAAO,CAAC;KACvB,CAAC;IACF,mEAAmE;IACnE,SAAS,CAAC,EAAE,qBAAqB,GAAG,KAAK,CAAC;IAC1C;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC;AAsBD,wBAAgB,UAAU,CAAC,OAAO,GAAE,iBAAsB,GAAG,MAAM,CAw2BlE"}
|
|
@@ -169,11 +169,13 @@ function smrtPlugin(options = {}) {
|
|
|
169
169
|
function preserveKnowledgeGeneratedAt(outputPath, nextKnowledge) {
|
|
170
170
|
if (!existsSync(outputPath)) return nextKnowledge;
|
|
171
171
|
try {
|
|
172
|
-
const current = JSON.parse(
|
|
172
|
+
const current = JSON.parse(
|
|
173
|
+
readFileSync(outputPath, "utf8")
|
|
174
|
+
);
|
|
173
175
|
if (semanticKnowledgeJson(current) === semanticKnowledgeJson(nextKnowledge)) {
|
|
174
176
|
return {
|
|
175
177
|
...nextKnowledge,
|
|
176
|
-
generatedAt: current.generatedAt
|
|
178
|
+
generatedAt: current.generatedAt ?? nextKnowledge.generatedAt
|
|
177
179
|
};
|
|
178
180
|
}
|
|
179
181
|
} catch {
|
|
@@ -494,13 +496,15 @@ function smrtPlugin(options = {}) {
|
|
|
494
496
|
}
|
|
495
497
|
},
|
|
496
498
|
async closeBundle() {
|
|
497
|
-
if (!manifest || !config
|
|
499
|
+
if (!manifest || !config?.build?.lib) {
|
|
498
500
|
return;
|
|
499
501
|
}
|
|
500
502
|
try {
|
|
501
503
|
const { writeFileSync, mkdirSync } = await import("node:fs");
|
|
502
504
|
const { resolve, dirname: dirname2 } = await import("node:path");
|
|
503
|
-
const
|
|
505
|
+
const rollupOutput = config.build?.rollupOptions?.output;
|
|
506
|
+
const rollupOutputDir = Array.isArray(rollupOutput) ? void 0 : rollupOutput?.dir;
|
|
507
|
+
const outDir = rollupOutputDir || config.build?.outDir || "dist";
|
|
504
508
|
const manifestPath = resolve(projectRoot, outDir, "manifest.json");
|
|
505
509
|
const knowledgePath = resolve(
|
|
506
510
|
projectRoot,
|
|
@@ -1354,9 +1358,7 @@ async function generateSchemaModule(manifest) {
|
|
|
1354
1358
|
packageName: manifest.packageName || "unknown",
|
|
1355
1359
|
schemas,
|
|
1356
1360
|
dependencies: Array.from(
|
|
1357
|
-
new Set(
|
|
1358
|
-
Object.values(schemas).flatMap((s) => s.dependencies || [])
|
|
1359
|
-
)
|
|
1361
|
+
new Set(Object.values(schemas).flatMap((s) => s.dependencies || []))
|
|
1360
1362
|
)
|
|
1361
1363
|
};
|
|
1362
1364
|
return `// Auto-generated schema manifest from SMRT objects
|