@terreno/api 0.0.17 → 0.1.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 (77) hide show
  1. package/.claude/CLAUDE.local.md +204 -0
  2. package/.cursor/rules/00-root.mdc +338 -0
  3. package/.github/copilot-instructions.md +333 -0
  4. package/AGENTS.md +333 -0
  5. package/README.md +76 -7
  6. package/biome.jsonc +1 -1
  7. package/dist/api.d.ts +68 -1
  8. package/dist/api.js +140 -5
  9. package/dist/api.query.test.js +1 -1
  10. package/dist/api.test.js +222 -484
  11. package/dist/auth.js +3 -1
  12. package/dist/errors.js +15 -12
  13. package/dist/example.js +7 -7
  14. package/dist/expressServer.d.ts +8 -2
  15. package/dist/expressServer.js +8 -1
  16. package/dist/githubAuth.d.ts +64 -0
  17. package/dist/githubAuth.js +293 -0
  18. package/dist/githubAuth.test.d.ts +1 -0
  19. package/dist/githubAuth.test.js +351 -0
  20. package/dist/index.d.ts +3 -0
  21. package/dist/index.js +3 -0
  22. package/dist/logger.js +1 -1
  23. package/dist/middleware.js +1 -1
  24. package/dist/notifiers/googleChatNotifier.js +1 -1
  25. package/dist/notifiers/googleChatNotifier.test.js +1 -1
  26. package/dist/notifiers/slackNotifier.js +1 -1
  27. package/dist/notifiers/slackNotifier.test.js +1 -1
  28. package/dist/notifiers/zoomNotifier.js +1 -1
  29. package/dist/notifiers/zoomNotifier.test.js +1 -1
  30. package/dist/openApi.test.js +8 -5
  31. package/dist/openApiBuilder.d.ts +69 -1
  32. package/dist/openApiBuilder.js +109 -5
  33. package/dist/openApiValidator.d.ts +296 -0
  34. package/dist/openApiValidator.js +698 -0
  35. package/dist/openApiValidator.test.d.ts +1 -0
  36. package/dist/openApiValidator.test.js +346 -0
  37. package/dist/permissions.js +1 -1
  38. package/dist/plugins.test.js +3 -3
  39. package/dist/terrenoPlugin.d.ts +4 -0
  40. package/dist/terrenoPlugin.js +2 -0
  41. package/dist/tests/bunSetup.js +2 -2
  42. package/dist/tests.js +34 -24
  43. package/package.json +7 -2
  44. package/src/__snapshots__/openApi.test.ts.snap +399 -0
  45. package/src/__snapshots__/openApiBuilder.test.ts.snap +108 -0
  46. package/src/api.query.test.ts +1 -1
  47. package/src/api.test.ts +161 -374
  48. package/src/api.ts +210 -4
  49. package/src/auth.ts +3 -1
  50. package/src/errors.ts +15 -12
  51. package/src/example.ts +7 -7
  52. package/src/expressServer.ts +18 -2
  53. package/src/githubAuth.test.ts +223 -0
  54. package/src/githubAuth.ts +335 -0
  55. package/src/index.ts +3 -0
  56. package/src/logger.ts +1 -1
  57. package/src/middleware.ts +1 -1
  58. package/src/notifiers/googleChatNotifier.test.ts +1 -1
  59. package/src/notifiers/googleChatNotifier.ts +1 -1
  60. package/src/notifiers/slackNotifier.test.ts +1 -1
  61. package/src/notifiers/slackNotifier.ts +1 -1
  62. package/src/notifiers/zoomNotifier.test.ts +1 -1
  63. package/src/notifiers/zoomNotifier.ts +1 -1
  64. package/src/openApi.test.ts +8 -5
  65. package/src/openApiBuilder.ts +188 -15
  66. package/src/openApiValidator.test.ts +241 -0
  67. package/src/openApiValidator.ts +860 -0
  68. package/src/permissions.ts +1 -1
  69. package/src/plugins.test.ts +3 -3
  70. package/src/terrenoPlugin.ts +5 -0
  71. package/src/tests/bunSetup.ts +2 -2
  72. package/src/tests.ts +34 -24
  73. package/CLAUDE.md +0 -107
  74. package/dist/response.d.ts +0 -0
  75. package/dist/response.js +0 -1
  76. package/index.ts +0 -1
  77. package/src/response.ts +0 -0
@@ -1,5 +1,5 @@
1
1
  // Defaults closed
2
- import * as Sentry from "@sentry/node";
2
+ import * as Sentry from "@sentry/bun";
3
3
  import type express from "express";
4
4
  import type {NextFunction} from "express";
5
5
  import mongoose, {type Model} from "mongoose";
@@ -39,9 +39,9 @@ interface StuffModelType extends Model<Stuff> {
39
39
  }
40
40
 
41
41
  const stuffSchema = new Schema<Stuff>({
42
- date: DateOnly,
43
- name: String,
44
- ownerId: String,
42
+ date: {description: "The date associated with this item", type: DateOnly as any},
43
+ name: {description: "The name of the item", type: String},
44
+ ownerId: {description: "The user who owns this item", type: String},
45
45
  });
46
46
 
47
47
  stuffSchema.plugin(isDeletedPlugin);
@@ -0,0 +1,5 @@
1
+ import type express from "express";
2
+
3
+ export interface TerrenoPlugin {
4
+ register(app: express.Application): void;
5
+ }
@@ -93,8 +93,8 @@ afterEach(() => {
93
93
  logs = [];
94
94
  });
