@loomcore/api 0.1.53 → 0.1.57

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 (96) hide show
  1. package/dist/__tests__/common-test.utils.d.ts +8 -2
  2. package/dist/__tests__/common-test.utils.js +50 -16
  3. package/dist/__tests__/index.d.ts +0 -1
  4. package/dist/__tests__/index.js +0 -1
  5. package/dist/__tests__/postgres-test-migrations/postgres-test-schema.d.ts +3 -0
  6. package/dist/__tests__/postgres-test-migrations/postgres-test-schema.js +97 -0
  7. package/dist/__tests__/postgres.test-database.js +9 -8
  8. package/dist/__tests__/setup/vitest-setup.js +15 -0
  9. package/dist/__tests__/test-objects.d.ts +6 -3
  10. package/dist/__tests__/test-objects.js +10 -2
  11. package/dist/controllers/api.controller.d.ts +2 -0
  12. package/dist/controllers/api.controller.js +66 -11
  13. package/dist/databases/migrations/migration-runner.js +6 -6
  14. package/dist/databases/models/database.interface.d.ts +9 -8
  15. package/dist/databases/mongo-db/commands/mongo-delete-by-id.command.d.ts +2 -1
  16. package/dist/databases/mongo-db/commands/mongo-full-updateby-id.command.d.ts +2 -1
  17. package/dist/databases/mongo-db/commands/mongo-partial-update-by-id.command.d.ts +2 -1
  18. package/dist/databases/mongo-db/migrations/{mongo-foundational.d.ts → mongo-initial-schema.d.ts} +1 -1
  19. package/dist/databases/mongo-db/migrations/mongo-initial-schema.js +264 -0
  20. package/dist/databases/mongo-db/mongo-db.database.d.ts +9 -8
  21. package/dist/databases/mongo-db/mongo-db.database.js +2 -2
  22. package/dist/databases/mongo-db/queries/mongo-get-by-id.query.d.ts +2 -1
  23. package/dist/databases/postgres/commands/postgres-batch-update.command.js +1 -1
  24. package/dist/databases/postgres/commands/postgres-create-many.command.d.ts +2 -1
  25. package/dist/databases/postgres/commands/postgres-create-many.command.js +23 -28
  26. package/dist/databases/postgres/commands/postgres-create.command.d.ts +2 -1
  27. package/dist/databases/postgres/commands/postgres-create.command.js +6 -5
  28. package/dist/databases/postgres/commands/postgres-delete-by-id.command.d.ts +2 -1
  29. package/dist/databases/postgres/commands/postgres-full-update-by-id.command.d.ts +2 -1
  30. package/dist/databases/postgres/commands/postgres-partial-update-by-id.command.d.ts +2 -1
  31. package/dist/databases/postgres/migrations/__tests__/test-migration-helper.d.ts +4 -0
  32. package/dist/databases/postgres/migrations/__tests__/test-migration-helper.js +67 -0
  33. package/dist/databases/postgres/migrations/index.d.ts +1 -2
  34. package/dist/databases/postgres/migrations/index.js +1 -2
  35. package/dist/databases/postgres/migrations/{postgres-foundational.d.ts → postgres-initial-schema.d.ts} +1 -1
  36. package/dist/databases/postgres/migrations/postgres-initial-schema.js +436 -0
  37. package/dist/databases/postgres/postgres.database.d.ts +10 -9
  38. package/dist/databases/postgres/postgres.database.js +2 -2
  39. package/dist/databases/postgres/queries/postgres-get-by-id.query.d.ts +2 -1
  40. package/dist/models/refresh-token.model.d.ts +5 -4
  41. package/dist/models/refresh-token.model.js +3 -3
  42. package/dist/services/auth.service.d.ts +3 -2
  43. package/dist/services/auth.service.js +4 -4
  44. package/dist/services/generic-api-service/generic-api-service.interface.d.ts +8 -7
  45. package/dist/services/generic-api-service/generic-api.service.d.ts +8 -7
  46. package/dist/services/generic-api-service/generic-api.service.js +23 -23
  47. package/dist/services/multi-tenant-api.service.d.ts +1 -1
  48. package/dist/services/multi-tenant-api.service.js +3 -3
  49. package/dist/services/organization.service.d.ts +4 -3
  50. package/dist/services/organization.service.js +3 -3
  51. package/dist/services/user.service.d.ts +3 -2
  52. package/dist/services/user.service.js +2 -2
  53. package/dist/services/utils/audit-for-create.util.js +2 -1
  54. package/dist/services/utils/audit-for-update.util.js +2 -1
  55. package/dist/services/utils/strip-sender-provided-system-properties.util.js +2 -1
  56. package/package.json +2 -2
  57. package/dist/__tests__/postgres-test-migrations/100-create-test-entities-table.migration.d.ts +0 -21
  58. package/dist/__tests__/postgres-test-migrations/100-create-test-entities-table.migration.js +0 -61
  59. package/dist/__tests__/postgres-test-migrations/101-create-categories-table.migration.d.ts +0 -21
  60. package/dist/__tests__/postgres-test-migrations/101-create-categories-table.migration.js +0 -51
  61. package/dist/__tests__/postgres-test-migrations/102-create-products-table.migration.d.ts +0 -21
  62. package/dist/__tests__/postgres-test-migrations/102-create-products-table.migration.js +0 -60
  63. package/dist/__tests__/postgres-test-migrations/103-create-test-items-table.migration.d.ts +0 -21
  64. package/dist/__tests__/postgres-test-migrations/103-create-test-items-table.migration.js +0 -59
  65. package/dist/__tests__/postgres-test-migrations/test-migrations.d.ts +0 -3
  66. package/dist/__tests__/postgres-test-migrations/test-migrations.js +0 -12
  67. package/dist/databases/mongo-db/migrations/mongo-foundational.js +0 -30
  68. package/dist/databases/postgres/migrations/001-create-migrations-table.migration.d.ts +0 -21
  69. package/dist/databases/postgres/migrations/001-create-migrations-table.migration.js +0 -51
  70. package/dist/databases/postgres/migrations/002-create-organizations-table.migration.d.ts +0 -21
  71. package/dist/databases/postgres/migrations/002-create-organizations-table.migration.js +0 -76
  72. package/dist/databases/postgres/migrations/003-create-users-table.migration.d.ts +0 -21
  73. package/dist/databases/postgres/migrations/003-create-users-table.migration.js +0 -77
  74. package/dist/databases/postgres/migrations/004-create-refresh-tokens-table.migration.d.ts +0 -21
  75. package/dist/databases/postgres/migrations/004-create-refresh-tokens-table.migration.js +0 -72
  76. package/dist/databases/postgres/migrations/005-create-meta-org.migration.d.ts +0 -22
  77. package/dist/databases/postgres/migrations/005-create-meta-org.migration.js +0 -64
  78. package/dist/databases/postgres/migrations/006-create-admin-user.migration.d.ts +0 -19
  79. package/dist/databases/postgres/migrations/006-create-admin-user.migration.js +0 -52
  80. package/dist/databases/postgres/migrations/007-create-roles-table.migration.d.ts +0 -21
  81. package/dist/databases/postgres/migrations/007-create-roles-table.migration.js +0 -65
  82. package/dist/databases/postgres/migrations/008-create-user-roles-table.migration.d.ts +0 -21
  83. package/dist/databases/postgres/migrations/008-create-user-roles-table.migration.js +0 -75
  84. package/dist/databases/postgres/migrations/009-create-features-table.migration.d.ts +0 -21
  85. package/dist/databases/postgres/migrations/009-create-features-table.migration.js +0 -65
  86. package/dist/databases/postgres/migrations/010-create-authorizations-table.migration.d.ts +0 -21
  87. package/dist/databases/postgres/migrations/010-create-authorizations-table.migration.js +0 -77
  88. package/dist/databases/postgres/migrations/011-create-admin-authorization.migration.d.ts +0 -23
  89. package/dist/databases/postgres/migrations/011-create-admin-authorization.migration.js +0 -130
  90. package/dist/databases/postgres/migrations/database-builder.d.ts +0 -15
  91. package/dist/databases/postgres/migrations/database-builder.interface.d.ts +0 -10
  92. package/dist/databases/postgres/migrations/database-builder.js +0 -65
  93. package/dist/databases/postgres/migrations/migration.interface.d.ts +0 -11
  94. package/dist/databases/postgres/migrations/migration.interface.js +0 -1
  95. package/dist/databases/postgres/migrations/postgres-foundational.js +0 -47
  96. /package/dist/{databases/postgres/migrations/database-builder.interface.js → __tests__/setup/vitest-setup.d.ts} +0 -0
