@terreno/api 0.9.3 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/bunfig.toml +5 -2
  2. package/bunfig.unit.toml +3 -0
  3. package/dist/auth.test.js +257 -0
  4. package/dist/consentApp.test.js +245 -0
  5. package/dist/expressServer.js +3 -9
  6. package/dist/expressServer.test.js +4 -7
  7. package/dist/githubAuth.test.js +380 -0
  8. package/dist/logger.test.d.ts +1 -0
  9. package/dist/logger.test.js +143 -0
  10. package/dist/notifiers/googleChatNotifier.test.js +37 -0
  11. package/dist/openApi.js +2 -2
  12. package/dist/openApi.test.js +125 -0
  13. package/dist/openApiBuilder.d.ts +1 -0
  14. package/dist/openApiBuilder.js +13 -2
  15. package/dist/openApiBuilder.test.js +66 -0
  16. package/dist/openApiEtag.test.js +8 -0
  17. package/dist/openApiValidator.test.js +309 -0
  18. package/dist/permissions.middleware.test.d.ts +1 -0
  19. package/dist/permissions.middleware.test.js +341 -0
  20. package/dist/plugins.d.ts +8 -8
  21. package/dist/plugins.js +38 -32
  22. package/dist/populate.test.js +99 -0
  23. package/dist/syncConsents.js +2 -2
  24. package/dist/syncConsents.test.js +273 -0
  25. package/dist/tests/bunSetup.js +27 -22
  26. package/dist/tests.d.ts +3 -3
  27. package/dist/tests.js +78 -82
  28. package/dist/utils.d.ts +2 -2
  29. package/dist/utils.js +7 -7
  30. package/package.json +2 -1
  31. package/src/__snapshots__/openApi.test.ts.snap +48 -0
  32. package/src/auth.test.ts +147 -0
  33. package/src/consentApp.test.ts +162 -0
  34. package/src/expressServer.test.ts +4 -11
  35. package/src/expressServer.ts +4 -8
  36. package/src/githubAuth.test.ts +307 -1
  37. package/src/logger.test.ts +149 -0
  38. package/src/notifiers/googleChatNotifier.test.ts +24 -0
  39. package/src/openApi.test.ts +157 -1
  40. package/src/openApi.ts +6 -2
  41. package/src/openApiBuilder.test.ts +81 -0
  42. package/src/openApiBuilder.ts +17 -2
  43. package/src/openApiEtag.test.ts +11 -0
  44. package/src/openApiValidator.test.ts +410 -0
  45. package/src/permissions.middleware.test.ts +197 -0
  46. package/src/plugins.ts +32 -23
  47. package/src/populate.test.ts +78 -2
  48. package/src/syncConsents.test.ts +145 -0
  49. package/src/syncConsents.ts +1 -1
  50. package/src/tests/bunSetup.ts +14 -8
  51. package/src/tests.ts +8 -8
  52. package/src/utils.ts +4 -4
package/src/plugins.ts CHANGED
@@ -15,10 +15,12 @@ export interface BaseUser {
15
15
  email: string;
16
16
  }
17
17
 
18
- export function baseUserPlugin(schema: Schema<any, any, any, any>) {
19
- schema.add({admin: {default: false, type: Boolean}});
20
- schema.add({email: {index: true, type: String}});
21
- }
18
+ export const baseUserPlugin = (schema: Schema<any, any, any, any>): void => {
19
+ schema.add({
20
+ admin: {default: false, description: "Whether the user has admin privileges", type: Boolean},
21
+ });
22
+ schema.add({email: {description: "The user's email address", index: true, type: String}});
23
+ };
22
24
 
23
25
  /** For models with the isDeletedPlugin, extend this interface to add the appropriate fields. */
