@radio-garden/ditojs-server 2.85.2-0.5067ad799

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 (137) hide show
  1. package/README.md +6 -0
  2. package/package.json +95 -0
  3. package/src/app/Application.js +1186 -0
  4. package/src/app/Validator.js +405 -0
  5. package/src/app/index.js +2 -0
  6. package/src/cli/console.js +152 -0
  7. package/src/cli/db/createMigration.js +241 -0
  8. package/src/cli/db/index.js +7 -0
  9. package/src/cli/db/listAssetConfig.js +10 -0
  10. package/src/cli/db/migrate.js +12 -0
  11. package/src/cli/db/reset.js +23 -0
  12. package/src/cli/db/rollback.js +12 -0
  13. package/src/cli/db/seed.js +80 -0
  14. package/src/cli/db/unlock.js +9 -0
  15. package/src/cli/index.js +72 -0
  16. package/src/controllers/AdminController.js +322 -0
  17. package/src/controllers/CollectionController.js +274 -0
  18. package/src/controllers/Controller.js +657 -0
  19. package/src/controllers/ControllerAction.js +370 -0
  20. package/src/controllers/MemberAction.js +27 -0
  21. package/src/controllers/ModelController.js +63 -0
  22. package/src/controllers/RelationController.js +93 -0
  23. package/src/controllers/UsersController.js +64 -0
  24. package/src/controllers/index.js +5 -0
  25. package/src/errors/AssetError.js +7 -0
  26. package/src/errors/AuthenticationError.js +7 -0
  27. package/src/errors/AuthorizationError.js +7 -0
  28. package/src/errors/ControllerError.js +14 -0
  29. package/src/errors/DatabaseError.js +37 -0
  30. package/src/errors/GraphError.js +7 -0
  31. package/src/errors/ModelError.js +12 -0
  32. package/src/errors/NotFoundError.js +7 -0
  33. package/src/errors/NotImplementedError.js +7 -0
  34. package/src/errors/QueryBuilderError.js +7 -0
  35. package/src/errors/RelationError.js +21 -0
  36. package/src/errors/ResponseError.js +56 -0
  37. package/src/errors/ValidationError.js +7 -0
  38. package/src/errors/index.js +13 -0
  39. package/src/graph/DitoGraphProcessor.js +213 -0
  40. package/src/graph/expression.js +53 -0
  41. package/src/graph/graph.js +258 -0
  42. package/src/graph/index.js +3 -0
  43. package/src/index.js +9 -0
  44. package/src/lib/EventEmitter.js +66 -0
  45. package/src/lib/KnexHelper.js +30 -0
  46. package/src/lib/index.js +2 -0
  47. package/src/middleware/attachLogger.js +8 -0
  48. package/src/middleware/createTransaction.js +33 -0
  49. package/src/middleware/extendContext.js +10 -0
  50. package/src/middleware/findRoute.js +20 -0
  51. package/src/middleware/handleConnectMiddleware.js +99 -0
  52. package/src/middleware/handleError.js +29 -0
  53. package/src/middleware/handleRoute.js +23 -0
  54. package/src/middleware/handleSession.js +77 -0
  55. package/src/middleware/handleUser.js +31 -0
  56. package/src/middleware/index.js +11 -0
  57. package/src/middleware/logRequests.js +125 -0
  58. package/src/middleware/setupRequestStorage.js +14 -0
  59. package/src/mixins/AssetMixin.js +78 -0
  60. package/src/mixins/SessionMixin.js +17 -0
  61. package/src/mixins/TimeStampedMixin.js +41 -0
  62. package/src/mixins/UserMixin.js +171 -0
  63. package/src/mixins/index.js +4 -0
  64. package/src/models/AssetModel.js +4 -0
  65. package/src/models/Model.js +1205 -0
  66. package/src/models/RelationAccessor.js +41 -0
  67. package/src/models/SessionModel.js +4 -0
  68. package/src/models/TimeStampedModel.js +4 -0
  69. package/src/models/UserModel.js +4 -0
  70. package/src/models/definitions/assets.js +5 -0
  71. package/src/models/definitions/filters.js +121 -0
  72. package/src/models/definitions/hooks.js +8 -0
  73. package/src/models/definitions/index.js +22 -0
  74. package/src/models/definitions/modifiers.js +5 -0
  75. package/src/models/definitions/options.js +5 -0
  76. package/src/models/definitions/properties.js +73 -0
  77. package/src/models/definitions/relations.js +5 -0
  78. package/src/models/definitions/schema.js +5 -0
  79. package/src/models/definitions/scopes.js +36 -0
  80. package/src/models/index.js +5 -0
  81. package/src/query/QueryBuilder.js +1077 -0
  82. package/src/query/QueryFilters.js +66 -0
  83. package/src/query/QueryParameters.js +79 -0
  84. package/src/query/Registry.js +29 -0
  85. package/src/query/index.js +3 -0
  86. package/src/schema/formats/_empty.js +4 -0
  87. package/src/schema/formats/_required.js +4 -0
  88. package/src/schema/formats/index.js +2 -0
  89. package/src/schema/index.js +5 -0
  90. package/src/schema/keywords/_computed.js +7 -0
  91. package/src/schema/keywords/_foreign.js +7 -0
  92. package/src/schema/keywords/_hidden.js +7 -0
  93. package/src/schema/keywords/_index.js +7 -0
  94. package/src/schema/keywords/_instanceof.js +45 -0
  95. package/src/schema/keywords/_primary.js +7 -0
  96. package/src/schema/keywords/_range.js +18 -0
  97. package/src/schema/keywords/_relate.js +13 -0
  98. package/src/schema/keywords/_specificType.js +7 -0
  99. package/src/schema/keywords/_unique.js +7 -0
  100. package/src/schema/keywords/_unsigned.js +7 -0
  101. package/src/schema/keywords/_validate.js +73 -0
  102. package/src/schema/keywords/index.js +12 -0
  103. package/src/schema/relations.js +324 -0
  104. package/src/schema/relations.test.js +177 -0
  105. package/src/schema/schema.js +289 -0
  106. package/src/schema/schema.test.js +720 -0
  107. package/src/schema/types/_asset.js +31 -0
  108. package/src/schema/types/_color.js +4 -0
  109. package/src/schema/types/index.js +2 -0
  110. package/src/services/Service.js +35 -0
  111. package/src/services/index.js +1 -0
  112. package/src/storage/AssetFile.js +81 -0
  113. package/src/storage/DiskStorage.js +114 -0
  114. package/src/storage/S3Storage.js +169 -0
  115. package/src/storage/Storage.js +231 -0
  116. package/src/storage/index.js +9 -0
  117. package/src/utils/duration.js +15 -0
  118. package/src/utils/emitter.js +8 -0
  119. package/src/utils/fs.js +10 -0
  120. package/src/utils/function.js +17 -0
  121. package/src/utils/function.test.js +77 -0
  122. package/src/utils/handler.js +17 -0
  123. package/src/utils/json.js +3 -0
  124. package/src/utils/model.js +35 -0
  125. package/src/utils/net.js +17 -0
  126. package/src/utils/object.js +82 -0
  127. package/src/utils/object.test.js +86 -0
  128. package/src/utils/scope.js +7 -0
  129. package/types/index.d.ts +3547 -0
  130. package/types/tests/application.test-d.ts +26 -0
  131. package/types/tests/controller.test-d.ts +113 -0
  132. package/types/tests/errors.test-d.ts +53 -0
  133. package/types/tests/fixtures.ts +19 -0
  134. package/types/tests/model.test-d.ts +193 -0
  135. package/types/tests/query-builder.test-d.ts +106 -0
  136. package/types/tests/relation.test-d.ts +83 -0
  137. package/types/tests/storage.test-d.ts +113 -0
