metal-orm 1.0.8 → 1.0.10

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 (153) hide show
  1. package/README.md +341 -146
  2. package/dist/decorators/index.cjs +2564 -0
  3. package/dist/decorators/index.cjs.map +1 -0
  4. package/dist/decorators/index.d.cts +53 -0
  5. package/dist/decorators/index.d.ts +53 -0
  6. package/dist/decorators/index.js +2530 -0
  7. package/dist/decorators/index.js.map +1 -0
  8. package/dist/index.cjs +4227 -0
  9. package/dist/index.cjs.map +1 -0
  10. package/dist/index.d.cts +701 -0
  11. package/dist/index.d.ts +701 -0
  12. package/dist/index.js +4131 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/select-654m4qy8.d.cts +1522 -0
  15. package/dist/select-654m4qy8.d.ts +1522 -0
  16. package/package.json +27 -20
  17. package/src/codegen/typescript.ts +405 -393
  18. package/src/core/ast/aggregate-functions.ts +30 -0
  19. package/src/core/ast/builders.ts +43 -0
  20. package/src/core/ast/expression-builders.ts +310 -0
  21. package/src/core/ast/expression-nodes.ts +211 -0
  22. package/src/core/ast/expression-visitor.ts +99 -0
  23. package/src/core/ast/expression.ts +5 -0
  24. package/src/{utils → core/ast}/join-node.ts +20 -20
  25. package/src/{ast → core/ast}/join.ts +18 -18
  26. package/src/{ast → core/ast}/query.ts +113 -113
  27. package/src/core/ast/window-functions.ts +140 -0
  28. package/src/{dialect → core/dialect}/abstract.ts +94 -94
  29. package/src/{dialect → core/dialect}/mssql/index.ts +31 -31
  30. package/src/{dialect → core/dialect}/mysql/index.ts +31 -31
  31. package/src/{dialect → core/dialect}/postgres/index.ts +45 -45
  32. package/src/{dialect → core/dialect}/sqlite/index.ts +45 -45
  33. package/src/{constants → core/sql}/sql-operator-config.ts +39 -39
  34. package/src/decorators/bootstrap.ts +126 -0
  35. package/src/decorators/column.ts +78 -0
  36. package/src/decorators/entity.ts +36 -0
  37. package/src/decorators/index.ts +4 -0
  38. package/src/decorators/relations.ts +107 -0
  39. package/src/global.d.ts +1 -0
  40. package/src/index.ts +22 -22
  41. package/src/orm/db-executor.ts +11 -0
  42. package/src/orm/domain-event-bus.ts +52 -0
  43. package/src/{runtime → orm}/entity-meta.ts +52 -52
  44. package/src/orm/entity-metadata.ts +140 -0
  45. package/src/{runtime → orm}/entity.ts +252 -252
  46. package/src/{runtime → orm}/execute.ts +36 -36
  47. package/src/{runtime → orm}/hydration.ts +103 -103
  48. package/src/orm/identity-map.ts +37 -0
  49. package/src/{runtime → orm}/lazy-batch.ts +205 -205
  50. package/src/orm/orm-context.ts +154 -0
  51. package/src/orm/relation-change-processor.ts +140 -0
  52. package/src/{runtime → orm}/relations/belongs-to.ts +92 -92
  53. package/src/{runtime → orm}/relations/has-many.ts +111 -111
  54. package/src/{runtime → orm}/relations/many-to-many.ts +149 -149
  55. package/src/orm/runtime-types.ts +39 -0
  56. package/src/orm/transaction-runner.ts +17 -0
  57. package/src/orm/unit-of-work.ts +232 -0
  58. package/src/{builder/operations → query-builder}/column-selector.ts +78 -78
  59. package/src/{builder → query-builder}/delete-query-state.ts +38 -42
  60. package/src/{builder → query-builder}/delete.ts +46 -57
  61. package/src/{builder → query-builder}/hydration-manager.ts +87 -87
  62. package/src/{builder → query-builder}/hydration-planner.ts +182 -182
  63. package/src/{builder → query-builder}/insert-query-state.ts +51 -62
  64. package/src/{builder → query-builder}/insert.ts +48 -59
  65. package/src/{builder → query-builder}/query-ast-service.ts +208 -226
  66. package/src/{utils → query-builder}/raw-column-parser.ts +32 -32
  67. package/src/{builder → query-builder}/relation-conditions.ts +112 -112
  68. package/src/{builder/operations → query-builder}/relation-manager.ts +82 -82
  69. package/src/{builder → query-builder}/relation-projection-helper.ts +101 -101
  70. package/src/{builder → query-builder}/relation-service.ts +284 -284
  71. package/src/{builder → query-builder}/relation-types.ts +21 -21
  72. package/src/{builder → query-builder}/relation-utils.ts +12 -12
  73. package/src/{builder → query-builder}/select-query-builder-deps.ts +112 -94
  74. package/src/{builder → query-builder}/select-query-state.ts +179 -179
  75. package/src/{builder → query-builder}/select.ts +78 -69
  76. package/src/{builder → query-builder}/update-query-state.ts +55 -59
  77. package/src/{builder → query-builder}/update.ts +50 -61
  78. package/src/schema/column.ts +25 -25
  79. package/src/schema/relation.ts +116 -116
  80. package/src/schema/table.ts +34 -34
  81. package/src/schema/types.ts +76 -76
  82. package/.github/workflows/publish-metal-orm.yml +0 -38
  83. package/ROADMAP.md +0 -125
  84. package/docs/CHANGES.md +0 -104
  85. package/docs/advanced-features.md +0 -176
  86. package/docs/api-reference.md +0 -31
  87. package/docs/dml-operations.md +0 -156
  88. package/docs/getting-started.md +0 -171
  89. package/docs/hydration.md +0 -115
  90. package/docs/index.md +0 -36
  91. package/docs/multi-dialect-support.md +0 -59
  92. package/docs/query-builder.md +0 -135
  93. package/docs/runtime.md +0 -105
  94. package/docs/schema-definition.md +0 -112
  95. package/metadata.json +0 -5
  96. package/playground/api/playground-api.ts +0 -94
  97. package/playground/index.html +0 -15
  98. package/playground/src/App.css +0 -1
  99. package/playground/src/App.tsx +0 -114
  100. package/playground/src/components/CodeDisplay.tsx +0 -43
  101. package/playground/src/components/QueryExecutor.tsx +0 -189
  102. package/playground/src/components/ResultsTable.tsx +0 -67
  103. package/playground/src/components/ResultsTabs.tsx +0 -105
  104. package/playground/src/components/ScenarioList.tsx +0 -56
  105. package/playground/src/components/logo.svg +0 -45
  106. package/playground/src/data/scenarios.ts +0 -2
  107. package/playground/src/main.tsx +0 -9
  108. package/playground/src/services/PlaygroundApiService.ts +0 -60
  109. package/postcss.config.cjs +0 -5
  110. package/sql_sql-ansi-cheatsheet-2025.md +0 -264
  111. package/src/ast/expression.ts +0 -658
  112. package/src/builder/operations/cte-manager.ts +0 -34
  113. package/src/builder/operations/filter-manager.ts +0 -68
  114. package/src/builder/operations/join-manager.ts +0 -36
  115. package/src/builder/operations/pagination-manager.ts +0 -36
  116. package/src/playground/features/playground/api/types.ts +0 -16
  117. package/src/playground/features/playground/clients/MockClient.ts +0 -17
  118. package/src/playground/features/playground/clients/SqliteClient.ts +0 -57
  119. package/src/playground/features/playground/common/IDatabaseClient.ts +0 -10
  120. package/src/playground/features/playground/data/scenarios/aggregation.ts +0 -36
  121. package/src/playground/features/playground/data/scenarios/basics.ts +0 -25
  122. package/src/playground/features/playground/data/scenarios/edge_cases.ts +0 -57
  123. package/src/playground/features/playground/data/scenarios/filtering.ts +0 -94
  124. package/src/playground/features/playground/data/scenarios/hydration.ts +0 -27
  125. package/src/playground/features/playground/data/scenarios/index.ts +0 -29
  126. package/src/playground/features/playground/data/scenarios/ordering.ts +0 -25
  127. package/src/playground/features/playground/data/scenarios/pagination.ts +0 -16
  128. package/src/playground/features/playground/data/scenarios/relationships.ts +0 -75
  129. package/src/playground/features/playground/data/scenarios/types.ts +0 -70
  130. package/src/playground/features/playground/data/schema.ts +0 -91
  131. package/src/playground/features/playground/data/seed.ts +0 -104
  132. package/src/playground/features/playground/services/QueryExecutionService.ts +0 -121
  133. package/src/runtime/orm-context.ts +0 -539
  134. package/tests/belongs-to-many.test.ts +0 -57
  135. package/tests/between.test.ts +0 -43
  136. package/tests/case-expression.test.ts +0 -58
  137. package/tests/complex-exists.test.ts +0 -230
  138. package/tests/cte.test.ts +0 -118
  139. package/tests/dml.test.ts +0 -206
  140. package/tests/exists.test.ts +0 -127
  141. package/tests/like.test.ts +0 -33
  142. package/tests/orm-runtime.test.ts +0 -254
  143. package/tests/postgres.test.ts +0 -30
  144. package/tests/right-join.test.ts +0 -89
  145. package/tests/subquery-having.test.ts +0 -193
  146. package/tests/window-function.test.ts +0 -151
  147. package/tsconfig.json +0 -30
  148. package/tsup.config.ts +0 -10
  149. package/vite.config.ts +0 -22
  150. package/vitest.config.ts +0 -14
  151. /package/src/{constants → core/sql}/sql.ts +0 -0
  152. /package/src/{runtime → orm}/als.ts +0 -0
  153. /package/src/{utils → query-builder}/relation-alias.ts +0 -0
