@loomcore/api 0.1.116 → 0.1.118
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/dist/__tests__/common-test.utils.d.ts +2 -1
- package/dist/__tests__/common-test.utils.js +1 -1
- package/dist/__tests__/models/product-with-category.model.d.ts +10 -12
- package/dist/__tests__/models/product-with-category.model.js +15 -9
- package/dist/__tests__/models/product.model.d.ts +1 -1
- package/dist/__tests__/models/product.model.js +1 -1
- package/dist/__tests__/postgres.test-database.js +11 -7
- package/dist/databases/postgres/utils/build-join-clauses.js +9 -11
- package/dist/databases/postgres/utils/build-select-clause.js +1 -13
- package/package.json +3 -2
- package/dist/databases/postgres/utils/build-things.util.d.ts +0 -2
- package/dist/databases/postgres/utils/build-things.util.js +0 -3
|
@@ -7,6 +7,7 @@ import { GenericApiService } from '../services/index.js';
|
|
|
7
7
|
import { IDatabase } from '../databases/models/index.js';
|
|
8
8
|
import { ICategory } from './models/category.model.js';
|
|
9
9
|
import { IProduct } from './models/product.model.js';
|
|
10
|
+
import { IProductWithCategory } from './models/product-with-category.model.js';
|
|
10
11
|
import { DbType } from '../databases/db-type.type.js';
|
|
11
12
|
declare function initialize(database: IDatabase): void;
|
|
12
13
|
declare function getRandomId(): string;
|
|
@@ -30,7 +31,7 @@ export declare class CategoryController extends ApiController<ICategory> {
|
|
|
30
31
|
constructor(app: Application, database: IDatabase);
|
|
31
32
|
}
|
|
32
33
|
export declare function setupTestConfig(isMultiTenant: boolean | undefined, dbType: DbType): void;
|
|
33
|
-
export declare class ProductService extends GenericApiService<
|
|
34
|
+
export declare class ProductService extends GenericApiService<IProductWithCategory> {
|
|
34
35
|
constructor(database: IDatabase);
|
|
35
36
|
}
|
|
36
37
|
export declare class ProductsController extends ApiController<IProduct> {
|
|
@@ -238,7 +238,7 @@ const prepareQueryCustom = (userContext, queryObject, operations) => {
|
|
|
238
238
|
queryObject: queryObject,
|
|
239
239
|
operations: [
|
|
240
240
|
...operations,
|
|
241
|
-
new LeftJoin('categories', '
|
|
241
|
+
new LeftJoin('categories', 'category_id', '_id', 'category')
|
|
242
242
|
]
|
|
243
243
|
};
|
|
244
244
|
};
|
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
import { ICategory } from "./category.model.js";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
description?: string;
|
|
6
|
-
internalNumber?: string;
|
|
2
|
+
import { IProduct } from "./product.model.js";
|
|
3
|
+
import { IEntity } from "@loomcore/common/models";
|
|
4
|
+
export interface IProductWithCategory extends IProduct, IEntity {
|
|
7
5
|
category: ICategory;
|
|
8
6
|
}
|
|
9
|
-
export declare const ProductWithCategorySchema: import("@sinclair/typebox").TObject<{
|
|
7
|
+
export declare const ProductWithCategorySchema: import("@sinclair/typebox").TIntersect<[import("@sinclair/typebox").TObject<{
|
|
10
8
|
name: import("@sinclair/typebox").TString;
|
|
11
9
|
description: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
12
10
|
internalNumber: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
11
|
+
categoryId: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TString, import("@sinclair/typebox").TNumber]>;
|
|
12
|
+
}>, import("@sinclair/typebox").TObject<{
|
|
13
13
|
category: import("@sinclair/typebox").TSchema;
|
|
14
|
-
}>;
|
|
15
|
-
export declare const
|
|
16
|
-
export declare const PublicProductWithCategorySchema: import("@sinclair/typebox").TObject<{
|
|
17
|
-
name: import("@sinclair/typebox").TString;
|
|
18
|
-
description: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
14
|
+
}>]>;
|
|
15
|
+
export declare const ProductWithCategoryPublicSchema: import("@sinclair/typebox").TIntersect<[import("@sinclair/typebox").TObject<{}>, import("@sinclair/typebox").TObject<{
|
|
19
16
|
category: import("@sinclair/typebox").TSchema;
|
|
20
|
-
}>;
|
|
17
|
+
}>]>;
|
|
18
|
+
export declare const ProductWithCategorySpec: import("@loomcore/common/models").IModelSpec<import("@sinclair/typebox").TSchema>;
|
|
21
19
|
export declare const ProductWithCategoryPublicSpec: import("@loomcore/common/models").IModelSpec<import("@sinclair/typebox").TSchema>;
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { entityUtils } from "@loomcore/common/utils";
|
|
2
2
|
import { Type } from "@sinclair/typebox";
|
|
3
3
|
import { CategorySpec } from "./category.model.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
export const
|
|
12
|
-
|
|
4
|
+
import { ProductPublicSchema, ProductSchema } from "./product.model.js";
|
|
5
|
+
export const ProductWithCategorySchema = Type.Intersect([
|
|
6
|
+
ProductSchema,
|
|
7
|
+
Type.Object({
|
|
8
|
+
category: CategorySpec.fullSchema,
|
|
9
|
+
})
|
|
10
|
+
]);
|
|
11
|
+
export const ProductWithCategoryPublicSchema = Type.Intersect([
|
|
12
|
+
ProductPublicSchema,
|
|
13
|
+
Type.Object({
|
|
14
|
+
category: CategorySpec.fullSchema,
|
|
15
|
+
})
|
|
16
|
+
]);
|
|
17
|
+
export const ProductWithCategorySpec = entityUtils.getModelSpec(ProductWithCategorySchema);
|
|
18
|
+
export const ProductWithCategoryPublicSpec = entityUtils.getModelSpec(ProductWithCategoryPublicSchema);
|
|
@@ -13,4 +13,4 @@ export declare const ProductSchema: import("@sinclair/typebox").TObject<{
|
|
|
13
13
|
categoryId: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TString, import("@sinclair/typebox").TNumber]>;
|
|
14
14
|
}>;
|
|
15
15
|
export declare const ProductSpec: import("@loomcore/common/models").IModelSpec<import("@sinclair/typebox").TSchema>;
|
|
16
|
-
export declare const
|
|
16
|
+
export declare const ProductPublicSchema: import("@sinclair/typebox").TObject<{}>;
|
|
@@ -7,4 +7,4 @@ export const ProductSchema = Type.Object({
|
|
|
7
7
|
categoryId: Type.Union([Type.String({ title: 'Category ID' }), Type.Number({ title: 'Category ID' })]),
|
|
8
8
|
});
|
|
9
9
|
export const ProductSpec = entityUtils.getModelSpec(ProductSchema, { isAuditable: true });
|
|
10
|
-
export const
|
|
10
|
+
export const ProductPublicSchema = Type.Omit(ProductSpec.fullSchema, ['internalNumber']);
|
|
@@ -53,7 +53,6 @@ export class TestPostgresDatabase {
|
|
|
53
53
|
connectionString,
|
|
54
54
|
connectionTimeoutMillis: 5000,
|
|
55
55
|
});
|
|
56
|
-
this.postgresPool = pool;
|
|
57
56
|
}
|
|
58
57
|
else {
|
|
59
58
|
const { Client } = newDb().adapters.createPg();
|
|
@@ -61,8 +60,8 @@ export class TestPostgresDatabase {
|
|
|
61
60
|
await postgresClient.connect();
|
|
62
61
|
pool = postgresClient;
|
|
63
62
|
}
|
|
64
|
-
|
|
65
|
-
this.database =
|
|
63
|
+
this.postgresPool = pool;
|
|
64
|
+
this.database = new PostgresDatabase(postgresClient);
|
|
66
65
|
this.postgresClient = postgresClient;
|
|
67
66
|
const { initializeSystemUserContext, isSystemUserContextInitialized } = await import('@loomcore/common/models');
|
|
68
67
|
if (!isSystemUserContextInitialized()) {
|
|
@@ -70,7 +69,7 @@ export class TestPostgresDatabase {
|
|
|
70
69
|
}
|
|
71
70
|
await runInitialSchemaMigrations(pool, config);
|
|
72
71
|
await runTestSchemaMigrations(pool, config);
|
|
73
|
-
testUtils.initialize(
|
|
72
|
+
testUtils.initialize(this.database);
|
|
74
73
|
await this.createIndexes(postgresClient);
|
|
75
74
|
await testUtils.createMetaOrg();
|
|
76
75
|
}
|
|
@@ -97,9 +96,14 @@ export class TestPostgresDatabase {
|
|
|
97
96
|
WHERE "table_schema" = 'public'
|
|
98
97
|
AND "table_type" = 'BASE TABLE'
|
|
99
98
|
`);
|
|
100
|
-
|
|
101
|
-
await this.postgresClient?.query(`TRUNCATE TABLE "${row.table_name}" RESTART IDENTITY CASCADE`);
|
|
102
|
-
}
|
|
99
|
+
if (USE_REAL_POSTGRES) {
|
|
100
|
+
await this.postgresClient?.query(`TRUNCATE TABLE ${result.rows.map(row => `"${row.table_name}"`).join(', ')} RESTART IDENTITY CASCADE`);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
result.rows.forEach(async (row) => {
|
|
104
|
+
await this.postgresClient?.query(`TRUNCATE TABLE "${row.table_name}" RESTART IDENTITY CASCADE`);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
103
107
|
}
|
|
104
108
|
async cleanup() {
|
|
105
109
|
if (this.database) {
|
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
import { LeftJoin } from "../../operations/left-join.operation.js";
|
|
2
2
|
import { InnerJoin } from "../../operations/inner-join.operation.js";
|
|
3
3
|
import { LeftJoinMany } from "../../operations/left-join-many.operation.js";
|
|
4
|
-
import { toSnakeCase } from "./convert-keys.util.js";
|
|
5
|
-
function convertFieldToSnakeCase(field) {
|
|
6
|
-
if (field.startsWith("_")) {
|
|
7
|
-
return field;
|
|
8
|
-
}
|
|
9
|
-
return toSnakeCase(field);
|
|
10
|
-
}
|
|
11
4
|
export function buildJoinClauses(operations, mainTableName) {
|
|
12
5
|
const joinClauses = [];
|
|
13
6
|
for (const operation of operations) {
|
|
14
7
|
if (operation instanceof LeftJoin || operation instanceof InnerJoin || operation instanceof LeftJoinMany) {
|
|
15
8
|
const joinType = operation instanceof InnerJoin ? "INNER JOIN" : "LEFT JOIN";
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
9
|
+
let leftSide;
|
|
10
|
+
if (operation.localField.includes(".")) {
|
|
11
|
+
const [alias, column] = operation.localField.split(".");
|
|
12
|
+
leftSide = `"${alias}"."${column}"`;
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
leftSide = mainTableName ? `"${mainTableName}"."${operation.localField}"` : `"${operation.localField}"`;
|
|
16
|
+
}
|
|
17
|
+
joinClauses.push(`${joinType} "${operation.from}" AS "${operation.as}" ON ${leftSide} = "${operation.as}"."${operation.foreignField}"`);
|
|
20
18
|
}
|
|
21
19
|
}
|
|
22
20
|
return joinClauses.join(' ');
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { LeftJoin } from '../../operations/left-join.operation.js';
|
|
2
2
|
import { InnerJoin } from '../../operations/inner-join.operation.js';
|
|
3
3
|
import { LeftJoinMany } from '../../operations/left-join-many.operation.js';
|
|
4
|
-
import { buildAs } from './build-things.util.js';
|
|
5
4
|
async function getTableColumns(client, tableName) {
|
|
6
5
|
const result = await client.query(`
|
|
7
6
|
SELECT column_name
|
|
@@ -12,17 +11,6 @@ async function getTableColumns(client, tableName) {
|
|
|
12
11
|
`, [tableName]);
|
|
13
12
|
return result.rows.map(row => row.column_name);
|
|
14
13
|
}
|
|
15
|
-
function findEnrichmentTarget(operation, operations) {
|
|
16
|
-
if (!operation.localField.includes('.')) {
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
const [alias] = operation.localField.split('.');
|
|
20
|
-
const target = operations.find(op => op instanceof LeftJoinMany && op.as === alias);
|
|
21
|
-
if (target && operations.indexOf(target) < operations.indexOf(operation)) {
|
|
22
|
-
return { target, field: operation.localField.split('.')[1] };
|
|
23
|
-
}
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
14
|
export async function buildSelectClause(client, mainTableName, operations) {
|
|
27
15
|
const leftJoinOperations = operations.filter(op => op instanceof LeftJoin);
|
|
28
16
|
const innerJoinOperations = operations.filter(op => op instanceof InnerJoin);
|
|
@@ -33,7 +21,7 @@ export async function buildSelectClause(client, mainTableName, operations) {
|
|
|
33
21
|
for (const join of [...leftJoinOperations, ...innerJoinOperations, ...leftJoinManyOperations]) {
|
|
34
22
|
const joinColumns = await getTableColumns(client, join.from);
|
|
35
23
|
for (const col of joinColumns) {
|
|
36
|
-
joinSelects.push(
|
|
24
|
+
joinSelects.push(`"${join.as}"."${col}" AS "${join.as}__${col}"`);
|
|
37
25
|
}
|
|
38
26
|
}
|
|
39
27
|
const allSelects = [...mainSelects, ...joinSelects];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loomcore/api",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.118",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Loom Core Api - An opinionated Node.js api using Typescript, Express, and MongoDb or PostgreSQL",
|
|
6
6
|
"scripts": {
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"typecheck": "tsc",
|
|
20
20
|
"test": "npm-run-all -s test:postgres test:mongodb",
|
|
21
21
|
"test:postgres": "cross-env NODE_ENV=test TEST_DATABASE=postgres vitest run",
|
|
22
|
-
"test:postgres:real": "cross-env NODE_ENV=test TEST_DATABASE=postgres USE_REAL_POSTGRES=true vitest run",
|
|
22
|
+
"test:postgres:real-test": "cross-env NODE_ENV=test TEST_DATABASE=postgres USE_REAL_POSTGRES=true vitest run",
|
|
23
|
+
"test:postgres:real": "npm-run-all -s test:db:start test:postgres:real-test test:db:stop",
|
|
23
24
|
"test:mongodb": "cross-env NODE_ENV=test TEST_DATABASE=mongodb vitest run",
|
|
24
25
|
"test:ci": "npm-run-all -s test:ci:postgres test:ci:mongodb",
|
|
25
26
|
"test:ci:postgres": "cross-env NODE_ENV=test TEST_DATABASE=postgres vitest run --reporter=json --outputFile=test-results-postgres.json",
|