@loomcore/api 0.2.35 → 0.2.37

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.
@@ -3,6 +3,7 @@ import { Pool } from 'pg';
3
3
  import { MongoClient } from 'mongodb';
4
4
  import fs from 'fs';
5
5
  import path from 'path';
6
+ import { pathToFileURL } from 'node:url';
6
7
  import { buildMongoUrl } from '../mongo-db/utils/build-mongo-url.util.js';
7
8
  import { buildPostgresUrl } from '../postgres/utils/build-postgres-url.util.js';
8
9
  import { getPostgresInitialSchema } from '../postgres/migrations/postgres-initial-schema.js';
@@ -144,14 +145,15 @@ export class MigrationRunner {
144
145
  };
145
146
  }
146
147
  else {
148
+ const importUrl = pathToFileURL(fullPath).href;
147
149
  return {
148
150
  name: f,
149
151
  up: async () => {
150
- const mod = await import(fullPath);
152
+ const mod = await import(importUrl);
151
153
  await mod.up({ context });
152
154
  },
153
155
  down: async () => {
154
- const mod = await import(fullPath);
156
+ const mod = await import(importUrl);
155
157
  await mod.down({ context });
156
158
  }
157
159
  };
@@ -29,16 +29,16 @@ export const getMongoInitialSchema = (dbConfig) => {
29
29
  migrations.push({
30
30
  name: "00000000000001_schema-organizations-domains",
31
31
  up: async ({ context: db }) => {
32
- await db.createCollection("organization_domains");
32
+ await db.createCollection("organizationDomains");
33
33
  await db
34
- .collection("organization_domains")
34
+ .collection("organizationDomains")
35
35
  .createIndex({ domain: 1 }, { unique: true });
36
36
  await db
37
- .collection("organization_domains")
37
+ .collection("organizationDomains")
38
38
  .createIndex({ organizationId: 1 });
39
39
  },
40
40
  down: async ({ context: db }) => {
41
- await db.collection("organization_domains").drop();
41
+ await db.collection("organizationDomains").drop();
42
42
  },
43
43
  });
44
44
  }
@@ -70,41 +70,41 @@ export const getMongoInitialSchema = (dbConfig) => {
70
70
  migrations.push({
71
71
  name: "00000000000003_schema-refresh-tokens",
72
72
  up: async ({ context: db }) => {
73
- await db.createCollection("refresh_tokens");
73
+ await db.createCollection("refreshTokens");
74
74
  await db
75
- .collection("refresh_tokens")
75
+ .collection("refreshTokens")
76
76
  .createIndex({ token: 1 }, { unique: true });
77
- await db.collection("refresh_tokens").createIndex({ userId: 1 });
78
- await db.collection("refresh_tokens").createIndex({ deviceId: 1 });
77
+ await db.collection("refreshTokens").createIndex({ userId: 1 });
78
+ await db.collection("refreshTokens").createIndex({ deviceId: 1 });
79
79
  if (isMultiTenant) {
80
- await db.collection("refresh_tokens").createIndex({ _orgId: 1 });
80
+ await db.collection("refreshTokens").createIndex({ _orgId: 1 });
81
81
  }
82
82
  },
83
83
  down: async ({ context: db }) => {
84
- await db.collection("refresh_tokens").drop();
84
+ await db.collection("refreshTokens").drop();
85
85
  },
86
86
  });
87
87
  if (isAuthEnabled)
88
88
  migrations.push({
89
89
  name: "00000000000004_schema-password-reset-tokens",
90
90
  up: async ({ context: db }) => {
91
- await db.createCollection("password_reset_tokens");
91
+ await db.createCollection("passwordResetTokens");
92
92
  if (isMultiTenant) {
93
93
  await db
94
- .collection("password_reset_tokens")
94
+ .collection("passwordResetTokens")
95
95
  .createIndex({ _orgId: 1, email: 1 }, { unique: true });
96
96
  await db
97
- .collection("password_reset_tokens")
97
+ .collection("passwordResetTokens")
98
98
  .createIndex({ _orgId: 1 });
99
99
  }
100
100
  else {
101
101
  await db
102
- .collection("password_reset_tokens")
102
+ .collection("passwordResetTokens")
103
103
  .createIndex({ email: 1 }, { unique: true });
104
104
  }
105
105
  },
106
106
  down: async ({ context: db }) => {
107
- await db.collection("password_reset_tokens").drop();
107
+ await db.collection("passwordResetTokens").drop();
108
108
  },
109
109
  });
110
110
  if (isAuthEnabled)
@@ -132,23 +132,23 @@ export const getMongoInitialSchema = (dbConfig) => {
132
132
  migrations.push({
133
133
  name: "00000000000006_schema-user-roles",
134
134
  up: async ({ context: db }) => {
135
- await db.createCollection("user_roles");
135
+ await db.createCollection("userRoles");
136
136
  if (isMultiTenant) {
137
137
  await db
138
- .collection("user_roles")
138
+ .collection("userRoles")
139
139
  .createIndex({ _orgId: 1, userId: 1, roleId: 1 }, { unique: true });
140
- await db.collection("user_roles").createIndex({ _orgId: 1 });
140
+ await db.collection("userRoles").createIndex({ _orgId: 1 });
141
141
  }
142
142
  else {
143
143
  await db
144
- .collection("user_roles")
144
+ .collection("userRoles")
145
145
  .createIndex({ userId: 1, roleId: 1 }, { unique: true });
146
146
  }
147
- await db.collection("user_roles").createIndex({ userId: 1 });
148
- await db.collection("user_roles").createIndex({ roleId: 1 });
147
+ await db.collection("userRoles").createIndex({ userId: 1 });
148
+ await db.collection("userRoles").createIndex({ roleId: 1 });
149
149
  },
150
150
  down: async ({ context: db }) => {
151
- await db.collection("user_roles").drop();
151
+ await db.collection("userRoles").drop();
152
152
  },
153
153
  });
154
154
  if (isAuthEnabled)
@@ -222,7 +222,7 @@ export const getMongoInitialSchema = (dbConfig) => {
222
222
  };
223
223
  const metaOrgDomains = dbConfig.multiTenant.metaOrgDomains ?? [];
224
224
  if (metaOrgDomains.length > 0) {
225
- await db.collection("organization_domains").insertMany(metaOrgDomains.map((domain) => ({
225
+ await db.collection("organizationDomains").insertMany(metaOrgDomains.map((domain) => ({
226
226
  organizationId: metaOrg._id,
227
227
  domain,
228
228
  _created: new Date(),
@@ -234,7 +234,7 @@ export const getMongoInitialSchema = (dbConfig) => {
234
234
  initializeSystemUserContext(dbConfig.email?.systemEmailAddress || "system@example.com", metaOrg);
235
235
  },
236
236
  down: async ({ context: db }) => {
237
- await db.collection("organization_domains").deleteMany({});
237
+ await db.collection("organizationDomains").deleteMany({});
238
238
  await db.collection("organizations").deleteMany({ isMetaOrg: true });
239
239
  },
240
240
  });
@@ -299,7 +299,7 @@ export const getMongoInitialSchema = (dbConfig) => {
299
299
  ...orgDoc,
300
300
  name: "admin",
301
301
  });
302
- await db.collection("user_roles").insertOne({
302
+ await db.collection("userRoles").insertOne({
303
303
  ...orgDoc,
304
304
  userId: adminUser._id,
305
305
  roleId: roleResult.insertedId,
@@ -355,7 +355,7 @@ export const getMongoInitialSchema = (dbConfig) => {
355
355
  .collection("features")
356
356
  .deleteMany({ ...orgFilter, name: "admin" });
357
357
  if (adminRole) {
358
- await db.collection("user_roles").deleteMany({
358
+ await db.collection("userRoles").deleteMany({
359
359
  ...orgFilter,
360
360
  roleId: adminRole._id,
361
361
  });
@@ -12,7 +12,7 @@ import { get as getQuery } from "./queries/postgres-get.query.js";
12
12
  import { getAll as getAllQuery } from "./queries/postgres-get-all.query.js";
13
13
  import { getById as getByIdQuery } from "./queries/postgres-get-by-id.query.js";
14
14
  import { getCount as getCountQuery } from "./queries/postgres-get-count.query.js";
15
- import { convertKeysToCamelCase, convertKeysToSnakeCase, } from "./utils/convert-keys.util.js";
15
+ import { convertKeysToCamelCase, convertKeysToSnakeCase, toPostgresStoreName, } from "./utils/convert-keys.util.js";
16
16
  import { convertNullToUndefined } from "./utils/convert-null-to-undefined.util.js";
17
17
  export class PostgresDatabase {
18
18
  connection;
@@ -36,45 +36,45 @@ export class PostgresDatabase {
36
36
  return convertKeysToCamelCase(withNullsConverted);
37
37
  }
38
38
  async getAll(operations, pluralResourceName) {
39
- return getAllQuery(this.connection, operations, pluralResourceName);
39
+ return getAllQuery(this.connection, operations, toPostgresStoreName(pluralResourceName));
40
40
  }
41
41
  async get(operations, queryOptions, modelSpec, pluralResourceName) {
42
- return getQuery(this.connection, operations, queryOptions, pluralResourceName);
42
+ return getQuery(this.connection, operations, queryOptions, toPostgresStoreName(pluralResourceName));
43
43
  }
44
44
  async getById(operations, queryObject, id, pluralResourceName) {
45
- return getByIdQuery(this.connection, operations, queryObject, id, pluralResourceName);
45
+ return getByIdQuery(this.connection, operations, queryObject, id, toPostgresStoreName(pluralResourceName));
46
46
  }
47
47
  async getCount(pluralResourceName) {
48
- return getCountQuery(this.connection, pluralResourceName);
48
+ return getCountQuery(this.connection, toPostgresStoreName(pluralResourceName));
49
49
  }
50
50
  async create(entity, pluralResourceName) {
51
- return createCommand(this.connection, pluralResourceName, entity);
51
+ return createCommand(this.connection, toPostgresStoreName(pluralResourceName), entity);
52
52
  }
53
53
  async createMany(entities, pluralResourceName) {
54
- return createManyCommand(this.connection, pluralResourceName, entities);
54
+ return createManyCommand(this.connection, toPostgresStoreName(pluralResourceName), entities);
55
55
  }
56
56
  async batchUpdate(entities, operations, queryObject, pluralResourceName) {
57
- return batchUpdateCommand(this.connection, entities, operations, queryObject, pluralResourceName);
57
+ return batchUpdateCommand(this.connection, entities, operations, queryObject, toPostgresStoreName(pluralResourceName));
58
58
  }
59
59
  async fullUpdateById(operations, id, entity, pluralResourceName) {
60
- return fullUpdateByIdCommand(this.connection, operations, id, entity, pluralResourceName);
60
+ return fullUpdateByIdCommand(this.connection, operations, id, entity, toPostgresStoreName(pluralResourceName));
61
61
  }
62
62
  async partialUpdateById(operations, id, entity, pluralResourceName) {
63
- return partialUpdateByIdCommand(this.connection, operations, id, entity, pluralResourceName);
63
+ return partialUpdateByIdCommand(this.connection, operations, id, entity, toPostgresStoreName(pluralResourceName));
64
64
  }
65
65
  async update(queryObject, entity, operations, pluralResourceName) {
66
- return updateCommand(this.connection, queryObject, entity, operations, pluralResourceName);
66
+ return updateCommand(this.connection, queryObject, entity, operations, toPostgresStoreName(pluralResourceName));
67
67
  }
68
68
  async deleteById(id, pluralResourceName) {
69
- return deleteByIdCommand(this.connection, id, pluralResourceName);
69
+ return deleteByIdCommand(this.connection, id, toPostgresStoreName(pluralResourceName));
70
70
  }
71
71
  async deleteMany(queryObject, pluralResourceName) {
72
- return deleteManyCommand(this.connection, queryObject, pluralResourceName);
72
+ return deleteManyCommand(this.connection, queryObject, toPostgresStoreName(pluralResourceName));
73
73
  }
74
74
  async find(queryObject, pluralResourceName) {
75
- return findQuery(this.connection, queryObject, pluralResourceName);
75
+ return findQuery(this.connection, queryObject, toPostgresStoreName(pluralResourceName));
76
76
  }
77
77
  async findOne(queryObject, pluralResourceName) {
78
- return findOneQuery(this.connection, queryObject, pluralResourceName);
78
+ return findOneQuery(this.connection, queryObject, toPostgresStoreName(pluralResourceName));
79
79
  }
80
80
  }
@@ -1,6 +1,7 @@
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 { toPostgresStoreName } from "./convert-keys.util.js";
4
5
  function getParentAlias(localField) {
5
6
  if (!localField.includes('.'))
6
7
  return null;
@@ -45,7 +46,8 @@ export function buildJoinClauses(operations, mainTableName, options) {
45
46
  else {
46
47
  leftSide = mainTableName ? `"${mainTableName}"."${operation.localField}"` : `"${operation.localField}"`;
47
48
  }
48
- joinClauses.push(`${joinType} "${operation.from}" AS "${operation.as}" ON ${leftSide} = "${operation.as}"."${operation.foreignField}"`);
49
+ const fromTable = toPostgresStoreName(operation.from);
50
+ joinClauses.push(`${joinType} "${fromTable}" AS "${operation.as}" ON ${leftSide} = "${operation.as}"."${operation.foreignField}"`);
49
51
  }
50
52
  }
51
53
  return joinClauses.join(' ');
@@ -1,15 +1,17 @@
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 { toPostgresStoreName } from './convert-keys.util.js';
4
5
  const PK = '_id';
5
6
  export async function getTableColumns(client, tableName) {
7
+ const storeName = toPostgresStoreName(tableName);
6
8
  const result = await client.query(`
7
9
  SELECT column_name
8
10
  FROM information_schema.columns
9
11
  WHERE table_schema = current_schema()
10
12
  AND table_name = $1
11
13
  ORDER BY ordinal_position
12
- `, [tableName]);
14
+ `, [storeName]);
13
15
  return result.rows.map(row => row.column_name);
14
16
  }
15
17
  function getParentAlias(localField) {
@@ -93,19 +95,21 @@ async function buildManyValue(client, op, operations, mainTableName, parentAlias
93
95
  if (throughJoin) {
94
96
  const throughAlias = throughJoin.as;
95
97
  const linkColumn = op.localField.split('.')[1];
96
- fromClause = `"${throughJoin.from}" AS "${throughAlias}" INNER JOIN "${op.from}" AS "${alias}" ON "${throughAlias}"."${linkColumn}" = "${alias}"."${op.foreignField}"`;
98
+ const throughFrom = toPostgresStoreName(throughJoin.from);
99
+ const opFrom = toPostgresStoreName(op.from);
100
+ fromClause = `"${throughFrom}" AS "${throughAlias}" INNER JOIN "${opFrom}" AS "${alias}" ON "${throughAlias}"."${linkColumn}" = "${alias}"."${op.foreignField}"`;
97
101
  const throughParentRef = buildParentRef(throughJoin.localField, mainTableName);
98
102
  whereClause = `"${throughAlias}"."${throughJoin.foreignField}" = ${throughParentRef}`;
99
103
  }
100
104
  else {
101
- fromClause = `"${op.from}" AS "${alias}"`;
105
+ fromClause = `"${toPostgresStoreName(op.from)}" AS "${alias}"`;
102
106
  whereClause = `"${alias}"."${op.foreignField}" = ${parentRef}`;
103
107
  }
104
108
  const throughAliasesUsedBySiblings = getThroughAliasesUsedBySiblings(childOps);
105
109
  const oneToOneChildren = childOps.filter(c => c instanceof LeftJoin || c instanceof InnerJoin);
106
110
  for (const child of oneToOneChildren) {
107
111
  const leftCol = child.localField.includes('.') ? child.localField.split('.')[1] : child.localField;
108
- fromClause += ` LEFT JOIN "${child.from}" AS "${child.as}" ON "${alias}"."${leftCol}" = "${child.as}"."${child.foreignField}"`;
112
+ fromClause += ` LEFT JOIN "${toPostgresStoreName(child.from)}" AS "${child.as}" ON "${alias}"."${leftCol}" = "${child.as}"."${child.foreignField}"`;
109
113
  }
110
114
  const itemEntries = columns.map(col => ({
111
115
  key: col,
@@ -1,4 +1,5 @@
1
1
  export declare function toSnakeCase(str: string): string;
2
+ export declare function toPostgresStoreName(logicalResourceName: string): string;
2
3
  export declare function toCamelCase(str: string): string;
3
4
  export declare function convertKeysToSnakeCase<T>(obj: T): T;
4
5
  export declare function convertKeysToCamelCase<T>(obj: T): T;
@@ -7,6 +7,9 @@ export function toSnakeCase(str) {
7
7
  .replace(/^_|_$/g, '');
8
8
  return result;
9
9
  }
10
+ export function toPostgresStoreName(logicalResourceName) {
11
+ return toSnakeCase(logicalResourceName);
12
+ }
10
13
  export function toCamelCase(str) {
11
14
  return str.split(/[-_]+/)
12
15
  .map((word, index) => {
@@ -1,8 +1,10 @@
1
+ import { toPostgresStoreName } from "./convert-keys.util.js";
1
2
  export async function doesTableExist(client, tableName) {
3
+ const storeName = toPostgresStoreName(tableName);
2
4
  const result = await client.query(`
3
5
  SELECT EXISTS (
4
6
  SELECT 1 FROM information_schema.tables WHERE table_schema = current_schema() AND table_name = $1
5
7
  )
6
- `, [tableName]);
8
+ `, [storeName]);
7
9
  return result.rows[0].exists;
8
10
  }
@@ -3,4 +3,5 @@ export * from './does-table-exist.util.js';
3
3
  export * from './build-where-clause.js';
4
4
  export * from './build-pagination-clause.js';
5
5
  export * from './build-order-by-clause.js';
6
+ export * from './convert-keys.util.js';
6
7
  export * from './convert-null-to-undefined.util.js';
@@ -3,4 +3,5 @@ export * from './does-table-exist.util.js';
3
3
  export * from './build-where-clause.js';
4
4
  export * from './build-pagination-clause.js';
5
5
  export * from './build-order-by-clause.js';
6
+ export * from './convert-keys.util.js';
6
7
  export * from './convert-null-to-undefined.util.js';
@@ -2,6 +2,6 @@ import { OrganizationDomainSpec, } from "@loomcore/common/models";
2
2
  import { GenericApiService } from "./generic-api-service/generic-api.service.js";
3
3
  export class OrganizationDomainService extends GenericApiService {
4
4
  constructor(database) {
5
- super(database, "organization_domains", "organization_domain", OrganizationDomainSpec);
5
+ super(database, "organizationDomains", "organizationDomain", OrganizationDomainSpec);
6
6
  }
7
7
  }
@@ -3,7 +3,7 @@ import { PasswordResetTokenSpec, } from "@loomcore/common/models";
3
3
  import { MultiTenantApiService } from "./multi-tenant-api.service.js";
4
4
  export class PasswordResetTokenService extends MultiTenantApiService {
5
5
  constructor(database) {
6
- super(database, "password_reset_tokens", "password_reset_token", PasswordResetTokenSpec);
6
+ super(database, "passwordResetTokens", "passwordResetToken", PasswordResetTokenSpec);
7
7
  }
8
8
  async createPasswordResetToken(userContext, email, expiresOn) {
9
9
  const lowerCaseEmail = email.toLowerCase();
@@ -2,6 +2,6 @@ import { refreshTokenModelSpec } from "@loomcore/common/models";
2
2
  import { MultiTenantApiService } from "./multi-tenant-api.service.js";
3
3
  export class RefreshTokenService extends MultiTenantApiService {
4
4
  constructor(database) {
5
- super(database, "refresh_tokens", "refresh_token", refreshTokenModelSpec);
5
+ super(database, "refreshTokens", "refreshToken", refreshTokenModelSpec);
6
6
  }
7
7
  }
@@ -3,7 +3,7 @@ import { MultiTenantApiService } from "./multi-tenant-api.service.js";
3
3
  import { assertUserHasFeature } from "../utils/index.js";
4
4
  export class UserRolesService extends MultiTenantApiService {
5
5
  constructor(database) {
6
- super(database, "user_roles", "user_role", UserRoleModelSpec);
6
+ super(database, "userRoles", "userRole", UserRoleModelSpec);
7
7
  }
8
8
  async preProcessEntity(userContext, entity, isCreate, allowId = true) {
9
9
  assertUserHasFeature(userContext, ['admin', 'system', 'user-roles']);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loomcore/api",
3
- "version": "0.2.35",
3
+ "version": "0.2.37",
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": {