bunsane 0.3.2 → 0.5.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/CHANGELOG.md +471 -370
- package/core/BatchLoader.ts +56 -32
- package/core/Entity.ts +93 -1020
- package/core/EntityHookManager.ts +52 -754
- package/core/Logger.ts +10 -0
- package/core/RequestContext.ts +94 -85
- package/core/RequestLoaders.ts +98 -5
- package/core/SchedulerManager.ts +28 -600
- package/core/app/cors.ts +2 -11
- package/core/app/preparedStatementWarmup.ts +9 -49
- package/core/app/requestRouter.ts +9 -8
- package/core/app/restRegistry.ts +8 -0
- package/core/archetype/fieldResolvers.ts +85 -40
- package/core/archetype/relationLoader.ts +135 -92
- package/core/cache/CacheManager.ts +91 -302
- package/core/cache/CompressionUtils.ts +34 -3
- package/core/cache/MemoryCache.ts +40 -37
- package/core/cache/RedisCache.ts +8 -7
- package/core/cache/health.ts +30 -0
- package/core/cache/invalidation.ts +96 -0
- package/core/cache/strategies/writeInvalidate.ts +111 -0
- package/core/cache/strategies/writeThrough.ts +233 -0
- package/core/components/BaseComponent.ts +25 -10
- package/core/components/ComponentRegistry.ts +28 -0
- package/core/decorators/IndexedField.ts +1 -1
- package/core/entity/cacheStrategies.ts +97 -0
- package/core/entity/componentAccess.ts +383 -0
- package/core/entity/finders.ts +202 -0
- package/core/entity/getCacheManager.ts +10 -0
- package/core/entity/pendingOps.ts +72 -0
- package/core/entity/saveEntity.ts +375 -0
- package/core/health.ts +93 -4
- package/core/hooks/dispatcher.ts +439 -0
- package/core/hooks/guards.ts +155 -0
- package/core/hooks/registry.ts +247 -0
- package/core/metadata/definitions/Component.ts +1 -1
- package/core/metadata/index.ts +15 -4
- package/core/middleware/RateLimit.ts +102 -105
- package/core/middleware/RequestId.ts +2 -9
- package/core/middleware/SecurityHeaders.ts +2 -11
- package/core/middleware/headers.ts +28 -0
- package/core/remote/OutboxWorker.ts +213 -183
- package/core/remote/RemoteManager.ts +401 -400
- package/core/remote/StreamConsumer.ts +535 -535
- package/core/remote/types.ts +153 -151
- package/core/requestScope.ts +34 -0
- package/core/scheduler/cronEvaluator.ts +174 -0
- package/core/scheduler/lifecycleHooks.ts +21 -0
- package/core/scheduler/lockCoordinator.ts +27 -0
- package/core/scheduler/metrics.ts +14 -0
- package/core/scheduler/taskRunner.ts +420 -0
- package/core/validateEnv.ts +10 -0
- package/database/DatabaseHelper.ts +128 -101
- package/database/IndexingStrategy.ts +72 -2
- package/database/PreparedStatementCache.ts +8 -2
- package/database/cancellable.ts +35 -22
- package/database/index.ts +29 -3
- package/database/instrumentedDb.ts +141 -141
- package/database/sqlHelpers.ts +3 -1
- package/endpoints/archetypes.ts +2 -8
- package/endpoints/tables.ts +6 -1
- package/gql/index.ts +1 -1
- package/gql/schema/index.ts +15 -4
- package/gql/visitors/ResolverGeneratorVisitor.ts +25 -4
- package/package.json +22 -1
- package/query/CTENode.ts +5 -3
- package/query/ComponentInclusionNode.ts +245 -14
- package/query/OrNode.ts +8 -19
- package/query/Query.ts +208 -79
- package/query/QueryContext.ts +6 -0
- package/query/QueryDAG.ts +7 -2
- package/query/membershipSource.ts +66 -0
- package/storage/LocalStorageProvider.ts +8 -3
- package/studio/dist/assets/index-BMZ67Npg.js +254 -0
- package/studio/dist/assets/index-BpbuYz9g.css +1 -0
- package/studio/{index.html → dist/index.html} +3 -2
- package/swagger/generator.ts +11 -1
- package/upload/UploadManager.ts +8 -6
- package/utils/uuid.ts +40 -10
- package/.claude/scheduled_tasks.lock +0 -1
- package/.claude/settings.local.json +0 -47
- package/.prettierrc +0 -4
- package/.serena/memories/architectural-decision-no-dependency-injection.md +0 -76
- package/.serena/memories/architecture.md +0 -154
- package/.serena/memories/cache-interface-refactoring-2026-01-24.md +0 -165
- package/.serena/memories/code_style_and_conventions.md +0 -76
- package/.serena/memories/project_overview.md +0 -43
- package/.serena/memories/schema-dsl-plan.md +0 -107
- package/.serena/memories/suggested_commands.md +0 -80
- package/.serena/memories/typescript-compilation-status.md +0 -54
- package/.serena/project.yml +0 -114
- package/BunSane.jpg +0 -0
- package/CLAUDE.md +0 -198
- package/TODO.md +0 -2
- package/bun.lock +0 -302
- package/bunfig.toml +0 -10
- package/docs/RFC_APP_REFACTOR.md +0 -248
- package/docs/RFC_REFACTOR_TARGETS.md +0 -251
- package/docs/SCALABILITY_PLAN.md +0 -175
- package/studio/bun.lock +0 -482
- package/studio/package.json +0 -39
- package/studio/postcss.config.js +0 -6
- package/studio/src/components/DataTable.tsx +0 -211
- package/studio/src/components/Layout.tsx +0 -13
- package/studio/src/components/PageContainer.tsx +0 -9
- package/studio/src/components/PageHeader.tsx +0 -13
- package/studio/src/components/SearchBar.tsx +0 -57
- package/studio/src/components/Sidebar.tsx +0 -294
- package/studio/src/components/ui/button.tsx +0 -56
- package/studio/src/components/ui/checkbox.tsx +0 -26
- package/studio/src/components/ui/input.tsx +0 -25
- package/studio/src/hooks/useDataTable.ts +0 -131
- package/studio/src/index.css +0 -36
- package/studio/src/lib/api.ts +0 -186
- package/studio/src/lib/utils.ts +0 -13
- package/studio/src/main.tsx +0 -17
- package/studio/src/pages/ArcheType.tsx +0 -239
- package/studio/src/pages/Components.tsx +0 -124
- package/studio/src/pages/EntityInspector.tsx +0 -302
- package/studio/src/pages/QueryRunner.tsx +0 -246
- package/studio/src/pages/Table.tsx +0 -94
- package/studio/src/pages/Welcome.tsx +0 -241
- package/studio/src/routes.tsx +0 -45
- package/studio/src/store/archeTypeSettings.ts +0 -30
- package/studio/src/store/studio.ts +0 -65
- package/studio/src/utils/columnHelpers.tsx +0 -114
- package/studio/studio-instructions.md +0 -81
- package/studio/tailwind.config.js +0 -77
- package/studio/utils.ts +0 -54
- package/studio/vite.config.js +0 -19
- package/tests/benchmark/BENCHMARK_DATABASES_PLAN.md +0 -338
- package/tests/benchmark/bunfig.toml +0 -9
- package/tests/benchmark/fixtures/EcommerceComponents.ts +0 -283
- package/tests/benchmark/fixtures/EcommerceDataGenerators.ts +0 -301
- package/tests/benchmark/fixtures/RelationTracker.ts +0 -159
- package/tests/benchmark/fixtures/index.ts +0 -6
- package/tests/benchmark/index.ts +0 -22
- package/tests/benchmark/noop-preload.ts +0 -3
- package/tests/benchmark/query-lateral-benchmark.test.ts +0 -372
- package/tests/benchmark/runners/BenchmarkLoader.ts +0 -132
- package/tests/benchmark/runners/index.ts +0 -4
- package/tests/benchmark/scenarios/query-benchmarks.test.ts +0 -465
- package/tests/benchmark/scripts/generate-db.ts +0 -344
- package/tests/benchmark/scripts/run-benchmarks.ts +0 -97
- package/tests/e2e/http.test.ts +0 -130
- package/tests/fixtures/archetypes/TestUserArchetype.ts +0 -21
- package/tests/fixtures/components/TestOrder.ts +0 -23
- package/tests/fixtures/components/TestProduct.ts +0 -23
- package/tests/fixtures/components/TestUser.ts +0 -20
- package/tests/fixtures/components/index.ts +0 -6
- package/tests/graphql/SchemaGeneration.test.ts +0 -90
- package/tests/graphql/builders/ResolverBuilder.test.ts +0 -223
- package/tests/graphql/builders/TypeDefBuilder.test.ts +0 -153
- package/tests/helpers/MockRedisClient.ts +0 -113
- package/tests/helpers/MockRedisStreamServer.ts +0 -448
- package/tests/integration/archetype/ArcheType.persistence.test.ts +0 -241
- package/tests/integration/cache/CacheInvalidation.test.ts +0 -259
- package/tests/integration/entity/Entity.persistence.test.ts +0 -333
- package/tests/integration/entity/Entity.saveTimeout.test.ts +0 -110
- package/tests/integration/loaders/RequestLoaders.abort.test.ts +0 -82
- package/tests/integration/query/Query.abort.test.ts +0 -66
- package/tests/integration/query/Query.complexAnalysis.test.ts +0 -557
- package/tests/integration/query/Query.edgeCases.test.ts +0 -595
- package/tests/integration/query/Query.exec.test.ts +0 -576
- package/tests/integration/query/Query.explainAnalyze.test.ts +0 -233
- package/tests/integration/query/Query.jsonbArray.test.ts +0 -214
- package/tests/integration/remote/dlq.test.ts +0 -175
- package/tests/integration/remote/event-dispatch.test.ts +0 -114
- package/tests/integration/remote/outbox.test.ts +0 -130
- package/tests/integration/remote/rpc.test.ts +0 -177
- package/tests/pglite-setup.ts +0 -62
- package/tests/setup.ts +0 -164
- package/tests/stress/BenchmarkRunner.ts +0 -203
- package/tests/stress/DataSeeder.ts +0 -190
- package/tests/stress/StressTestReporter.ts +0 -229
- package/tests/stress/cursor-perf-test.ts +0 -171
- package/tests/stress/fixtures/RealisticComponents.ts +0 -235
- package/tests/stress/fixtures/StressTestComponents.ts +0 -58
- package/tests/stress/index.ts +0 -7
- package/tests/stress/scenarios/query-benchmarks.test.ts +0 -285
- package/tests/stress/scenarios/realistic-scenarios.test.ts +0 -1081
- package/tests/stress/scenarios/timeout-investigation.test.ts +0 -522
- package/tests/unit/BatchLoader.test.ts +0 -196
- package/tests/unit/archetype/ArcheType.test.ts +0 -107
- package/tests/unit/cache/CacheManager.test.ts +0 -498
- package/tests/unit/cache/MemoryCache.test.ts +0 -260
- package/tests/unit/cache/RedisCache.test.ts +0 -411
- package/tests/unit/database/cancellable.test.ts +0 -81
- package/tests/unit/database/instrumentedDb.test.ts +0 -160
- package/tests/unit/entity/Entity.components.test.ts +0 -317
- package/tests/unit/entity/Entity.drainSideEffects.test.ts +0 -51
- package/tests/unit/entity/Entity.reload.test.ts +0 -63
- package/tests/unit/entity/Entity.requireComponents.test.ts +0 -72
- package/tests/unit/entity/Entity.test.ts +0 -345
- package/tests/unit/gql/depthLimit.test.ts +0 -203
- package/tests/unit/gql/operationMiddleware.test.ts +0 -293
- package/tests/unit/health/Health.test.ts +0 -129
- package/tests/unit/middleware/AccessLog.test.ts +0 -37
- package/tests/unit/middleware/Middleware.test.ts +0 -98
- package/tests/unit/middleware/RequestId.test.ts +0 -54
- package/tests/unit/middleware/SecurityHeaders.test.ts +0 -66
- package/tests/unit/query/FilterBuilder.test.ts +0 -111
- package/tests/unit/query/JsonbArrayBuilder.test.ts +0 -178
- package/tests/unit/query/Query.emptyString.test.ts +0 -69
- package/tests/unit/query/Query.test.ts +0 -310
- package/tests/unit/remote/CircuitBreaker.test.ts +0 -159
- package/tests/unit/remote/RemoteError.test.ts +0 -55
- package/tests/unit/remote/decorators.test.ts +0 -195
- package/tests/unit/remote/metrics.test.ts +0 -115
- package/tests/unit/remote/mockRedisStreamServer.test.ts +0 -104
- package/tests/unit/scheduler/DistributedLock.test.ts +0 -274
- package/tests/unit/scheduler/SchedulerManager.timeBased.test.ts +0 -95
- package/tests/unit/schema/schema-integration.test.ts +0 -426
- package/tests/unit/schema/schema.test.ts +0 -580
- package/tests/unit/storage/S3StorageProvider.test.ts +0 -567
- package/tests/unit/upload/RestUpload.test.ts +0 -267
- package/tests/unit/validateEnv.test.ts +0 -82
- package/tests/utils/entity-tracker.ts +0 -57
- package/tests/utils/index.ts +0 -13
- package/tests/utils/test-context.ts +0 -149
|
@@ -1,344 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/**
|
|
3
|
-
* CLI script to generate persistent PGlite benchmark databases.
|
|
4
|
-
*
|
|
5
|
-
* This script is self-contained and does not depend on the framework's
|
|
6
|
-
* database connection - it writes directly to PGlite.
|
|
7
|
-
*
|
|
8
|
-
* Usage:
|
|
9
|
-
* bun tests/benchmark/scripts/generate-db.ts [tier] [--force] [--all]
|
|
10
|
-
*
|
|
11
|
-
* Examples:
|
|
12
|
-
* bun tests/benchmark/scripts/generate-db.ts xs
|
|
13
|
-
* bun tests/benchmark/scripts/generate-db.ts md --force
|
|
14
|
-
* bun tests/benchmark/scripts/generate-db.ts --all
|
|
15
|
-
*/
|
|
16
|
-
import { PGlite } from '@electric-sql/pglite';
|
|
17
|
-
import { existsSync, rmSync, mkdirSync } from 'node:fs';
|
|
18
|
-
import { join, dirname } from 'node:path';
|
|
19
|
-
import { fileURLToPath } from 'node:url';
|
|
20
|
-
import { createHash } from 'node:crypto';
|
|
21
|
-
|
|
22
|
-
import {
|
|
23
|
-
SeededRandom,
|
|
24
|
-
generateUserData,
|
|
25
|
-
generateProductData,
|
|
26
|
-
generateOrderData,
|
|
27
|
-
generateOrderItemData,
|
|
28
|
-
generateReviewData
|
|
29
|
-
} from '../fixtures/EcommerceDataGenerators';
|
|
30
|
-
import { RelationTracker } from '../fixtures/RelationTracker';
|
|
31
|
-
|
|
32
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
33
|
-
const DATABASES_DIR = join(__dirname, '..', 'databases');
|
|
34
|
-
|
|
35
|
-
// Database tier configurations
|
|
36
|
-
const TIERS = {
|
|
37
|
-
xs: { users: 1000, products: 2000, orders: 3000, orderItems: 3000, reviews: 1000 },
|
|
38
|
-
sm: { users: 5000, products: 10000, orders: 15000, orderItems: 15000, reviews: 5000 },
|
|
39
|
-
md: { users: 10000, products: 20000, orders: 30000, orderItems: 30000, reviews: 10000 },
|
|
40
|
-
lg: { users: 50000, products: 100000, orders: 150000, orderItems: 150000, reviews: 50000 },
|
|
41
|
-
xl: { users: 100000, products: 200000, orders: 300000, orderItems: 300000, reviews: 100000 }
|
|
42
|
-
} as const;
|
|
43
|
-
|
|
44
|
-
type Tier = keyof typeof TIERS;
|
|
45
|
-
|
|
46
|
-
const DEFAULT_SEED = 42;
|
|
47
|
-
const BATCH_SIZE = 1000;
|
|
48
|
-
|
|
49
|
-
// Component names and their type IDs (generated deterministically)
|
|
50
|
-
const COMPONENT_TYPE_IDS = new Map<string, string>();
|
|
51
|
-
|
|
52
|
-
function generateTypeId(name: string): string {
|
|
53
|
-
if (COMPONENT_TYPE_IDS.has(name)) {
|
|
54
|
-
return COMPONENT_TYPE_IDS.get(name)!;
|
|
55
|
-
}
|
|
56
|
-
// Generate a SHA256 hash (64 hex chars, matches framework's metadata-storage.ts)
|
|
57
|
-
const typeId = createHash('sha256').update(name).digest('hex');
|
|
58
|
-
COMPONENT_TYPE_IDS.set(name, typeId);
|
|
59
|
-
return typeId;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Simple UUID v7 implementation (time-ordered)
|
|
63
|
-
function uuidv7(): string {
|
|
64
|
-
const now = Date.now();
|
|
65
|
-
const timeHex = now.toString(16).padStart(12, '0');
|
|
66
|
-
const randomBytes = crypto.getRandomValues(new Uint8Array(10));
|
|
67
|
-
const randomHex = Array.from(randomBytes).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
68
|
-
return `${timeHex.slice(0, 8)}-${timeHex.slice(8, 12)}-7${randomHex.slice(0, 3)}-${(0x80 | (randomBytes[4]! & 0x3f)).toString(16)}${randomHex.slice(5, 7)}-${randomHex.slice(7, 19)}`;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
interface GenerationResult {
|
|
72
|
-
tier: Tier;
|
|
73
|
-
totalEntities: number;
|
|
74
|
-
totalTime: number;
|
|
75
|
-
recordsPerSecond: number;
|
|
76
|
-
path: string;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async function initializeSchema(pg: PGlite): Promise<void> {
|
|
80
|
-
await pg.exec(`
|
|
81
|
-
CREATE TABLE IF NOT EXISTS entities (
|
|
82
|
-
id UUID PRIMARY KEY,
|
|
83
|
-
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
84
|
-
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
85
|
-
deleted_at TIMESTAMPTZ DEFAULT NULL
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
CREATE TABLE IF NOT EXISTS components (
|
|
89
|
-
id UUID PRIMARY KEY,
|
|
90
|
-
entity_id UUID NOT NULL REFERENCES entities(id) ON DELETE CASCADE,
|
|
91
|
-
type_id VARCHAR(64) NOT NULL,
|
|
92
|
-
name VARCHAR(128),
|
|
93
|
-
data JSONB NOT NULL DEFAULT '{}',
|
|
94
|
-
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
95
|
-
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
96
|
-
deleted_at TIMESTAMPTZ DEFAULT NULL
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
CREATE TABLE IF NOT EXISTS entity_components (
|
|
100
|
-
entity_id UUID NOT NULL REFERENCES entities(id) ON DELETE CASCADE,
|
|
101
|
-
type_id VARCHAR(64) NOT NULL,
|
|
102
|
-
component_id UUID NOT NULL REFERENCES components(id) ON DELETE CASCADE,
|
|
103
|
-
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
104
|
-
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
105
|
-
deleted_at TIMESTAMPTZ DEFAULT NULL,
|
|
106
|
-
PRIMARY KEY (entity_id, type_id)
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
CREATE INDEX IF NOT EXISTS idx_components_entity_id ON components(entity_id);
|
|
110
|
-
CREATE INDEX IF NOT EXISTS idx_components_type_id ON components(type_id);
|
|
111
|
-
CREATE INDEX IF NOT EXISTS idx_components_name ON components(name);
|
|
112
|
-
CREATE INDEX IF NOT EXISTS idx_entity_components_type_id ON entity_components(type_id);
|
|
113
|
-
CREATE INDEX IF NOT EXISTS idx_entities_deleted_null ON entities(id) WHERE deleted_at IS NULL;
|
|
114
|
-
`);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async function seedComponent(
|
|
118
|
-
pg: PGlite,
|
|
119
|
-
componentName: string,
|
|
120
|
-
count: number,
|
|
121
|
-
dataGenerator: (index: number) => Record<string, any>,
|
|
122
|
-
tracker: RelationTracker,
|
|
123
|
-
trackFn?: (entityId: string, data: Record<string, any>) => void,
|
|
124
|
-
onProgress?: (current: number) => void
|
|
125
|
-
): Promise<string[]> {
|
|
126
|
-
const typeId = generateTypeId(componentName);
|
|
127
|
-
const entityIds: string[] = [];
|
|
128
|
-
|
|
129
|
-
for (let i = 0; i < count; i += BATCH_SIZE) {
|
|
130
|
-
const batchSize = Math.min(BATCH_SIZE, count - i);
|
|
131
|
-
const now = new Date().toISOString();
|
|
132
|
-
|
|
133
|
-
let entitiesValues = '';
|
|
134
|
-
let componentsValues = '';
|
|
135
|
-
let entityComponentsValues = '';
|
|
136
|
-
|
|
137
|
-
for (let j = 0; j < batchSize; j++) {
|
|
138
|
-
const entityId = uuidv7();
|
|
139
|
-
const componentId = uuidv7();
|
|
140
|
-
const data = dataGenerator(i + j);
|
|
141
|
-
|
|
142
|
-
entityIds.push(entityId);
|
|
143
|
-
|
|
144
|
-
if (trackFn) {
|
|
145
|
-
trackFn(entityId, data);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const sep = j > 0 ? ',' : '';
|
|
149
|
-
entitiesValues += `${sep}('${entityId}', '${now}', '${now}')`;
|
|
150
|
-
componentsValues += `${sep}('${componentId}', '${entityId}', '${typeId}', '${componentName}', '${JSON.stringify(data).replace(/'/g, "''")}', '${now}', '${now}')`;
|
|
151
|
-
entityComponentsValues += `${sep}('${entityId}', '${typeId}', '${componentId}', '${now}', '${now}')`;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
await pg.exec(`INSERT INTO entities (id, created_at, updated_at) VALUES ${entitiesValues}`);
|
|
155
|
-
await pg.exec(`INSERT INTO components (id, entity_id, type_id, name, data, created_at, updated_at) VALUES ${componentsValues}`);
|
|
156
|
-
await pg.exec(`INSERT INTO entity_components (entity_id, type_id, component_id, created_at, updated_at) VALUES ${entityComponentsValues} ON CONFLICT (entity_id, type_id) DO NOTHING`);
|
|
157
|
-
|
|
158
|
-
if (onProgress) {
|
|
159
|
-
onProgress(i + batchSize);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return entityIds;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
async function generateDatabase(tier: Tier, force: boolean): Promise<GenerationResult> {
|
|
167
|
-
const config = TIERS[tier];
|
|
168
|
-
const dbPath = join(DATABASES_DIR, tier);
|
|
169
|
-
|
|
170
|
-
if (existsSync(dbPath)) {
|
|
171
|
-
if (!force) {
|
|
172
|
-
console.log(`Database for tier '${tier}' already exists at ${dbPath}`);
|
|
173
|
-
console.log('Use --force to regenerate');
|
|
174
|
-
process.exit(0);
|
|
175
|
-
}
|
|
176
|
-
console.log(`Removing existing database at ${dbPath}...`);
|
|
177
|
-
rmSync(dbPath, { recursive: true, force: true });
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
mkdirSync(dbPath, { recursive: true });
|
|
181
|
-
|
|
182
|
-
console.log(`\n=== Generating ${tier.toUpperCase()} tier database ===`);
|
|
183
|
-
console.log(`Path: ${dbPath}`);
|
|
184
|
-
console.log(`Configuration:`);
|
|
185
|
-
console.log(` Users: ${config.users.toLocaleString()}`);
|
|
186
|
-
console.log(` Products: ${config.products.toLocaleString()}`);
|
|
187
|
-
console.log(` Orders: ${config.orders.toLocaleString()}`);
|
|
188
|
-
console.log(` Order Items: ${config.orderItems.toLocaleString()}`);
|
|
189
|
-
console.log(` Reviews: ${config.reviews.toLocaleString()}`);
|
|
190
|
-
|
|
191
|
-
const totalEntities = config.users + config.products + config.orders + config.orderItems + config.reviews;
|
|
192
|
-
console.log(` Total: ${totalEntities.toLocaleString()}`);
|
|
193
|
-
console.log('');
|
|
194
|
-
|
|
195
|
-
const startTime = performance.now();
|
|
196
|
-
|
|
197
|
-
console.log('Initializing PGlite...');
|
|
198
|
-
const pg = new PGlite(dbPath, { relaxedDurability: true });
|
|
199
|
-
await pg.waitReady;
|
|
200
|
-
|
|
201
|
-
console.log('Creating schema...');
|
|
202
|
-
await initializeSchema(pg);
|
|
203
|
-
|
|
204
|
-
const tracker = new RelationTracker();
|
|
205
|
-
const rng = new SeededRandom(DEFAULT_SEED);
|
|
206
|
-
|
|
207
|
-
// Seed Users
|
|
208
|
-
console.log('\nSeeding Users...');
|
|
209
|
-
const userStart = performance.now();
|
|
210
|
-
await seedComponent(
|
|
211
|
-
pg,
|
|
212
|
-
'BenchUser',
|
|
213
|
-
config.users,
|
|
214
|
-
(idx) => generateUserData(idx, rng),
|
|
215
|
-
tracker,
|
|
216
|
-
(entityId) => tracker.addUser(entityId),
|
|
217
|
-
(current) => process.stdout.write(`\r Progress: ${current.toLocaleString()}/${config.users.toLocaleString()}`)
|
|
218
|
-
);
|
|
219
|
-
console.log(`\n Done in ${((performance.now() - userStart) / 1000).toFixed(1)}s`);
|
|
220
|
-
|
|
221
|
-
// Seed Products
|
|
222
|
-
console.log('\nSeeding Products...');
|
|
223
|
-
const productStart = performance.now();
|
|
224
|
-
await seedComponent(
|
|
225
|
-
pg,
|
|
226
|
-
'BenchProduct',
|
|
227
|
-
config.products,
|
|
228
|
-
(idx) => generateProductData(idx, rng),
|
|
229
|
-
tracker,
|
|
230
|
-
(entityId) => tracker.addProduct(entityId),
|
|
231
|
-
(current) => process.stdout.write(`\r Progress: ${current.toLocaleString()}/${config.products.toLocaleString()}`)
|
|
232
|
-
);
|
|
233
|
-
console.log(`\n Done in ${((performance.now() - productStart) / 1000).toFixed(1)}s`);
|
|
234
|
-
|
|
235
|
-
// Seed Orders
|
|
236
|
-
console.log('\nSeeding Orders...');
|
|
237
|
-
const orderStart = performance.now();
|
|
238
|
-
await seedComponent(
|
|
239
|
-
pg,
|
|
240
|
-
'BenchOrder',
|
|
241
|
-
config.orders,
|
|
242
|
-
(idx) => generateOrderData(idx, rng, tracker),
|
|
243
|
-
tracker,
|
|
244
|
-
(entityId, data) => tracker.addOrder(entityId, data.userId),
|
|
245
|
-
(current) => process.stdout.write(`\r Progress: ${current.toLocaleString()}/${config.orders.toLocaleString()}`)
|
|
246
|
-
);
|
|
247
|
-
console.log(`\n Done in ${((performance.now() - orderStart) / 1000).toFixed(1)}s`);
|
|
248
|
-
|
|
249
|
-
// Seed Order Items
|
|
250
|
-
console.log('\nSeeding Order Items...');
|
|
251
|
-
const itemStart = performance.now();
|
|
252
|
-
await seedComponent(
|
|
253
|
-
pg,
|
|
254
|
-
'BenchOrderItem',
|
|
255
|
-
config.orderItems,
|
|
256
|
-
(idx) => generateOrderItemData(idx, rng, tracker),
|
|
257
|
-
tracker,
|
|
258
|
-
undefined,
|
|
259
|
-
(current) => process.stdout.write(`\r Progress: ${current.toLocaleString()}/${config.orderItems.toLocaleString()}`)
|
|
260
|
-
);
|
|
261
|
-
console.log(`\n Done in ${((performance.now() - itemStart) / 1000).toFixed(1)}s`);
|
|
262
|
-
|
|
263
|
-
// Seed Reviews
|
|
264
|
-
console.log('\nSeeding Reviews...');
|
|
265
|
-
const reviewStart = performance.now();
|
|
266
|
-
await seedComponent(
|
|
267
|
-
pg,
|
|
268
|
-
'BenchReview',
|
|
269
|
-
config.reviews,
|
|
270
|
-
(idx) => generateReviewData(idx, rng, tracker),
|
|
271
|
-
tracker,
|
|
272
|
-
undefined,
|
|
273
|
-
(current) => process.stdout.write(`\r Progress: ${current.toLocaleString()}/${config.reviews.toLocaleString()}`)
|
|
274
|
-
);
|
|
275
|
-
console.log(`\n Done in ${((performance.now() - reviewStart) / 1000).toFixed(1)}s`);
|
|
276
|
-
|
|
277
|
-
// Run VACUUM ANALYZE
|
|
278
|
-
console.log('\nRunning VACUUM ANALYZE...');
|
|
279
|
-
await pg.exec('VACUUM ANALYZE entities');
|
|
280
|
-
await pg.exec('VACUUM ANALYZE components');
|
|
281
|
-
await pg.exec('VACUUM ANALYZE entity_components');
|
|
282
|
-
|
|
283
|
-
console.log('Syncing to disk...');
|
|
284
|
-
await pg.close();
|
|
285
|
-
|
|
286
|
-
const totalTime = (performance.now() - startTime) / 1000;
|
|
287
|
-
const recordsPerSecond = Math.round(totalEntities / totalTime);
|
|
288
|
-
|
|
289
|
-
console.log('\n=== Generation Complete ===');
|
|
290
|
-
console.log(`Total time: ${totalTime.toFixed(1)}s`);
|
|
291
|
-
console.log(`Records/second: ${recordsPerSecond.toLocaleString()}`);
|
|
292
|
-
console.log(`Database path: ${dbPath}`);
|
|
293
|
-
|
|
294
|
-
return {
|
|
295
|
-
tier,
|
|
296
|
-
totalEntities,
|
|
297
|
-
totalTime,
|
|
298
|
-
recordsPerSecond,
|
|
299
|
-
path: dbPath
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// Parse CLI arguments
|
|
304
|
-
const args = process.argv.slice(2);
|
|
305
|
-
const force = args.includes('--force');
|
|
306
|
-
const all = args.includes('--all');
|
|
307
|
-
const tierArg = args.find(a => !a.startsWith('--'));
|
|
308
|
-
|
|
309
|
-
if (!all && !tierArg) {
|
|
310
|
-
console.log('Usage: bun tests/benchmark/scripts/generate-db.ts [tier] [--force] [--all]');
|
|
311
|
-
console.log('\nTiers: xs, sm, md, lg, xl');
|
|
312
|
-
console.log('\nOptions:');
|
|
313
|
-
console.log(' --force Regenerate even if database exists');
|
|
314
|
-
console.log(' --all Generate all tiers');
|
|
315
|
-
console.log('\nExamples:');
|
|
316
|
-
console.log(' bun tests/benchmark/scripts/generate-db.ts xs');
|
|
317
|
-
console.log(' bun tests/benchmark/scripts/generate-db.ts md --force');
|
|
318
|
-
console.log(' bun tests/benchmark/scripts/generate-db.ts --all');
|
|
319
|
-
process.exit(1);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
if (all) {
|
|
323
|
-
console.log('Generating all database tiers...\n');
|
|
324
|
-
const results: GenerationResult[] = [];
|
|
325
|
-
|
|
326
|
-
for (const tier of Object.keys(TIERS) as Tier[]) {
|
|
327
|
-
results.push(await generateDatabase(tier, force));
|
|
328
|
-
console.log('');
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
console.log('\n=== Summary ===');
|
|
332
|
-
for (const r of results) {
|
|
333
|
-
console.log(`${r.tier.toUpperCase().padEnd(3)} | ${r.totalEntities.toLocaleString().padStart(10)} entities | ${r.totalTime.toFixed(1).padStart(6)}s | ${r.recordsPerSecond.toLocaleString().padStart(8)} rec/s`);
|
|
334
|
-
}
|
|
335
|
-
} else {
|
|
336
|
-
const tier = tierArg as Tier;
|
|
337
|
-
if (!TIERS[tier]) {
|
|
338
|
-
console.error(`Unknown tier: ${tier}`);
|
|
339
|
-
console.error('Valid tiers: xs, sm, md, lg, xl');
|
|
340
|
-
process.exit(1);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
await generateDatabase(tier, force);
|
|
344
|
-
}
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/**
|
|
3
|
-
* Benchmark runner script.
|
|
4
|
-
*
|
|
5
|
-
* Loads a pre-generated PGlite database and runs benchmarks against it.
|
|
6
|
-
* Sets up the correct environment variables before spawning the test process.
|
|
7
|
-
*
|
|
8
|
-
* Usage:
|
|
9
|
-
* bun tests/benchmark/scripts/run-benchmarks.ts [tier]
|
|
10
|
-
* bun tests/benchmark/scripts/run-benchmarks.ts xs
|
|
11
|
-
* bun tests/benchmark/scripts/run-benchmarks.ts md
|
|
12
|
-
*/
|
|
13
|
-
import { PGlite } from '@electric-sql/pglite';
|
|
14
|
-
import { PGLiteSocketServer } from '@electric-sql/pglite-socket';
|
|
15
|
-
import { existsSync } from 'node:fs';
|
|
16
|
-
import { join, dirname } from 'node:path';
|
|
17
|
-
import { fileURLToPath } from 'node:url';
|
|
18
|
-
import { spawn } from 'child_process';
|
|
19
|
-
|
|
20
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
21
|
-
const DATABASES_DIR = join(__dirname, '..', 'databases');
|
|
22
|
-
const PORT = 54322;
|
|
23
|
-
|
|
24
|
-
type Tier = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
25
|
-
|
|
26
|
-
const tier = (process.argv[2] || 'xs') as Tier;
|
|
27
|
-
const dbPath = join(DATABASES_DIR, tier);
|
|
28
|
-
|
|
29
|
-
if (!existsSync(dbPath)) {
|
|
30
|
-
console.error(`Benchmark database for tier '${tier}' not found at ${dbPath}`);
|
|
31
|
-
console.error('\nGenerate it first with:');
|
|
32
|
-
console.error(` bun tests/benchmark/scripts/generate-db.ts ${tier}`);
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
console.log(`[benchmark] Loading ${tier.toUpperCase()} tier database from ${dbPath}...`);
|
|
37
|
-
|
|
38
|
-
const pg = new PGlite(dbPath);
|
|
39
|
-
await pg.waitReady;
|
|
40
|
-
|
|
41
|
-
// Verify database has data
|
|
42
|
-
const countResult = await pg.query<{ count: string }>('SELECT COUNT(*) as count FROM entities');
|
|
43
|
-
const entityCount = parseInt(countResult.rows[0]?.count || '0');
|
|
44
|
-
|
|
45
|
-
if (entityCount === 0) {
|
|
46
|
-
await pg.close();
|
|
47
|
-
console.error(`Benchmark database for tier '${tier}' is empty.`);
|
|
48
|
-
console.error('Regenerate with:');
|
|
49
|
-
console.error(` bun tests/benchmark/scripts/generate-db.ts ${tier} --force`);
|
|
50
|
-
process.exit(1);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
console.log(`[benchmark] Loaded ${entityCount.toLocaleString()} entities`);
|
|
54
|
-
|
|
55
|
-
const server = new PGLiteSocketServer({ db: pg, port: PORT });
|
|
56
|
-
await server.start();
|
|
57
|
-
console.log(`[benchmark] Socket server running on port ${PORT}`);
|
|
58
|
-
|
|
59
|
-
// Spawn the test process with correct env vars set before import
|
|
60
|
-
// Use --config to specify benchmark-specific bunfig without the standard preload
|
|
61
|
-
const proc = spawn('bun', ['test', '--config', 'tests/benchmark/bunfig.toml', 'tests/benchmark/scenarios/', '--timeout', '300000'], {
|
|
62
|
-
env: {
|
|
63
|
-
...process.env,
|
|
64
|
-
SKIP_TEST_DB_SETUP: 'true',
|
|
65
|
-
USE_PGLITE: 'true',
|
|
66
|
-
BENCHMARK_TIER: tier,
|
|
67
|
-
// Clear DB_CONNECTION_URL so individual POSTGRES_* vars take precedence
|
|
68
|
-
DB_CONNECTION_URL: '',
|
|
69
|
-
POSTGRES_HOST: 'localhost',
|
|
70
|
-
POSTGRES_PORT: String(PORT),
|
|
71
|
-
POSTGRES_USER: 'postgres',
|
|
72
|
-
POSTGRES_PASSWORD: 'postgres',
|
|
73
|
-
POSTGRES_DB: 'postgres',
|
|
74
|
-
POSTGRES_MAX_CONNECTIONS: '10',
|
|
75
|
-
LOG_LEVEL: 'info',
|
|
76
|
-
// Disable direct partition access since PGlite uses a single components table
|
|
77
|
-
BUNSANE_USE_DIRECT_PARTITION: 'false',
|
|
78
|
-
// Disable LATERAL joins - they don't work correctly with INTERSECT queries
|
|
79
|
-
BUNSANE_USE_LATERAL_JOINS: 'false',
|
|
80
|
-
},
|
|
81
|
-
stdio: 'inherit',
|
|
82
|
-
cwd: join(__dirname, '..', '..', '..'),
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
proc.on('exit', async (code) => {
|
|
86
|
-
console.log('[benchmark] Stopping server...');
|
|
87
|
-
try { await server.stop(); } catch {}
|
|
88
|
-
try { await pg.close(); } catch {}
|
|
89
|
-
process.exit(code ?? 1);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
proc.on('error', async (err) => {
|
|
93
|
-
console.error('[benchmark] Failed to spawn bun test:', err);
|
|
94
|
-
try { await server.stop(); } catch {}
|
|
95
|
-
try { await pg.close(); } catch {}
|
|
96
|
-
process.exit(1);
|
|
97
|
-
});
|
package/tests/e2e/http.test.ts
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeAll, afterAll } from "bun:test";
|
|
2
|
-
import App from "../../core/App";
|
|
3
|
-
|
|
4
|
-
const PORT = 19876;
|
|
5
|
-
const BASE = `http://localhost:${PORT}`;
|
|
6
|
-
|
|
7
|
-
let app: App;
|
|
8
|
-
|
|
9
|
-
beforeAll(async () => {
|
|
10
|
-
app = new App("E2E Test App", "0.0.1");
|
|
11
|
-
// Start without init() — skips DB/component lifecycle
|
|
12
|
-
process.env.APP_PORT = String(PORT);
|
|
13
|
-
await app.start();
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
afterAll(async () => {
|
|
17
|
-
await app.shutdown();
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
describe("E2E HTTP Routes", () => {
|
|
21
|
-
it("GET /health returns JSON with expected structure", async () => {
|
|
22
|
-
const res = await fetch(`${BASE}/health`);
|
|
23
|
-
expect(res.headers.get("Content-Type")).toBe("application/json");
|
|
24
|
-
const body = await res.json();
|
|
25
|
-
expect(body).toHaveProperty("status");
|
|
26
|
-
expect(body).toHaveProperty("timestamp");
|
|
27
|
-
expect(body).toHaveProperty("uptime");
|
|
28
|
-
expect(body).toHaveProperty("checks");
|
|
29
|
-
expect(body.checks).toHaveProperty("database");
|
|
30
|
-
expect(body.checks).toHaveProperty("cache");
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("GET /health/ready returns 200 when server is up", async () => {
|
|
34
|
-
const res = await fetch(`${BASE}/health/ready`);
|
|
35
|
-
const body = await res.json();
|
|
36
|
-
expect(body).toHaveProperty("status");
|
|
37
|
-
expect(body).toHaveProperty("timestamp");
|
|
38
|
-
expect(body).toHaveProperty("uptime");
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("GET /metrics returns JSON with process and cache stats", async () => {
|
|
42
|
-
const res = await fetch(`${BASE}/metrics`);
|
|
43
|
-
expect(res.status).toBe(200);
|
|
44
|
-
expect(res.headers.get("Content-Type")).toBe("application/json");
|
|
45
|
-
const body = await res.json();
|
|
46
|
-
expect(body).toHaveProperty("timestamp");
|
|
47
|
-
expect(body).toHaveProperty("uptime");
|
|
48
|
-
expect(body).toHaveProperty("process");
|
|
49
|
-
expect(body.process).toHaveProperty("rss");
|
|
50
|
-
expect(body.process).toHaveProperty("heapUsed");
|
|
51
|
-
expect(body).toHaveProperty("scheduler");
|
|
52
|
-
expect(body).toHaveProperty("preparedStatements");
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("GET /openapi.json returns valid JSON", async () => {
|
|
56
|
-
const res = await fetch(`${BASE}/openapi.json`);
|
|
57
|
-
expect(res.status).toBe(200);
|
|
58
|
-
expect(res.headers.get("Content-Type")).toBe("application/json");
|
|
59
|
-
const body = await res.json();
|
|
60
|
-
expect(body).toHaveProperty("openapi");
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("GET /docs returns HTML with swagger-ui", async () => {
|
|
64
|
-
const res = await fetch(`${BASE}/docs`);
|
|
65
|
-
expect(res.status).toBe(200);
|
|
66
|
-
expect(res.headers.get("Content-Type")).toBe("text/html");
|
|
67
|
-
const html = await res.text();
|
|
68
|
-
expect(html).toContain("swagger-ui");
|
|
69
|
-
expect(html).toContain("E2E Test App");
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("GET /nonexistent returns 404", async () => {
|
|
73
|
-
const res = await fetch(`${BASE}/nonexistent`);
|
|
74
|
-
expect(res.status).toBe(404);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it("OPTIONS /health returns 204 when CORS configured", async () => {
|
|
78
|
-
app.setCors({ origin: "*" });
|
|
79
|
-
// Preflight must carry Origin header per CORS spec; otherwise the
|
|
80
|
-
// server emits no Access-Control-Allow-Origin (no `|| '*'` fallback).
|
|
81
|
-
const res = await fetch(`${BASE}/health`, {
|
|
82
|
-
method: "OPTIONS",
|
|
83
|
-
headers: { Origin: "https://client.example" },
|
|
84
|
-
});
|
|
85
|
-
expect(res.status).toBe(204);
|
|
86
|
-
expect(res.headers.get("Access-Control-Allow-Origin")).toBe("*");
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it("Security headers: responses include standard security headers when middleware registered", async () => {
|
|
90
|
-
// Import and register the security headers middleware
|
|
91
|
-
const { securityHeaders } = await import("../../core/middleware/SecurityHeaders");
|
|
92
|
-
app.use(securityHeaders());
|
|
93
|
-
// Re-compose middleware to include new middleware - access start() sets composedHandler
|
|
94
|
-
// For this test, we need to trigger re-composition. Calling start() again would
|
|
95
|
-
// bind another server. Instead, test that middleware works by verifying next request.
|
|
96
|
-
// Actually, composedHandler is set in start(), adding middleware after start() won't
|
|
97
|
-
// take effect. So we just verify the security headers are NOT present (middleware not active).
|
|
98
|
-
const res = await fetch(`${BASE}/health`);
|
|
99
|
-
// Middleware was added after start(), so it's not in the composed chain yet.
|
|
100
|
-
// This verifies the baseline — security header tests belong in unit tests.
|
|
101
|
-
expect(res.headers.get("Content-Type")).toBe("application/json");
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it("Shutdown completes without error and is idempotent", async () => {
|
|
105
|
-
const shutdownApp = new App("Shutdown Test", "0.0.1");
|
|
106
|
-
const shutdownPort = 19877;
|
|
107
|
-
process.env.APP_PORT = String(shutdownPort);
|
|
108
|
-
await shutdownApp.start();
|
|
109
|
-
|
|
110
|
-
// Verify server responds before shutdown
|
|
111
|
-
const before = await fetch(`http://localhost:${shutdownPort}/openapi.json`);
|
|
112
|
-
expect(before.status).toBe(200);
|
|
113
|
-
|
|
114
|
-
// Shutdown completes without throwing
|
|
115
|
-
await shutdownApp.shutdown();
|
|
116
|
-
|
|
117
|
-
// Second shutdown is a no-op (idempotent)
|
|
118
|
-
await shutdownApp.shutdown();
|
|
119
|
-
|
|
120
|
-
// Restore port for other tests
|
|
121
|
-
process.env.APP_PORT = String(PORT);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it("Request timeout returns 408 for long requests", async () => {
|
|
125
|
-
// This is hard to test without a slow endpoint. Verify the timeout
|
|
126
|
-
// mechanism exists by checking a fast request completes normally.
|
|
127
|
-
const res = await fetch(`${BASE}/openapi.json`);
|
|
128
|
-
expect(res.status).toBe(200);
|
|
129
|
-
});
|
|
130
|
-
});
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Test archetype for user entities
|
|
3
|
-
*/
|
|
4
|
-
import { BaseArcheType, ArcheType, ArcheTypeField } from '../../../core/ArcheType';
|
|
5
|
-
import { TestUser } from '../components/TestUser';
|
|
6
|
-
import { TestOrder } from '../components/TestOrder';
|
|
7
|
-
|
|
8
|
-
@ArcheType({ name: 'TestUserArchetype' })
|
|
9
|
-
export class TestUserArchetype extends BaseArcheType {
|
|
10
|
-
@ArcheTypeField(TestUser)
|
|
11
|
-
user!: TestUser;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
@ArcheType({ name: 'TestUserWithOrdersArchetype' })
|
|
15
|
-
export class TestUserWithOrdersArchetype extends BaseArcheType {
|
|
16
|
-
@ArcheTypeField(TestUser)
|
|
17
|
-
user!: TestUser;
|
|
18
|
-
|
|
19
|
-
@ArcheTypeField(TestOrder)
|
|
20
|
-
order!: TestOrder;
|
|
21
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Test component representing an order
|
|
3
|
-
*/
|
|
4
|
-
import { BaseComponent } from '../../../core/components/BaseComponent';
|
|
5
|
-
import { Component, CompData } from '../../../core/components/Decorators';
|
|
6
|
-
|
|
7
|
-
@Component
|
|
8
|
-
export class TestOrder extends BaseComponent {
|
|
9
|
-
@CompData({ indexed: true })
|
|
10
|
-
orderNumber!: string;
|
|
11
|
-
|
|
12
|
-
@CompData()
|
|
13
|
-
total!: number;
|
|
14
|
-
|
|
15
|
-
@CompData()
|
|
16
|
-
status!: string;
|
|
17
|
-
|
|
18
|
-
@CompData()
|
|
19
|
-
createdAt!: Date;
|
|
20
|
-
|
|
21
|
-
@CompData({ nullable: true })
|
|
22
|
-
notes?: string;
|
|
23
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Test component representing a product
|
|
3
|
-
*/
|
|
4
|
-
import { BaseComponent } from '../../../core/components/BaseComponent';
|
|
5
|
-
import { Component, CompData } from '../../../core/components/Decorators';
|
|
6
|
-
|
|
7
|
-
@Component
|
|
8
|
-
export class TestProduct extends BaseComponent {
|
|
9
|
-
@CompData({ indexed: true })
|
|
10
|
-
sku!: string;
|
|
11
|
-
|
|
12
|
-
@CompData()
|
|
13
|
-
name!: string;
|
|
14
|
-
|
|
15
|
-
@CompData()
|
|
16
|
-
price!: number;
|
|
17
|
-
|
|
18
|
-
@CompData({ nullable: true })
|
|
19
|
-
description?: string;
|
|
20
|
-
|
|
21
|
-
@CompData()
|
|
22
|
-
inStock!: boolean;
|
|
23
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Test component representing a user
|
|
3
|
-
*/
|
|
4
|
-
import { BaseComponent } from '../../../core/components/BaseComponent';
|
|
5
|
-
import { Component, CompData } from '../../../core/components/Decorators';
|
|
6
|
-
|
|
7
|
-
@Component
|
|
8
|
-
export class TestUser extends BaseComponent {
|
|
9
|
-
@CompData({ indexed: true })
|
|
10
|
-
name!: string;
|
|
11
|
-
|
|
12
|
-
@CompData({ indexed: true })
|
|
13
|
-
email!: string;
|
|
14
|
-
|
|
15
|
-
@CompData()
|
|
16
|
-
age!: number;
|
|
17
|
-
|
|
18
|
-
@CompData({ nullable: true })
|
|
19
|
-
bio?: string;
|
|
20
|
-
}
|