95
95
 
96
- // Mock @sentry/node module
97
- mock.module("@sentry/node", () => {
96
+ // Mock @sentry/bun module
97
+ mock.module("@sentry/bun", () => {
98
98
  const mockFn = (): ReturnType<typeof mock> => mock(() => {});
99
99
 
100
100
  // Mock Scope
package/src/tests.ts CHANGED
@@ -54,10 +54,10 @@ export interface Food {
54
54
  }
55
55
 
56
56
  const userSchema = new Schema<User>({
57
- admin: {default: false, type: Boolean},
58
- age: Number,
59
- name: String,
60
- username: String,
57
+ admin: {default: false, description: "Whether the user has admin privileges", type: Boolean},
58
+ age: {description: "The user's age", type: Number},
59
+ name: {description: "The user's display name", type: String},
60
+ username: {description: "The user's username", type: String},
61
61
  });
62
62
 
63
63
  userSchema.plugin(passportLocalMongoose as any, {
@@ -80,55 +80,65 @@ userSchema.methods.postCreate = async function (body: any) {
80
80
  export const UserModel = model<User>("User", userSchema);
81
81
 
82
82
  const superUserSchema = new Schema<SuperUser>({
83
- superTitle: {required: true, type: String},
83
+ superTitle: {description: "The super user's title", required: true, type: String},
84
84
  });
85
85
  export const SuperUserModel = UserModel.discriminator("SuperUser", superUserSchema);
86
86
 
87
87
  const staffUserSchema = new Schema<StaffUser>({
88
- department: {required: true, type: String},
88
+ department: {
89
+ description: "The department the staff member belongs to",
90
+ required: true,
91
+ type: String,
92
+ },
89
93
  });
90
94
  export const StaffUserModel = UserModel.discriminator("Staff", staffUserSchema);
91
95
 
92
96
  const foodCategorySchema = new Schema<FoodCategory>(
93
97
  {
94
- name: String,
95
- show: Boolean,
98
+ name: {description: "The name of the food category", type: String},
99
+ show: {description: "Whether this category is visible", type: Boolean},
96
100
  },
97
101
  {timestamps: {createdAt: "created", updatedAt: "updated"}}
98
102
  );
99
103
 
100
104
  const likesSchema = new Schema<any>({
101
- likes: Boolean,
102
- userId: {ref: "User", type: "ObjectId"},
105
+ likes: {description: "Whether the user liked the item", type: Boolean},
106
+ userId: {description: "The user who liked the item", ref: "User", type: "ObjectId"},
103
107
  });
104
108
 
105
109
  const foodSchema = new Schema<Food>(
106
110
  {
107
- calories: Number,
108
- categories: [foodCategorySchema],
109
- created: Date,
111
+ calories: {description: "Number of calories in the food", type: Number},
112
+ categories: {description: "Categories this food belongs to", type: [foodCategorySchema]},
113
+ created: {description: "When this food was created", type: Date},
110
114
  eatenBy: [
111
115
  {
116
+ description: "Users who have eaten this food",
112
117
  ref: "User",
113
118
  required: true,
114
119
  type: Schema.Types.ObjectId,
115
120
  },
116
121
  ],
117
- expiration: DateOnly,
118
- hidden: {default: false, type: Boolean},
122
+ expiration: {description: "Expiration date of the food", type: DateOnly as any},
123
+ hidden: {
124
+ default: false,
125
+ description: "Whether this food is hidden from listings",
126
+ type: Boolean,
127
+ },
119
128
  lastEatenWith: {
129
+ description: "Map of user names to dates they last ate this food with",
120
130
  of: Date,
121
131
  type: Map,
122
132
  },
123
- likesIds: {required: true, type: [likesSchema]},
124
- name: String,
125
- ownerId: {ref: "User", type: "ObjectId"},
133
+ likesIds: {description: "User likes for this food", required: true, type: [likesSchema]},
134
+ name: {description: "The name of the food", type: String},
135
+ ownerId: {description: "The user who owns this food entry", ref: "User", type: "ObjectId"},
126
136
  source: {
127
- dateAdded: String,
128
- href: String,
129
- name: String,
137
+ dateAdded: {description: "When the source was added", type: String},
138
+ href: {description: "URL of the source", type: String},
139
+ name: {description: "Name of the source", type: String},
130
140
  },
131
- tags: [String],
141
+ tags: {description: "Tags associated with this food", type: [String]},
132
142
  },
133
143
  {strict: "throw", toJSON: {virtuals: true}, toObject: {virtuals: true}}
134
144
  );
@@ -145,8 +155,8 @@ interface RequiredField {
145
155
  }
146
156
 
147
157
  const requiredSchema = new Schema<RequiredField>({
148
- about: String,
149
- name: {required: true, type: String},
158
+ about: {description: "Information about the item", type: String},
159
+ name: {description: "The name of the item", required: true, type: String},
150
160
  });
151
161
  export const RequiredModel = model<RequiredField>("Required", requiredSchema);
152
162
 
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
- ```
File without changes
package/dist/response.js DELETED
@@ -1 +0,0 @@
1
- "use strict";
package/index.ts DELETED
@@ -1 +0,0 @@
1
- console.log("Hello via Bun!");
package/src/response.ts DELETED
File without changes