@declaro/core 2.0.0-beta.9 → 2.0.0-y.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 (90) hide show
  1. package/dist/app/app-context.d.ts +8 -0
  2. package/dist/app/app-lifecycle.d.ts +4 -0
  3. package/dist/app/app.d.ts +22 -0
  4. package/dist/app/index.d.ts +3 -20
  5. package/dist/auth/permission-validator.d.ts +34 -0
  6. package/dist/auth/permission-validator.test.d.ts +1 -0
  7. package/dist/context/context.d.ts +88 -13
  8. package/dist/context/legacy-context.test.d.ts +1 -0
  9. package/dist/errors/errors.d.ts +36 -0
  10. package/dist/events/event-manager.d.ts +11 -6
  11. package/dist/http/headers.d.ts +4 -0
  12. package/dist/http/headers.spec.d.ts +1 -0
  13. package/dist/http/request-context.d.ts +12 -0
  14. package/dist/http/request-context.spec.d.ts +1 -0
  15. package/dist/http/request.d.ts +8 -0
  16. package/dist/http/request.spec.d.ts +1 -0
  17. package/dist/http/url.d.ts +8 -0
  18. package/dist/http/url.spec.d.ts +1 -0
  19. package/dist/index.d.ts +9 -3
  20. package/dist/pkg.cjs +30 -2
  21. package/dist/pkg.mjs +56461 -207
  22. package/dist/schema/application.d.ts +83 -0
  23. package/dist/schema/application.test.d.ts +1 -0
  24. package/dist/schema/define-model.d.ts +7 -4
  25. package/dist/schema/index.d.ts +7 -0
  26. package/dist/schema/labels.d.ts +13 -0
  27. package/dist/schema/labels.test.d.ts +1 -0
  28. package/dist/schema/module.d.ts +7 -0
  29. package/dist/schema/module.test.d.ts +1 -0
  30. package/dist/schema/properties.d.ts +19 -0
  31. package/dist/schema/response.d.ts +31 -0
  32. package/dist/schema/response.test.d.ts +1 -0
  33. package/dist/schema/transform-model.d.ts +1 -1
  34. package/dist/schema/types.d.ts +81 -15
  35. package/dist/schema/types.test.d.ts +1 -0
  36. package/dist/typescript/constant-manipulation/snake-case.d.ts +22 -0
  37. package/dist/typescript/index.d.ts +1 -0
  38. package/dist/typescript/objects.d.ts +6 -0
  39. package/package.json +8 -3
  40. package/src/app/app-context.ts +14 -0
  41. package/src/app/app-lifecycle.ts +14 -0
  42. package/src/app/app.ts +45 -0
  43. package/src/app/index.ts +3 -34
  44. package/src/auth/permission-validator.test.ts +209 -0
  45. package/src/auth/permission-validator.ts +135 -0
  46. package/src/context/context.test.ts +585 -94
  47. package/src/context/context.ts +348 -32
  48. package/src/context/legacy-context.test.ts +141 -0
  49. package/src/errors/errors.ts +73 -0
  50. package/src/events/event-manager.spec.ts +54 -8
  51. package/src/events/event-manager.ts +40 -24
  52. package/src/http/headers.spec.ts +48 -0
  53. package/src/http/headers.ts +16 -0
  54. package/src/http/request-context.spec.ts +39 -0
  55. package/src/http/request-context.ts +43 -0
  56. package/src/http/request.spec.ts +52 -0
  57. package/src/http/request.ts +22 -0
  58. package/src/http/url.spec.ts +87 -0
  59. package/src/http/url.ts +48 -0
  60. package/src/index.ts +9 -3
  61. package/src/schema/application.test.ts +286 -0
  62. package/src/schema/application.ts +150 -0
  63. package/src/schema/define-model.test.ts +48 -2
  64. package/src/schema/define-model.ts +40 -9
  65. package/src/schema/index.ts +7 -0
  66. package/src/schema/labels.test.ts +60 -0
  67. package/src/schema/labels.ts +30 -0
  68. package/src/schema/module.test.ts +39 -0
  69. package/src/schema/module.ts +6 -0
  70. package/src/schema/properties.ts +40 -0
  71. package/src/schema/response.test.ts +101 -0
  72. package/src/schema/response.ts +93 -0
  73. package/src/schema/transform-model.ts +1 -1
  74. package/src/schema/types.test.ts +28 -0
  75. package/src/schema/types.ts +135 -15
  76. package/src/typescript/constant-manipulation/snake-case.md +496 -0
  77. package/src/typescript/constant-manipulation/snake-case.ts +76 -0
  78. package/src/typescript/index.ts +1 -0
  79. package/src/typescript/objects.ts +8 -5
  80. package/tsconfig.json +4 -1
  81. package/dist/context/index.d.ts +0 -3
  82. package/dist/interfaces/IDatastoreProvider.d.ts +0 -16
  83. package/dist/interfaces/IStore.d.ts +0 -4
  84. package/dist/interfaces/index.d.ts +0 -2
  85. package/dist/server/index.d.ts +0 -2
  86. package/src/context/index.ts +0 -3
  87. package/src/interfaces/IDatastoreProvider.ts +0 -23
  88. package/src/interfaces/IStore.ts +0 -4
  89. package/src/interfaces/index.ts +0 -2
  90. package/src/server/index.ts +0 -3