@@ -0,0 +1,289 @@
1
+ import { isObject, isArray, isString, equals } from '@ditojs/utils'
2
+
3
+ const schemaCaches = {}
4
+
5
+ function getSchemaCache(options) {
6
+ const key = Object.entries(options || {})
7
+ .toSorted()
8
+ .map(([key, value]) => `${key}:${value}`)
9
+ .join(',') || 'default'
10
+ return (schemaCaches[key] ||= new WeakMap())
11
+ }
12
+
13
+ export function convertSchema(
14
+ schema,
15
+ options = {},
16
+ parentEntry = null
17
+ ) {
18
+ const original = schema
19
+ const isRoot = parentEntry === null
20
+
21
+ const schemaCache = getSchemaCache(options)
22
+ if (schemaCache.has(original)) {
23
+ const { schema, definitions, parentEntries } = schemaCache.get(original)
24
+ parentEntries.push(parentEntry)
25
+ if (definitions) {
26
+ if (isRoot) {
27
+ return { ...schema, definitions }
28
+ } else {
29
+ mergeDefinitions(parentEntry.definitions, definitions)
30
+ }
31
+ }
32
+ // When the schema is null, a circular reference is being processed.
33
+ // Break the chain by cloning the original schema and converting it again:
34
+ return schema === null
35
+ ? convertSchema({ ...original }, options, parentEntry)
36
+ : schema
37
+ }
38
+
39
+ const entry = {
40
+ schema: null,
41
+ definitions: {},
42
+ parentEntries: parentEntry ? [parentEntry] : []
43
+ }
44
+
45
+ // To prevent circular references, cache the entry before all conversion work.
46
+ schemaCache.set(original, entry)
47
+
48
+ let definitions = null
49
+ if (isArray(schema)) {
50
+ // Needed for allOf, anyOf, oneOf, not, items, see below:
51
+ schema = schema.map(item => convertSchema(item, options, entry))
52
+ } else if (isObject(schema)) {
53
+ // Create a shallow clone so we can modify and return:
54
+ // Also collect and propagate the definitions up to the root schema through
55
+ // `options.definitions`, as passed from `Model static get jsonSchema()`:
56
+ const { definitions: defs, ...rest } = schema
57
+ definitions = defs
58
+ schema = rest
59
+ const { $ref, type } = schema
60
+ const jsonType = jsonTypes[type]
61
+
62
+ if (schema.required === true) {
63
+ // Our 'required' is not the same as JSON Schema's: Use the 'required'
64
+ // format instead that only validates if the required value is not empty,
65
+ // meaning neither nullish nor an empty string. The JSON schema `required`
66
+ // array is generated separately below through `convertProperties()`.
67
+ delete schema.required
68
+ schema = addFormat(schema, 'required')
69
+ }
70
+
71
+ // Convert array items
72
+ schema.prefixItems &&= convertSchema(schema.prefixItems, options, entry)
73
+ schema.items &&= convertSchema(schema.items, options, entry)
74
+
75
+ // Handle nested allOf, anyOf, oneOf & co. fields
76
+ for (const key of ['allOf', 'anyOf', 'oneOf', 'not', '$extend']) {
77
+ if (key in schema) {
78
+ schema[key] = convertSchema(schema[key], options, entry)
79
+ }
80
+ }
81
+
82
+ if (isString($ref)) {
83
+ // If the $ref is a nested Dito.js definition, convert it to a JSON schema
84
+ // reference. If it is a full URL, use it as is.
85
+ schema.$ref = $ref.startsWith('#')
86
+ ? `#/definitions/${$ref}`
87
+ : $ref
88
+ } else if (isString(type)) {
89
+ if (jsonType) {
90
+ schema.type = jsonType
91
+ } else if (['date', 'datetime', 'timestamp'].includes(type)) {
92
+ // Date properties can be submitted both as a string or a Date object.
93
+ // Provide validation through date-time format, which in Ajv appears
94
+ // to handle both types correctly.
95
+ schema.type = ['string', 'object']
96
+ schema = addFormat(schema, 'date-time')
97
+ } else if (type !== 'null') {
98
+ // A reference to another model as nested JSON data, use $ref or
99
+ // instanceof instead of type, based on the passed option:
100
+ if (options.useInstanceOf) {
101
+ schema.type = 'object'
102
+ schema.instanceof = type
103
+ } else {
104
+ // Move `type` to `$ref`, but still keep other properties for now,
105
+ // to support `nullable`.
106
+ delete schema.type
107
+ // TODO: Consider moving to `model` keyword instead that would support
108
+ // model validation and still could be combined with other keywords.
109
+ schema.$ref = type
110
+ }
111
+ }
112
+ }
113
+ if (excludeDefaults[schema.default]) {
114
+ delete schema.default
115
+ }
116
+ if (schema.nullable) {
117
+ if (schema.$ref) {
118
+ // `$ref` doesn't play with `nullable`, so convert to `oneOf`
119
+ delete schema.nullable
120
+ schema = {
121
+ oneOf: [
122
+ { type: 'null' },
123
+ schema
124
+ ]
125
+ }
126
+ } else if (schema.enum && !schema.enum.includes(null)) {
127
+ // Make nullable work with enum, see the issue for more details:
128
+ // https://github.com/ajv-validator/ajv/issues/1471
129
+ schema.enum.push(null)
130
+ }
131
+ }
132
+
133
+ // Convert properties last. This is needed for circular references
134
+ // to work correctly, as the properties may reference the same schema
135
+ // that is being converted right now.
136
+ let hasConvertedProperties = false
137
+ if (schema.properties) {
138
+ const { properties, required } = convertProperties(
139
+ schema.properties,
140
+ options,
141
+ entry
142
+ )
143
+ schema.properties = properties
144
+ if (required.length > 0) {
145
+ schema.required = required
146
+ }
147
+ hasConvertedProperties = true
148
+ }
149
+ for (const key of ['additionalProperties', 'patternProperties']) {
150
+ if (isObject(schema[key])) {
151
+ // TODO: Don't we need to handle required here too?
152
+ const { properties } = convertProperties(
153
+ schema[key],
154
+ options,
155
+ entry
156
+ )
157
+ schema[key] = properties
158
+ hasConvertedProperties = true
159
+ }
160
+ }
161
+ if (
162
+ jsonType &&
163
+ (hasConvertedProperties || schema.discriminator) &&
164
+ !('unevaluatedProperties' in schema)
165
+ ) {
166
+ // Invert the logic of `unevaluatedProperties` so that it needs to be
167
+ // explicitly set to `true`:
168
+ schema.unevaluatedProperties = false
169
+ }
170
+ }
171
+
172
+ entry.schema = schema
173
+
174
+ // Only convert definitions once `entry.schema` is set, so that it works as
175
+ // expected with circular references.
176
+ if (definitions) {
177
+ mergeDefinitions(
178
+ entry.definitions,
179
+ convertDefinitions(definitions, options, entry)
180
+ )
181
+ }
182
+
183
+ if (Object.keys(entry.definitions).length > 0) {
184
+ // Propagate the definitions up the parent entry chains, that due to
185
+ // circular references may not be up to date yet.
186
+ mergeDefinitionsRecursively(entry, entry.definitions)
187
+ if (isRoot) {
188
+ schema.definitions = entry.definitions
189
+ }
190
+ }
191
+
192
+ return schema
193
+ }
194
+
195
+ function convertProperties(schemaProperties, options, entry) {
196
+ const properties = {}
197
+ const required = []
198
+ for (const [key, property] of Object.entries(schemaProperties)) {
199
+ properties[key] = convertSchema(property, options, entry)
200
+ if (property?.required) {
201
+ required.push(key)
202
+ }
203
+ }
204
+ return { properties, required }
205
+ }
206
+
207
+ function convertDefinitions(definitions, options, entry) {
208
+ let converted = null
209
+ for (const [key, schema] of Object.entries(definitions)) {
210
+ if (!key.startsWith('#')) {
211
+ throw new Error(
212
+ `Invalid definition '${
213
+ key
214
+ }', the name of nested Dito.js definitions must start with '#': ${
215
+ JSON.stringify(schema)
216
+ }`
217
+ )
218
+ }
219
+ converted ??= {}
220
+ converted[key] = convertSchema(schema, options, entry)
221
+ }
222
+ return converted
223
+ }
224
+
225
+ function mergeDefinitions(definitions, defs) {
226
+ for (const [key, def] of Object.entries(defs)) {
227
+ const definition = definitions[key]
228
+ if (definition && !equals(definition, def)) {
229
+ throw new Error(
230
+ `Duplicate nested definition for '${key}' with different schema: ${
231
+ JSON.stringify(def, null, 2)
232
+ }, ${
233
+ JSON.stringify(definition, null, 2)
234
+ }`
235
+ )
236
+ }
237
+ definitions[key] ??= def
238
+ }
239
+ }
240
+
241
+ function mergeDefinitionsRecursively(
242
+ entry,
243
+ definitions,
244
+ visited = new WeakSet()
245
+ ) {
246
+ if (!visited.has(entry)) {
247
+ visited.add(entry)
248
+
249
+ if (definitions !== entry.definitions) {
250
+ mergeDefinitions(entry.definitions, definitions)
251
+ }
252
+ for (const parentEntry of entry.parentEntries) {
253
+ mergeDefinitionsRecursively(parentEntry, definitions, visited)
254
+ }
255
+ }
256
+ }
257
+
258
+ function addFormat(schema, newFormat) {
259
+ // Support multiple `format` keywords through `allOf`:
260
+ const { allOf, format, ...rest } = schema
261
+ if (format || allOf) {
262
+ if (!allOf?.find(({ format }) => format === newFormat)) {
263
+ schema = {
264
+ ...rest,
265
+ allOf: [...(allOf ?? []), { format }, { format: newFormat }]
266
+ }
267
+ }
268
+ } else {
269
+ schema.format = newFormat
270
+ }
271
+ return schema
272
+ }
273
+
274
+ // Table to translate schema types to JSON schema types. Other types are allowed
275
+ // also, e.g. 'date', 'datetime', 'timestamp', but they need special treatment.
276
+ // Anything not recognized as a type is used as a $ref instead.
277
+ const jsonTypes = {
278
+ string: 'string',
279
+ text: 'string',
280
+ number: 'number',
281
+ integer: 'integer',
282
+ boolean: 'boolean',
283
+ object: 'object',
284
+ array: 'array'
285
+ }
286
+
287
+ const excludeDefaults = {
288
+ 'now()': true
289
+ }