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
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeAll, mock } from "bun:test";
|
|
2
|
-
import Query from "../core/Query";
|
|
3
|
-
import { BaseComponent, CompData, Component } from "../core/Components";
|
|
4
|
-
import ComponentRegistry from "../core/ComponentRegistry";
|
|
5
|
-
|
|
6
|
-
// Define test components
|
|
7
|
-
@Component
|
|
8
|
-
class TestUserComponent extends BaseComponent {
|
|
9
|
-
@CompData()
|
|
10
|
-
name: string = "";
|
|
11
|
-
|
|
12
|
-
@CompData()
|
|
13
|
-
age: number = 0;
|
|
14
|
-
|
|
15
|
-
@CompData()
|
|
16
|
-
score: number = 0;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
@Component
|
|
20
|
-
class TestPostComponent extends BaseComponent {
|
|
21
|
-
@CompData()
|
|
22
|
-
title: string = "";
|
|
23
|
-
|
|
24
|
-
@CompData()
|
|
25
|
-
createdAt: string = "";
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
describe("Query Sorting", () => {
|
|
29
|
-
beforeAll(() => {
|
|
30
|
-
// Mock ComponentRegistry for testing
|
|
31
|
-
mock.restore();
|
|
32
|
-
|
|
33
|
-
// Mock the getComponentId method to return predictable IDs
|
|
34
|
-
(ComponentRegistry as any).getComponentId = mock((name: string) => {
|
|
35
|
-
if (name === "TestUserComponent") return "test-user-type-id";
|
|
36
|
-
if (name === "TestPostComponent") return "test-post-type-id";
|
|
37
|
-
return undefined;
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("should create a query with sortBy method", () => {
|
|
42
|
-
const query = new Query();
|
|
43
|
-
const result = query.with(TestUserComponent).sortBy(TestUserComponent, "age", "DESC");
|
|
44
|
-
|
|
45
|
-
expect(result).toBeInstanceOf(Query);
|
|
46
|
-
expect((result as any).sortOrders).toHaveLength(1);
|
|
47
|
-
expect((result as any).sortOrders[0]).toEqual({
|
|
48
|
-
component: "TestUserComponent",
|
|
49
|
-
property: "age",
|
|
50
|
-
direction: "DESC",
|
|
51
|
-
nullsFirst: false
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("should validate component is included in query before sorting", () => {
|
|
56
|
-
const query = new Query();
|
|
57
|
-
|
|
58
|
-
expect(() => {
|
|
59
|
-
query.sortBy(TestUserComponent, "age");
|
|
60
|
-
}).toThrow("Cannot sort by component TestUserComponent that is not included in the query");
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("should support orderBy with multiple sort orders", () => {
|
|
64
|
-
const query = new Query();
|
|
65
|
-
const sortOrders = [
|
|
66
|
-
{ component: "TestUserComponent", property: "age", direction: "DESC" as const },
|
|
67
|
-
{ component: "TestUserComponent", property: "name", direction: "ASC" as const }
|
|
68
|
-
];
|
|
69
|
-
|
|
70
|
-
const result = query.with(TestUserComponent).orderBy(sortOrders);
|
|
71
|
-
|
|
72
|
-
expect((result as any).sortOrders).toEqual(sortOrders);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("should build correct ORDER BY clause for sorting", () => {
|
|
76
|
-
const query = new Query();
|
|
77
|
-
query.with(TestUserComponent).sortBy(TestUserComponent, "age", "DESC");
|
|
78
|
-
|
|
79
|
-
const orderByClause = (query as any).buildOrderByClause(["test-user-type-id"]);
|
|
80
|
-
|
|
81
|
-
expect(orderByClause).toContain("ORDER BY");
|
|
82
|
-
expect(orderByClause).toContain("DESC");
|
|
83
|
-
expect(orderByClause).toContain("ec.entity_id ASC");
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it("should handle nulls first option", () => {
|
|
87
|
-
const query = new Query();
|
|
88
|
-
query.with(TestUserComponent).sortBy(TestUserComponent, "age", "ASC", true);
|
|
89
|
-
|
|
90
|
-
expect((query as any).sortOrders[0].nullsFirst).toBe(true);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it("should build ORDER BY clause with nulls first", () => {
|
|
94
|
-
const query = new Query();
|
|
95
|
-
query.with(TestUserComponent).sortBy(TestUserComponent, "age", "ASC", true);
|
|
96
|
-
|
|
97
|
-
const orderByClause = (query as any).buildOrderByClause(["test-user-type-id"]);
|
|
98
|
-
|
|
99
|
-
expect(orderByClause).toContain("NULLS FIRST");
|
|
100
|
-
});
|
|
101
|
-
});
|
package/tests/query.test.ts
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import {describe, test, expect, beforeAll} from "bun:test"
|
|
2
|
-
import App from "core/App"
|
|
3
|
-
import { BaseComponent, CompData, Component } from "core/Components";
|
|
4
|
-
import { Entity } from "core/Entity";
|
|
5
|
-
import Query from "core/Query";
|
|
6
|
-
import ComponentRegistry from "core/ComponentRegistry";
|
|
7
|
-
|
|
8
|
-
let app;
|
|
9
|
-
beforeAll(async () => {
|
|
10
|
-
app = new App();
|
|
11
|
-
app.init();
|
|
12
|
-
await app.waitForAppReady();
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
@Component
|
|
16
|
-
class QueryTestComponent extends BaseComponent {
|
|
17
|
-
@CompData()
|
|
18
|
-
value: string = "";
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
@Component
|
|
22
|
-
class CountTestComponent extends BaseComponent {
|
|
23
|
-
@CompData()
|
|
24
|
-
value: string = "";
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
describe("Query test", async () => {
|
|
28
|
-
test("Create and Update Entity", async () => {
|
|
29
|
-
const entity = Entity.Create()
|
|
30
|
-
.add(QueryTestComponent, {value: "Test"})
|
|
31
|
-
await entity.save();
|
|
32
|
-
|
|
33
|
-
const fetchedEntity = await Entity.FindById(entity.id);
|
|
34
|
-
expect(fetchedEntity).not.toBeNull();
|
|
35
|
-
expect(fetchedEntity?.componentList().length).toBe(1);
|
|
36
|
-
|
|
37
|
-
await fetchedEntity?.set(QueryTestComponent, {value: "UpdatedTest"});
|
|
38
|
-
console.log("Updating Entity");
|
|
39
|
-
await fetchedEntity?.save();
|
|
40
|
-
|
|
41
|
-
const updatedEntity = await Entity.FindById(entity.id);
|
|
42
|
-
expect(updatedEntity).not.toBeNull();
|
|
43
|
-
expect(updatedEntity?.componentList().length).toBe(1);
|
|
44
|
-
const comp = await updatedEntity?.get(QueryTestComponent)
|
|
45
|
-
expect(comp?.value).toBe("UpdatedTest");
|
|
46
|
-
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test("Count method should return total entities matching query", async () => {
|
|
50
|
-
// Create a few test entities without relying on component registration
|
|
51
|
-
const entity1 = Entity.Create();
|
|
52
|
-
const entity2 = Entity.Create();
|
|
53
|
-
const entity3 = Entity.Create();
|
|
54
|
-
|
|
55
|
-
await entity1.save();
|
|
56
|
-
await entity2.save();
|
|
57
|
-
await entity3.save();
|
|
58
|
-
|
|
59
|
-
// Test count with specific entity ID
|
|
60
|
-
const specificCount = await new Query()
|
|
61
|
-
.findById(entity1.id)
|
|
62
|
-
.count();
|
|
63
|
-
|
|
64
|
-
expect(specificCount).toBe(1);
|
|
65
|
-
|
|
66
|
-
// Test count with empty query (no components required)
|
|
67
|
-
const emptyQueryCount = await new Query().count();
|
|
68
|
-
expect(emptyQueryCount).toBe(0); // Empty query should return 0
|
|
69
|
-
|
|
70
|
-
// Test count with multiple entity IDs by creating a more complex scenario
|
|
71
|
-
// Since we can't easily test component-based counting without registration,
|
|
72
|
-
// let's focus on the core functionality that works
|
|
73
|
-
const entityIds = [entity1.id, entity2.id, entity3.id];
|
|
74
|
-
|
|
75
|
-
// Test that we can count entities that exist
|
|
76
|
-
for (const id of entityIds) {
|
|
77
|
-
const count = await new Query().findById(id).count();
|
|
78
|
-
expect(count).toBe(1);
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
})
|
package/tests/relations.test.ts
DELETED
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect, beforeAll, beforeEach } from "bun:test";
|
|
2
|
-
import App from "core/App";
|
|
3
|
-
import { BaseComponent, CompData, Component } from "core/Components";
|
|
4
|
-
import { Entity } from "core/Entity";
|
|
5
|
-
import { BatchLoader } from "core/BatchLoader";
|
|
6
|
-
import { isFieldRequestedSafe } from "gql/helpers";
|
|
7
|
-
import db from "database";
|
|
8
|
-
|
|
9
|
-
let app: App;
|
|
10
|
-
|
|
11
|
-
beforeAll(async () => {
|
|
12
|
-
app = new App();
|
|
13
|
-
app.init();
|
|
14
|
-
await app.waitForAppReady();
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
beforeEach(async () => {
|
|
18
|
-
await db`TRUNCATE TABLE entities CASCADE;`;
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
@Component
|
|
22
|
-
class AuthorComponent extends BaseComponent {
|
|
23
|
-
@CompData()
|
|
24
|
-
value: string = ""; // related user id
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
@Component
|
|
28
|
-
class TitleComponent extends BaseComponent {
|
|
29
|
-
@CompData()
|
|
30
|
-
value: string = "";
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
@Component
|
|
34
|
-
class UserComponent extends BaseComponent {
|
|
35
|
-
@CompData()
|
|
36
|
-
name: string = "";
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
describe('Relations Tests', () => {
|
|
40
|
-
test('BatchLoader.loadRelatedEntitiesBatched fetches relations in one query', async () => {
|
|
41
|
-
// Create users
|
|
42
|
-
const user1 = Entity.Create().add(UserComponent, { name: "User1" });
|
|
43
|
-
const user2 = Entity.Create().add(UserComponent, { name: "User2" });
|
|
44
|
-
await Promise.all([user1.save(), user2.save()]);
|
|
45
|
-
|
|
46
|
-
// Create posts with authors
|
|
47
|
-
const post1 = Entity.Create()
|
|
48
|
-
.add(TitleComponent, { value: "Post1" })
|
|
49
|
-
.add(AuthorComponent, { value: user1.id });
|
|
50
|
-
const post2 = Entity.Create()
|
|
51
|
-
.add(TitleComponent, { value: "Post2" })
|
|
52
|
-
.add(AuthorComponent, { value: user2.id });
|
|
53
|
-
await Promise.all([post1.save(), post2.save()]);
|
|
54
|
-
|
|
55
|
-
// Test batched loading
|
|
56
|
-
const loader = async (ids: string[]) => {
|
|
57
|
-
const entities = await Entity.LoadMultiple(ids);
|
|
58
|
-
return entities;
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const result = await BatchLoader.loadRelatedEntitiesBatched([post1, post2], AuthorComponent, loader);
|
|
62
|
-
|
|
63
|
-
expect(result.size).toBe(2);
|
|
64
|
-
expect(result.get(user1.id)?.id).toBe(user1.id);
|
|
65
|
-
expect(result.get(user2.id)?.id).toBe(user2.id);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test('BatchLoader handles large parent list efficiently', async () => {
|
|
69
|
-
// Create 100 users
|
|
70
|
-
const users = [];
|
|
71
|
-
for (let i = 0; i < 100; i++) {
|
|
72
|
-
const user = Entity.Create().add(UserComponent, { name: `User${i}` });
|
|
73
|
-
users.push(user);
|
|
74
|
-
}
|
|
75
|
-
await Promise.all(users.map(u => u.save()));
|
|
76
|
-
|
|
77
|
-
// Create 100 posts with random authors
|
|
78
|
-
const posts = [];
|
|
79
|
-
for (let i = 0; i < 100; i++) {
|
|
80
|
-
const randomUser = users[Math.floor(Math.random() * users.length)]!;
|
|
81
|
-
const post = Entity.Create()
|
|
82
|
-
.add(TitleComponent, { value: `Post${i}` })
|
|
83
|
-
.add(AuthorComponent, { value: randomUser.id });
|
|
84
|
-
posts.push(post);
|
|
85
|
-
}
|
|
86
|
-
await Promise.all(posts.map(p => p.save()));
|
|
87
|
-
|
|
88
|
-
// Test batched loading performance
|
|
89
|
-
const startTime = Date.now();
|
|
90
|
-
const loader = async (ids: string[]) => {
|
|
91
|
-
return await Entity.LoadMultiple(ids);
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
const result = await BatchLoader.loadRelatedEntitiesBatched(posts, AuthorComponent, loader);
|
|
95
|
-
const endTime = Date.now();
|
|
96
|
-
|
|
97
|
-
expect(result.size).toBeGreaterThan(0);
|
|
98
|
-
expect(endTime - startTime).toBeLessThan(1000); // Should complete under 1 second
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test('isFieldRequestedSafe handles nested selections', () => {
|
|
102
|
-
const info = {
|
|
103
|
-
fieldNodes: [{
|
|
104
|
-
selectionSet: {
|
|
105
|
-
selections: [
|
|
106
|
-
{ kind: 'Field', name: { value: 'id' } },
|
|
107
|
-
{
|
|
108
|
-
kind: 'Field',
|
|
109
|
-
name: { value: 'author' },
|
|
110
|
-
selectionSet: {
|
|
111
|
-
selections: [{ kind: 'Field', name: { value: 'name' } }]
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
]
|
|
115
|
-
}
|
|
116
|
-
}]
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
expect(isFieldRequestedSafe(info, 'id')).toBe(true);
|
|
120
|
-
expect(isFieldRequestedSafe(info, 'author')).toBe(true);
|
|
121
|
-
expect(isFieldRequestedSafe(info, 'author', 'name')).toBe(true);
|
|
122
|
-
expect(isFieldRequestedSafe(info, 'title')).toBe(false);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
test('isFieldRequestedSafe handles fragments', () => {
|
|
126
|
-
const info = {
|
|
127
|
-
fieldNodes: [{
|
|
128
|
-
selectionSet: {
|
|
129
|
-
selections: [
|
|
130
|
-
{ kind: 'FragmentSpread', name: { value: 'UserFragment' } }
|
|
131
|
-
]
|
|
132
|
-
}
|
|
133
|
-
}]
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
// Assuming fragments are expanded, but for test, mock as if selections are there
|
|
137
|
-
// In real scenario, fragments would be resolved
|
|
138
|
-
expect(isFieldRequestedSafe(info, 'id')).toBe(false); // since no direct field
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
test('isFieldRequestedSafe handles missing selectionSet', () => {
|
|
142
|
-
const info = {
|
|
143
|
-
fieldNodes: [{
|
|
144
|
-
// no selectionSet
|
|
145
|
-
}]
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
expect(isFieldRequestedSafe(info, 'id')).toBe(false);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
test('Entity.doDelete resolves promise correctly', async () => {
|
|
152
|
-
const entity = Entity.Create().add(TitleComponent, { value: "Test" });
|
|
153
|
-
await entity.save();
|
|
154
|
-
|
|
155
|
-
const deletePromise = entity.delete();
|
|
156
|
-
await expect(deletePromise).resolves.toBe(true);
|
|
157
|
-
|
|
158
|
-
// Verify deleted (soft delete - check deleted_at is set)
|
|
159
|
-
const check = await db`SELECT id, deleted_at FROM entities WHERE id = ${entity.id}`;
|
|
160
|
-
expect(check.length).toBe(1);
|
|
161
|
-
expect(check[0].deleted_at).not.toBeNull();
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
test('Entity.doDelete resolves false for non-persisted entity', async () => {
|
|
165
|
-
const entity = new Entity(); // not saved
|
|
166
|
-
|
|
167
|
-
const deletePromise = entity.delete();
|
|
168
|
-
await expect(deletePromise).resolves.toBe(false);
|
|
169
|
-
});
|
|
170
|
-
});
|