@@ -1,3 +1,4 @@
1
1
  import { Db } from "mongodb";
2
2
  import { DeleteResult as GenericDeleteResult } from "../../models/delete-result.js";
3
- export declare function deleteById(db: Db, id: string, pluralResourceName: string): Promise<GenericDeleteResult>;
3
+ import type { AppIdType } from "@loomcore/common/types";
4
+ export declare function deleteById(db: Db, id: AppIdType, pluralResourceName: string): Promise<GenericDeleteResult>;
@@ -1,3 +1,4 @@
1
1
  import { Db } from "mongodb";
2
2
  import { Operation } from "../../operations/operation.js";
3
- export declare function fullUpdateById<T>(db: Db, operations: Operation[], id: string, entity: any, pluralResourceName: string): Promise<T>;
3
+ import type { AppIdType } from "@loomcore/common/types";
4
+ export declare function fullUpdateById<T>(db: Db, operations: Operation[], id: AppIdType, entity: any, pluralResourceName: string): Promise<T>;
@@ -1,3 +1,4 @@
1
1
  import { Db } from "mongodb";
2
2
  import { Operation } from "../../operations/operation.js";
3
- export declare function partialUpdateById<T>(db: Db, operations: Operation[], id: string, entity: Partial<any>, pluralResourceName: string): Promise<T>;
3
+ import type { AppIdType } from "@loomcore/common/types";
4
+ export declare function partialUpdateById<T>(db: Db, operations: Operation[], id: AppIdType, entity: Partial<any>, pluralResourceName: string): Promise<T>;
@@ -9,4 +9,4 @@ export interface SyntheticMigration {
9
9
  context: Db;
10
10
  }) => Promise<void>;