24
26
  export interface IsDeleted {
@@ -26,7 +28,7 @@ export interface IsDeleted {
26
28
  deleted: boolean;
27
29
  }
28
30
 
29
- export function isDeletedPlugin(schema: Schema<any, any, any, any>, defaultValue = false) {
31
+ export const isDeletedPlugin = (schema: Schema<any, any, any, any>, defaultValue = false): void => {
30
32
  schema.add({
31
33
  deleted: {
32
34
  default: defaultValue,
@@ -37,21 +39,24 @@ export function isDeletedPlugin(schema: Schema<any, any, any, any>, defaultValue
37
39
  type: Boolean,
38
40
  },
39
41
  });
40
- function applyDeleteFilter(q: Query<any, any>) {
42
+ const applyDeleteFilter = (q: Query<any, any>): void => {
41
43
  const query = q.getQuery();
42
44
  if (query && query.deleted === undefined) {
43
45
  void q.where({deleted: {$ne: true}});
44
46
  }
45
- }
47
+ };
46
48
  schema.pre("find", function () {
47
49
  applyDeleteFilter(this);
48
50
  });
49
51
  schema.pre("findOne", function () {
50
52
  applyDeleteFilter(this);
51
53
  });
52
- }
54
+ };
53
55
 
54
- export function isDisabledPlugin(schema: Schema<any, any, any, any>, defaultValue = false) {
56
+ export const isDisabledPlugin = (
57
+ schema: Schema<any, any, any, any>,
58
+ defaultValue = false
59
+ ): void => {
55
60
  schema.add({
56
61
  disabled: {
57
62
  default: defaultValue,
@@ -60,16 +65,18 @@ export function isDisabledPlugin(schema: Schema<any, any, any, any>, defaultValu
60
65
  type: Boolean,
61
66
  },
62
67
  });
63
- }
68
+ };
64
69
 
65
70
  export interface CreatedDeleted {
66
71
  updated: {type: Date; required: true};
67
72
  created: {type: Date; required: true};
68
73
  }
69
74
 
70
- export function createdUpdatedPlugin(schema: Schema<any, any, any, any>) {
71
- schema.add({updated: {index: true, type: Date}});
72
- schema.add({created: {index: true, type: Date}});
75
+ export const createdUpdatedPlugin = (schema: Schema<any, any, any, any>): void => {
76
+ schema.add({
77
+ updated: {description: "When this document was last updated", index: true, type: Date},
78
+ });
79
+ schema.add({created: {description: "When this document was created", index: true, type: Date}});
73
80
 
74
81
  schema.pre("save", function () {
75
82
  if (this.disableCreatedUpdatedPlugin === true) {
@@ -86,11 +93,13 @@ export function createdUpdatedPlugin(schema: Schema<any, any, any, any>) {
86
93
  schema.pre(/save|updateOne|insertMany/, function () {
87
94
  void this.updateOne({}, {$set: {updated: new Date()}});
88
95
  });
89
- }
96
+ };
90
97
 
91
- export function firebaseJWTPlugin(schema: Schema) {
92
- schema.add({firebaseId: {index: true, type: String}});
93
- }
98
+ export const firebaseJWTPlugin = (schema: Schema): void => {
99
+ schema.add({
100
+ firebaseId: {description: "The user's Firebase authentication ID", index: true, type: String},
101
+ });
102
+ };
94
103
 
95
104
  /**
96
105
  * This adds a static method `Model.findOneOrNone` to the schema. This should replace `Model.findOne` in most instances.
@@ -99,7 +108,7 @@ export function firebaseJWTPlugin(schema: Schema) {
99
108
  * document, or throws an exception if multiple are found.
100
109
  * @param schema Mongoose Schema
101
110
  */
102
- export function findOneOrNone<T>(schema: Schema<T>) {
111
+ export const findOneOrNone = <T>(schema: Schema<T>): void => {
103
112
  schema.statics.findOneOrNone = async function (
104
113
  query: Record<string, any>,
105
114
  errorArgs?: Partial<APIErrorConstructor>
@@ -118,7 +127,7 @@ export function findOneOrNone<T>(schema: Schema<T>) {
118
127
  }
119
128
  return results[0];
120
129
  };
121
- }
130
+ };
122
131
 
123
132
  /**
124
133
  * This adds a static method `Model.findExactlyOne` to the schema. This or findOneOrNone should replace `Model.findOne`
@@ -128,7 +137,7 @@ export function findOneOrNone<T>(schema: Schema<T>) {
128
137
  * multiple or none are found.
129
138
  * @param schema Mongoose Schema
130
139
  */
131
- export function findExactlyOne<T>(schema: Schema<T>) {
140
+ export const findExactlyOne = <T>(schema: Schema<T>): void => {
132
141
  schema.statics.findExactlyOne = async function (
133
142
  query: Record<string, any>,
134
143
  errorArgs?: Partial<APIErrorConstructor>
@@ -152,7 +161,7 @@ export function findExactlyOne<T>(schema: Schema<T>) {
152
161
  }
153
162
  return results[0];
154
163
  };
155
- }
164
+ };
156
165
 
157
166
  /**
158
167
  * This adds a static method `Model.upsert` to the schema. This method will either update an existing document
@@ -160,7 +169,7 @@ export function findExactlyOne<T>(schema: Schema<T>) {
160
169
  * match the conditions to prevent ambiguous updates.
161
170
  * @param schema Mongoose Schema
162
171
  */
163
- export function upsertPlugin<T>(schema: Schema<any, any, any, any>) {
172
+ export const upsertPlugin = <T>(schema: Schema<any, any, any, any>): void => {
164
173
  schema.statics.upsert = async function (
165
174
  conditions: Record<string, any>,
166
175
  update: Record<string, any>
@@ -187,7 +196,7 @@ export function upsertPlugin<T>(schema: Schema<any, any, any, any>) {
187
196
  const newDoc = new this(combinedData);
188
197
  return newDoc.save();
189
198
  };
190
- }
199
+ };
191
200
 
192
201
  /** For models with the upsertPlugin, extend this interface to add the upsert static method. */
193
202
  export interface HasUpsert<T> {
@@ -1,7 +1,8 @@
1
1
  import {beforeEach, describe, expect, it} from "bun:test";
2
+ import mongoose, {Schema} from "mongoose";
2
3
 
3
- import {unpopulate} from "./populate";
4
- import {FoodModel, setupDb} from "./tests";
4
+ import {fixMixedFields, getOpenApiSpecForModel, unpopulate} from "./populate";
5
+ import {FoodModel, setupDb, UserModel} from "./tests";
5
6
 
6
7
  describe("populate functions", () => {
7
8
  let admin: any;
@@ -121,3 +122,78 @@ describe("unpopulate edge cases", () => {
121
122
  expect(result.containers[1].items).toEqual(["item-3", "item-4"]);
122
123
  });
123
124
  });
125
+
126
+ describe("fixMixedFields", () => {
127
+ it("returns early when schema is missing", () => {
128
+ const properties = {foo: {type: "object"}};
129
+ expect(() => fixMixedFields(null, properties)).not.toThrow();
130
+ });
131
+
132
+ it("returns early when properties is missing", () => {
133
+ const schema = new Schema({});
134
+ expect(() => fixMixedFields(schema, null as any)).not.toThrow();
135
+ });
136
+
137
+ it("replaces Mixed fields with only description", () => {
138
+ const schema = new Schema({data: {description: "any data", type: Schema.Types.Mixed}});
139
+ const properties: any = {data: {description: "any data", type: "object"}};
140
+ fixMixedFields(schema, properties);
141
+ expect(properties.data).toEqual({description: "any data"});
142
+ });
143
+
144
+ it("recurses into arrays of sub-documents", () => {
145
+ const subSchema = new Schema({meta: {type: Schema.Types.Mixed}});
146
+ const schema = new Schema({items: [subSchema]});
147
+ const properties: any = {
148
+ items: {
149
+ items: {
150
+ properties: {
151
+ meta: {type: "object"},
152
+ },
153
+ },
154
+ type: "array",
155
+ },
156
+ };
157
+ fixMixedFields(schema, properties);
158
+ expect(properties.items.items.properties.meta).toEqual({description: undefined});
159
+ });
160
+
161
+ it("skips unknown paths", () => {
162
+ const schema = new Schema({foo: String});
163
+ const properties = {unknownKey: {type: "string"}};
164
+ expect(() => fixMixedFields(schema, properties)).not.toThrow();
165
+ });
166
+ });
167
+
168
+ describe("getOpenApiSpecForModel edge cases", () => {
169
+ it("returns model properties without populatePaths", () => {
170
+ const result = getOpenApiSpecForModel(UserModel);
171
+ expect(result.properties).toBeDefined();
172
+ });
173
+
174
+ it("returns with extraModelProperties merged", () => {
175
+ const result = getOpenApiSpecForModel(UserModel, {
176
+ extraModelProperties: {customField: {type: "string"}},
177
+ });
178
+ expect(result.properties.customField).toEqual({type: "string"});
179
+ });
180
+
181
+ it("skips populate paths without ref", () => {
182
+ // Create a schema with a non-referenced ObjectId field
183
+ const testSchema = new Schema({name: String, simpleId: Schema.Types.ObjectId});
184
+ const TestModelNoRef =
185
+ mongoose.models.TestModelNoRef || mongoose.model("TestModelNoRef", testSchema);
186
+ const result = getOpenApiSpecForModel(TestModelNoRef, {
187
+ populatePaths: [{path: "simpleId"}],
188
+ });
189
+ // Should not throw, simpleId stays as-is
190
+ expect(result.properties).toBeDefined();
191
+ });
192
+
193
+ it("populates with fields allowlist", () => {
194
+ const result = getOpenApiSpecForModel(FoodModel, {
195
+ populatePaths: [{fields: ["name"], path: "ownerId"}],
196
+ });
197
+ expect(result.properties).toBeDefined();
198
+ });
199
+ });
@@ -121,4 +121,149 @@ describe("syncConsents", () => {
121
121
  expect(forms[0].slug).toBe("terms");
122
122
  expect(forms[1].slug).toBe("privacy");
123
123
  });
124
+
125
+ it("publishes new version when type changes", async () => {
126
+ await syncConsents({terms: baseDef});
127
+ const updated: ConsentFormDefinition = {...baseDef, type: "privacy"};
128
+ const result = await syncConsents({terms: updated});
129
+ expect(result.updated).toEqual(["terms"]);
130
+ });
131
+
132
+ it("publishes new version when order changes", async () => {
133
+ await syncConsents({terms: baseDef});
134
+ const updated = {...baseDef, order: 99};
135
+ const result = await syncConsents({terms: updated});
136
+ expect(result.updated).toEqual(["terms"]);
137
+ });
138
+
139
+ it("publishes new version when required changes", async () => {
140
+ await syncConsents({terms: baseDef});
141
+ const updated = {...baseDef, required: false};
142
+ const result = await syncConsents({terms: updated});
143
+ expect(result.updated).toEqual(["terms"]);
144
+ });
145
+
146
+ it("publishes new version when requireScrollToBottom changes", async () => {
147
+ await syncConsents({terms: baseDef});
148
+ const updated = {...baseDef, requireScrollToBottom: true};
149
+ const result = await syncConsents({terms: updated});
150
+ expect(result.updated).toEqual(["terms"]);
151
+ });
152
+
153
+ it("publishes new version when captureSignature changes", async () => {
154
+ await syncConsents({terms: baseDef});
155
+ const updated = {...baseDef, captureSignature: true};
156
+ const result = await syncConsents({terms: updated});
157
+ expect(result.updated).toEqual(["terms"]);
158
+ });
159
+
160
+ it("publishes new version when agreeButtonText changes", async () => {
161
+ await syncConsents({terms: baseDef});
162
+ const updated = {...baseDef, agreeButtonText: "Consent"};
163
+ const result = await syncConsents({terms: updated});
164
+ expect(result.updated).toEqual(["terms"]);
165
+ });
166
+
167
+ it("publishes new version when allowDecline changes", async () => {
168
+ await syncConsents({terms: baseDef});
169
+ const updated = {...baseDef, allowDecline: true};
170
+ const result = await syncConsents({terms: updated});
171
+ expect(result.updated).toEqual(["terms"]);
172
+ });
173
+
174
+ it("publishes new version when declineButtonText changes", async () => {
175
+ await syncConsents({terms: baseDef});
176
+ const updated = {...baseDef, allowDecline: true, declineButtonText: "No Thanks"};
177
+ const result = await syncConsents({terms: updated});
178
+ expect(result.updated).toEqual(["terms"]);
179
+ });
180
+
181
+ it("publishes new version when defaultLocale changes", async () => {
182
+ await syncConsents({terms: baseDef});
183
+ const updated = {...baseDef, defaultLocale: "es"};
184
+ const result = await syncConsents({terms: updated});
185
+ expect(result.updated).toEqual(["terms"]);
186
+ });
187
+
188
+ it("publishes new version when content locale count changes", async () => {
189
+ await syncConsents({terms: baseDef});
190
+ const updated = {...baseDef, content: {en: baseDef.content.en, es: "# Términos"}};
191
+ const result = await syncConsents({terms: updated});
192
+ expect(result.updated).toEqual(["terms"]);
193
+ });
194
+
195
+ it("publishes new version when checkbox count changes", async () => {
196
+ await syncConsents({
197
+ terms: {
198
+ ...baseDef,
199
+ checkboxes: [{label: "Agree", required: true}],
200
+ },
201
+ });
202
+ const updated = {
203
+ ...baseDef,
204
+ checkboxes: [
205
+ {label: "Agree", required: true},
206
+ {label: "Also agree", required: false},
207
+ ],
208
+ };
209
+ const result = await syncConsents({terms: updated});
210
+ expect(result.updated).toEqual(["terms"]);
211
+ });
212
+
213
+ it("publishes new version when checkbox label changes", async () => {
214
+ await syncConsents({
215
+ terms: {
216
+ ...baseDef,
217
+ checkboxes: [{label: "Agree", required: true}],
218
+ },
219
+ });
220
+ const updated = {
221
+ ...baseDef,
222
+ checkboxes: [{label: "I Agree", required: true}],
223
+ };
224
+ const result = await syncConsents({terms: updated});
225
+ expect(result.updated).toEqual(["terms"]);
226
+ });
227
+
228
+ it("publishes new version when checkbox confirmationPrompt changes", async () => {
229
+ await syncConsents({
230
+ terms: {
231
+ ...baseDef,
232
+ checkboxes: [{confirmationPrompt: "Sure?", label: "Agree", required: true}],
233
+ },
234
+ });
235
+ const updated = {
236
+ ...baseDef,
237
+ checkboxes: [{confirmationPrompt: "Are you sure?", label: "Agree", required: true}],
238
+ };
239
+ const result = await syncConsents({terms: updated});
240
+ expect(result.updated).toEqual(["terms"]);
241
+ });
242
+
243
+ it("leaves unchanged forms alone with checkboxes present", async () => {
244
+ const withCheckboxes = {
245
+ ...baseDef,
246
+ checkboxes: [{confirmationPrompt: "Sure?", label: "Agree", required: true}],
247
+ };
248
+ await syncConsents({terms: withCheckboxes});
249
+ const result = await syncConsents({terms: withCheckboxes});
250
+ expect(result.unchanged).toEqual(["terms"]);
251
+ });
252
+
253
+ it("dry run does not create new versions", async () => {
254
+ await syncConsents({terms: baseDef});
255
+ const updated = {...baseDef, title: "Updated"};
256
+ const result = await syncConsents({terms: updated}, {dryRun: true});
257
+ expect(result.updated).toEqual(["terms"]);
258
+ const forms = await ConsentForm.find({slug: "terms"});
259
+ expect(forms).toHaveLength(1); // No new version created
260
+ });
261
+
262
+ it("dry run does not deactivate forms", async () => {
263
+ await syncConsents({privacy: {...baseDef, title: "Privacy", type: "privacy"}, terms: baseDef});
264
+ const result = await syncConsents({terms: baseDef}, {deactivateRemoved: true, dryRun: true});
265
+ expect(result.deactivated).toEqual(["privacy"]);
266
+ const privacy = await ConsentForm.findOne({slug: "privacy"});
267
+ expect(privacy?.active).toBe(true); // Still active
268
+ });
124
269
  });
@@ -237,7 +237,7 @@ export const syncConsents = async (
237
237
 
238
238
  // Deactivate forms that are no longer in definitions
239
239
  if (deactivateRemoved) {
240
- for (const [slug, form] of activeBySlug) {
240
+ for (const [slug] of activeBySlug) {
241
241
  if (!definitions[slug]) {
242
242
  logger.info(`syncConsents: deactivating "${slug}"`, {dryRun});
243
243
  if (!dryRun) {
@@ -6,17 +6,23 @@ import winston from "winston";
6
6
  import {setupEnvironment} from "../expressServer";
7
7
  import {logger, winstonLogger} from "../logger";
8
8
 
9
+ const shouldConnectToTestDb = process.env.BUN_TEST_DISABLE_DB !== "true";
10
+
9
11
  // Connect to MongoDB once for all tests
10
- beforeAll(async () => {
11
- await mongoose
12
- .connect("mongodb://127.0.0.1/terreno?&connectTimeoutMS=360000")
13
- .catch(logger.catch);
14
- });
12
+ if (shouldConnectToTestDb) {
13
+ beforeAll(async () => {
14
+ await mongoose
15
+ .connect("mongodb://127.0.0.1/terreno?&connectTimeoutMS=360000")
16
+ .catch(logger.catch);
17
+ });
18
+ }
15
19
 
16
20
  // Close MongoDB connection after all tests
17
- afterAll(async () => {
18
- await mongoose.connection.close();
19
- });
21
+ if (shouldConnectToTestDb) {
22
+ afterAll(async () => {
23
+ await mongoose.connection.close();
24
+ });
25
+ }
20
26
 
21
27
  let logs: string[] = [];
22
28
 
package/src/tests.ts CHANGED
@@ -162,7 +162,7 @@ const requiredSchema = new Schema<RequiredField>({
162
162
  });
163
163
  export const RequiredModel = model<RequiredField>("Required", requiredSchema);
164
164
 
165
- export function getBaseServer(): Express {
165
+ export const getBaseServer = (): Express => {
166
166
  const app = express();
167
167
  app.set("query parser", (str: string) => qs.parse(str, {arrayLimit: 200}));
168
168
 
@@ -186,12 +186,12 @@ export function getBaseServer(): Express {
186
186
  });
187
187
  app.use(express.json());
188
188
  return app;
189
- }
189
+ };
190
190
 
191
- export async function authAsUser(
191
+ export const authAsUser = async (
192
192
  app: express.Application,
193
193
  type: "admin" | "notAdmin"
194
- ): Promise<TestAgent> {
194
+ ): Promise<TestAgent> => {
195
195
  const email = type === "admin" ? "admin@example.com" : "notAdmin@example.com";
196
196
  const password = type === "admin" ? "securePassword" : "password";
197
197
 
@@ -199,9 +199,9 @@ export async function authAsUser(
199
199
  const res = await agent.post("/auth/login").send({email, password}).expect(200);
200
200
  await agent.set("authorization", `Bearer ${res.body.data.token}`);
201
201
  return agent;
202
- }
202
+ };
203
203
 
204
- export async function setupDb() {
204
+ export const setupDb = async () => {
205
205
  await mongoose
206
206
  .connect("mongodb://127.0.0.1/terreno?&connectTimeoutMS=360000")
207
207
  .catch(logger.catch);
@@ -233,7 +233,7 @@ export async function setupDb() {
233
233
 
234
234
  return [admin, notAdmin, adminOther];
235
235
  } catch (error) {
236
- console.error("Error setting up DB", error);
236
+ logger.error("Error setting up DB", error);
237
237
  throw error;
238
238
  }
239
- }
239
+ };
package/src/utils.ts CHANGED
@@ -4,14 +4,14 @@ import {logger} from "./logger";
4
4
 
5
5
  // A better version of mongoose's ObjectId.isValid,
6
6
  // which falsely will say any 12 character string is valid.
7
- export function isValidObjectId(id: string): boolean {
7
+ export const isValidObjectId = (id: string): boolean => {
8
8
  try {
9
9
  return new Types.ObjectId(id).toString() === id;
10
10
  } catch (error) {
11
11
  logger.error(`Error validating object id ${id}: ${error}`);
12
12
  return false;
13
13
  }
14
- }
14
+ };
15
15
 
16
16
  export const timeout = async (ms: number): Promise<NodeJS.Timeout> => {
17
17
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -25,7 +25,7 @@ export const timeout = async (ms: number): Promise<NodeJS.Timeout> => {
25
25
  * @param ignoredModels - Array of model names to skip validation for
26
26
  * @throws Error if any model is not set to strict mode or missing virtual settings
27
27
  */
28
- export function checkModelsStrict(ignoredModels: string[] = []): void {
28
+ export const checkModelsStrict = (ignoredModels: string[] = []): void => {
29
29
  const models = mongoose.modelNames();
30
30
  for (const model of models) {
31
31
  const schema = mongoose.model(model).schema;
@@ -44,4 +44,4 @@ export function checkModelsStrict(ignoredModels: string[] = []): void {
44
44
  throw new Error(`Model ${model} is not set to strict mode.`);
45
45
  }
46
46
  }
47
- }
47
+ };