@terreno/api 0.0.11-beta.1 → 0.0.11

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/src/api.ts CHANGED
@@ -241,6 +241,16 @@ export interface ModelRouterOptions<T> {
241
241
  request: express.Request,
242
242
  options: ModelRouterOptions<T>
243
243
  ) => Promise<JSONValue>;
244
+ /**
245
+ * The discriminatorKey that you passed when creating the Mongoose models. Defaults to __t. See:
246
+ * https://mongoosejs.com/docs/discriminators.html If this key is provided,
247
+ * you must provide the same key as part of the top level of the body when making performing
248
+ * update or delete operations on this model.
249
+ * \{discriminatorKey: "__t"\}
250
+ *
251
+ * PATCH \{__t: "SuperUser", name: "Foo"\} // __t is required or there will be a 404 error.
252
+ */
253
+ discriminatorKey?: string;
244
254
  /**
245
255
  * The OpenAPI generator for this server. This is used to generate the OpenAPI documentation.
246
256
  */
@@ -265,6 +275,23 @@ export interface ModelRouterOptions<T> {
265
275
  openApiExtraModelProperties?: any;
266
276
  }
267
277
 
278
+ // A function to decide which model to use. If no discriminators are provided,
279
+ // just returns the base model. If
280
+ export function getModel(baseModel: Model<any>, body?: any, options?: ModelRouterOptions<any>) {
281
+ const discriminatorKey = options?.discriminatorKey ?? "__t";
282
+ const modelName = body?.[discriminatorKey];
283
+ if (!modelName) {
284
+ return baseModel;
285
+ }
286
+ const model = baseModel.discriminators?.[modelName];
287
+ if (!model) {
288
+ throw new Error(
289
+ `Could not find discriminator model for key ${modelName}, baseModel: ${baseModel}`
290
+ );
291
+ }
292
+ return model;
293
+ }
294
+
268
295
  // Ensures query params are allowed. Also checks nested query params when using $and/$or.
