@@ -58,9 +58,109 @@ export interface FunctionTable {
58
58
[ functionName : string ] : FunctionSignature ;
59
59
}
60
60
61
- export class Runtime {
61
+ // Built-in function names for TypeScript 5.x template literal type checking
62
+ export type BuiltInFunctionNames =
63
+ | 'abs'
64
+ | 'avg'
65
+ | 'ceil'
66
+ | 'contains'
67
+ | 'ends_with'
68
+ | 'find_first'
69
+ | 'find_last'
70
+ | 'floor'
71
+ | 'from_items'
72
+ | 'group_by'
73
+ | 'items'
74
+ | 'join'
75
+ | 'keys'
76
+ | 'length'
77
+ | 'lower'
78
+ | 'map'
79
+ | 'max'
80
+ | 'max_by'
81
+ | 'merge'
82
+ | 'min'
83
+ | 'min_by'
84
+ | 'not_null'
85
+ | 'pad_left'
86
+ | 'pad_right'
87
+ | 'replace'
88
+ | 'reverse'
89
+ | 'sort'
90
+ | 'sort_by'
91
+ | 'split'
92
+ | 'starts_with'
93
+ | 'sum'
94
+ | 'to_array'
95
+ | 'to_number'
96
+ | 'to_string'
97
+ | 'type'
98
+ | 'upper'
99
+ | 'values'
100
+ | 'zip' ;
101
+
102
+ // Registration options for enhanced registerFunction behavior
103
+ export interface RegisterOptions {
104
+ /**
105
+ * Allow overriding existing functions. Default: false
106
+ * When true, replaces existing function without error
107
+ * When false, throws error if function already exists (backward compatible)
108
+ */
109
+ override ?: boolean ;
110
+ /**
111
+ * Emit warning when overriding existing functions. Default: false
112
+ * Only applies when override is true
113
+ */
114
+ warn ?: boolean ;
115
+ }
116
+
117
+ // Registration result for better error handling and introspection
118
+ export type RegistrationResult =
119
+ | { success : true ; message ?: string }
120
+ | { success : false ; reason : 'already-exists' | 'invalid-signature' | 'invalid-name' ; message : string } ;
121
+
122
+ // Enhanced function registry interface for state management
123
+ export interface FunctionRegistry {
124
+ /**
125
+ * Register a new function with optional override behavior
126
+ */
127
+ register < T extends string > (
128
+ name : T extends BuiltInFunctionNames ? never : T ,
129
+ func : RuntimeFunction < ( JSONValue | ExpressionNode ) [ ] , JSONValue > ,
130
+ signature : InputSignature [ ] ,
131
+ options ?: RegisterOptions ,
132
+ ) : RegistrationResult ;
133
+
134
+ /**
135
+ * Unregister a custom function (built-in functions cannot be unregistered)
136
+ */
137
+ unregister < T extends string > ( name : T extends BuiltInFunctionNames ? never : T ) : boolean ;
138
+
139
+ /**
140
+ * Check if a function is registered
141
+ */
142
+ isRegistered ( name : string ) : boolean ;
143
+
144
+ /**
145
+ * Get list of all registered function names
146
+ */
147
+ getRegistered ( ) : string [ ] ;
148
+
149
+ /**
150
+ * Get list of custom (non-built-in) function names
151
+ */
152
+ getCustomFunctions ( ) : string [ ] ;
153
+
154
+ /**
155
+ * Clear all custom functions (built-in functions remain)
156
+ */
157
+ clearCustomFunctions ( ) : void ;
158
+ }
159
+
160
+ export class Runtime implements FunctionRegistry {
62
161
_interpreter : TreeInterpreter ;
63
162
_functionTable : FunctionTable ;
163
+ private _customFunctions : Set < string > = new Set ( ) ;
64
164
TYPE_NAME_TABLE : { [ InputArgument : number ] : string } = {
65
165
[ InputArgument . TYPE_NUMBER ] : 'number' ,
66
166
[ InputArgument . TYPE_ANY ] : 'any' ,
@@ -81,18 +181,139 @@ export class Runtime {
81
181
this . _functionTable = this . functionTable ;
82
182
}
83
183
184
+ /**
185
+ * Enhanced registerFunction with backward compatibility and new options
186
+ * @deprecated Use register() method for enhanced functionality
187
+ */
84
188
registerFunction (
85
189
name : string ,
86
190
customFunction : RuntimeFunction < ( JSONValue | ExpressionNode ) [ ] , JSONValue > ,
87
191
signature : InputSignature [ ] ,
192
+ options ?: RegisterOptions ,
88
193
) : void {
89
- if ( name in this . _functionTable ) {
90
- throw new Error ( `Function already defined: ${ name } ()` ) ;
194
+ // For backward compatibility, we bypass the type checking here
195
+ // The register method will still validate the function name at runtime
196
+ const result = this . _registerInternal ( name , customFunction , signature , options ) ;
197
+ if ( ! result . success ) {
198
+ throw new Error ( result . message ) ;
91
199
}
200
+ }
201
+
202
+ /**
203
+ * Internal registration method that bypasses TypeScript type checking
204
+ */
205
+ private _registerInternal (
206
+ name : string ,
207
+ customFunction : RuntimeFunction < ( JSONValue | ExpressionNode ) [ ] , JSONValue > ,
208
+ signature : InputSignature [ ] ,
209
+ options : RegisterOptions = { } ,
210
+ ) : RegistrationResult {
211
+ // Validate function name
212
+ if ( ! name || typeof name !== 'string' || name . trim ( ) === '' ) {
213
+ return {
214
+ success : false ,
215
+ reason : 'invalid-name' ,
216
+ message : 'Function name must be a non-empty string' ,
217
+ } ;
218
+ }
219
+
220
+ // Validate signature
221
+ try {
222
+ this . validateInputSignatures ( name , signature ) ;
223
+ } catch ( error ) {
224
+ return {
225
+ success : false ,
226
+ reason : 'invalid-signature' ,
227
+ message : error instanceof Error ? error . message : 'Invalid function signature' ,
228
+ } ;
229
+ }
230
+
231
+ const { override = false , warn = false } = options ;
232
+ const exists = name in this . _functionTable ;
233
+
234
+ // Handle existing function
235
+ if ( exists && ! override ) {
236
+ return {
237
+ success : false ,
238
+ reason : 'already-exists' ,
239
+ message : `Function already defined: ${ name } (). Use { override: true } to replace it.` ,
240
+ } ;
241
+ }
242
+
243
+ // Emit warning if requested
244
+ if ( exists && override && warn ) {
245
+ console . warn ( `Warning: Overriding existing function: ${ name } ()` ) ;
246
+ }
247
+
248
+ // Register the function
92
249
this . _functionTable [ name ] = {
93
250
_func : customFunction . bind ( this ) ,
94
251
_signature : signature ,
95
252
} ;
253
+
254
+ // Track custom functions (exclude built-ins)
255
+ this . _customFunctions . add ( name ) ;
256
+
257
+ const message = exists
258
+ ? `Function ${ name } () overridden successfully`
259
+ : `Function ${ name } () registered successfully` ;
260
+ return { success : true , message } ;
261
+ }
262
+
263
+ /**
264
+ * Register a new function with enhanced options and type safety
265
+ */
266
+ register < T extends string > (
267
+ name : T extends BuiltInFunctionNames ? never : T ,
268
+ customFunction : RuntimeFunction < ( JSONValue | ExpressionNode ) [ ] , JSONValue > ,
269
+ signature : InputSignature [ ] ,
270
+ options : RegisterOptions = { } ,
271
+ ) : RegistrationResult {
272
+ return this . _registerInternal ( name , customFunction , signature , options ) ;
273
+ }
274
+
275
+ /**
276
+ * Unregister a custom function (built-in functions cannot be unregistered)
277
+ */
278
+ unregister < T extends string > ( name : T extends BuiltInFunctionNames ? never : T ) : boolean {
279
+ if ( ! this . _customFunctions . has ( name ) ) {
280
+ return false ; // Function doesn't exist or is built-in
281
+ }
282
+
283
+ delete this . _functionTable [ name ] ;
284
+ this . _customFunctions . delete ( name ) ;
285
+ return true ;
286
+ }
287
+
288
+ /**
289
+ * Check if a function is registered
290
+ */
291
+ isRegistered ( name : string ) : boolean {
292
+ return name in this . _functionTable ;
293
+ }
294
+
295
+ /**
296
+ * Get list of all registered function names
297
+ */
298
+ getRegistered ( ) : string [ ] {
299
+ return Object . keys ( this . _functionTable ) ;
300
+ }
301
+
302
+ /**
303
+ * Get list of custom (non-built-in) function names
304
+ */
305
+ getCustomFunctions ( ) : string [ ] {
306
+ return Array . from ( this . _customFunctions ) ;
307
+ }
308
+
309
+ /**
310
+ * Clear all custom functions (built-in functions remain)
311
+ */
312
+ clearCustomFunctions ( ) : void {
313
+ for ( const name of this . _customFunctions ) {
314
+ delete this . _functionTable [ name ] ;
315
+ }
316
+ this . _customFunctions . clear ( ) ;
96
317
}
97
318
98
319
callFunction ( name : string , resolvedArgs : ( JSONValue | ExpressionNode ) [ ] ) : JSONValue {
0 commit comments