@@ -0,0 +1,286 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { Application } from './application'
3
+ import { Module } from './module'
4
+ import { defineModel } from './define-model'
5
+ import type { OpenAPIV3_1 } from 'openapi-types'
6
+ import { Response } from './response'
7
+
8
+ describe('Application schema', () => {
9
+ it('should define an application schema', () => {
10
+ const app = new Application({
11
+ title: 'My API',
12
+ version: '1.0.0',
13
+ description: 'My API description',
14
+ termsOfService: 'https://example.com/terms',
15
+ contact: {
16
+ name: 'API Support',
17
+ url: 'https://example.com/support',
18
+ email: 'test@test.com',
19
+ },
20
+ })
21
+
22
+ expect(app.info.title).toBe('My API')
23
+ expect(app.info.version).toBe('1.0.0')
24
+ expect(app.info.description).toBe('My API description')
25
+ expect(app.info.termsOfService).toBe('https://example.com/terms')
26
+ expect(app.info.contact.name).toBe('API Support')
27
+ expect(app.info.contact.url).toBe('https://example.com/support')
28
+ expect(app.info.contact.email).toBe('test@test.com')
29
+ })
30
+
31
+ it('should be able to add a module', () => {
32
+ const app = new Application({
33
+ title: 'My API',
34
+ version: '1.0.0',
35
+ description: 'My API description',
36
+ termsOfService: 'https://example.com/terms',
37
+ contact: {
38
+ name: 'API Support',
39
+ url: 'https://example.com/support',
40
+ email: 'test@test.com',
41
+ },
42
+ })
43
+
44
+ const result = app.defineModule({
45
+ name: 'Module',
46
+ description: 'Module description',
47
+ externalDocs: {
48
+ description: 'External documentation',
49
+ url: 'https://example.com/docs',
50
+ },
51
+ })
52
+
53
+ expect(result).toBeInstanceOf(Module)
54
+
55
+ const module = app.getModule('Module')
56
+
57
+ expect(module).toBeInstanceOf(Module)
58
+
59
+ expect(module.application).toBeInstanceOf(Application)
60
+ expect(module.application.info.title).toBe('My API')
61
+ expect(module.application.info.version).toBe('1.0.0')
62
+
63
+ expect(module).toBeInstanceOf(Module)
64
+ expect(module.tag.name).toBe('Module')
65
+ expect(module.tag.description).toBe('Module description')
66
+ expect(module.tag.externalDocs.description).toBe('External documentation')
67
+ })
68
+
69
+ it('Should allow multiple module definitions to be chained', () => {
70
+ const app = new Application({
71
+ title: 'My API',
72
+ version: '1.0.0',
73
+ description: 'My API description',
74
+ termsOfService: 'https://example.com/terms',
75
+ contact: {
76
+ name: 'API Support',
77
+ url: 'https://example.com/support',
78
+ email: 'test@test.com',
79
+ },
80
+ })
81
+
82
+ app.defineModule({
83
+ name: 'Module1',
84
+ description: 'Module 1 description',
85
+ externalDocs: {
86
+ description: 'External documentation 1',
87
+ url: 'https://example.com/docs1',
88
+ },
89
+ })
90
+ app.defineModule({
91
+ name: 'Module2',
92
+ description: 'Module 2 description',
93
+ externalDocs: {
94
+ description: 'External documentation 2',
95
+ url: 'https://example.com/docs2',
96
+ },
97
+ })
98
+
99
+ const module1 = app.getModule('Module1')
100
+ const module2 = app.getModule('Module2')
101
+
102
+ expect(module1.tag.name).toBe('Module1')
103
+ expect(module1.tag.description).toBe('Module 1 description')
104
+ expect(module1.tag.externalDocs.description).toBe('External documentation 1')
105
+ expect(module1.tag.externalDocs.url).toBe('https://example.com/docs1')
106
+
107
+ expect(module2.tag.name).toBe('Module2')
108
+ expect(module2.tag.description).toBe('Module 2 description')
109
+ expect(module2.tag.externalDocs.description).toBe('External documentation 2')
110
+ expect(module2.tag.externalDocs.url).toBe('https://example.com/docs2')
111
+ })
112
+
113
+ it('Should allow models to be added to the application', () => {
114
+ const app = new Application({
115
+ title: 'My API',
116
+ version: '1.0.0',
117
+ description: 'My API description',
118
+ termsOfService: 'https://example.com/terms',
119
+ contact: {
120
+ name: 'API Support',
121
+ url: 'https://example.com/support',
122
+ email: 'test@test.com',
123
+ },
124
+ })
125
+
126
+ const model1 = defineModel('Model1', {
127
+ type: 'object',
128
+ properties: {
129
+ name: {
130
+ type: 'string',
131
+ },
132
+ },
133
+ })
134
+
135
+ const model2 = defineModel('Model2', {
136
+ type: 'object',
137
+ properties: {
138
+ number: {
139
+ type: 'integer',
140
+ },
141
+ },
142
+ })
143
+
144
+ app.addModel(model1, model2)
145
+
146
+ const allModels = app.getModels()
147
+
148
+ expect(allModels).toHaveLength(2)
149
+
150
+ expect(allModels[0].name).toBe('Model1')
151
+ expect(allModels[1].name).toBe('Model2')
152
+ })
153
+
154
+ it('Should allow responses to be added to the application', () => {
155
+ const app = new Application({
156
+ title: 'My API',
157
+ version: '1.0.0',
158
+ description: 'My API description',
159
+ termsOfService: 'https://example.com/terms',
160
+ contact: {
161
+ name: 'API Support',
162
+ url: 'https://example.com/support',
163
+ email: 'test@test.com',
164
+ },
165
+ })
166
+
167
+ const error500 = new Response(500, {
168
+ description: 'Internal server error',
169
+ }).content('application/json', {
170
+ schema: {
171
+ type: 'object',
172
+ properties: {
173
+ message: {
174
+ type: 'string',
175
+ },
176
+ },
177
+ },
178
+ })
179
+
180
+ const error404 = new Response(404, {
181
+ description: 'Not found',
182
+ }).content('application/json', {
183
+ schema: {
184
+ type: 'object',
185
+ properties: {
186
+ message: {
187
+ type: 'string',
188
+ },
189
+ },
190
+ },
191
+ })
192
+
193
+ const error400 = new Response(400, {
194
+ description: 'Bad request',
195
+ }).content('application/json', {
196
+ schema: {
197
+ type: 'object',
198
+ properties: {
199
+ message: {
200
+ type: 'string',
201
+ },
202
+ },
203
+ },
204
+ })
205
+
206
+ app.addResponse(error500, error404, error400)
207
+
208
+ const response500 = app.getResponse(500)
209
+ const response404 = app.getResponse(404)
210
+ const response400 = app.getResponse(400)
211
+
212
+ const allResponses = app.getResponseSchema()
213
+
214
+ expect(response500.code).toBe(500)
215
+ expect(response500.schema.description).toBe('Internal server error')
216
+
217
+ expect(response404.code).toBe(404)
218
+ expect(response404.schema.description).toBe('Not found')
219
+
220
+ expect(response400.code).toBe(400)
221
+ expect(response400.schema.description).toBe('Bad request')
222
+
223
+ expect(allResponses[500].description).toBe('Internal server error')
224
+ expect((allResponses[500] as any).content['application/json'].schema.type).toBe('object')
225
+ expect((allResponses[500] as any).content['application/json'].schema.properties.message.type).toBe('string')
226
+
227
+ expect(allResponses[404].description).toBe('Not found')
228
+ expect((allResponses[404] as any).content['application/json'].schema.type).toBe('object')
229
+ expect((allResponses[404] as any).content['application/json'].schema.properties.message.type).toBe('string')
230
+
231
+ expect(allResponses[400].description).toBe('Bad request')
232
+ expect((allResponses[400] as any).content['application/json'].schema.type).toBe('object')
233
+ expect((allResponses[400] as any).content['application/json'].schema.properties.message.type).toBe('string')
234
+ })
235
+
236
+ it('Should allow paths to be added to the application', () => {
237
+ function test(doc: OpenAPIV3_1.Document) {}
238
+
239
+ test({
240
+ openapi: '3.0.0',
241
+ info: {
242
+ title: 'My API',
243
+ version: '1.0.0',
244
+ },
245
+ paths: {
246
+ '/test': {
247
+ get: {
248
+ description: 'Test endpoint',
249
+ responses: {
250
+ 200: {
251
+ description: 'Successful response',
252
+ content: {
253
+ 'application/json': {
254
+ schema: {
255
+ oneOf: [
256
+ {
257
+ type: 'object',
258
+ properties: {
259
+ message: {
260
+ type: 'string',
261
+ },
262
+ },
263
+ },
264
+ {
265
+ type: 'object',
266
+ properties: {
267
+ error: {
268
+ type: 'string',
269
+ },
270
+ },
271
+ },
272
+ ],
273
+ },
274
+ },
275
+ },
276
+ },
277
+ 500: {
278
+ description: 'Internal server error',
279
+ },
280
+ },
281
+ },
282
+ },
283
+ },
284
+ })
285
+ })
286
+ })
@@ -0,0 +1,150 @@
1
+ import { cloneDeep } from 'lodash-es'
2
+ import type { Model } from './define-model'
3
+ import { Module } from './module'
4
+ import type { DeclaroSchema } from './types'
5
+ import { Response } from './response'
6
+
7
+ export type ModuleFactory = (mod: Module) => Module | undefined
8
+
9
+ export class Application {
10
+ private _info: DeclaroSchema.InfoObject
11
+ private _modules: Map<string, Module>
12
+ private _models: Map<string, Model>
13
+ private _responses: Map<number, Response>
14
+
15
+ constructor(info: DeclaroSchema.InfoObject) {
16
+ this._info = info
17
+ this._modules = new Map()
18
+ this._models = new Map()
19
+ this._responses = new Map()
20
+ }
21
+
22
+ /**
23
+ * Get the application info (read-only). Application info can only be set in the constructor of the application.
24
+ *
25
+ * Note: This should be set by the application implementation. It would be dangerous for any one module to be able to change the application info for all of the others.
26
+ */
27
+ get info() {
28
+ return cloneDeep(this._info)
29
+ }
30
+
31
+ /**
32
+ * Define a module for the application.
33
+ *
34
+ * @param tag The tag info for the module, to be used in the OpenAPI schema
35
+ * @param factory A convenience function to define the module, adding endpoints, etc.
36
+ * @returns
37
+ */
38
+ defineModule(tag: DeclaroSchema.TagObject, factory?: ModuleFactory) {
39
+ let mod = new Module(this, tag)
40
+ const result = factory?.(mod)
41
+
42
+ if (result instanceof Module) {
43
+ mod = result
44
+ } else if (!!result) {
45
+ throw new Error('Module factory must return a Module instance or undefined')
46
+ }
47
+
48
+ this._modules.set(tag.name, mod)
49
+
50
+ return mod
51
+ }
52
+
53
+ /**
54
+ * Get a module by name.
55
+ *
56
+ * @param name The name of the module to get
57
+ * @returns The `Module` instance for the given name
58
+ */
59
+ getModule(name: string): Module {
60
+ return this._modules.get(name)
61
+ }
62
+
63
+ /**
64
+ * Add models to the application.
65
+ *
66
+ * @param models The models to add to the application
67
+ * @example app.addModel(User)
68
+ * @example app.addModel(User, Post)
69
+ * @example app.addModel(...myModels)
70
+ * @returns The application instance
71
+ */
72
+ addModel(...models: Model[]) {
73
+ models.forEach((model) => {
74
+ this._models.set(model.name, model)
75
+ })
76
+
77
+ return this
78
+ }
79
+
80
+ /**
81
+ * Get a model by name.
82
+ *
83
+ * @param name The name of the model to get
84
+ * @returns The `Model` instance for the given name
85
+ */
86
+ getModel(name: string): Model {
87
+ return this._models.get(name)
88
+ }
89
+
90
+ /**
91
+ * Get all models.
92
+ *
93
+ * @returns All models in the application
94
+ */
95
+ getModels(): Model[] {
96
+ return Array.from(this._models.values())
97
+ }
98
+
99
+ /**
100
+ * Add a response to the application.
101
+ *
102
+ * @param code The HTTP status code for the response
103
+ * @param response The response object
104
+ * @returns The application instance
105
+ */
106
+ addResponse(...responses: Response[]) {
107
+ responses.forEach((response) => {
108
+ const existingResponse = this.getResponse(response.code)
109
+
110
+ if (existingResponse) {
111
+ existingResponse.merge(response)
112
+ } else {
113
+ this._responses.set(response.code, response)
114
+ }
115
+ })
116
+ return this
117
+ }
118
+
119
+ /**
120
+ * Get a response by status code.
121
+ *
122
+ * @param code The HTTP status code for the response
123
+ * @returns The `Response` instance for the given status code
124
+ */
125
+ getResponse(code: number): Response {
126
+ return this._responses.get(code)
127
+ }
128
+
129
+ /**
130
+ * Get all responses. (read-only)
131
+ *
132
+ * @returns All responses in the application
133
+ */
134
+ get responses(): Map<number, Response> {
135
+ return new Map(this._responses)
136
+ }
137
+
138
+ /**
139
+ * Get all responses as a hash of keys and values.
140
+ *
141
+ * @returns A hash of keys and values representing all responses in the application
142
+ */
143
+ getResponseSchema(): DeclaroSchema.ResponsesObject {
144
+ const responsesHash: { [key: string]: DeclaroSchema.ResponseObject } = {}
145
+ this._responses.forEach((value, key) => {
146
+ responsesHash[key] = value.schema
147
+ })
148
+ return responsesHash
149
+ }
150
+ }
@@ -1,5 +1,6 @@
1
1
  import { describe, expect, it } from 'vitest'