269
296
  function checkQueryParamAllowed(
270
297
  queryParam: string,
@@ -310,12 +337,15 @@ function checkQueryParamAllowed(
310
337
  // }
311
338
 
312
339
  /**
313
- * Create a set of CRUD routes given a Mongoose model and configuration options.
340
+ * Create a set of CRUD routes given a Mongoose model $baseModel and configuration options.
314
341
  *
315
- * @param model A Mongoose Model
342
+ * @param baseModel A Mongoose Model
316
343
  * @param options Options for configuring the REST API, such as permissions, transformers, and hooks.
317
344
  */
318
- export function modelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>): express.Router {
345
+ export function modelRouter<T>(
346
+ baseModel: Model<T>,
347
+ options: ModelRouterOptions<T>
348
+ ): express.Router {
319
349
  const router = express.Router();
320
350
 
321
351
  // Do before the other router options so endpoints take priority.
@@ -329,10 +359,12 @@ export function modelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
329
359
  "/",
330
360
  [
331
361
  authenticateMiddleware(options.allowAnonymous),
332
- createOpenApiMiddleware(model, options),
333
- permissionMiddleware(model, options),
362
+ createOpenApiMiddleware(baseModel, options),
363
+ permissionMiddleware(baseModel, options),
334
364
  ],
335
365
  asyncHandler(async (req: Request, res: Response) => {
366
+ const model = getModel(baseModel, req.body?.__t, options);
367
+
336
368
  let body: Partial<T> | (Partial<T> | undefined)[] | null | undefined;
337
369
  try {
338
370
  body = transform<T>(options, req.body, "create", req.user);
@@ -394,7 +426,7 @@ export function modelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
394
426
 
395
427
  if (options.populatePaths) {
396
428
  try {
397
- let populateQuery: any = model.findById(data._id);
429
+ let populateQuery = model.findById(data._id);
398
430
  populateQuery = addPopulateToQuery(populateQuery, options.populatePaths);
399
431
  data = await populateQuery.exec();
400
432
  } catch (error: any) {
@@ -437,10 +469,13 @@ export function modelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
437
469
  "/",
438
470
  [
439
471
  authenticateMiddleware(options.allowAnonymous),
440
- permissionMiddleware(model, options),
441
- listOpenApiMiddleware(model, options),
472
+ permissionMiddleware(baseModel, options),
473
+ listOpenApiMiddleware(baseModel, options),
442
474
  ],
443
475
  asyncHandler(async (req: Request, res: Response) => {
476
+ // For pure read queries, Mongoose will return the correct data with just the base model.
477
+ const model = baseModel;
478
+
444
479
  let query: any = {};
445
480
  for (const queryParam of Object.keys(options.defaultQueryParams ?? [])) {
446
481
  query[queryParam] = options.defaultQueryParams?.[queryParam];
@@ -589,8 +624,8 @@ export function modelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
589
624
  "/:id",
590
625
  [
591
626
  authenticateMiddleware(options.allowAnonymous),
592
- getOpenApiMiddleware(model, options),
593
- permissionMiddleware(model, options),
627
+ getOpenApiMiddleware(baseModel, options),
628
+ permissionMiddleware(baseModel, options),
594
629
  ],
595
630
  asyncHandler(async (req: Request, res: Response) => {
596
631
  const data: mongoose.Document & T = (req as any).obj;
@@ -623,10 +658,12 @@ export function modelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
623
658
  "/:id",
624
659
  [
625
660
  authenticateMiddleware(options.allowAnonymous),
626
- patchOpenApiMiddleware(model, options),
627
- permissionMiddleware(model, options),
661
+ patchOpenApiMiddleware(baseModel, options),
662
+ permissionMiddleware(baseModel, options),
628
663
  ],
629
664
  asyncHandler(async (req: Request, res: Response) => {
665
+ const model = getModel(baseModel, req.body, options);
666
+
630
667
  let doc: mongoose.Document & T = (req as any).obj;
631
668
 
632
669
  let body;
@@ -694,7 +731,7 @@ export function modelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
694
731
  }
695
732
 
696
733
  if (options.populatePaths) {
697
- let populateQuery: any = model.findById(doc._id);
734
+ let populateQuery = model.findById(doc._id);
698
735
  populateQuery = addPopulateToQuery(populateQuery, options.populatePaths);
699
736
  doc = await populateQuery.exec();
700
737
  }
@@ -729,10 +766,12 @@ export function modelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
729
766
  "/:id",
730
767
  [
731
768
  authenticateMiddleware(options.allowAnonymous),
732
- deleteOpenApiMiddleware(model, options),
733
- permissionMiddleware(model, options),
769
+ deleteOpenApiMiddleware(baseModel, options),
770
+ permissionMiddleware(baseModel, options),
734
771
  ],
735
772
  asyncHandler(async (req: Request, res: Response) => {
773
+ const model = getModel(baseModel, req.body, options);
774
+
736
775
  const doc: mongoose.Document & T & {deleted?: boolean} = (req as any).obj;
737
776
 
738
777
  if (options.preDelete) {
@@ -810,6 +849,7 @@ export function modelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
810
849
  operation: "POST" | "PATCH" | "DELETE"
811
850
  ) {
812
851
  // TODO Combine array operations and .patch(), as they are very similar.
852
+ const model = getModel(baseModel, req.body, options);
813
853
 
814
854
  if (!(await checkPermissions("update", options.permissions.update, req.user))) {
815
855
  throw new APIError({
@@ -821,7 +861,9 @@ export function modelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
821
861
  const doc = await model.findById(req.params.id);
822
862
  // Make a copy for passing pre-saved values to hooks.
823
863
  const prevDoc = cloneDeep(doc);
824
- if (!doc) {
864
+ // We fail here because we might fetch the document without the __t but we'd be missing all the
865
+ // hooks.
866
+ if (!doc || (doc.__t && !req.body.__t)) {
825
867
  throw new APIError({
826
868
  status: 404,
827
869
  title: `Could not find document to PATCH: ${req.params.id}`,
@@ -937,7 +979,7 @@ export function modelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
937
979
 
938
980
  if (options.postUpdate) {
939
981
  try {
940
- await options.postUpdate(doc as any, body, req, prevDoc as any);
982
+ await options.postUpdate(doc, body, req, prevDoc);
941
983
  } catch (error: any) {
942
984
  throw new APIError({
943
985
  disableExternalErrorTracking: getDisableExternalErrorTracking(error),
@@ -947,7 +989,7 @@ export function modelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
947
989
  });
948
990
  }
949
991
  }
950
- return res.json({data: serialize<T>(req, options, doc as any)});
992
+ return res.json({data: serialize<T>(req, options, doc)});
951
993
  }
952
994
 
953
995
  async function arrayPost(req: Request, res: Response) {
@@ -962,7 +1004,7 @@ export function modelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
962
1004
  return arrayOperation(req, res, "DELETE");
963
1005
  }
964
1006
  // Set up routes for managing array fields. Check if there any array fields to add this for.
965
- if (Object.values(model.schema.paths).find((config: any) => config.instance === "Array")) {
1007
+ if (Object.values(baseModel.schema.paths).find((config: any) => config.instance === "Array")) {
966
1008
  router.post(
967
1009
  "/:id/:field",
968
1010
  authenticateMiddleware(options.allowAnonymous),
@@ -4,7 +4,7 @@ import type express from "express";
4
4
  import type {NextFunction} from "express";
5
5
  import mongoose, {type Model} from "mongoose";
6
6
 
7
- import {addPopulateToQuery, type ModelRouterOptions, type RESTMethod} from "./api";
7
+ import {addPopulateToQuery, getModel, type ModelRouterOptions, type RESTMethod} from "./api";
8
8
  import type {User} from "./auth";
9
9
  import {APIError} from "./errors";
10
10
  import {logger} from "./logger";
@@ -101,8 +101,8 @@ export async function checkPermissions<T>(
101
101
  // finds the relevant object, checks the permissions, and attaches the object to the request as
102
102
  // req.obj.
103
103
  export function permissionMiddleware<T>(
104
- model: Model<T>,
105
- options: Pick<ModelRouterOptions<T>, "permissions" | "populatePaths">
104
+ baseModel: Model<T>,
105
+ options: Pick<ModelRouterOptions<T>, "permissions" | "populatePaths" | "discriminatorKey">
106
106
  ) {
107
107
  return async (req: express.Request, _res: express.Response, next: NextFunction) => {
108
108
  if (req.method === "OPTIONS") {
@@ -131,6 +131,8 @@ export function permissionMiddleware<T>(
131
131
  });
132
132
  }
133
133
 
134
+ const model = getModel(baseModel, req.body, options);
135
+
134
136
  // All methods check for permissions.
135
137
  if (!(await checkPermissions(method, options.permissions[method], req.user))) {
136
138
  throw new APIError({
@@ -157,7 +159,15 @@ export function permissionMiddleware<T>(
157
159
  title: `GET failed on ${req.params.id}`,
158
160
  });
159
161
  }
160
- if (!data) {
162
+ if (!data || (["update", "delete"].includes(method) && data?.__t && !req.body?.__t)) {
163
+ // For discriminated models, return 404 without checking hidden state
164
+ if (["update", "delete"].includes(method) && data?.__t && !req.body?.__t) {
165
+ throw new APIError({
166
+ status: 404,
167
+ title: `Document ${req.params.id} not found for model ${model.modelName}`,
168
+ });
169
+ }
170
+
161
171
  // Check if document exists but is hidden. Completely skip plugins.
162
172
  const hiddenDoc = await model.collection.findOne({
163
173
  _id: new mongoose.Types.ObjectId(req.params.id),
package/src/utils.test.ts CHANGED
@@ -1,194 +1,14 @@
1
- import {describe, expect, it, spyOn} from "bun:test";
2
- import mongoose from "mongoose";
1
+ import {describe, expect, it} from "bun:test";
3
2
 
4
- import {checkModelsStrict, isValidObjectId} from "./utils";
3
+ import {isValidObjectId} from "./utils";
5
4
 
6
5
  describe("utils", () => {
7
- describe("isValidObjectId", () => {
8
- it("checks valid ObjectIds", () => {
9
- expect(isValidObjectId("62c44da0003d9f8ee8cc925c")).toBe(true);
10
- expect(isValidObjectId("620000000000000000000000")).toBe(true);
11
- // Mongoose's builtin "ObjectId.isValid" will falsely say this is an ObjectId.
12
- expect(isValidObjectId("1234567890ab")).toBe(false);
13
- expect(isValidObjectId("microsoft123")).toBe(false);
14
- expect(isValidObjectId("62c44da0003d9f8ee8cc925x")).toBe(false);
15
- });
16
- });
17
-
18
- describe("checkModelsStrict", () => {
19
- it("throws error when toObject.virtuals is not true", () => {
20
- // Create a schema without toObject.virtuals
21
- const testSchema = new mongoose.Schema({name: String});
22
- testSchema.set("strict", "throw");
23
- // Not setting toObject.virtuals
24
-
25
- if (mongoose.models.ToObjectTestModel) {
26
- delete mongoose.models.ToObjectTestModel;
27
- }
28
- mongoose.model("ToObjectTestModel", testSchema);
29
-
30
- try {
31
- // This should throw because ToObjectTestModel doesn't have toObject.virtuals
32
- expect(() => checkModelsStrict()).toThrow("toObject.virtuals not set to true");
33
- } finally {
34
- delete mongoose.models.ToObjectTestModel;
35
- }
36
- });
37
-
38
- it("throws error when toJSON.virtuals is not true", () => {
39
- // Create a schema with toObject.virtuals but without toJSON.virtuals
40
- const testSchema = new mongoose.Schema({name: String});
41
- testSchema.set("toObject", {virtuals: true});
42
- testSchema.set("strict", "throw");
43
- // Not setting toJSON.virtuals
44
-
45
- if (mongoose.models.ToJsonTestModel) {
46
- delete mongoose.models.ToJsonTestModel;
47
- }
48
- mongoose.model("ToJsonTestModel", testSchema);
49
-
50
- // Use spyOn to intercept modelNames and return only our test model
51
- const spy = spyOn(mongoose, "modelNames").mockReturnValue(["ToJsonTestModel"]);
52
-
53
- try {
54
- expect(() => checkModelsStrict()).toThrow("toJSON.virtuals not set to true");
55
- } finally {
56
- spy.mockRestore();
57
- delete mongoose.models.ToJsonTestModel;
58
- }
59
- });
60
-
61
- it("throws error when strict mode is not set to throw", () => {
62
- // Create a schema with virtuals but without strict mode
63
- const testSchema = new mongoose.Schema({name: String});
64
- testSchema.set("toObject", {virtuals: true});
65
- testSchema.set("toJSON", {virtuals: true});
66
- // Not setting strict to "throw"
67
-
68
- if (mongoose.models.StrictTestModel) {
69
- delete mongoose.models.StrictTestModel;
70
- }
71
- mongoose.model("StrictTestModel", testSchema);
72
-
73
- const spy = spyOn(mongoose, "modelNames").mockReturnValue(["StrictTestModel"]);
74
-
75
- try {
76
- expect(() => checkModelsStrict()).toThrow("is not set to strict mode");
77
- } finally {
78
- spy.mockRestore();
79
- delete mongoose.models.StrictTestModel;
80
- }
81
- });
82
-
83
- it("passes when all checks pass", () => {
84
- // Create a properly configured schema
85
- const testSchema = new mongoose.Schema({name: String});
86
- testSchema.set("toObject", {virtuals: true});
87
- testSchema.set("toJSON", {virtuals: true});
88
- testSchema.set("strict", "throw");
89
-
90
- if (mongoose.models.GoodTestModel) {
91
- delete mongoose.models.GoodTestModel;
92
- }
93
- mongoose.model("GoodTestModel", testSchema);
94
-
95
- const spy = spyOn(mongoose, "modelNames").mockReturnValue(["GoodTestModel"]);
96
-
97
- try {
98
- expect(() => checkModelsStrict()).not.toThrow();
99
- } finally {
100
- spy.mockRestore();
101
- delete mongoose.models.GoodTestModel;
102
- }
103
- });
104
-
105
- it("skips strict mode check for ignored models", () => {
106
- // Create a properly configured model
107
- const goodSchema = new mongoose.Schema({name: String});
108
- goodSchema.set("toObject", {virtuals: true});
109
- goodSchema.set("toJSON", {virtuals: true});
110
- goodSchema.set("strict", "throw");
111
-
112
- if (mongoose.models.GoodModel) {
113
- delete mongoose.models.GoodModel;
114
- }
115
- mongoose.model("GoodModel", goodSchema);
116
-
117
- // Create a model without strict mode that we'll ignore
118
- const badSchema = new mongoose.Schema({name: String});
119
- badSchema.set("toObject", {virtuals: true});
120
- badSchema.set("toJSON", {virtuals: true});
121
- // Not setting strict - should fail unless ignored
122
-
123
- if (mongoose.models.IgnoredModel) {
124
- delete mongoose.models.IgnoredModel;
125
- }
126
- mongoose.model("IgnoredModel", badSchema);
127
-
128
- const spy = spyOn(mongoose, "modelNames").mockReturnValue(["GoodModel", "IgnoredModel"]);
129
-
130
- try {
131
- // Without ignoring, should throw for IgnoredModel
132
- expect(() => checkModelsStrict()).toThrow("is not set to strict mode");
133
-
134
- // With ignoring IgnoredModel, should pass
135
- expect(() => checkModelsStrict(["IgnoredModel"])).not.toThrow();
136
- } finally {
137
- spy.mockRestore();
138
- delete mongoose.models.GoodModel;
139
- delete mongoose.models.IgnoredModel;
140
- }
141
- });
142
-
143
- it("handles multiple models and validates all", () => {
144
- // Create three properly configured models
145
- const schema1 = new mongoose.Schema({name: String});
146
- schema1.set("toObject", {virtuals: true});
147
- schema1.set("toJSON", {virtuals: true});
148
- schema1.set("strict", "throw");
149
-
150
- const schema2 = new mongoose.Schema({value: Number});
151
- schema2.set("toObject", {virtuals: true});
152
- schema2.set("toJSON", {virtuals: true});
153
- schema2.set("strict", "throw");
154
-
155
- const schema3 = new mongoose.Schema({active: Boolean});
156
- schema3.set("toObject", {virtuals: true});
157
- schema3.set("toJSON", {virtuals: true});
158
- schema3.set("strict", "throw");
159
-
160
- if (mongoose.models.MultiModel1) delete mongoose.models.MultiModel1;
161
- if (mongoose.models.MultiModel2) delete mongoose.models.MultiModel2;
162
- if (mongoose.models.MultiModel3) delete mongoose.models.MultiModel3;
163
-
164
- mongoose.model("MultiModel1", schema1);
165
- mongoose.model("MultiModel2", schema2);
166
- mongoose.model("MultiModel3", schema3);
167
-
168
- const spy = spyOn(mongoose, "modelNames").mockReturnValue([
169
- "MultiModel1",
170
- "MultiModel2",
171
- "MultiModel3",
172
- ]);
173
-
174
- try {
175
- expect(() => checkModelsStrict()).not.toThrow();
176
- } finally {
177
- spy.mockRestore();
178
- delete mongoose.models.MultiModel1;
179
- delete mongoose.models.MultiModel2;
180
- delete mongoose.models.MultiModel3;
181
- }
182
- });
183
-
184
- it("handles empty model list", () => {
185
- const spy = spyOn(mongoose, "modelNames").mockReturnValue([]);
186
-
187
- try {
188
- expect(() => checkModelsStrict()).not.toThrow();
189
- } finally {
190
- spy.mockRestore();
191
- }
192
- });
6
+ it("checks valid ObjectIds", () => {
7
+ expect(isValidObjectId("62c44da0003d9f8ee8cc925c")).toBe(true);
8
+ expect(isValidObjectId("620000000000000000000000")).toBe(true);
9
+ // Mongoose's builtin "ObjectId.isValid" will falsely say this is an ObjectId.
10
+ expect(isValidObjectId("1234567890ab")).toBe(false);
11
+ expect(isValidObjectId("microsoft123")).toBe(false);
12
+ expect(isValidObjectId("62c44da0003d9f8ee8cc925x")).toBe(false);
193
13
  });
194
14
  });
package/CLAUDE.md DELETED
@@ -1,107 +0,0 @@
1
- # @terreno/api
2
-
3
- REST API framework built on Express/Mongoose, styled after Django REST Framework.
4
-
5
- ## Commands
6
-
7
- ```bash
8
- bun run compile # Compile TypeScript
9
- bun run dev # Watch mode
10
- bun run test # Run tests
11
- bun run lint # Lint code
12
- bun run lint:fix # Fix lint issues
13
- ```
14
-
15
- ## Architecture
16
-
17
- ### modelRouter
18
-
19
- Automatically creates RESTful CRUD APIs for Mongoose models with built-in permissions, population, filtering, and lifecycle hooks.
20
-
21
- ```typescript
22
- import {modelRouter, modelRouterOptions, Permissions} from "@terreno/api";
23
-
24
- const router = modelRouter(YourModel, {
25
- permissions: {
26
- list: [Permissions.IsAuthenticated],
27
- create: [Permissions.IsAuthenticated],
28
- read: [Permissions.IsOwner],
29
- update: [Permissions.IsOwner],
30
- delete: [], // Disabled
31
- },
32
- sort: "-created",
33
- queryFields: ["_id", "type", "name"],
34
- });
35
- ```
36
-
37
- ### Custom Routes
38
-
39
- For non-CRUD endpoints, use the OpenAPI builder:
40
-
41
- ```typescript
42
- import {asyncHandler, authenticateMiddleware, createOpenApiBuilder} from "@terreno/api";
43
-
44
- router.get("/yourRoute/:id", [
45
- authenticateMiddleware(),
46
- createOpenApiBuilder(options)
47
- .withTags(["yourTag"])
48
- .withSummary("Brief summary")
49
- .withPathParameter("id", {type: "string"})
50
- .withResponse(200, {data: {type: "object"}})
51
- .build(),
52
- ], asyncHandler(async (req, res) => {
53
- return res.json({data: result});
54
- }));
55
- ```
56
-
57
- ## Conventions
58
-
59
- ### Error Handling
60
- - Throw `APIError` with appropriate status codes: `throw new APIError({status: 400, title: "Message"})`
61
- - Services should throw user-friendly errors
62
-
63
- ### Mongoose
64
- - Do not use `Model.findOne` - use `Model.findExactlyOne` or `Model.findOneOrThrow`
65
- - Define statics/methods by direct assignment: `schema.methods = {bar() {}}`
66
- - All model types live in `src/modelInterfaces.ts`
67
-
68
- ### User Type Casting
69
- - In API routes: `req.user` is `UserDocument | undefined`
70
- - In @terreno/api callbacks: cast with `const user = u as unknown as UserDocument`
71
- - Never use `as any as UserDocument`
72
-
73
- ### Logging
74
- - Use `logger.info/warn/error/debug` for permanent logs (not `console.log`)
75
-
76
- ### Testing
77
- - Use bun test with expect for testing
78
- - Use existing manual mocks from `src/__mocks__/`
79
- - Never mock @terreno/api or models
80
-
81
- ## Model Type Generation
82
-
83
- When creating/modifying Mongoose models, update `src/modelInterfaces.ts`:
84
-
85
- ```typescript
86
- export type YourModelMethods = {
87
- customMethod: (this: YourModelDocument, param: string) => Promise<void>;
88
- };
89
-
90
- export type YourModelStatics = DefaultStatics<YourModelDocument> & {
91
- customStatic: (this: YourModelModel, param: string) => Promise<YourModelDocument>;
92
- };
93
-
94
- export type YourModelModel = DefaultModel<YourModelDocument> & YourModelStatics;
95
- export type YourModelSchema = mongoose.Schema<YourModelDocument, YourModelModel, YourModelMethods>;
96
- export type YourModelDocument = DefaultDoc & YourModelMethods & {
97
- fieldName: string;
98
- };
99
- ```
100
-
101
- ## SDK Generation
102
-
103
- After modifying routes, regenerate the SDK:
104
-
105
- ```bash
106
- bun run sdk
107
- ```