@atomic-ehr/fhirpath 0.0.1-canary.1825db0.20250725140030

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 (59) hide show
  1. package/README.md +400 -0
  2. package/dist/index.d.ts +398 -0
  3. package/dist/index.js +8372 -0
  4. package/dist/index.js.map +1 -0
  5. package/package.json +51 -0
  6. package/src/analyzer/analyzer.ts +486 -0
  7. package/src/analyzer/model-provider.ts +244 -0
  8. package/src/analyzer/schemas/index.ts +2 -0
  9. package/src/analyzer/schemas/types.ts +40 -0
  10. package/src/analyzer/types.ts +142 -0
  11. package/src/api/builder.ts +155 -0
  12. package/src/api/errors.ts +134 -0
  13. package/src/api/expression.ts +156 -0
  14. package/src/api/index.ts +70 -0
  15. package/src/api/inspect.ts +96 -0
  16. package/src/api/registry.ts +128 -0
  17. package/src/api/types.ts +210 -0
  18. package/src/compiler/compiler.ts +546 -0
  19. package/src/compiler/index.ts +2 -0
  20. package/src/compiler/prototype-context-adapter.ts +99 -0
  21. package/src/compiler/types.ts +24 -0
  22. package/src/index.ts +76 -0
  23. package/src/interpreter/README.md +78 -0
  24. package/src/interpreter/interpreter.ts +463 -0
  25. package/src/interpreter/types.ts +108 -0
  26. package/src/lexer/char-tables.ts +37 -0
  27. package/src/lexer/errors.ts +31 -0
  28. package/src/lexer/index.ts +5 -0
  29. package/src/lexer/lexer.ts +745 -0
  30. package/src/lexer/token.ts +104 -0
  31. package/src/parser/ast.ts +123 -0
  32. package/src/parser/index.ts +3 -0
  33. package/src/parser/parser.ts +701 -0
  34. package/src/parser/pprint.ts +169 -0
  35. package/src/registry/default-analyzers.ts +257 -0
  36. package/src/registry/default-compilers.ts +31 -0
  37. package/src/registry/index.ts +94 -0
  38. package/src/registry/operations/arithmetic.ts +506 -0
  39. package/src/registry/operations/collection.ts +425 -0
  40. package/src/registry/operations/comparison.ts +432 -0
  41. package/src/registry/operations/existence.ts +703 -0
  42. package/src/registry/operations/filtering.ts +358 -0
  43. package/src/registry/operations/literals.ts +341 -0
  44. package/src/registry/operations/logical.ts +439 -0
  45. package/src/registry/operations/math.ts +128 -0
  46. package/src/registry/operations/membership.ts +132 -0
  47. package/src/registry/operations/string.ts +507 -0
  48. package/src/registry/operations/subsetting.ts +174 -0
  49. package/src/registry/operations/type-checking.ts +162 -0
  50. package/src/registry/operations/type-conversion.ts +404 -0
  51. package/src/registry/operations/type-operators.ts +308 -0
  52. package/src/registry/operations/utility.ts +644 -0
  53. package/src/registry/registry.ts +146 -0
  54. package/src/registry/types.ts +161 -0
  55. package/src/registry/utils/evaluation-helpers.ts +93 -0
  56. package/src/registry/utils/index.ts +3 -0
  57. package/src/registry/utils/type-system.ts +173 -0
  58. package/src/runtime/context.ts +158 -0
  59. package/src/runtime/debug-context.ts +135 -0