2
- import { defineModel } from '.'
2
+ import { defineModel, type ModelName, type ModelProperties } from '.'
3
+ import { t } from './properties'
3
4
 
4
5
  describe('Model definition', async () => {
5
6
  it('should define a model', async () => {
@@ -8,6 +9,10 @@ describe('Model definition', async () => {
8
9
  properties: {
9
10
  title: {
10
11
  type: 'string',
12
+ labels: {
13
+ singularEntityName: 'Title',
14
+ pluralEntityName: 'Titles',
15
+ },
11
16
  },
12
17
  year: {
13
18
  type: 'integer',
@@ -15,21 +20,62 @@ describe('Model definition', async () => {
15
20
  },
16
21
  },
17
22
  required: ['title'],
23
+ labels: {
24
+ singularEntityName: 'Movie',
25
+ pluralEntityName: 'Movies',
26
+ },
18
27
  })
19
28
 
29
+ const name: ModelName<typeof movie> = movie.name
30
+ const properties: ModelProperties<typeof movie> = movie.schema.properties
31
+
32
+ expect(name).toBe('Movie')
33
+ expect(properties.title.type).toBe('string')
34
+
20
35
  expect(movie.name).toBe('Movie')
21
- expect(movie.schema).toEqual({
36
+ expect(movie.isModel).toBe(true)
37
+ expect(movie.schema.type).toBe('object')
38
+ expect(movie.schema.properties.title['type']).toBe('string')
39
+ expect(movie.schema.properties.year['type']).toBe('integer')
40
+ expect(movie.schema.properties.year['format']).toBe('int32')
41
+ expect(movie.schema.required).toEqual(['title'])
42
+ expect(movie.schema.labels).toBeTypeOf('object')
43
+ expect(movie.schema.labels.pluralEntityName).toBe('Movies')
44
+ expect(movie.schema.labels.singularEntityName).toBe('Movie')
45
+
46
+ expect(movie.schema.properties.title.labels).toBeTypeOf('object')
47
+ expect(movie.schema.properties.title.labels.pluralEntityName).toBe('Titles')
48
+ expect(movie.schema.properties.title.labels.singularEntityName).toBe('Title')
49
+ expect((movie.schema.properties.title.labels as any).pluralSlug).toBe(undefined) // Only include explicitly defined labels—leave the rest up to the framework
50
+ })
51
+
52
+ it('should define a model with nested properties', async () => {
53
+ const movie = defineModel('Movie', {
22
54
  type: 'object',
23
55
  properties: {
24
56
  title: {
25
57
  type: 'string',
58
+ labels: {
59
+ singularEntityName: 'Title',
60
+ pluralEntityName: 'Titles',
61
+ },
26
62
  },
27
63
  year: {
28
64
  type: 'integer',
29
65
  format: 'int32',
30
66
  },
67
+ meta: {
68
+ type: 'object',
69
+ properties: {
70
+ rating: t.integer(),
71
+ },
72
+ },
31
73
  },
32
74
  required: ['title'],
75
+ labels: {
76
+ singularEntityName: 'Movie',
77
+ pluralEntityName: 'Movies',
78
+ },
33
79
  })
34
80
  })
35
81
  })
@@ -1,19 +1,50 @@
1
- import { OpenAPIV3, type OpenAPIV3_1 } from 'openapi-types'
2
1
  import type { DeclaroSchema } from './types'
3
2
 
4
- export type Model = {
5
- name: string
6
- schema: DeclaroSchema.SchemaObject
3
+ export type Model<
4
+ T extends DeclaroSchema.AnyObjectProperties = DeclaroSchema.AnyObjectProperties,
5
+ N extends Readonly<string> = string,
6
+ > = {
7
+ name: N
8
+ schema: DeclaroSchema.SchemaObject<T>
7
9
  isModel: true
8
10
  }
9
11
 
10
- export function defineModel(
12
+ type TraverseSchemaFn<T extends DeclaroSchema.AnyObjectProperties> = (
11
13
  name: string,
12
- doc: DeclaroSchema.SchemaObject,
13
- ): Model {
14
+ property: DeclaroSchema.SchemaObject<any>,
15
+ schema: DeclaroSchema.SchemaObject<T>,
16
+ ) => DeclaroSchema.SchemaObject<any>
17
+
18
+ export type ModelName<T extends Model<any, string>> = T['name']
19
+ export type ModelProperties<T extends Model<any, string>> = T['schema']['properties']
20
+
21
+ function traverseSchema<T extends DeclaroSchema.AnyObjectProperties>(
22
+ schema: DeclaroSchema.SchemaObject<T>,
23
+ fn: TraverseSchemaFn<T>,
24
+ ) {
25
+ for (const [key, value] of Object.entries(schema.properties ?? {})) {
26
+ let properties: DeclaroSchema.AnyObjectProperties = schema.properties!
27
+ properties[key] = fn(key, value, schema)
28
+ }
29
+ }
30
+
31
+ export function initializeModel(schema: DeclaroSchema.SchemaObject<any>) {
32
+ return schema
33
+ }
34
+
35
+ export function defineModel<T extends DeclaroSchema.AnyObjectProperties, N extends Readonly<string>>(
36
+ name: N,
37
+ doc: DeclaroSchema.SchemaObject<T>,
38
+ ): Model<T, N> {
39
+ traverseSchema(doc, (name, property, schema) => {
40
+ return initializeModel({
41
+ propertyName: name,
42
+ ...property,
43
+ })
44
+ })
14
45
  return {
15
- name,
16
- schema: doc,
46
+ name: name as Readonly<N>,
47
+ schema: { ...doc },
17
48
  isModel: true,
18
49
  }
19
50
  }
@@ -1,3 +1,10 @@
1
1
  export * from './define-model'
2
2
  export * from './supported-types'
3
3
  export * from './transform-model'
4
+ export * from './labels'
5
+ export * from './module'
6
+ export * from './application'
7
+ export * from './response'
8
+ export * from './formats'
9
+ export * from './properties'
10
+ export * from './types'
@@ -0,0 +1,60 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { getEntityLabels } from './labels'
3
+
4
+ describe('Entity Labels', () => {
5
+ it('Should be able to define labels for entities', () => {
6
+ const labels = getEntityLabels('User')
7
+
8
+ expect(labels.singularLabel).toBe('User')
9
+ expect(labels.pluralLabel).toBe('Users')
10
+ expect(labels.singularParameter).toBe('user')
11
+ expect(labels.pluralParameter).toBe('users')
12
+ expect(labels.singularSlug).toBe('user')
13
+ expect(labels.pluralSlug).toBe('users')
14
+ expect(labels.singularEntityName).toBe('User')
15
+ expect(labels.pluralEntityName).toBe('Users')
16
+ expect(labels.singularTableName).toBe('user')
17
+ expect(labels.pluralTableName).toBe('users')
18
+ })
19
+
20
+ it('Should handle different types of entity names', () => {
21
+ const twoWords = getEntityLabels('SomeEntity')
22
+
23
+ expect(twoWords.singularLabel).toBe('Some Entity')
24
+ expect(twoWords.pluralLabel).toBe('Some Entities')
25
+ expect(twoWords.singularParameter).toBe('someEntity')
26
+ expect(twoWords.pluralParameter).toBe('someEntities')
27
+ expect(twoWords.singularSlug).toBe('some-entity')
28
+ expect(twoWords.pluralSlug).toBe('some-entities')
29
+ expect(twoWords.singularEntityName).toBe('SomeEntity')
30
+ expect(twoWords.pluralEntityName).toBe('SomeEntities')
31
+ expect(twoWords.singularTableName).toBe('some-entity')
32
+ expect(twoWords.pluralTableName).toBe('some-entities')
33
+
34
+ const threeWords = getEntityLabels('SomeOtherEntity')
35
+
36
+ expect(threeWords.singularLabel).toBe('Some Other Entity')
37
+ expect(threeWords.pluralLabel).toBe('Some Other Entities')
38
+ expect(threeWords.singularParameter).toBe('someOtherEntity')
39
+ expect(threeWords.pluralParameter).toBe('someOtherEntities')
40
+ expect(threeWords.singularSlug).toBe('some-other-entity')
41
+ expect(threeWords.pluralSlug).toBe('some-other-entities')
42
+ expect(threeWords.singularEntityName).toBe('SomeOtherEntity')
43
+ expect(threeWords.pluralEntityName).toBe('SomeOtherEntities')
44
+ expect(threeWords.singularTableName).toBe('some-other-entity')
45
+ expect(threeWords.pluralTableName).toBe('some-other-entities')
46
+
47
+ const sentence = getEntityLabels('Some other entity')
48
+
49
+ expect(sentence.singularLabel).toBe('Some Other Entity')
50
+ expect(sentence.pluralLabel).toBe('Some Other Entities')
51
+ expect(sentence.singularParameter).toBe('someOtherEntity')
52
+ expect(sentence.pluralParameter).toBe('someOtherEntities')
53
+ expect(sentence.singularSlug).toBe('some-other-entity')
54
+ expect(sentence.pluralSlug).toBe('some-other-entities')
55
+ expect(sentence.singularEntityName).toBe('SomeOtherEntity')
56
+ expect(sentence.pluralEntityName).toBe('SomeOtherEntities')
57
+ expect(sentence.singularTableName).toBe('some-other-entity')
58
+ expect(sentence.pluralTableName).toBe('some-other-entities')
59
+ })
60
+ })
@@ -0,0 +1,30 @@
1
+ import { capitalCase, camelCase, paramCase, pascalCase } from 'change-case'
2
+ import pluralize from 'pluralize'
3
+
4
+ export type EntityLabels = {
5
+ singularLabel?: string
6
+ pluralLabel?: string
7
+ singularParameter?: string
8
+ pluralParameter?: string
9
+ singularSlug?: string
10
+ pluralSlug?: string
11
+ singularEntityName?: string
12
+ pluralEntityName?: string
13
+ singularTableName?: string
14
+ pluralTableName?: string
15
+ }
16
+
17
+ export function getEntityLabels(entity: string, labels: Partial<EntityLabels> = {}): EntityLabels {
18
+ return {
19
+ singularLabel: labels.singularLabel ?? capitalCase(pluralize(entity, 1)),
20
+ pluralLabel: labels.pluralLabel ?? capitalCase(pluralize(entity, 10)),
21
+ singularParameter: labels.singularParameter ?? camelCase(pluralize(entity, 1)),
22
+ pluralParameter: labels.pluralParameter ?? camelCase(pluralize(entity, 10)),
23
+ singularSlug: labels.singularSlug ?? paramCase(pluralize(entity, 1)),
24
+ pluralSlug: labels.pluralSlug ?? paramCase(pluralize(entity, 10)),
25
+ singularEntityName: labels.singularEntityName ?? pascalCase(pluralize(entity, 1)),
26
+ pluralEntityName: labels.pluralEntityName ?? pascalCase(pluralize(entity, 10)),
27
+ singularTableName: labels.singularTableName ?? paramCase(pluralize(entity, 1)),
28
+ pluralTableName: labels.pluralTableName ?? paramCase(pluralize(entity, 10)),
29
+ }
30
+ }