11
11
  }
12
- export declare const getMongoFoundational: (config: IBaseApiConfig) => SyntheticMigration[];
12
+ export declare const getMongoInitialSchema: (config: IBaseApiConfig) => SyntheticMigration[];
@@ -0,0 +1,264 @@
1
+ import { randomUUID } from 'crypto';
2
+ import { initializeSystemUserContext, EmptyUserContext, getSystemUserContext } from '@loomcore/common/models';
3
+ import { MongoDBDatabase } from '../mongo-db.database.js';
4
+ import { AuthService, OrganizationService } from '../../../services/index.js';
5
+ export const getMongoInitialSchema = (config) => {
6
+ const migrations = [];
7
+ const isMultiTenant = config.app.isMultiTenant === true;
8
+ if (isMultiTenant) {
9
+ migrations.push({
10
+ name: '00000000000001_schema-organizations',
11
+ up: async ({ context: db }) => {
12
+ await db.createCollection('organizations');
13
+ await db.collection('organizations').createIndex({ name: 1 }, { unique: true });
14
+ await db.collection('organizations').createIndex({ code: 1 }, { unique: true });
15
+ await db.collection('organizations').createIndex({ isMetaOrg: 1 });
16
+ },
17
+ down: async ({ context: db }) => {
18
+ await db.collection('organizations').drop();
19
+ }
20
+ });
21
+ }
22
+ migrations.push({
23
+ name: '00000000000002_schema-users',
24
+ up: async ({ context: db }) => {
25
+ await db.createCollection('users');
26
+ if (isMultiTenant) {
27
+ await db.collection('users').createIndex({ _orgId: 1, email: 1 }, { unique: true });
28
+ await db.collection('users').createIndex({ _orgId: 1 });
29
+ }
30
+ else {
31
+ await db.collection('users').createIndex({ email: 1 }, { unique: true });
32
+ }
33
+ },
34
+ down: async ({ context: db }) => {
35
+ await db.collection('users').drop();
36
+ }
37
+ });
38
+ migrations.push({
39
+ name: '00000000000003_schema-refresh-tokens',
40
+ up: async ({ context: db }) => {
41
+ await db.createCollection('refreshTokens');
42
+ await db.collection('refreshTokens').createIndex({ token: 1 }, { unique: true });
43
+ await db.collection('refreshTokens').createIndex({ userId: 1 });
44
+ await db.collection('refreshTokens').createIndex({ deviceId: 1 });
45
+ if (isMultiTenant) {
46
+ await db.collection('refreshTokens').createIndex({ _orgId: 1 });
47
+ }
48
+ },
49
+ down: async ({ context: db }) => {
50
+ await db.collection('refreshTokens').drop();
51
+ }
52
+ });
53
+ migrations.push({
54
+ name: '00000000000004_schema-roles',
55
+ up: async ({ context: db }) => {
56
+ await db.createCollection('roles');
57
+ if (isMultiTenant) {
58
+ await db.collection('roles').createIndex({ _orgId: 1, name: 1 }, { unique: true });
59
+ await db.collection('roles').createIndex({ _orgId: 1 });
60
+ }
61
+ else {
62
+ await db.collection('roles').createIndex({ name: 1 }, { unique: true });
63
+ }
64
+ },
65
+ down: async ({ context: db }) => {
66
+ await db.collection('roles').drop();
67
+ }
68
+ });
69
+ migrations.push({
70
+ name: '00000000000005_schema-user-roles',
71
+ up: async ({ context: db }) => {
72
+ await db.createCollection('user_roles');
73
+ if (isMultiTenant) {
74
+ await db.collection('user_roles').createIndex({ _orgId: 1, userId: 1, roleId: 1 }, { unique: true });
75
+ await db.collection('user_roles').createIndex({ _orgId: 1 });
76
+ }
77
+ else {
78
+ await db.collection('user_roles').createIndex({ userId: 1, roleId: 1 }, { unique: true });
79
+ }
80
+ await db.collection('user_roles').createIndex({ userId: 1 });
81
+ await db.collection('user_roles').createIndex({ roleId: 1 });
82
+ },
83
+ down: async ({ context: db }) => {
84
+ await db.collection('user_roles').drop();
85
+ }
86
+ });
87
+ migrations.push({
88
+ name: '00000000000006_schema-features',
89
+ up: async ({ context: db }) => {
90
+ await db.createCollection('features');
91
+ if (isMultiTenant) {
92
+ await db.collection('features').createIndex({ _orgId: 1, name: 1 }, { unique: true });
93
+ await db.collection('features').createIndex({ _orgId: 1 });
94
+ }
95
+ else {
96
+ await db.collection('features').createIndex({ name: 1 }, { unique: true });
97
+ }
98
+ },
99
+ down: async ({ context: db }) => {
100
+ await db.collection('features').drop();
101
+ }
102
+ });
103
+ migrations.push({
104
+ name: '00000000000007_schema-authorizations',
105
+ up: async ({ context: db }) => {
106
+ await db.createCollection('authorizations');
107
+ if (isMultiTenant) {
108
+ await db.collection('authorizations').createIndex({ _orgId: 1, roleId: 1, featureId: 1 }, { unique: true });
109
+ await db.collection('authorizations').createIndex({ _orgId: 1 });
110
+ }
111
+ else {
112
+ await db.collection('authorizations').createIndex({ roleId: 1, featureId: 1 }, { unique: true });
113
+ }
114
+ await db.collection('authorizations').createIndex({ roleId: 1 });
115
+ await db.collection('authorizations').createIndex({ featureId: 1 });
116
+ },
117
+ down: async ({ context: db }) => {
118
+ await db.collection('authorizations').drop();
119
+ }
120
+ });
121
+ if (isMultiTenant && config.app.metaOrgName && config.app.metaOrgCode) {
122
+ migrations.push({
123
+ name: '00000000000008_data-meta-org',
124
+ up: async ({ context: db }) => {
125
+ const _id = randomUUID().toString();
126
+ const metaOrg = {
127
+ _id,
128
+ name: config.app.metaOrgName,
129
+ code: config.app.metaOrgCode,
130
+ description: undefined,
131
+ status: 1,
132
+ isMetaOrg: true,
133
+ authToken: undefined,
134
+ _created: new Date(),
135
+ _createdBy: 'system',
136
+ _updated: new Date(),
137
+ _updatedBy: 'system',
138
+ _deleted: undefined,
139
+ _deletedBy: undefined
140
+ };
141
+ await db.collection('organizations').insertOne(metaOrg);
142
+ initializeSystemUserContext(config.email?.systemEmailAddress || 'system@example.com', metaOrg);
143
+ },
144
+ down: async ({ context: db }) => {
145
+ await db.collection('organizations').deleteMany({ isMetaOrg: true });
146
+ }
147
+ });
148
+ }
149
+ if (config.adminUser?.email && config.adminUser?.password) {
150
+ migrations.push({
151
+ name: '00000000000009_data-admin-user',
152
+ up: async ({ context: db }) => {
153
+ if (!config.adminUser?.email || !config.adminUser?.password) {
154
+ throw new Error('Admin user email and password must be provided in config');
155
+ }
156
+ const database = new MongoDBDatabase(db);
157
+ const authService = new AuthService(database);
158
+ const systemUserContext = getSystemUserContext();
159
+ const _id = randomUUID().toString();
160
+ await authService.createUser(systemUserContext, {
161
+ _id: _id,
162
+ _orgId: systemUserContext.organization?._id,
163
+ email: config.adminUser.email,
164
+ password: config.adminUser.password,
165
+ firstName: 'Admin',
166
+ lastName: 'User',
167
+ displayName: 'Admin User',
168
+ });
169
+ },
170
+ down: async ({ context: db }) => {
171
+ if (!config.adminUser?.email)
172
+ return;
173
+ await db.collection('users').deleteOne({ email: config.adminUser.email });
174
+ }
175
+ });
176
+ }
177
+ if (config.adminUser?.email && isMultiTenant) {
178
+ migrations.push({
179
+ name: '00000000000010_data-admin-authorizations',
180
+ up: async ({ context: db }) => {
181
+ if (!config.adminUser?.email) {
182
+ throw new Error('Admin user email not found in config');
183
+ }
184
+ const database = new MongoDBDatabase(db);
185
+ const organizationService = new OrganizationService(database);
186
+ const authService = new AuthService(database);
187
+ const metaOrg = await organizationService.getMetaOrg(EmptyUserContext);
188
+ if (!metaOrg) {
189
+ throw new Error('Meta organization not found. Ensure meta-org migration ran successfully.');
190
+ }
191
+ const adminUser = await authService.getUserByEmail(config.adminUser.email);
192
+ if (!adminUser) {
193
+ throw new Error('Admin user not found. Ensure admin-user migration ran successfully.');
194
+ }
195
+ const roleId = randomUUID().toString();
196
+ await db.collection('roles').insertOne({
197
+ _id: roleId,
198
+ _orgId: metaOrg._id,
199
+ name: 'admin'
200
+ });
201
+ const userRoleId = randomUUID().toString();
202
+ await db.collection('user_roles').insertOne({
203
+ _id: userRoleId,
204
+ _orgId: metaOrg._id,
205
+ userId: adminUser._id,
206
+ roleId: roleId,
207
+ _created: new Date(),
208
+ _createdBy: 'system',
209
+ _updated: new Date(),
210
+ _updatedBy: 'system',
211
+ _deleted: undefined,
212
+ _deletedBy: undefined
213
+ });
214
+ const featureId = randomUUID().toString();
215
+ await db.collection('features').insertOne({
216
+ _id: featureId,
217
+ _orgId: metaOrg._id,
218
+ name: 'admin'
219
+ });
220
+ const authorizationId = randomUUID().toString();
221
+ await db.collection('authorizations').insertOne({
222
+ _id: authorizationId,
223
+ _orgId: metaOrg._id,
224
+ roleId: roleId,
225
+ featureId: featureId,
226
+ startDate: undefined,
227
+ endDate: undefined,
228
+ config: undefined,
229
+ _created: new Date(),
230
+ _createdBy: 'system',
231
+ _updated: new Date(),
232
+ _updatedBy: 'system',
233
+ _deleted: undefined,
234
+ _deletedBy: undefined
235
+ });
236
+ },
237
+ down: async ({ context: db }) => {
238
+ const database = new MongoDBDatabase(db);
239
+ const organizationService = new OrganizationService(database);
240
+ const metaOrg = await organizationService.getMetaOrg(EmptyUserContext);
241
+ if (!metaOrg)
242
+ return;
243
+ const adminRole = await db.collection('roles').findOne({ _orgId: metaOrg._id, name: 'admin' });
244
+ const adminFeature = await db.collection('features').findOne({ _orgId: metaOrg._id, name: 'admin' });
245
+ if (adminRole && adminFeature) {
246
+ await db.collection('authorizations').deleteMany({
247
+ _orgId: metaOrg._id,
248
+ roleId: adminRole._id,
249
+ featureId: adminFeature._id
250
+ });
251
+ }
252
+ await db.collection('features').deleteMany({ _orgId: metaOrg._id, name: 'admin' });
253
+ if (adminRole) {
254
+ await db.collection('user_roles').deleteMany({
255
+ _orgId: metaOrg._id,
256
+ roleId: adminRole._id
257
+ });
258
+ }
259
+ await db.collection('roles').deleteMany({ _orgId: metaOrg._id, name: 'admin' });
260
+ }
261
+ });
262
+ }
263
+ return migrations;
264
+ };
@@ -1,5 +1,6 @@
1
1
  import { Db } from "mongodb";
