bunsane 0.1.4 → 0.2.0
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/.claude/settings.local.json +47 -0
- package/.claude/skills/update-memory.md +74 -0
- package/.prettierrc +4 -0
- package/.serena/memories/architectural-decision-no-dependency-injection.md +76 -0
- package/.serena/memories/architecture.md +154 -0
- package/.serena/memories/cache-interface-refactoring-2026-01-24.md +165 -0
- package/.serena/memories/code_style_and_conventions.md +76 -0
- package/.serena/memories/project_overview.md +43 -0
- package/.serena/memories/schema-dsl-plan.md +107 -0
- package/.serena/memories/suggested_commands.md +80 -0
- package/.serena/memories/typescript-compilation-status.md +54 -0
- package/.serena/project.yml +114 -0
- package/TODO.md +1 -7
- package/bun.lock +150 -4
- package/bunfig.toml +10 -0
- package/config/cache.config.ts +77 -0
- package/config/upload.config.ts +4 -5
- package/core/App.ts +870 -123
- package/core/ArcheType.ts +2268 -377
- package/core/BatchLoader.ts +181 -71
- package/core/Config.ts +153 -0
- package/core/Decorators.ts +4 -1
- package/core/Entity.ts +621 -92
- package/core/EntityHookManager.ts +1 -1
- package/core/EntityInterface.ts +3 -1
- package/core/EntityManager.ts +1 -13
- package/core/ErrorHandler.ts +8 -2
- package/core/Logger.ts +9 -0
- package/core/Middleware.ts +34 -0
- package/core/RequestContext.ts +5 -1
- package/core/RequestLoaders.ts +227 -93
- package/core/SchedulerManager.ts +193 -52
- package/core/cache/CacheAnalytics.ts +399 -0
- package/core/cache/CacheFactory.ts +145 -0
- package/core/cache/CacheManager.ts +520 -0
- package/core/cache/CacheProvider.ts +34 -0
- package/core/cache/CacheWarmer.ts +157 -0
- package/core/cache/CompressionUtils.ts +110 -0
- package/core/cache/MemoryCache.ts +251 -0
- package/core/cache/MultiLevelCache.ts +180 -0
- package/core/cache/NoOpCache.ts +53 -0
- package/core/cache/RedisCache.ts +464 -0
- package/core/cache/TTLStrategy.ts +254 -0
- package/core/cache/index.ts +6 -0
- package/core/components/BaseComponent.ts +120 -0
- package/core/{ComponentRegistry.ts → components/ComponentRegistry.ts} +148 -54
- package/core/components/Decorators.ts +88 -0
- package/core/components/Interfaces.ts +7 -0
- package/core/components/index.ts +5 -0
- package/core/decorators/EntityHooks.ts +0 -3
- package/core/decorators/IndexedField.ts +26 -0
- package/core/decorators/ScheduledTask.ts +0 -47
- package/core/events/EntityLifecycleEvents.ts +1 -1
- package/core/health.ts +112 -0
- package/core/metadata/definitions/ArcheType.ts +14 -0
- package/core/metadata/definitions/Component.ts +9 -0
- package/core/metadata/definitions/gqlObject.ts +1 -1
- package/core/metadata/index.ts +42 -1
- package/core/metadata/metadata-storage.ts +28 -2
- package/core/middleware/AccessLog.ts +59 -0
- package/core/middleware/RequestId.ts +38 -0
- package/core/middleware/SecurityHeaders.ts +62 -0
- package/core/middleware/index.ts +3 -0
- package/core/scheduler/DistributedLock.ts +266 -0
- package/core/scheduler/index.ts +15 -0
- package/core/validateEnv.ts +92 -0
- package/database/DatabaseHelper.ts +416 -40
- package/database/IndexingStrategy.ts +342 -0
- package/database/PreparedStatementCache.ts +226 -0
- package/database/index.ts +32 -7
- package/database/sqlHelpers.ts +14 -2
- package/endpoints/archetypes.ts +362 -0
- package/endpoints/components.ts +58 -0
- package/endpoints/entity.ts +80 -0
- package/endpoints/index.ts +27 -0
- package/endpoints/query.ts +93 -0
- package/endpoints/stats.ts +76 -0
- package/endpoints/tables.ts +212 -0
- package/endpoints/types.ts +155 -0
- package/gql/ArchetypeOperations.ts +32 -86
- package/gql/Generator.ts +27 -315
- package/gql/GeneratorV2.ts +37 -0
- package/gql/builders/InputTypeBuilder.ts +99 -0
- package/gql/builders/ResolverBuilder.ts +234 -0
- package/gql/builders/TypeDefBuilder.ts +105 -0
- package/gql/builders/index.ts +3 -0
- package/gql/decorators/Upload.ts +1 -1
- package/gql/depthLimit.ts +85 -0
- package/gql/graph/GraphNode.ts +224 -0
- package/gql/graph/SchemaGraph.ts +278 -0
- package/gql/helpers.ts +8 -2
- package/gql/index.ts +56 -4
- package/gql/middleware.ts +79 -0
- package/gql/orchestration/GraphQLSchemaOrchestrator.ts +241 -0
- package/gql/orchestration/index.ts +1 -0
- package/gql/scanner/ServiceScanner.ts +347 -0
- package/gql/schema/index.ts +458 -0
- package/gql/strategies/TypeGenerationStrategy.ts +329 -0
- package/gql/types.ts +1 -0
- package/gql/utils/TypeSignature.ts +220 -0
- package/gql/utils/index.ts +1 -0
- package/gql/visitors/ArchetypePreprocessorVisitor.ts +80 -0
- package/gql/visitors/DeduplicationVisitor.ts +82 -0
- package/gql/visitors/GraphVisitor.ts +78 -0
- package/gql/visitors/ResolverGeneratorVisitor.ts +122 -0
- package/gql/visitors/SchemaGeneratorVisitor.ts +851 -0
- package/gql/visitors/TypeCollectorVisitor.ts +79 -0
- package/gql/visitors/VisitorComposer.ts +96 -0
- package/gql/visitors/index.ts +7 -0
- package/package.json +59 -37
- package/plugins/index.ts +2 -2
- package/query/CTENode.ts +97 -0
- package/query/ComponentInclusionNode.ts +689 -0
- package/query/FilterBuilder.ts +127 -0
- package/query/FilterBuilderRegistry.ts +202 -0
- package/query/OrNode.ts +517 -0
- package/query/OrQuery.ts +42 -0
- package/query/Query.ts +1022 -0
- package/query/QueryContext.ts +170 -0
- package/query/QueryDAG.ts +122 -0
- package/query/QueryNode.ts +65 -0
- package/query/SourceNode.ts +53 -0
- package/query/builders/FullTextSearchBuilder.ts +236 -0
- package/query/index.ts +21 -0
- package/scheduler/index.ts +40 -8
- package/service/Service.ts +2 -1
- package/service/ServiceRegistry.ts +6 -5
- package/{core/storage → storage}/LocalStorageProvider.ts +2 -2
- package/storage/S3StorageProvider.ts +316 -0
- package/{core/storage → storage}/StorageProvider.ts +7 -3
- package/studio/bun.lock +482 -0
- package/studio/index.html +13 -0
- package/studio/package.json +39 -0
- package/studio/postcss.config.js +6 -0
- package/studio/src/components/DataTable.tsx +211 -0
- package/studio/src/components/Layout.tsx +13 -0
- package/studio/src/components/PageContainer.tsx +9 -0
- package/studio/src/components/PageHeader.tsx +13 -0
- package/studio/src/components/SearchBar.tsx +57 -0
- package/studio/src/components/Sidebar.tsx +294 -0
- package/studio/src/components/ui/button.tsx +56 -0
- package/studio/src/components/ui/checkbox.tsx +26 -0
- package/studio/src/components/ui/input.tsx +25 -0
- package/studio/src/hooks/useDataTable.ts +131 -0
- package/studio/src/index.css +36 -0
- package/studio/src/lib/api.ts +186 -0
- package/studio/src/lib/utils.ts +13 -0
- package/studio/src/main.tsx +17 -0
- package/studio/src/pages/ArcheType.tsx +239 -0
- package/studio/src/pages/Components.tsx +124 -0
- package/studio/src/pages/EntityInspector.tsx +302 -0
- package/studio/src/pages/QueryRunner.tsx +246 -0
- package/studio/src/pages/Table.tsx +94 -0
- package/studio/src/pages/Welcome.tsx +241 -0
- package/studio/src/routes.tsx +45 -0
- package/studio/src/store/archeTypeSettings.ts +30 -0
- package/studio/src/store/studio.ts +65 -0
- package/studio/src/utils/columnHelpers.tsx +114 -0
- package/studio/studio-instructions.md +81 -0
- package/studio/tailwind.config.js +77 -0
- package/studio/tsconfig.json +24 -0
- package/studio/utils.ts +54 -0
- package/studio/vite.config.js +19 -0
- package/swagger/generator.ts +1 -1
- package/tests/e2e/http.test.ts +126 -0
- package/tests/fixtures/archetypes/TestUserArchetype.ts +21 -0
- package/tests/fixtures/components/TestOrder.ts +23 -0
- package/tests/fixtures/components/TestProduct.ts +23 -0
- package/tests/fixtures/components/TestUser.ts +20 -0
- package/tests/fixtures/components/index.ts +6 -0
- package/tests/graphql/SchemaGeneration.test.ts +90 -0
- package/tests/graphql/builders/ResolverBuilder.test.ts +223 -0
- package/tests/graphql/builders/TypeDefBuilder.test.ts +153 -0
- package/tests/integration/archetype/ArcheType.persistence.test.ts +241 -0
- package/tests/integration/cache/CacheInvalidation.test.ts +259 -0
- package/tests/integration/entity/Entity.persistence.test.ts +333 -0
- package/tests/integration/query/Query.exec.test.ts +523 -0
- package/tests/pglite-setup.ts +61 -0
- package/tests/setup.ts +164 -0
- package/tests/stress/BenchmarkRunner.ts +203 -0
- package/tests/stress/DataSeeder.ts +190 -0
- package/tests/stress/StressTestReporter.ts +229 -0
- package/tests/stress/cursor-perf-test.ts +171 -0
- package/tests/stress/fixtures/StressTestComponents.ts +58 -0
- package/tests/stress/index.ts +7 -0
- package/tests/stress/scenarios/query-benchmarks.test.ts +285 -0
- package/tests/unit/BatchLoader.test.ts +82 -0
- package/tests/unit/archetype/ArcheType.test.ts +107 -0
- package/tests/unit/cache/CacheManager.test.ts +347 -0
- package/tests/unit/cache/MemoryCache.test.ts +260 -0
- package/tests/unit/cache/RedisCache.test.ts +411 -0
- package/tests/unit/entity/Entity.components.test.ts +244 -0
- package/tests/unit/entity/Entity.test.ts +345 -0
- package/tests/unit/gql/depthLimit.test.ts +203 -0
- package/tests/unit/gql/operationMiddleware.test.ts +293 -0
- package/tests/unit/health/Health.test.ts +129 -0
- package/tests/unit/middleware/AccessLog.test.ts +37 -0
- package/tests/unit/middleware/Middleware.test.ts +98 -0
- package/tests/unit/middleware/RequestId.test.ts +54 -0
- package/tests/unit/middleware/SecurityHeaders.test.ts +66 -0
- package/tests/unit/query/FilterBuilder.test.ts +111 -0
- package/tests/unit/query/Query.test.ts +308 -0
- package/tests/unit/scheduler/DistributedLock.test.ts +274 -0
- package/tests/unit/schema/schema-integration.test.ts +426 -0
- package/tests/unit/schema/schema.test.ts +580 -0
- package/tests/unit/storage/S3StorageProvider.test.ts +571 -0
- package/tests/unit/upload/RestUpload.test.ts +267 -0
- package/tests/unit/validateEnv.test.ts +82 -0
- package/tests/utils/entity-tracker.ts +57 -0
- package/tests/utils/index.ts +13 -0
- package/tests/utils/test-context.ts +149 -0
- package/tsconfig.json +5 -1
- package/types/archetype.types.ts +6 -0
- package/types/hooks.types.ts +1 -1
- package/types/query.types.ts +110 -0
- package/types/scheduler.types.ts +68 -7
- package/types/upload.types.ts +1 -0
- package/{core → upload}/FileValidator.ts +10 -1
- package/upload/RestUpload.ts +130 -0
- package/{core/components → upload}/UploadComponent.ts +11 -11
- package/{core → upload}/UploadManager.ts +3 -3
- package/upload/index.ts +23 -7
- package/utils/UploadHelper.ts +27 -6
- package/utils/cronParser.ts +16 -6
- package/.github/workflows/deploy-docs.yml +0 -57
- package/core/Components.ts +0 -202
- package/core/EntityCache.ts +0 -15
- package/core/Query.ts +0 -880
- package/docs/README.md +0 -149
- package/docs/_coverpage.md +0 -36
- package/docs/_sidebar.md +0 -23
- package/docs/api/core.md +0 -568
- package/docs/api/hooks.md +0 -554
- package/docs/api/index.md +0 -222
- package/docs/api/query.md +0 -678
- package/docs/api/service.md +0 -744
- package/docs/core-concepts/archetypes.md +0 -512
- package/docs/core-concepts/components.md +0 -498
- package/docs/core-concepts/entity.md +0 -314
- package/docs/core-concepts/hooks.md +0 -683
- package/docs/core-concepts/query.md +0 -588
- package/docs/core-concepts/services.md +0 -647
- package/docs/examples/code-examples.md +0 -425
- package/docs/getting-started.md +0 -337
- package/docs/index.html +0 -97
- package/tests/bench/insert.bench.ts +0 -60
- package/tests/bench/relations.bench.ts +0 -270
- package/tests/bench/sorting.bench.ts +0 -416
- package/tests/component-hooks-simple.test.ts +0 -117
- package/tests/component-hooks.test.ts +0 -1461
- package/tests/component.test.ts +0 -339
- package/tests/errorHandling.test.ts +0 -155
- package/tests/hooks.test.ts +0 -667
- package/tests/query-sorting.test.ts +0 -101
- package/tests/query.test.ts +0 -81
- package/tests/relations.test.ts +0 -170
- package/tests/scheduler.test.ts +0 -724
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom Filter Builder System for Bunsane Query Framework
|
|
3
|
+
*
|
|
4
|
+
* This module provides the core types and interfaces for extensible query filtering,
|
|
5
|
+
* enabling plugins to register custom filter operators that integrate seamlessly
|
|
6
|
+
* with the DAG-based Query system.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { QueryFilter } from "./QueryContext";
|
|
10
|
+
import type { QueryContext } from "./QueryContext";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Result returned by a custom filter builder function
|
|
14
|
+
*/
|
|
15
|
+
export interface FilterResult {
|
|
16
|
+
/** The SQL fragment to append to the WHERE clause */
|
|
17
|
+
sql: string;
|
|
18
|
+
/** Number of parameters added to the context by this filter */
|
|
19
|
+
addedParams: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Function signature for custom filter builders
|
|
24
|
+
*
|
|
25
|
+
* @param filter - The filter specification containing field, operator, and value
|
|
26
|
+
* @param alias - The table alias for the component table (e.g., "c" for components)
|
|
27
|
+
* @param context - The query context for parameter management and caching
|
|
28
|
+
* @returns FilterResult containing SQL fragment and parameter count
|
|
29
|
+
*/
|
|
30
|
+
export type FilterBuilder = (filter: QueryFilter, alias: string, context: QueryContext) => FilterResult;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Options for advanced filter builder configuration
|
|
34
|
+
*/
|
|
35
|
+
export interface FilterBuilderOptions {
|
|
36
|
+
/** Whether this filter supports LATERAL join optimization */
|
|
37
|
+
supportsLateral?: boolean;
|
|
38
|
+
/** Whether this filter requires database indexes for optimal performance */
|
|
39
|
+
requiresIndex?: boolean;
|
|
40
|
+
/** Complexity score for performance monitoring (0-10, higher = more complex) */
|
|
41
|
+
complexityScore?: number;
|
|
42
|
+
/** Optional validation function for filter values */
|
|
43
|
+
validate?: (filter: QueryFilter) => boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Build a JSON path expression for nested field access
|
|
48
|
+
*
|
|
49
|
+
* @param field - The field path (e.g., "location.coordinates.latitude")
|
|
50
|
+
* @param alias - The table alias (e.g., "c")
|
|
51
|
+
* @returns PostgreSQL JSON path expression
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* buildJSONPath("device.unique_id", "c") // "c.data->'device'->>'unique_id'"
|
|
55
|
+
* buildJSONPath("latitude", "c") // "c.data->>'latitude'"
|
|
56
|
+
*/
|
|
57
|
+
export function buildJSONPath(field: string, alias: string): string {
|
|
58
|
+
if (field.includes('.')) {
|
|
59
|
+
const parts = field.split('.');
|
|
60
|
+
const lastPart = parts.pop()!;
|
|
61
|
+
const nestedPath = parts.map(p => `'${p}'`).join('->');
|
|
62
|
+
return `${alias}.data->${nestedPath}->>'${lastPart}'`;
|
|
63
|
+
} else {
|
|
64
|
+
return `${alias}.data->>'${field}'`;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Compose multiple filter builders into a single builder that applies all conditions
|
|
70
|
+
*
|
|
71
|
+
* This allows chaining multiple custom filters together (e.g., spatial proximity AND full-text search).
|
|
72
|
+
* All builders are executed and their SQL fragments are combined with AND.
|
|
73
|
+
*
|
|
74
|
+
* @param builders - Array of filter builders to compose
|
|
75
|
+
* @returns A composed filter builder function
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* const spatialAndTextBuilder = composeFilters([withinDistanceBuilder, fullTextSearchBuilder]);
|
|
79
|
+
* // Results in: (spatial_condition) AND (text_search_condition)
|
|
80
|
+
*/
|
|
81
|
+
export function composeFilters(builders: FilterBuilder[]): FilterBuilder {
|
|
82
|
+
if (builders.length === 0) {
|
|
83
|
+
throw new Error('Cannot compose empty array of filter builders');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return (filter: QueryFilter, alias: string, context: QueryContext): FilterResult => {
|
|
87
|
+
const conditions: string[] = [];
|
|
88
|
+
let totalParams = 0;
|
|
89
|
+
|
|
90
|
+
for (const builder of builders) {
|
|
91
|
+
const result = builder(filter, alias, context);
|
|
92
|
+
if (result.sql.trim()) {
|
|
93
|
+
conditions.push(`(${result.sql})`);
|
|
94
|
+
}
|
|
95
|
+
totalParams += result.addedParams;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
sql: conditions.join(' AND '),
|
|
100
|
+
addedParams: totalParams
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Create a filter builder that adds SQL hints for index usage
|
|
107
|
+
*
|
|
108
|
+
* This wrapper adds PostgreSQL query hints to suggest index usage to the planner.
|
|
109
|
+
* Useful for custom filters that require specific indexes for optimal performance.
|
|
110
|
+
*
|
|
111
|
+
* @param builder - The original filter builder
|
|
112
|
+
* @param indexHint - The index name to hint (e.g., "idx_spatial_location")
|
|
113
|
+
* @returns A filter builder that includes index hints
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* const hintedBuilder = withIndexHint(spatialBuilder, 'idx_spatial_location');
|
|
117
|
+
* // Generates: /* INDEX: idx_spatial_location */ (spatial_condition)
|
|
118
|
+
*/
|
|
119
|
+
export function withIndexHint(builder: FilterBuilder, indexHint: string): FilterBuilder {
|
|
120
|
+
return (filter: QueryFilter, alias: string, context: QueryContext): FilterResult => {
|
|
121
|
+
const result = builder(filter, alias, context);
|
|
122
|
+
return {
|
|
123
|
+
sql: `/* INDEX: ${indexHint} */ ${result.sql}`,
|
|
124
|
+
addedParams: result.addedParams
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global Registry for Custom Filter Builders
|
|
3
|
+
*
|
|
4
|
+
* Provides thread-safe registration and lookup of custom filter operators.
|
|
5
|
+
* This registry enables plugins to extend the query system with domain-specific
|
|
6
|
+
* filtering capabilities (e.g., spatial queries, full-text search).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { FilterBuilder, FilterBuilderOptions } from "./FilterBuilder";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Registry entry containing the builder function and its options
|
|
13
|
+
*/
|
|
14
|
+
interface RegistryEntry {
|
|
15
|
+
builder: FilterBuilder;
|
|
16
|
+
options?: FilterBuilderOptions;
|
|
17
|
+
registeredBy?: string; // Plugin name for debugging
|
|
18
|
+
registeredAt: Date;
|
|
19
|
+
version?: string; // Semantic version for the filter implementation
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Global registry for custom filter builders
|
|
24
|
+
*
|
|
25
|
+
* This class provides a static interface for registering and retrieving
|
|
26
|
+
* custom filter builders. It ensures thread-safe operations during
|
|
27
|
+
* plugin initialization.
|
|
28
|
+
*/
|
|
29
|
+
export class FilterBuilderRegistry {
|
|
30
|
+
private static registry: Map<string, RegistryEntry> = new Map();
|
|
31
|
+
private static lock: boolean = false;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Register a custom filter builder for a specific operator
|
|
35
|
+
*
|
|
36
|
+
* @param operator - The filter operator (e.g., "within_distance", "contains_point")
|
|
37
|
+
* @param builder - The filter builder function
|
|
38
|
+
* @param options - Optional configuration for the filter builder
|
|
39
|
+
* @param pluginName - Name of the plugin registering this builder (for debugging)
|
|
40
|
+
* @param version - Optional semantic version for the filter implementation (allows upgrades)
|
|
41
|
+
* @throws Error if the operator is already registered by a different plugin (unless version allows override)
|
|
42
|
+
*/
|
|
43
|
+
public static register(
|
|
44
|
+
operator: string,
|
|
45
|
+
builder: FilterBuilder,
|
|
46
|
+
options?: FilterBuilderOptions,
|
|
47
|
+
pluginName?: string,
|
|
48
|
+
version?: string
|
|
49
|
+
): void {
|
|
50
|
+
// Simple lock mechanism for thread safety
|
|
51
|
+
while (this.lock) {
|
|
52
|
+
// Busy wait - in practice, this should be very short
|
|
53
|
+
// Consider using a proper mutex in high-concurrency environments
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this.lock = true;
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const existing = this.registry.get(operator);
|
|
60
|
+
|
|
61
|
+
if (existing) {
|
|
62
|
+
// Allow override if same plugin or if version is newer
|
|
63
|
+
const canOverride =
|
|
64
|
+
(pluginName && existing.registeredBy === pluginName) ||
|
|
65
|
+
(version && existing.version && this.isNewerVersion(version, existing.version));
|
|
66
|
+
|
|
67
|
+
if (!canOverride) {
|
|
68
|
+
throw new Error(`Filter operator '${operator}' is already registered by '${existing.registeredBy || 'unknown'}' (v${existing.version || 'unknown'}). ` +
|
|
69
|
+
`Cannot register from '${pluginName || 'unknown'}' (v${version || 'unknown'}) without version upgrade.`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this.registry.set(operator, {
|
|
74
|
+
builder,
|
|
75
|
+
options,
|
|
76
|
+
registeredBy: pluginName,
|
|
77
|
+
registeredAt: new Date(),
|
|
78
|
+
version
|
|
79
|
+
});
|
|
80
|
+
} finally {
|
|
81
|
+
this.lock = false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Check if a filter operator has a custom builder registered
|
|
87
|
+
*
|
|
88
|
+
* @param operator - The filter operator to check
|
|
89
|
+
* @returns true if a custom builder is registered for this operator
|
|
90
|
+
*/
|
|
91
|
+
public static has(operator: string): boolean {
|
|
92
|
+
return this.registry.has(operator);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get the custom filter builder for a specific operator
|
|
97
|
+
*
|
|
98
|
+
* @param operator - The filter operator
|
|
99
|
+
* @returns The filter builder function, or undefined if not registered
|
|
100
|
+
*/
|
|
101
|
+
public static get(operator: string): FilterBuilder | undefined {
|
|
102
|
+
return this.registry.get(operator)?.builder;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get the options for a registered filter builder
|
|
107
|
+
*
|
|
108
|
+
* @param operator - The filter operator
|
|
109
|
+
* @returns The filter builder options, or undefined if not registered
|
|
110
|
+
*/
|
|
111
|
+
public static getOptions(operator: string): FilterBuilderOptions | undefined {
|
|
112
|
+
return this.registry.get(operator)?.options;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Unregister a custom filter builder
|
|
117
|
+
*
|
|
118
|
+
* @param operator - The filter operator to remove
|
|
119
|
+
* @returns true if the operator was registered and removed, false otherwise
|
|
120
|
+
*/
|
|
121
|
+
public static unregister(operator: string): boolean {
|
|
122
|
+
// Simple lock mechanism
|
|
123
|
+
while (this.lock) {
|
|
124
|
+
// Busy wait
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
this.lock = true;
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
return this.registry.delete(operator);
|
|
131
|
+
} finally {
|
|
132
|
+
this.lock = false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* List all registered filter operators with their metadata
|
|
138
|
+
*
|
|
139
|
+
* @returns Array of registered operators with metadata
|
|
140
|
+
*/
|
|
141
|
+
public static listRegistered(): Array<{
|
|
142
|
+
operator: string;
|
|
143
|
+
options?: FilterBuilderOptions;
|
|
144
|
+
registeredBy?: string;
|
|
145
|
+
registeredAt: Date;
|
|
146
|
+
version?: string;
|
|
147
|
+
}> {
|
|
148
|
+
return Array.from(this.registry.entries()).map(([operator, entry]) => ({
|
|
149
|
+
operator,
|
|
150
|
+
options: entry.options,
|
|
151
|
+
registeredBy: entry.registeredBy,
|
|
152
|
+
registeredAt: entry.registeredAt,
|
|
153
|
+
version: entry.version
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Clear all registered filter builders (for testing purposes)
|
|
159
|
+
*/
|
|
160
|
+
public static clear(): void {
|
|
161
|
+
// Simple lock mechanism
|
|
162
|
+
while (this.lock) {
|
|
163
|
+
// Busy wait
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
this.lock = true;
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
this.registry.clear();
|
|
170
|
+
} finally {
|
|
171
|
+
this.lock = false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Compare two semantic versions to determine if the first is newer
|
|
177
|
+
*
|
|
178
|
+
* @param newVersion - The new version string
|
|
179
|
+
* @param oldVersion - The old version string
|
|
180
|
+
* @returns true if newVersion is semantically newer than oldVersion
|
|
181
|
+
*/
|
|
182
|
+
private static isNewerVersion(newVersion: string, oldVersion: string): boolean {
|
|
183
|
+
try {
|
|
184
|
+
const newParts = newVersion.split('.').map(n => parseInt(n, 10));
|
|
185
|
+
const oldParts = oldVersion.split('.').map(n => parseInt(n, 10));
|
|
186
|
+
|
|
187
|
+
// Compare major, minor, patch versions
|
|
188
|
+
for (let i = 0; i < Math.max(newParts.length, oldParts.length); i++) {
|
|
189
|
+
const newPart = newParts[i] || 0;
|
|
190
|
+
const oldPart = oldParts[i] || 0;
|
|
191
|
+
|
|
192
|
+
if (newPart > oldPart) return true;
|
|
193
|
+
if (newPart < oldPart) return false;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return false; // Same version
|
|
197
|
+
} catch {
|
|
198
|
+
// If parsing fails, fall back to string comparison
|
|
199
|
+
return newVersion > oldVersion;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|