@@ -1,68 +0,0 @@
1
- import { ColumnDef } from '../../schema/column';
2
- import { ColumnNode, ExpressionNode } from '../../ast/expression';
3
- import { SelectQueryBuilderContext, SelectQueryBuilderEnvironment } from '../select-query-builder-deps';
4
- import { OrderDirection } from '../../constants/sql';
5
-
6
- /**
7
- * Manages filtering, ordering, and grouping operations for queries
8
- */
9
- export class FilterManager {
10
- /**
11
- * Creates a new FilterManager instance
12
- * @param env - Query builder environment
13
- */
14
- constructor(private readonly env: SelectQueryBuilderEnvironment) {}
15
-
16
- /**
17
- * Adds a WHERE clause to the query
18
- * @param context - Current query context
19
- * @param expr - Expression for the WHERE clause
20
- * @returns Updated query context with WHERE clause
21
- */
22
- where(context: SelectQueryBuilderContext, expr: ExpressionNode): SelectQueryBuilderContext {
23
- const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
24
- const nextState = astService.withWhere(expr);
25
- return { state: nextState, hydration: context.hydration };
26
- }
27
-
28
- /**
29
- * Adds a GROUP BY clause to the query
30
- * @param context - Current query context
31
- * @param column - Column to group by
32
- * @returns Updated query context with GROUP BY clause
33
- */
34
- groupBy(context: SelectQueryBuilderContext, column: ColumnDef | ColumnNode): SelectQueryBuilderContext {
35
- const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
36
- const nextState = astService.withGroupBy(column);
37
- return { state: nextState, hydration: context.hydration };
38
- }
39
-
40
- /**
41
- * Adds a HAVING clause to the query
42
- * @param context - Current query context
43
- * @param expr - Expression for the HAVING clause
44
- * @returns Updated query context with HAVING clause
45
- */
46
- having(context: SelectQueryBuilderContext, expr: ExpressionNode): SelectQueryBuilderContext {
47
- const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
48
- const nextState = astService.withHaving(expr);
49
- return { state: nextState, hydration: context.hydration };
50
- }
51
-
52
- /**
53
- * Adds an ORDER BY clause to the query
54
- * @param context - Current query context
55
- * @param column - Column to order by
56
- * @param direction - Order direction (ASC/DESC)
57
- * @returns Updated query context with ORDER BY clause
58
- */
59
- orderBy(
60
- context: SelectQueryBuilderContext,
61
- column: ColumnDef | ColumnNode,
62
- direction: OrderDirection
63
- ): SelectQueryBuilderContext {
64
- const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
65
- const nextState = astService.withOrderBy(column, direction);
66
- return { state: nextState, hydration: context.hydration };
67
- }
68
- }
@@ -1,36 +0,0 @@
1
- import { BinaryExpressionNode } from '../../ast/expression';
2
- import { TableDef } from '../../schema/table';
3
- import { SelectQueryBuilderContext, SelectQueryBuilderEnvironment } from '../select-query-builder-deps';
4
- import { JoinKind } from '../../constants/sql';
5
- import { createJoinNode } from '../../utils/join-node';
6
-
7
- /**
8
- * Manages JOIN operations for query building
9
- */
10
- export class JoinManager {
11
- /**
12
- * Creates a new JoinManager instance
13
- * @param env - Query builder environment
14
- */
15
- constructor(private readonly env: SelectQueryBuilderEnvironment) {}
16
-
17
- /**
18
- * Adds a JOIN clause to the query
19
- * @param context - Current query context
20
- * @param table - Table to join
21
- * @param condition - Join condition expression
22
- * @param kind - Type of join (INNER, LEFT, RIGHT, etc.)
23
- * @returns Updated query context with JOIN clause
24
- */
25
- join(
26
- context: SelectQueryBuilderContext,
27
- table: TableDef,
28
- condition: BinaryExpressionNode,
29
- kind: JoinKind
30
- ): SelectQueryBuilderContext {
31
- const joinNode = createJoinNode(kind, table.name, condition);
32
- const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
33
- const nextState = astService.withJoin(joinNode);
34
- return { state: nextState, hydration: context.hydration };
35
- }
36
- }
@@ -1,36 +0,0 @@
1
- import { SelectQueryBuilderContext, SelectQueryBuilderEnvironment } from '../select-query-builder-deps';
2
-
3
- /**
4
- * Manages pagination operations (LIMIT and OFFSET) for queries
5
- */
6
- export class PaginationManager {
7
- /**
8
- * Creates a new PaginationManager instance
9
- * @param env - Query builder environment
10
- */
11
- constructor(private readonly env: SelectQueryBuilderEnvironment) {}
12
-
13
- /**
14
- * Adds a LIMIT clause to the query
15
- * @param context - Current query context
16
- * @param value - Maximum number of rows to return
17
- * @returns Updated query context with LIMIT clause
18
- */
19
- limit(context: SelectQueryBuilderContext, value: number): SelectQueryBuilderContext {
20
- const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
21
- const nextState = astService.withLimit(value);
22
- return { state: nextState, hydration: context.hydration };
23
- }
24
-
25
- /**
26
- * Adds an OFFSET clause to the query
27
- * @param context - Current query context
28
- * @param value - Number of rows to skip
29
- * @returns Updated query context with OFFSET clause
30
- */
31
- offset(context: SelectQueryBuilderContext, value: number): SelectQueryBuilderContext {
32
- const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
33
- const nextState = astService.withOffset(value);
34
- return { state: nextState, hydration: context.hydration };
35
- }
36
- }
@@ -1,16 +0,0 @@
1
- import type { QueryResult } from '../common/IDatabaseClient';
2
-
3
- export interface QueryExecutionResult {
4
- sql: string;
5
- params: unknown[];
6
- typescriptCode: string;
7
- results: QueryResult[];
8
- hydratedResults?: Record<string, any>[];
9
- error: string | null;
10
- executionTime: number;
11
- }
12
-
13
- export interface ApiStatusResponse {
14
- ready: boolean;
15
- error: string | null;
16
- }
@@ -1,17 +0,0 @@
1
- import { IDatabaseClient, QueryResult } from "../common/IDatabaseClient";
2
- import { DialectName } from "../../../../constants/sql";
3
-
4
- export type SupportedDialect = DialectName;
5
-
6
- export class MockClient implements IDatabaseClient {
7
- isReady: boolean = true;
8
- error: string | null = null;
9
-
10
- constructor(dialect: SupportedDialect) {
11
- this.error = `${dialect} is not supported yet.`;
12
- }
13
-
14
- public async executeSql(sql: string, _params: unknown[] = []): Promise<QueryResult[]> {
15
- return [];
16
- }
17
- }
@@ -1,57 +0,0 @@
1
- import { IDatabaseClient, QueryResult } from "../common/IDatabaseClient";
2
- import { SEED_SQL } from "../data/seed";
3
- import sqlite3 from 'sqlite3';
4
-
5
- export class SqliteClient implements IDatabaseClient {
6
- isReady: boolean = false;
7
- error: string | null = null;
8
- private db: sqlite3.Database | null = null;
9
-
10
- constructor() {
11
- this.initDB();
12
- }
13
-
14
- private async initDB() {
15
- try {
16
- this.db = new sqlite3.Database(':memory:');
17
- await new Promise<void>((resolve, reject) => {
18
- this.db!.exec(SEED_SQL, (err) => {
19
- if (err) reject(err);
20
- else resolve();
21
- });
22
- });
23
- this.isReady = true;
24
- } catch (e) {
25
- console.error("Failed to load DB", e);
26
- this.error = "Failed to initialize SQLite database.";
27
- }
28
- }
29
-
30
- public async executeSql(sql: string, params: unknown[] = []): Promise<QueryResult[]> {
31
- if (!this.db) {
32
- this.error = "Database not ready.";
33
- return [];
34
- }
35
-
36
- return new Promise((resolve, reject) => {
37
- this.db!.all(sql, params, (err, rows) => {
38
- if (err) {
39
- this.error = err.message;
40
- resolve([]);
41
- } else {
42
- this.error = null;
43
- if (rows.length === 0) {
44
- resolve([]);
45
- } else {
46
- const columns = Object.keys(rows[0]);
47
- const values = rows.map(row => columns.map(col => row[col]));
48
- resolve([{
49
- columns,
50
- values,
51
- }]);
52
- }
53
- }
54
- });
55
- });
56
- }
57
- }
@@ -1,10 +0,0 @@
1
- export type QueryResult = {
2
- columns: string[];
3
- values: any[][];
4
- };
5
-
6
- export interface IDatabaseClient {
7
- isReady: boolean;
8
- error: string | null;
9
- executeSql(sql: string, params?: unknown[]): Promise<QueryResult[]>;
10
- }
@@ -1,36 +0,0 @@
1
- import { eq, count, sum } from '../../../../../ast/expression';
2
- import { createLiteral } from '../../../../../builder/select';
3
- import { Users, Orders } from '../schema';
4
- import { Scenario } from './types';
5
-
6
- export const AGGREGATION_SCENARIOS = [
7
- {
8
- id: 'analytics',
9
- category: 'Aggregation',
10
- title: 'Sales Analytics',
11
- description: 'Aggregating sales data using GROUP BY and sorting with ORDER BY DESC.',
12
- build: (qb) => qb
13
- .select({
14
- user: Users.columns.name,
15
- orderCount: count(Orders.columns.id)
16
- })
17
- .joinRelation('orders')
18
- .groupBy(Users.columns.name)
19
- .orderBy(Users.columns.name, 'DESC')
20
- },
21
- {
22
- id: 'revenue_by_role',
23
- category: 'Aggregation',
24
- title: 'Revenue by Role',
25
- description: 'Summing completed order totals and grouping the result by user role.',
26
- build: (qb) => qb
27
- .select({
28
- role: Users.columns.role,
29
- revenue: sum(Orders.columns.total)
30
- })
31
- .joinRelation('orders')
32
- .where(eq(Orders.columns.status, createLiteral('completed')))
33
- .groupBy(Users.columns.role)
34
- .orderBy(Users.columns.role, 'ASC')
35
- }
36
- ];
@@ -1,25 +0,0 @@
1
- import { Users } from '../schema';
2
- import { createScenario } from './types';
3
-
4
- export const BASIC_SCENARIOS = [
5
- createScenario({
6
- id: 'basic',
7
- category: 'Basics',
8
- title: 'Hello World',
9
- description: 'A basic projection query fetching specific columns from the Users table.',
10
- build: (qb) => qb.select({ id: Users.columns.id, name: Users.columns.name }).limit(5)
11
- }),
12
- createScenario({
13
- id: 'aliased_projection',
14
- category: 'Basics',
15
- title: 'Aliased Projection',
16
- description: 'Expose friendly aliases when projecting columns for DTOs.',
17
- build: (qb) => qb
18
- .select({
19
- userId: Users.columns.id,
20
- userName: Users.columns.name,
21
- userRole: Users.columns.role
22
- })
23
- .limit(4)
24
- })
25
- ];
@@ -1,57 +0,0 @@
1
- import { eq, and, or, inList, jsonPath, isNull } from '../../../../../ast/expression';
2
- import { createLiteral } from '../../../../../builder/select';
3
- import { Users } from '../schema';
4
- import { Scenario } from './types';
5
-
6
- export const EDGE_CASE_SCENARIOS = [
7
- {
8
- id: 'empty_in_list',
9
- category: 'Edge Cases',
10
- title: 'Empty IN List',
11
- description: 'Filtering with an empty list in IN clause. Should return no results or handle gracefully.',
12
- build: (qb) => qb
13
- .select({ name: Users.columns.name })
14
- .where(inList(Users.columns.id, []))
15
- },
16
- {
17
- id: 'deep_nested_logic',
18
- category: 'Edge Cases',
19
- title: 'Deeply Nested Logic',
20
- description: 'Complex nested AND/OR logic to test parser/compiler robustness.',
21
- build: (qb) => qb
22
- .select({ name: Users.columns.name })
23
- .where(
24
- or(
25
- and(
26
- eq(Users.columns.role, 'admin'),
27
- or(
28
- eq(Users.columns.name, 'Alice'),
29
- eq(Users.columns.name, 'Bob')
30
- )
31
- ),
32
- and(
33
- eq(Users.columns.role, 'user'),
34
- isNull(Users.columns.deleted_at)
35
- )
36
- )
37
- )
38
- },
39
- {
40
- id: 'json_path_missing',
41
- category: 'Edge Cases',
42
- title: 'Non-existent JSON Path',
43
- description: 'Querying a JSON path that likely does not exist.',
44
- build: (qb) => qb
45
- .select({ name: Users.columns.name })
46
- .where(eq(jsonPath(Users.columns.settings, '$.non_existent_key'), createLiteral('some_value')))
47
- },
48
- {
49
- id: 'zero_limit',
50
- category: 'Edge Cases',
51
- title: 'Zero Limit',
52
- description: 'Limit 0 should return empty result set.',
53
- build: (qb) => qb
54
- .select({ name: Users.columns.name })
55
- .limit(0)
56
- }
57
- ];
@@ -1,94 +0,0 @@
1
- import { eq, and, or, gt, like, jsonPath, isNull, isNotNull, inList } from '../../../../../ast/expression';
2
- import { createLiteral } from '../../../../../builder/select';
3
- import { Users, Orders } from '../schema';
4
- import { createScenario } from './types';
5
-
6
- export const FILTERING_SCENARIOS = [
7
- createScenario({
8
- id: 'filter_basic',
9
- category: 'Filtering',
10
- title: 'Active Admins',
11
- description: 'Filtering records using a WHERE clause with type-safe operators.',
12
- build: (qb) => qb
13
- .select({ name: Users.columns.name, role: Users.columns.role })
14
- .where(eq(Users.columns.role, createLiteral('admin')))
15
- }),
16
- createScenario({
17
- id: 'search',
18
- category: 'Filtering',
19
- title: 'Text Search (LIKE)',
20
- description: 'Using pattern matching to find users starting with "Alice".',
21
- build: (qb) => qb
22
- .select({ name: Users.columns.name, role: Users.columns.role })
23
- .where(like(Users.columns.name, 'Alice%'))
24
- }),
25
- createScenario({
26
- id: 'json_filter',
27
- category: 'Filtering',
28
- title: 'JSON Extract',
29
- description: 'Extracting values from JSON columns using dialect-specific syntax.',
30
- build: (qb) => qb
31
- .select({ name: Users.columns.name, settings: Users.columns.settings })
32
- .where(eq(jsonPath(Users.columns.settings, '$.theme'), createLiteral('dark')))
33
- }),
34
- createScenario({
35
- id: 'advanced_filter',
36
- category: 'Filtering',
37
- title: 'Nulls & Lists',
38
- description: 'Advanced filtering using IN(...) clause and IS NULL checks.',
39
- build: (qb) => qb
40
- .select({ name: Users.columns.name, role: Users.columns.role })
41
- .where(
42
- and(
43
- inList(Users.columns.role, ['admin', 'manager']),
44
- isNull(Users.columns.deleted_at)
45
- )
46
- )
47
- }),
48
- createScenario({
49
- id: 'complex_filter',
50
- category: 'Filtering',
51
- title: 'Logical Groups',
52
- description: 'Using Nested AND/OR to filter for Admins OR users with specific ID.',
53
- build: (qb) => qb
54
- .select({ name: Users.columns.name, role: Users.columns.role })
55
- .where(
56
- or(
57
- eq(Users.columns.role, createLiteral('admin')),
58
- and(
59
- eq(Users.columns.role, createLiteral('user')),
60
- eq(Users.columns.id, createLiteral(2))
61
- )
62
- )
63
- )
64
- }),
65
- createScenario({
66
- id: 'soft_deleted_users',
67
- category: 'Filtering',
68
- title: 'Soft Deleted Users',
69
- description: 'Reveal users that were soft-deleted via a non-null deleted_at.',
70
- build: (qb) => qb
71
- .select({ name: Users.columns.name, deletedAt: Users.columns.deleted_at })
72
- .where(isNotNull(Users.columns.deleted_at))
73
- }),
74
- createScenario({
75
- id: 'high_value_admins',
76
- category: 'Filtering',
77
- title: 'High Value Admins',
78
- description: 'Filter admins with completed orders exceeding 200 via a joined WHERE clause.',
79
- build: (qb) => qb
80
- .select({
81
- name: Users.columns.name,
82
- role: Users.columns.role,
83
- total: Orders.columns.total
84
- })
85
- .joinRelation('orders')
86
- .where(
87
- and(
88
- eq(Users.columns.role, createLiteral('admin')),
89
- gt(Orders.columns.total, createLiteral(200)),
90
- eq(Orders.columns.status, createLiteral('completed'))
91
- )
92
- )
93
- })
94
- ];
@@ -1,27 +0,0 @@
1
- import { eq } from '../../../../../ast/expression';
2
- import { Users } from '../schema';
3
- import { createScenario } from './types';
4
-
5
- export const HYDRATION_SCENARIOS = [
6
- createScenario({
7
- id: 'user_with_orders',
8
- category: 'Hydration',
9
- title: 'Nested User Graph',
10
- description: 'Eager-load orders for a single user and hydrate a JSON graph from flat SQL rows.',
11
- build: (qb) => qb
12
- .include('orders', { columns: ['id', 'total', 'status', 'user_id'] })
13
- .where(eq(Users.columns.id, 1))
14
- }),
15
- createScenario({
16
- id: 'user_projects_with_pivot',
17
- category: 'Hydration',
18
- title: 'Projects with Pivot Data',
19
- description: 'Include projects for a user along with pivot metadata stored in `_pivot`.',
20
- build: (qb) => qb
21
- .include('projects', {
22
- columns: ['id', 'name', 'client'],
23
- pivot: { columns: ['assigned_at', 'role_id'] }
24
- })
25
- .where(eq(Users.columns.id, 1))
26
- })
27
- ];
@@ -1,29 +0,0 @@
1
- import type { Scenario } from './types';
2
- import { BASIC_SCENARIOS } from './basics';
3
- import { FILTERING_SCENARIOS } from './filtering';
4
- import { RELATIONSHIP_SCENARIOS } from './relationships';
5
- import { AGGREGATION_SCENARIOS } from './aggregation';
6
- import { PAGINATION_SCENARIOS } from './pagination';
7
- import { ORDERING_SCENARIOS } from './ordering';
8
- import { HYDRATION_SCENARIOS } from './hydration';
9
- import { EDGE_CASE_SCENARIOS } from './edge_cases';
10
-
11
- export * from './types';
12
- export { BASIC_SCENARIOS };
13
- export { FILTERING_SCENARIOS };
14
- export { RELATIONSHIP_SCENARIOS };
15
- export { AGGREGATION_SCENARIOS };
16
- export { PAGINATION_SCENARIOS };
17
- export { ORDERING_SCENARIOS };
18
- export { HYDRATION_SCENARIOS };
19
-
20
- export const SCENARIOS: Scenario[] = [
21
- ...BASIC_SCENARIOS,
22
- ...FILTERING_SCENARIOS,
23
- ...RELATIONSHIP_SCENARIOS,
24
- ...AGGREGATION_SCENARIOS,
25
- ...ORDERING_SCENARIOS,
26
- ...PAGINATION_SCENARIOS,
27
- ...HYDRATION_SCENARIOS,
28
- ...EDGE_CASE_SCENARIOS
29
- ];
@@ -1,25 +0,0 @@
1
-
2
- import { eq } from '../../../../../ast/expression';
3
- import { createLiteral } from '../../../../../builder/select';
4
- import { Users, Orders } from '../schema';
5
- import { Scenario } from './types';
6
-
7
- export const ORDERING_SCENARIOS: Scenario[] = [
8
- {
9
- id: 'order_recent_completed',
10
- category: 'Ordering',
11
- title: 'Order by Completed Total',
12
- description: 'Sort completed orders first by total (DESC) then by user name (ASC).',
13
- build: (qb) => qb
14
- .select({
15
- user: Users.columns.name,
16
- status: Orders.columns.status,
17
- total: Orders.columns.total
18
- })
19
- .joinRelation('orders')
20
- .where(eq(Orders.columns.status, createLiteral('completed')))
21
- .orderBy(Orders.columns.total, 'DESC')
22
- .orderBy(Users.columns.name, 'ASC')
23
- .limit(5)
24
- }
25
- ];
@@ -1,16 +0,0 @@
1
- import { Users } from '../schema';
2
- import { Scenario } from './types';
3
-
4
- export const PAGINATION_SCENARIOS: Scenario[] = [
5
- {
6
- id: 'pagination',
7
- category: 'Pagination',
8
- title: 'Basic Pagination',
9
- description: 'Implement basic pagination with LIMIT and OFFSET.',
10
- build: (qb) => qb
11
- .select({ name: Users.columns.name, role: Users.columns.role })
12
- .orderBy(Users.columns.name, 'ASC')
13
- .limit(2)
14
- .offset(1)
15
- }
16
- ];
@@ -1,75 +0,0 @@
1
- import { eq, gt, like, inList } from '../../../../../ast/expression';
2
- import { Users, Orders, Profiles, Roles, UserRoles } from '../schema';
3
- import { Scenario } from './types';
4
-
5
- export const RELATIONSHIP_SCENARIOS: Scenario[] = [
6
- {
7
- id: 'smart_join',
8
- category: 'Relationships',
9
- title: 'Smart Join (1:N)',
10
- description: 'Using defined schema relationships to join tables automatically without manual ON clauses.',
11
- build: (qb) => qb
12
- .select({ user: Users.columns.name, total: Orders.columns.total })
13
- .joinRelation('orders')
14
- .where(gt(Orders.columns.total, 100))
15
- },
16
- {
17
- id: 'join',
18
- category: 'Relationships',
19
- title: 'Manual Join',
20
- description: 'Performing a manual INNER JOIN with explicit conditions.',
21
- build: (qb) => qb
22
- .select({ user: Users.columns.name, amount: Orders.columns.total, status: Orders.columns.status })
23
- .innerJoin(Orders, eq(Users.columns.id, Orders.columns.user_id))
24
- .where(eq(Orders.columns.status, 'completed'))
25
- },
26
- {
27
- id: 'profile_inspection',
28
- category: 'Relationships',
29
- title: 'Profile Inspection (1:1)',
30
- description: 'Join every user with their profile and filter by bio keywords.',
31
- build: (qb) => qb
32
- .select({
33
- user: Users.columns.name,
34
- bio: Profiles.columns.bio,
35
- twitter: Profiles.columns.twitter
36
- })
37
- .joinRelation('profiles')
38
- .where(like(Profiles.columns.bio, '%Engineer%'))
39
- },
40
- {
41
- id: 'role_filter',
42
- category: 'Relationships',
43
- title: 'Role-Based Filter (N:N)',
44
- description: 'Traverse the pivot table to surface admins and managers.',
45
- build: (qb) => qb
46
- .select({
47
- user: Users.columns.name,
48
- role: Roles.columns.name
49
- })
50
- .innerJoin(UserRoles, eq(UserRoles.columns.user_id, Users.columns.id))
51
- .innerJoin(Roles, eq(Roles.columns.id, UserRoles.columns.role_id))
52
- .where(inList(Roles.columns.name, ['admin', 'manager']))
53
- .orderBy(Users.columns.name, 'ASC')
54
- },
55
- {
56
- id: 'contain_orders',
57
- category: 'Relationships',
58
- title: 'Contain Orders (LEFT JOIN + filter)',
59
- description: 'Fetch users and hydrate only completed orders without dropping users that have none.',
60
- build: (qb) => qb
61
- .include('orders', { columns: ['id', 'total', 'status', 'user_id'], filter: eq(Orders.columns.status, 'completed') })
62
- .select({ id: Users.columns.id, name: Users.columns.name, role: Users.columns.role })
63
- .orderBy(Users.columns.id, 'ASC')
64
- },
65
- {
66
- id: 'match_orders',
67
- category: 'Relationships',
68
- title: 'Match Orders (INNER JOIN + DISTINCT)',
69
- description: 'Return only users that match a relation predicate, similar to CakePHP matching().',
70
- build: (qb) => qb
71
- .match('orders', eq(Orders.columns.status, 'completed'))
72
- .select({ id: Users.columns.id, name: Users.columns.name })
73
- .orderBy(Users.columns.id, 'ASC')
74
- }
75
- ];