2
2
  import { IModelSpec, IQueryOptions, IPagedResult, IEntity } from "@loomcore/common/models";
3
+ import type { AppIdType } from "@loomcore/common/types";
3
4
  import { Operation } from "../operations/operation.js";
4
5
  import { DeleteResult as GenericDeleteResult } from "../models/delete-result.js";
5
6
  import { TSchema } from "@sinclair/typebox";
@@ -7,25 +8,25 @@ import { IDatabase } from "../models/database.interface.js";
7
8
  export declare class MongoDBDatabase implements IDatabase {
8
9
  private db;
9
10
  constructor(db: Db);
10
- preprocessEntity<T extends IEntity>(entity: Partial<T>, schema: TSchema): Partial<T>;
11
- postprocessEntity<T extends IEntity>(single: T, schema: TSchema): T;
11
+ preProcessEntity<T extends IEntity>(entity: Partial<T>, schema: TSchema): Partial<T>;
12
+ postProcessEntity<T extends IEntity>(single: T, schema: TSchema): T;
12
13
  getAll<T extends IEntity>(operations: Operation[], pluralResourceName: string): Promise<T[]>;
13
14
  get<T extends IEntity>(operations: Operation[], queryOptions: IQueryOptions, modelSpec: IModelSpec, pluralResourceName: string): Promise<IPagedResult<T>>;
14
- getById<T extends IEntity>(operations: Operation[], queryObject: IQueryOptions, id: string, pluralResourceName: string): Promise<T | null>;
15
+ getById<T extends IEntity>(operations: Operation[], queryObject: IQueryOptions, id: AppIdType, pluralResourceName: string): Promise<T | null>;
15
16
  getCount(pluralResourceName: string): Promise<number>;
16
17
  create<T extends IEntity>(entity: Partial<T>, pluralResourceName: string): Promise<{
17
- insertedId: string;
18
+ insertedId: AppIdType;
18
19
  entity: T;
19
20
  }>;
20
21
  createMany<T extends IEntity>(entities: Partial<T>[], pluralResourceName: string): Promise<{
21
- insertedIds: string[];
22
+ insertedIds: AppIdType[];
22
23
  entities: T[];
23
24
  }>;
24
25
  batchUpdate<T extends IEntity>(entities: Partial<T>[], operations: Operation[], queryObject: IQueryOptions, pluralResourceName: string): Promise<T[]>;
25
- fullUpdateById<T extends IEntity>(operations: Operation[], id: string, entity: Partial<T>, pluralResourceName: string): Promise<T>;
26
- partialUpdateById<T extends IEntity>(operations: Operation[], id: string, entity: Partial<T>, pluralResourceName: string): Promise<T>;
26
+ fullUpdateById<T extends IEntity>(operations: Operation[], id: AppIdType, entity: Partial<T>, pluralResourceName: string): Promise<T>;
27
+ partialUpdateById<T extends IEntity>(operations: Operation[], id: AppIdType, entity: Partial<T>, pluralResourceName: string): Promise<T>;
27
28
  update<T extends IEntity>(queryObject: IQueryOptions, entity: Partial<T>, operations: Operation[], pluralResourceName: string): Promise<T[]>;
28
- deleteById(id: string, pluralResourceName: string): Promise<GenericDeleteResult>;
29
+ deleteById(id: AppIdType, pluralResourceName: string): Promise<GenericDeleteResult>;
29
30
  deleteMany(queryObject: IQueryOptions, pluralResourceName: string): Promise<GenericDeleteResult>;
30
31
  find<T extends IEntity>(queryObject: IQueryOptions, pluralResourceName: string): Promise<T[]>;
31
32
  findOne<T extends IEntity>(queryObject: IQueryOptions, pluralResourceName: string): Promise<T | null>;
@@ -8,13 +8,13 @@ export class MongoDBDatabase {
8
8
  constructor(db) {
9
9
  this.db = db;
10
10
  }
11
- preprocessEntity(entity, schema) {
11
+ preProcessEntity(entity, schema) {
12
12
  if (entity._id && !entityUtils.isValidObjectId(entity._id)) {
13
13
  throw new BadRequestError('id is not a valid ObjectId');
14
14
  }
15
15
  return convertStringsToObjectIds(entity, schema);
16
16
  }
17
- postprocessEntity(single, schema) {
17
+ postProcessEntity(single, schema) {
18
18
  if (!single)
19
19
  return single;
20
20
  return convertObjectIdsToStrings(single);
@@ -1,4 +1,5 @@
1
1
  import { Db } from "mongodb";
2
2
  import { Operation } from "../../operations/operation.js";
3
3
  import { IQueryOptions } from "@loomcore/common/models";
4
- export declare function getById<T>(db: Db, operations: Operation[], queryObject: IQueryOptions, id: string, pluralResourceName: string): Promise<T | null>;
4
+ import type { AppIdType } from "@loomcore/common/types";
5
+ export declare function getById<T>(db: Db, operations: Operation[], queryObject: IQueryOptions, id: AppIdType, pluralResourceName: string): Promise<T | null>;
@@ -9,7 +9,7 @@ export async function batchUpdate(client, entities, operations, queryObject, plu
9
9
  }
10
10
  const entityIds = [];
11
11
  for (const entity of entities) {
12
- if (!entity._id || typeof entity._id !== 'string') {
12
+ if (!entity._id || (typeof entity._id !== 'string' && typeof entity._id !== 'number')) {
13
13
  throw new BadRequestError('Each entity in a batch update must have a valid _id.');
14
14
  }
15
15
  entityIds.push(entity._id);
@@ -1,6 +1,7 @@
1
1
  import { Client } from 'pg';
2
2
  import { IEntity } from '@loomcore/common/models';
3
+ import type { AppIdType } from '@loomcore/common/types';
3
4
  export declare function createMany<T extends IEntity>(client: Client, pluralResourceName: string, entities: Partial<T>[]): Promise<{
4
- insertedIds: string[];
5
+ insertedIds: AppIdType[];
5
6
  entities: T[];
6
7
  }>;
@@ -1,5 +1,4 @@
1
1
  import { BadRequestError, DuplicateKeyError } from "../../../errors/index.js";
2
- import { randomUUID } from 'crypto';
3
2
  export async function createMany(client, pluralResourceName, entities) {
4
3
  if (entities.length === 0) {
5
4
  return {
@@ -8,50 +7,46 @@ export async function createMany(client, pluralResourceName, entities) {
8
7
  };
9
8
  }
10
9
  try {
11
- const entitiesWithIds = entities.map(entity => {
12
- entity._id = entity._id ?? randomUUID().toString();
13
- return entity;
10
+ entities.forEach(entity => {
11
+ delete entity._id;
14
12
  });
15
- const tableColumns = await client.query(`
16
- SELECT column_name, column_default
17
- FROM information_schema.columns
18
- WHERE table_schema = current_schema()
19
- AND table_name = $1
20
- ORDER BY ordinal_position
21
- `, [pluralResourceName]);
22
- if (tableColumns.rows.length === 0) {
23
- throw new BadRequestError(`Unable to resolve columns for ${pluralResourceName}`);
13
+ const allColumns = new Set();
14
+ entities.forEach(entity => {
15
+ Object.keys(entity).forEach(key => {
16
+ if (key !== '_id') {
17
+ allColumns.add(key);
18
+ }
19
+ });
20
+ });
21
+ const columns = Array.from(allColumns);
22
+ if (columns.length === 0) {
23
+ throw new BadRequestError(`No columns provided for ${pluralResourceName}`);
24
24
  }
25
25
  const allValues = [];
26
26
  const valueClauses = [];
27
- entitiesWithIds.forEach((entity, entityIndex) => {
28
- const objectEntries = Object.entries(entity);
29
- const values = tableColumns.rows.map(column => {
30
- if (objectEntries.find(entry => entry[0] === column.column_name)) {
31
- return objectEntries.find(entry => entry[0] === column.column_name)?.[1];
32
- }
33
- return column.column_default;
34
- });
27
+ entities.forEach((entity, entityIndex) => {
28
+ const values = columns.map(column => entity[column]);
35
29
  const placeholders = values.map((_, valueIndex) => {
36
- const paramIndex = entityIndex * values.length + valueIndex + 1;
30
+ const paramIndex = entityIndex * columns.length + valueIndex + 1;
37
31
  return `$${paramIndex}`;
38
32
  }).join(', ');
39
33
  valueClauses.push(`(${placeholders})`);
40
34
  allValues.push(...values);
41
35
  });
42
36
  const query = `
43
- INSERT INTO "${pluralResourceName}" (${tableColumns.rows.map(column => `"${column.column_name}"`).join(', ')})
37
+ INSERT INTO "${pluralResourceName}" (${columns.map(col => `"${col}"`).join(', ')})
44
38
  VALUES ${valueClauses.join(', ')}
45
- RETURNING _id
39
+ RETURNING *
46
40
  `;
47
41
  const result = await client.query(query, allValues);
48
- if (result.rows.length !== entitiesWithIds.length) {
49
- throw new BadRequestError(`Error creating ${pluralResourceName}: Expected ${entitiesWithIds.length} rows, got ${result.rows.length}`);
42
+ if (result.rows.length !== entities.length) {
43
+ throw new BadRequestError(`Error creating ${pluralResourceName}: Expected ${entities.length} rows, got ${result.rows.length}`);
50
44
  }
51
- const insertedIds = result.rows.map(row => row._id);
45
+ const insertedRows = result.rows;
46
+ const insertedIds = insertedRows.map(row => row._id);
52
47
  return {
53
48
  insertedIds,
54
- entities: entitiesWithIds
49
+ entities: insertedRows
55
50
  };
56
51
  }
57
52
  catch (err) {
@@ -1,6 +1,7 @@
1
1
  import { Client } from 'pg';
2
2
  import { IEntity } from "@loomcore/common/models";
3
+ import type { AppIdType } from "@loomcore/common/types";
3
4
  export declare function create<T extends IEntity>(client: Client, pluralResourceName: string, entity: Partial<T>): Promise<{
4
- insertedId: string;
5
+ insertedId: AppIdType;
5
6
  entity: T;
6
7
  }>;
@@ -1,23 +1,24 @@
1
1
  import { BadRequestError, DuplicateKeyError } from "../../../errors/index.js";
2
- import { randomUUID } from 'crypto';
3
2
  import { columnsAndValuesFromEntity } from '../utils/columns-and-values-from-entity.js';
4
3
  export async function create(client, pluralResourceName, entity) {
5
4
  try {
6
- entity._id = entity._id ?? randomUUID().toString();
5
+ delete entity._id;
7
6
  const { columns, values } = columnsAndValuesFromEntity(entity);
8
7
  const placeholders = columns.map((_, index) => `$${index + 1}`).join(', ');
9
8
  const query = `
10
9
  INSERT INTO "${pluralResourceName}" (${columns.join(', ')})
11
10
  VALUES (${placeholders})
12
- RETURNING _id
11
+ RETURNING *
13
12
  `;
14
13
  const result = await client.query(query, values);
15
14
  if (result.rows.length === 0) {
16
15
  throw new BadRequestError(`Error creating ${pluralResourceName}: No row returned`);
17
16
  }
17
+ const insertedRow = result.rows[0];
18
+ const insertedId = insertedRow._id;
18
19
  return {
19
- insertedId: entity._id,
20
- entity: entity
20
+ insertedId: insertedId,
21
+ entity: insertedRow
21
22
  };
22
23
  }
23
24
  catch (err) {
@@ -1,3 +1,4 @@
1
1
  import { Client } from 'pg';
2
2
  import { DeleteResult } from "../../models/delete-result.js";
3
- export declare function deleteById(client: Client, id: string, pluralResourceName: string): Promise<DeleteResult>;
3
+ import type { AppIdType } from '@loomcore/common/types';
4
+ export declare function deleteById(client: Client, id: AppIdType, pluralResourceName: string): Promise<DeleteResult>;
@@ -1,4 +1,5 @@
1
1
  import { Client } from 'pg';
2
2
  import { Operation } from "../../operations/operation.js";
3
3
  import { IEntity } from '@loomcore/common/models';
4
- export declare function fullUpdateById<T extends IEntity>(client: Client, operations: Operation[], id: string, entity: Partial<T>, pluralResourceName: string): Promise<T>;
4
+ import type { AppIdType } from '@loomcore/common/types';
5
+ export declare function fullUpdateById<T extends IEntity>(client: Client, operations: Operation[], id: AppIdType, entity: Partial<T>, pluralResourceName: string): Promise<T>;
@@ -1,4 +1,5 @@
1
1
  import { Client } from 'pg';
2
2
  import { Operation } from "../../operations/operation.js";
3
3
  import { IEntity } from '@loomcore/common/models';
4
- export declare function partialUpdateById<T extends IEntity>(client: Client, operations: Operation[], id: string, entity: Partial<T>, pluralResourceName: string): Promise<T>;
4
+ import type { AppIdType } from '@loomcore/common/types';
5
+ export declare function partialUpdateById<T extends IEntity>(client: Client, operations: Operation[], id: AppIdType, entity: Partial<T>, pluralResourceName: string): Promise<T>;
@@ -0,0 +1,4 @@
1
+ import { Pool } from 'pg';
2
+ import { IBaseApiConfig } from '../../../../models/base-api-config.interface.js';
3
+ export declare function runInitialSchemaMigrations(pool: Pool, config: IBaseApiConfig): Promise<void>;
4
+ export declare function runTestSchemaMigrations(pool: Pool, config: IBaseApiConfig): Promise<void>;
@@ -0,0 +1,67 @@
1
+ import { Umzug } from 'umzug';
2
+ import { getPostgresInitialSchema } from '../postgres-initial-schema.js';
3
+ import { getPostgresTestSchema } from '../../../../__tests__/postgres-test-migrations/postgres-test-schema.js';
4
+ export async function runInitialSchemaMigrations(pool, config) {
5
+ const initialSchema = getPostgresInitialSchema(config);
6
+ const umzug = new Umzug({
7
+ migrations: async () => {
8
+ return initialSchema.map(m => ({
9
+ name: m.name,
10
+ up: async () => {
11
+ await m.up({ context: pool });
12
+ },
13
+ down: async () => {
14
+ await m.down({ context: pool });
15
+ }
16
+ }));
17
+ },
18
+ context: pool,
19
+ storage: {
20
+ async executed({ context }) {
21
+ await context.query(`CREATE TABLE IF NOT EXISTS migrations (name text)`);
22
+ const result = await context.query(`SELECT name FROM migrations`);
23
+ return result.rows.map((r) => r.name);
24
+ },
25
+ async logMigration({ name, context }) {
26
+ await context.query(`INSERT INTO migrations (name) VALUES ($1)`, [name]);
27
+ },
28
+ async unlogMigration({ name, context }) {
29
+ await context.query(`DELETE FROM migrations WHERE name = $1`, [name]);
30
+ }
31
+ },
32
+ logger: undefined,
33
+ });
34
+ await umzug.up();
35
+ }
36
+ export async function runTestSchemaMigrations(pool, config) {
37
+ const testSchema = getPostgresTestSchema(config);
38
+ const umzug = new Umzug({
39
+ migrations: async () => {
40
+ return testSchema.map(m => ({
41
+ name: m.name,
42
+ up: async () => {
43
+ await m.up({ context: pool });
44
+ },
45
+ down: async () => {
46
+ await m.down({ context: pool });
47
+ }
48
+ }));
49
+ },
50
+ context: pool,
51
+ storage: {
52
+ async executed({ context }) {
53
+ await context.query(`CREATE TABLE IF NOT EXISTS migrations (name text)`);
54
+ const result = await context.query(`SELECT name FROM migrations`);
55
+ return result.rows.map((r) => r.name);
56
+ },
57
+ async logMigration({ name, context }) {
58
+ await context.query(`INSERT INTO migrations (name) VALUES ($1)`, [name]);
59
+ },
60
+ async unlogMigration({ name, context }) {
61
+ await context.query(`DELETE FROM migrations WHERE name = $1`, [name]);
62
+ }
63
+ },
64
+ logger: undefined,
65
+ });
66
+ await umzug.up();
67
+ }
@@ -1,2 +1 @@
1
- export * from './migration.interface.js';
2
- export * from './database-builder.js';
1
+ export * from './postgres-initial-schema.js';
@@ -1,2 +1 @@
1
- export * from './migration.interface.js';
2
- export * from './database-builder.js';
1
+ export * from './postgres-initial-schema.js';
@@ -9,4 +9,4 @@ export interface SyntheticMigration {
9
9
  context: Pool;
10
10
  }) => Promise<void>;
11
11
  }
12
- export declare const getPostgresFoundational: (config: IBaseApiConfig) => SyntheticMigration[];
12
+ export declare const getPostgresInitialSchema: (config: IBaseApiConfig) => SyntheticMigration[];