@ditojs/server 1.8.0 → 1.9.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ditojs/server",
3
- "version": "1.8.0",
3
+ "version": "1.9.1",
4
4
  "type": "module",
5
5
  "description": "Dito.js Server – Dito.js is a declarative and modern web framework, based on Objection.js, Koa.js and Vue.js",
6
6
  "repository": "https://github.com/ditojs/dito/tree/master/packages/server",
@@ -21,24 +21,24 @@
21
21
  "node >= 16"
22
22
  ],
23
23
  "dependencies": {
24
- "@ditojs/admin": "^1.8.0",
24
+ "@ditojs/admin": "^1.9.1",
25
25
  "@ditojs/build": "^1.8.0",
26
- "@ditojs/router": "^1.8.0",
27
- "@ditojs/utils": "^1.8.0",
26
+ "@ditojs/router": "^1.9.0",
27
+ "@ditojs/utils": "^1.9.0",
28
28
  "@koa/cors": "^3.3.0",
29
29
  "@koa/multer": "^3.0.0",
30
30
  "@originjs/vite-plugin-commonjs": "^1.0.3",
31
31
  "ajv": "^8.11.0",
32
32
  "ajv-formats": "^2.1.1",
33
- "aws-sdk": "^2.1141.0",
33
+ "aws-sdk": "^2.1180.0",
34
34
  "axios": "^0.27.2",
35
35
  "bcryptjs": "^2.4.3",
36
36
  "bytes": "^3.1.2",
37
37
  "data-uri-to-buffer": "^4.0.0",
38
- "eventemitter2": "^6.4.5",
39
- "file-type": "^17.1.1",
38
+ "eventemitter2": "^6.4.6",
39
+ "file-type": "^17.1.3",
40
40
  "fs-extra": "^10.1.0",
41
- "image-size": "^1.0.1",
41
+ "image-size": "^1.0.2",
42
42
  "is-svg": "^4.3.2",
43
43
  "koa": "^2.13.4",
44
44
  "koa-bodyparser": "^4.3.0",
@@ -53,31 +53,31 @@
53
53
  "koa-session": "^6.2.0",
54
54
  "koa-static": "^5.0.0",
55
55
  "mime-types": "^2.1.35",
56
- "multer": "^1.4.4",
56
+ "multer": "^1.4.5-lts.1",
57
57
  "multer-s3": "^2.10.0",
58
- "nanoid": "^3.3.4",
58
+ "nanoid": "^4.0.0",
59
59
  "parse-duration": "^1.0.2",
60
60
  "passport-local": "^1.0.0",
61
61
  "passthrough-counter": "^1.0.0",
62
62
  "picocolors": "^1.0.0",
63
63
  "picomatch": "^2.3.1",
64
- "pino": "^7.11.0",
65
- "pino-pretty": "^7.6.1",
64
+ "pino": "^8.3.0",
65
+ "pino-pretty": "^8.1.0",
66
66
  "pluralize": "^8.0.0",
67
67
  "repl": "^0.1.3",
68
68
  "uuid": "^8.3.2",
69
- "vite": "^2.9.9",
70
- "vite-plugin-vue2": "^2.0.1",
71
- "vue": "^2.6.14",
72
- "vue-template-compiler": "^2.6.14"
69
+ "vite": "^3.0.2",
70
+ "vite-plugin-vue2": "^2.0.2",
71
+ "vue": "^2.7.8",
72
+ "vue-template-compiler": "^2.7.8"
73
73
  },
74
74
  "peerDependencies": {
75
75
  "knex": "^1.0.5",
76
76
  "objection": "^3.0.1"
77
77
  },
78
78
  "devDependencies": {
79
- "knex": "^2.0.0",
79
+ "knex": "^2.2.0",
80
80
  "objection": "^3.0.1"
81
81
  },
82
- "gitHead": "bb2c9e85083eabc51d1f4b1fd0bffd6b306b0e47"
82
+ "gitHead": "36ad7c74fedaf7ea7c94eb5bb7ecb4b2dca3a0ba"
83
83
  }