@@ -0,0 +1,134 @@
1
+ import type { Location } from './types';
2
+
3
+ export enum ErrorCode {
4
+ // Parse errors
5
+ PARSE_ERROR = 'PARSE_ERROR',
6
+ SYNTAX_ERROR = 'SYNTAX_ERROR',
7
+ UNEXPECTED_TOKEN = 'UNEXPECTED_TOKEN',
8
+ UNTERMINATED_STRING = 'UNTERMINATED_STRING',
9
+ INVALID_ESCAPE = 'INVALID_ESCAPE',
10
+
11
+ // Type errors
12
+ TYPE_ERROR = 'TYPE_ERROR',
13
+ TYPE_MISMATCH = 'TYPE_MISMATCH',
14
+ UNKNOWN_TYPE = 'UNKNOWN_TYPE',
15
+ CARDINALITY_ERROR = 'CARDINALITY_ERROR',
16
+
17
+ // Runtime errors
18
+ RUNTIME_ERROR = 'RUNTIME_ERROR',
19
+ UNDEFINED_VARIABLE = 'UNDEFINED_VARIABLE',
20
+ UNDEFINED_FUNCTION = 'UNDEFINED_FUNCTION',
21
+ INVALID_ARGUMENT = 'INVALID_ARGUMENT',
22
+ DIVISION_BY_ZERO = 'DIVISION_BY_ZERO',
23
+
24
+ // Analysis errors
25
+ ANALYSIS_ERROR = 'ANALYSIS_ERROR',
26
+ UNREACHABLE_CODE = 'UNREACHABLE_CODE',
27
+ AMBIGUOUS_TYPE = 'AMBIGUOUS_TYPE',
28
+ }
29
+
30
+ export class FHIRPathError extends Error {
31
+ constructor(
32
+ message: string,
33
+ public code: ErrorCode,
34
+ public location?: Location,
35
+ public expression?: string
36
+ ) {
37
+ super(message);
38
+ this.name = 'FHIRPathError';
39
+
40
+ // Ensure proper prototype chain
41
+ Object.setPrototypeOf(this, FHIRPathError.prototype);
42
+ }
43
+
44
+ override toString(): string {
45
+ let result = `${this.name}: ${this.message}`;
46
+
47
+ if (this.expression && this.location) {
48
+ result += `\n\n${this.expression}`;
49
+ if (this.location.offset >= 0 && this.location.length > 0) {
50
+ const indent = ' '.repeat(this.location.offset);
51
+ const marker = '^'.repeat(this.location.length);
52
+ result += `\n${indent}${marker}`;
53
+ }
54
+ }
55
+
56
+ if (this.location) {
57
+ result += `\n\nAt line ${this.location.line}, column ${this.location.column}`;
58
+ }
59
+
60
+ return result;
61
+ }
62
+ }
63
+
64
+ // Error factory functions
65
+ export function parseError(
66
+ message: string,
67
+ location?: Location,
68
+ expression?: string
69
+ ): FHIRPathError {
70
+ return new FHIRPathError(message, ErrorCode.PARSE_ERROR, location, expression);
71
+ }
72
+
73
+ export function syntaxError(
74
+ message: string,
75
+ location?: Location,
76
+ expression?: string
77
+ ): FHIRPathError {
78
+ return new FHIRPathError(message, ErrorCode.SYNTAX_ERROR, location, expression);
79
+ }
80
+
81
+ export function typeError(
82
+ message: string,
83
+ location?: Location,
84
+ expression?: string
85
+ ): FHIRPathError {
86
+ return new FHIRPathError(message, ErrorCode.TYPE_ERROR, location, expression);
87
+ }
88
+
89
+ export function runtimeError(
90
+ message: string,
91
+ location?: Location,
92
+ expression?: string
93
+ ): FHIRPathError {
94
+ return new FHIRPathError(message, ErrorCode.RUNTIME_ERROR, location, expression);
95
+ }
96
+
97
+ export function undefinedVariable(
98
+ name: string,
99
+ location?: Location,
100
+ expression?: string
101
+ ): FHIRPathError {
102
+ return new FHIRPathError(
103
+ `Undefined variable: ${name}`,
104
+ ErrorCode.UNDEFINED_VARIABLE,
105
+ location,
106
+ expression
107
+ );
108
+ }
109
+
110
+ export function undefinedFunction(
111
+ name: string,
112
+ location?: Location,
113
+ expression?: string
114
+ ): FHIRPathError {
115
+ return new FHIRPathError(
116
+ `Undefined function: ${name}`,
117
+ ErrorCode.UNDEFINED_FUNCTION,
118
+ location,
119
+ expression
120
+ );
121
+ }
122
+
123
+ export function invalidArgument(
124
+ message: string,
125
+ location?: Location,
126
+ expression?: string
127
+ ): FHIRPathError {
128
+ return new FHIRPathError(
129
+ `Invalid argument: ${message}`,
130
+ ErrorCode.INVALID_ARGUMENT,
131
+ location,
132
+ expression
133
+ );
134
+ }
@@ -0,0 +1,156 @@
1
+ import type { ASTNode } from '../parser/ast';
2
+ import type {
3
+ FHIRPathExpression as IFHIRPathExpression,
4
+ CompiledExpression,
5
+ EvaluationContext,
6
+ CompileOptions,
7
+ AnalyzeOptions,
8
+ AnalysisResult
9
+ } from './types';
10
+ import { Interpreter } from '../interpreter/interpreter';
11
+ import { Compiler } from '../compiler/compiler';
12
+ import { analyzeFHIRPath } from '../analyzer/analyzer';
13
+ import { RuntimeContextManager } from '../runtime/context';
14
+ import { pprint } from '../parser/pprint';
15
+
16
+ export class FHIRPathExpression implements IFHIRPathExpression {
17
+ constructor(
18
+ public readonly ast: ASTNode,
19
+ private readonly originalExpression: string
20
+ ) {}
21
+
22
+ evaluate(input?: any, context?: EvaluationContext): any[] {
23
+ const interpreter = new Interpreter();
24
+ const inputArray = input === undefined ? [] : Array.isArray(input) ? input : [input];
25
+ const ctx = this.createContext(context, inputArray);
26
+
27
+ const result = interpreter.evaluate(this.ast, inputArray, ctx);
28
+
29
+ return result.value;
30
+ }
31
+
32
+ compile(options?: CompileOptions): CompiledExpression {
33
+ const compiler = new Compiler();
34
+ const compiled = compiler.compile(this.ast);
35
+
36
+ // Create the compiled function
37
+ const fn = (input?: any, context?: EvaluationContext): any[] => {
38
+ const inputArray = input === undefined ? [] : Array.isArray(input) ? input : [input];
39
+ const ctx = this.createContext(context, inputArray);
40
+
41
+ return compiled.fn(ctx);
42
+ };
43
+
44
+ // Add source property
45
+ Object.defineProperty(fn, 'source', {
46
+ value: options?.sourceMap ? this.originalExpression : this.toString(),
47
+ writable: false,
48
+ enumerable: true
49
+ });
50
+
51
+ return fn as CompiledExpression;
52
+ }
53
+
54
+ analyze(options?: AnalyzeOptions): AnalysisResult {
55
+ try {
56
+ // Create analyzer-compatible model provider
57
+ const analyzerModelProvider = options?.modelProvider ? {
58
+ resolveType: options.modelProvider.resolveType.bind(options.modelProvider),
59
+ getPropertyType: (type: any, propName: string) => {
60
+ const props = options.modelProvider!.getProperties(options.modelProvider!.getTypeName ? options.modelProvider!.getTypeName(type) : String(type));
61
+ const prop = props.find(p => p.name === propName);
62
+ if (!prop) return undefined;
63
+ return {
64
+ type: options.modelProvider!.resolveType(prop.type),
65
+ isSingleton: !prop.isCollection
66
+ };
67
+ },
68
+ isAssignable: (from: any, to: any) => {
69
+ const hierarchy = options.modelProvider!.getTypeHierarchy(options.modelProvider!.getTypeName ? options.modelProvider!.getTypeName(from) : String(from));
70
+ const toName = options.modelProvider!.getTypeName ? options.modelProvider!.getTypeName(to) : String(to);
71
+ return hierarchy.includes(toName);
72
+ },
73
+ getTypeName: (type: any) => String(type)
74
+ } : {
75
+ resolveType: () => undefined,
76
+ getPropertyType: () => undefined,
77
+ isAssignable: () => false,
78
+ getTypeName: () => 'unknown'
79
+ };
80
+
81
+ const result = analyzeFHIRPath(this.ast, analyzerModelProvider);
82
+
83
+ return {
84
+ type: result.resultType || { kind: 'unknown' },
85
+ isSingleton: result.resultIsSingleton || false,
86
+ errors: result.diagnostics.filter(d => d.severity === 'error').map(d => ({
87
+ message: d.message,
88
+ location: d.position ? {
89
+ line: d.position.line,
90
+ column: d.position.column,
91
+ offset: d.position.offset,
92
+ length: 1 // We don't have length info in Position
93
+ } : undefined,
94
+ code: 'ANALYSIS_ERROR'
95
+ })),
96
+ warnings: result.diagnostics.filter(d => d.severity === 'warning').map(d => ({
97
+ message: d.message,
98
+ location: d.position ? {
99
+ line: d.position.line,
100
+ column: d.position.column,
101
+ offset: d.position.offset,
102
+ length: 1
103
+ } : undefined,
104
+ code: 'ANALYSIS_WARNING'
105
+ }))
106
+ };
107
+ } catch (error) {
108
+ return {
109
+ type: { kind: 'unknown' },
110
+ isSingleton: false,
111
+ errors: [{
112
+ message: error instanceof Error ? error.message : String(error),
113
+ code: 'ANALYSIS_ERROR'
114
+ }],
115
+ warnings: []
116
+ };
117
+ }
118
+ }
119
+
120
+ toString(): string {
121
+ return pprint(this.ast);
122
+ }
123
+
124
+ private createContext(evalContext?: EvaluationContext, input: any[] = []) {
125
+ let ctx = RuntimeContextManager.create(input);
126
+
127
+ if (evalContext?.variables) {
128
+ for (const [name, value] of Object.entries(evalContext.variables)) {
129
+ ctx = RuntimeContextManager.setVariable(ctx, name, Array.isArray(value) ? value : [value]);
130
+ }
131
+ }
132
+
133
+ if (evalContext?.environment) {
134
+ // Environment variables are stored as regular variables with appropriate prefix
135
+ for (const [name, value] of Object.entries(evalContext.environment)) {
136
+ if (name.startsWith('$')) {
137
+ // Special environment variables
138
+ const varName = name.substring(1); // Remove $ prefix
139
+ ctx = RuntimeContextManager.setSpecialVariable(ctx, varName, Array.isArray(value) ? value : [value]);
140
+ } else {
141
+ // Regular environment variables
142
+ ctx = RuntimeContextManager.setVariable(ctx, name, Array.isArray(value) ? value : [value]);
143
+ }
144
+ }
145
+ }
146
+
147
+ // Store custom functions in context for interpreter to access
148
+ if (evalContext?.customFunctions) {
149
+ // TODO: Custom functions need to be implemented through the registry
150
+ // For now, we'll store them in env
151
+ (ctx as any).customFunctions = evalContext.customFunctions;
152
+ }
153
+
154
+ return ctx;
155
+ }
156
+ }
@@ -0,0 +1,70 @@
1
+ import type {
2
+ FHIRPathExpression,
3
+ CompiledExpression,
4
+ EvaluationContext,
5
+ CompileOptions,
6
+ AnalyzeOptions,
7
+ AnalysisResult,
8
+ InspectResult,
9
+ InspectOptions
10
+ } from './types';
11
+ import { FHIRPathParser } from '../parser/parser';
12
+ import { FHIRPathExpression as Expression } from './expression';
13
+ import { FHIRPathError, parseError } from './errors';
14
+ import { publicRegistry } from './registry';
15
+ import { inspect as inspectImpl } from './inspect';
16
+
17
+ // Parse expression into AST
18
+ export function parse(expression: string): FHIRPathExpression {
19
+ try {
20
+ const parser = new FHIRPathParser(expression);
21
+ const ast = parser.parse();
22
+ return new Expression(ast, expression);
23
+ } catch (error) {
24
+ if (error instanceof Error) {
25
+ throw parseError(error.message, undefined, expression);
26
+ }
27
+ throw parseError(String(error), undefined, expression);
28
+ }
29
+ }
30
+
31
+ // Evaluate expression directly
32
+ export function evaluate(
33
+ expression: string | FHIRPathExpression,
34
+ input?: any,
35
+ context?: EvaluationContext
36
+ ): any[] {
37
+ const expr = typeof expression === 'string' ? parse(expression) : expression;
38
+ return expr.evaluate(input, context);
39
+ }
40
+
41
+ // Compile to optimized function
42
+ export function compile(
43
+ expression: string | FHIRPathExpression,
44
+ options?: CompileOptions
45
+ ): CompiledExpression {
46
+ const expr = typeof expression === 'string' ? parse(expression) : expression;
47
+ return expr.compile(options);
48
+ }
49
+
50
+ // Analyze expression for validation
51
+ export function analyze(
52
+ expression: string | FHIRPathExpression,
53
+ options?: AnalyzeOptions
54
+ ): AnalysisResult {
55
+ const expr = typeof expression === 'string' ? parse(expression) : expression;
56
+ return expr.analyze(options);
57
+ }
58
+
59
+ // Inspect expression with debugging information
60
+ export function inspect(
61
+ expression: string | FHIRPathExpression,
62
+ input?: any,
63
+ context?: EvaluationContext,
64
+ options?: InspectOptions
65
+ ): InspectResult {
66
+ return inspectImpl(expression, input, context, options);
67
+ }
68
+
69
+ // Default registry instance
70
+ export const registry = publicRegistry;
@@ -0,0 +1,96 @@
1
+ import type {
2
+ FHIRPathExpression,
3
+ EvaluationContext,
4
+ InspectResult,
5
+ InspectOptions,
6
+ ErrorInfo,
7
+ WarningInfo
8
+ } from './types';
9
+ import { parse } from './index';
10
+ import { Interpreter } from '../interpreter/interpreter';
11
+ import { RuntimeContextManager } from '../runtime/context';
12
+ import { createDebugContext, isDebugContext } from '../runtime/debug-context';
13
+ import { FHIRPathError } from './errors';
14
+
15
+ /**
16
+ * Inspect a FHIRPath expression, providing rich debugging information
17
+ * including traces, AST, execution time, and optionally step-by-step evaluation
18
+ */
19
+ export function inspect(
20
+ expression: string | FHIRPathExpression,
21
+ input?: any,
22
+ context?: EvaluationContext,
23
+ options?: InspectOptions
24
+ ): InspectResult {
25
+ const startTime = performance.now();
26
+
27
+ // Parse if string
28
+ const expr = typeof expression === 'string' ? parse(expression) : expression;
29
+ const exprString = typeof expression === 'string' ? expression : expr.toString();
30
+
31
+ // Prepare input (default to empty array)
32
+ const inputValue = input === undefined ? [] : Array.isArray(input) ? input : [input];
33
+
34
+ // Create runtime context
35
+ const runtimeContext = RuntimeContextManager.create(inputValue, context?.variables);
36
+
37
+ // Convert to debug context
38
+ const debugContext = createDebugContext(runtimeContext, options);
39
+
40
+ // Initialize result
41
+ const errors: ErrorInfo[] = [];
42
+ const warnings: WarningInfo[] = [];
43
+ let result: any[] = [];
44
+
45
+ try {
46
+ // Create interpreter
47
+ const interpreter = new Interpreter();
48
+
49
+ // Evaluate with debug context
50
+ const evalResult = interpreter.evaluate(expr.ast, inputValue, debugContext);
51
+ result = evalResult.value;
52
+ } catch (error) {
53
+ // Capture error information
54
+ const errorInfo: ErrorInfo = {
55
+ message: error instanceof Error ? error.message : String(error),
56
+ type: error instanceof FHIRPathError ? error.name : 'Error'
57
+ };
58
+
59
+ if (error instanceof Error && error.stack) {
60
+ errorInfo.stack = error.stack;
61
+ }
62
+
63
+ errors.push(errorInfo);
64
+
65
+ // Re-throw if it's a critical error
66
+ if (error instanceof FHIRPathError && error.name === 'ParseError') {
67
+ throw error;
68
+ }
69
+ }
70
+
71
+ const executionTime = performance.now() - startTime;
72
+
73
+ // Build result
74
+ const inspectResult: InspectResult = {
75
+ result,
76
+ expression: exprString,
77
+ ast: expr.ast,
78
+ executionTime,
79
+ traces: debugContext.traces
80
+ };
81
+
82
+ // Add optional fields
83
+ if (debugContext.steps && debugContext.steps.length > 0) {
84
+ inspectResult.evaluationSteps = debugContext.steps;
85
+ }
86
+
87
+ if (errors.length > 0) {
88
+ inspectResult.errors = errors;
89
+ }
90
+
91
+ if (warnings.length > 0) {
92
+ inspectResult.warnings = warnings;
93
+ }
94
+
95
+ return inspectResult;
96
+ }
@@ -0,0 +1,128 @@
1
+ import type {
2
+ RegistryAPI,
3
+ OperationMetadata,
4
+ OperationInfo
5
+ } from './types';
6
+ import { Registry } from '../registry/registry';
7
+ import type { Operation } from '../registry/types';
8
+
9
+ export class PublicRegistryAPI implements RegistryAPI {
10
+ listFunctions(): OperationMetadata[] {
11
+ return Registry.getAllFunctions().map(op => this.toMetadata(op));
12
+ }
13
+
14
+ listOperators(): OperationMetadata[] {
15
+ const operators = [
16
+ ...Registry.getOperatorsByForm('infix'),
17
+ ...Registry.getOperatorsByForm('prefix'),
18
+ ...Registry.getOperatorsByForm('postfix')
19
+ ];
20
+ return operators.map(op => this.toMetadata(op));
21
+ }
22
+
23
+ listAllOperations(): OperationMetadata[] {
24
+ return Registry.getAllOperations().map(op => this.toMetadata(op));
25
+ }
26
+
27
+ hasOperation(name: string): boolean {
28
+ return Registry.get(name) !== undefined;
29
+ }
30
+
31
+ hasFunction(name: string): boolean {
32
+ const op = Registry.get(name);
33
+ return op !== undefined && op.kind === 'function';
34
+ }
35
+
36
+ hasOperator(symbol: string): boolean {
37
+ const op = Registry.get(symbol);
38
+ return op !== undefined && op.kind === 'operator';
39
+ }
40
+
41
+ getOperationInfo(name: string): OperationInfo | undefined {
42
+ const op = Registry.get(name);
43
+ if (!op) return undefined;
44
+
45
+ return this.toOperationInfo(op);
46
+ }
47
+
48
+ canRegisterFunction(name: string): boolean {
49
+ // Check if name is valid and not already taken
50
+ if (!name || typeof name !== 'string') return false;
51
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) return false;
52
+
53
+ // Check if already exists as built-in
54
+ return !this.hasOperation(name);
55
+ }
56
+
57
+ private toMetadata(op: Operation): OperationMetadata {
58
+ return {
59
+ name: op.name,
60
+ kind: op.kind,
61
+ syntax: {
62
+ notation: op.syntax.notation
63
+ }
64
+ };
65
+ }
66
+
67
+ private toOperationInfo(op: Operation): OperationInfo {
68
+ const info: OperationInfo = {
69
+ ...this.toMetadata(op),
70
+ signature: {}
71
+ };
72
+
73
+ // Add input signature for functions
74
+ if (op.kind === 'function' && op.signature.input) {
75
+ info.signature.input = {
76
+ types: this.extractTypes(op.signature.input.types),
77
+ cardinality: op.signature.input.cardinality
78
+ };
79
+ }
80
+
81
+ // Add parameters
82
+ if ('parameters' in op.signature && op.signature.parameters && op.signature.parameters.length > 0) {
83
+ info.signature.parameters = op.signature.parameters.map((param: any) => ({
84
+ name: param.name,
85
+ types: this.extractTypes(param.types),
86
+ cardinality: param.cardinality,
87
+ optional: param.optional
88
+ }));
89
+ }
90
+
91
+ // Add output signature
92
+ if (op.signature.output) {
93
+ info.signature.output = {
94
+ type: typeof op.signature.output.type === 'string'
95
+ ? op.signature.output.type
96
+ : 'dynamic',
97
+ cardinality: typeof op.signature.output.cardinality === 'string'
98
+ ? (op.signature.output.cardinality === 'all-singleton' ? 'singleton' : op.signature.output.cardinality as any)
99
+ : undefined
100
+ };
101
+ }
102
+
103
+ // Add description if available (for now, operations don't have description in the type)
104
+ // TODO: Add description to Operation type if needed
105
+
106
+ // Add examples if available (for now, operations don't have examples in the type)
107
+ // TODO: Add examples to Operation type if needed
108
+
109
+ return info;
110
+ }
111
+
112
+ private extractTypes(constraint?: any): string[] | undefined {
113
+ if (!constraint) return undefined;
114
+
115
+ if (constraint.kind === 'primitive' || constraint.kind === 'class') {
116
+ return constraint.types;
117
+ } else if (constraint.kind === 'union' && constraint.types) {
118
+ return constraint.types;
119
+ } else if (constraint.kind === 'any') {
120
+ return ['any'];
121
+ }
122
+
123
+ return undefined;
124
+ }
125
+ }
126
+
127
+ // Singleton instance
128
+ export const publicRegistry = new PublicRegistryAPI();