@@ -368,6 +368,7 @@ const validatorOptions = {
368
368
  $data: false,
369
369
  $comment: false,
370
370
  coerceTypes: false,
371
+ discriminator: true,
371
372
  multipleOfPrecision: false,
372
373
  ownProperties: true,
373
374
  removeAdditional: false,
@@ -15,7 +15,7 @@ import { merge } from '@ditojs/utils'
15
15
  import { Controller } from './Controller.js'
16
16
  import { handleConnectMiddleware } from '../middleware/index.js'
17
17
  import { ControllerError } from '../errors/index.js'
18
- import { formatJson, deprecate } from '../utils/index.js'
18
+ import { formatJson, getRandomFreePort, deprecate } from '../utils/index.js'
19
19
 
20
20
  export class AdminController extends Controller {
21
21
  // @override
@@ -130,7 +130,12 @@ export class AdminController extends Controller {
130
130
  const server = await createServer({
131
131
  ...config,
132
132
  server: {
133
- middlewareMode: 'html',
133
+ middlewareMode: true,
134
+ hmr: {
135
+ // Use a random free port instead of vite's default 24678, since we
136
+ // may be running multiple servers in parallel (e.g. e2e and dev).
137
+ port: await getRandomFreePort()
138
+ },
134
139
  watch: {
135
140
  // Watch the @ditojs packages while in dev mode, although they are
136
141
  // inside the node_modules folder.
@@ -5,51 +5,61 @@ export function convertSchema(schema, options = {}) {
5
5
  // Needed for allOf, anyOf, oneOf, not, items:
6
6
  schema = schema.map(entry => convertSchema(entry, options))
7
7
  } else if (isObject(schema)) {
8
+ // Create a shallow clone so we can modify and return:
9
+ schema = { ...schema }
8
10
  const { type } = schema
9
- // Create a shallow clone so we can modify and return, excluding our
10
- // `required` boolean which will get converted to a format further down, and
11
- // to JSON schema's `required` array through `convertProperties()`:
12
- const { required, ...rest } = schema
13
- schema = rest
11
+ if (schema.required === true) {
12
+ // Our 'required' is not the same as JSON Schema's: Use the 'required'
13
+ // format instead that only validates if the required value is not empty,
14
+ // meaning neither nullish nor an empty string. The JSON schema `required`
15
+ // array is generated seperately below through `convertProperties()`.
16
+ delete schema.required
17
+ schema = addFormat(schema, 'required')
18
+ }
19
+
20
+ // Convert properties
21
+ let hasConvertedProperties = false
22
+ if (schema.properties) {
23
+ const { properties, required } = convertProperties(
24
+ schema.properties,
25
+ options
26
+ )
27
+ schema.properties = properties
28
+ if (required.length > 0) {
29
+ schema.required = required
30
+ }
31
+ hasConvertedProperties = true
32
+ }
33
+ if (schema.patternProperties) {
34
+ // TODO: Don't we need to handle required here too?
35
+ const { properties } = convertProperties(
36
+ schema.patternProperties,
37
+ options
38
+ )
39
+ schema.patternProperties = properties
40
+ hasConvertedProperties = true
41
+ }
42
+
43
+ // Convert array items
44
+ schema.prefixItems &&= convertSchema(schema.prefixItems, options)
45
+ schema.items &&= convertSchema(schema.items, options)
46
+
47
+ // Handle nested allOf, anyOf, oneOf, not fields
48
+ for (const key of ['allOf', 'anyOf', 'oneOf', 'not']) {
49
+ if (key in schema) {
50
+ schema[key] = convertSchema(schema[key], options)
51
+ }
52
+ }
53
+
14
54
  if (isString(type)) {
15
55
  // Convert schema property notation to JSON schema
16
56
  const jsonType = jsonTypes[type]
17
57
  if (jsonType) {
18
58
  schema.type = jsonType
19
- if (jsonType === 'object') {
20
- let setAdditionalProperties = false
21
- if (schema.properties) {
22
- const { properties, required } = convertProperties(
23
- schema.properties,
24
- options
25
- )
26
- schema.properties = properties
27
- if (required.length > 0) {
28
- schema.required = required
29
- }
30
- setAdditionalProperties = true
31
- }
32
- if (schema.patternProperties) {
33
- // TODO: Don't we need to handle required here too?
34
- const { properties } = convertProperties(
35
- schema.patternProperties,
36
- options
37
- )
38
- schema.patternProperties = properties
39
- setAdditionalProperties = true
40
- }
41
- if (setAdditionalProperties) {
42
- // Invert the logic of `additionalProperties` so that it needs to be
43
- // explicitely set to `true`:
44
- if (!('additionalProperties' in schema)) {
45
- schema.additionalProperties = false
46
- }
47
- }
48
- } else if (jsonType === 'array') {
49
- const { items } = schema
50
- if (items) {
51
- schema.items = convertSchema(items, options)
52
- }
59
+ if (hasConvertedProperties && !('additionalProperties' in schema)) {
60
+ // Invert the logic of `additionalProperties` so that it needs to be
61
+ // explicitely set to `true`:
62
+ schema.additionalProperties = false
53
63
  }
54
64
  } else if (['date', 'datetime', 'timestamp'].includes(type)) {
55
65
  // Date properties can be submitted both as a string or a Date object.
@@ -57,7 +67,7 @@ export function convertSchema(schema, options = {}) {
57
67
  // to handle both types correctly.
58
68
  schema.type = ['string', 'object']
59
69
  schema = addFormat(schema, 'date-time')
60
- } else {
70
+ } else if (type !== 'null') {
61
71
  // A reference to another model as nested JSON data, use $ref or
62
72
  // instanceof instead of type, based on the passed option:
63
73
  if (options.useInstanceOf) {
@@ -82,19 +92,6 @@ export function convertSchema(schema, options = {}) {
82
92
  }
83
93
  }
84
94
  }
85
- } else {
86
- // Handle nested allOf, anyOf, oneOf, not properties
87
- for (const key of ['allOf', 'anyOf', 'oneOf', 'not']) {
88
- if (key in schema) {
89
- schema[key] = convertSchema(schema[key], options)
90
- }
91
- }
92
- }
93
- if (required) {
94
- // Our 'required' is not the same as JSON Schema's: Use the 'required'
95
- // format instead that only validates if the required value is not
96
- // empty, meaning neither nullish nor an empty string.
97
- schema = addFormat(schema, 'required')
98
95
  }
99
96
  if (excludeDefaults[schema.default]) {
100
97
  delete schema.default
@@ -122,12 +119,13 @@ export function convertProperties(schemaProperties, options) {
122
119
 
123
120
  function addFormat(schema, newFormat) {
124
121
  // Support multiple `format` keywords through `allOf`:
125
- let { allOf, format, ...rest } = schema
122
+ const { allOf, format, ...rest } = schema
126
123
  if (format || allOf) {
127
- allOf ||= []
128
- if (!allOf.find(({ format }) => format === newFormat)) {
129
- allOf.push({ format }, { format: newFormat })
130
- schema = { ...rest, allOf }
124
+ if (!allOf?.find(({ format }) => format === newFormat)) {
125
+ schema = {
126
+ ...rest,
127
+ allOf: [...(allOf ?? []), { format }, { format: newFormat }]
128
+ }
131
129
  }
132
130
  } else {
133
131
  schema.format = newFormat
@@ -118,6 +118,25 @@ describe('convertSchema()', () => {
118
118
  })
119
119
  })
120
120
 
121
+ it('preserves JSON schema-style `required` arrays', () => {
122
+ expect(convertSchema({
123
+ type: 'object',
124
+ required: ['myString', 'myNumber'],
125
+ properties: {
126
+ myString: { type: 'string' },
127
+ myNumber: { type: 'number' }
128
+ }
129
+ })).toEqual({
130
+ type: 'object',
131
+ properties: {
132
+ myString: { type: 'string' },
133
+ myNumber: { type: 'number' }
134
+ },
135
+ additionalProperties: false,
136
+ required: ['myString', 'myNumber']
137
+ })
138
+ })
139
+
121
140
  it(`expands 'object' schemas with properties to JSON schemas allowing no additional properties`, () => {
122
141
  expect(convertSchema({
123
142
  type: 'object',
@@ -390,7 +409,7 @@ describe('convertSchema()', () => {
390
409
  })
391
410
  })
392
411
 
393
- it('convert schemas within oneOf properties', () => {
412
+ it('converts schemas within oneOf properties', () => {
394
413
  expect(convertSchema({
395
414
  type: 'object',
396
415
  properties: {
@@ -473,7 +492,7 @@ describe('convertSchema()', () => {
473
492
  })
474
493
  })
475
494
 
476
- it('support `required: true` on object', () => {
495
+ it('supports `required: true` on object', () => {
477
496
  expect(convertSchema({
478
497
  type: 'object',
479
498
  properties: {
@@ -516,4 +535,58 @@ describe('convertSchema()', () => {
516
535
  required: ['myObject']
517
536
  })
518
537
  })
538
+
539
+ it('processes discriminator schemas correctly', () => {
540
+ expect(convertSchema({
541
+ type: 'object',
542
+ discriminator: { propertyName: 'foo' },
543
+ required: ['foo'],
544
+ oneOf: [
545
+ {
546
+ properties: {
547
+ foo: { const: 'x' },
548
+ a: {
549
+ type: 'string',
550
+ required: true
551
+ }
552
+ }
553
+ },
554
+ {
555
+ properties: {
556
+ foo: { enum: ['y', 'z'] },
557
+ b: {
558
+ type: 'string',
559
+ required: true
560
+ }
561
+ }
562
+ }
563
+ ]
564
+ })).toEqual({
565
+ type: 'object',
566
+ discriminator: { propertyName: 'foo' },
567
+ required: ['foo'],
568
+ oneOf: [
569
+ {
570
+ properties: {
571
+ foo: { const: 'x' },
572
+ a: {
573
+ type: 'string',
574
+ format: 'required'
575
+ }
576
+ },
577
+ required: ['a']
578
+ },
579
+ {
580
+ properties: {
581
+ foo: { enum: ['y', 'z'] },
582
+ b: {
583
+ type: 'string',
584
+ format: 'required'
585
+ }
586
+ },
587
+ required: ['b']
588
+ }
589
+ ]
590
+ })
591
+ })
519
592
  })
@@ -4,5 +4,6 @@ export * from './emitter.js'
4
4
  export * from './function.js'
5
5
  export * from './handler.js'
6
6
  export * from './json.js'
7
+ export * from './net.js'
7
8
  export * from './object.js'
8
9
  export * from './scope.js'
@@ -0,0 +1,17 @@
1
+ import net from 'net'
2
+
3
+ export async function getRandomFreePort() {
4
+ return new Promise((resolve, reject) => {
5
+ const srv = net.createServer()
6
+ srv.listen(0, () => {
7
+ const port = srv.address().port
8
+ srv.close(err => {
9
+ if (err) {
10
+ reject(err)
11
+ } else {
12
+ resolve(port)
13
+ }
14
+ })
15
+ })
16
+ })
17
+ }