@contember/schema-utils 2.1.0-beta.2 → 2.2.0-alpha.2

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 (247) hide show
  1. package/dist/development/index.cjs +11 -0
  2. package/dist/development/index.cjs.map +1 -1
  3. package/dist/development/index.js +11 -0
  4. package/dist/development/index.js.map +1 -1
  5. package/dist/development/src/acl/PredicateDefinitionProcessor.cjs.map +1 -1
  6. package/dist/development/src/acl/PredicateDefinitionProcessor.js.map +1 -1
  7. package/dist/development/src/dataUtils.cjs +1 -0
  8. package/dist/development/src/dataUtils.cjs.map +1 -1
  9. package/dist/development/src/dataUtils.js +1 -0
  10. package/dist/development/src/dataUtils.js.map +1 -1
  11. package/dist/development/src/definition-generator/AclDefinitionCodeGenerator.cjs.map +1 -1
  12. package/dist/development/src/definition-generator/AclDefinitionCodeGenerator.js.map +1 -1
  13. package/dist/development/src/definition-generator/DefinitionCodeGenerator.cjs +37 -2
  14. package/dist/development/src/definition-generator/DefinitionCodeGenerator.cjs.map +1 -1
  15. package/dist/development/src/definition-generator/DefinitionCodeGenerator.js +37 -2
  16. package/dist/development/src/definition-generator/DefinitionCodeGenerator.js.map +1 -1
  17. package/dist/development/src/definition-generator/TsDefinitionGenerator.cjs.map +1 -1
  18. package/dist/development/src/definition-generator/TsDefinitionGenerator.js.map +1 -1
  19. package/dist/development/src/json-schema/collectUnsupportedKeywords.cjs +45 -0
  20. package/dist/development/src/json-schema/collectUnsupportedKeywords.cjs.map +1 -0
  21. package/dist/development/src/json-schema/collectUnsupportedKeywords.js +45 -0
  22. package/dist/development/src/json-schema/collectUnsupportedKeywords.js.map +1 -0
  23. package/dist/development/src/json-schema/jsonSchemaToTsType.cjs +138 -0
  24. package/dist/development/src/json-schema/jsonSchemaToTsType.cjs.map +1 -0
  25. package/dist/development/src/json-schema/jsonSchemaToTsType.js +138 -0
  26. package/dist/development/src/json-schema/jsonSchemaToTsType.js.map +1 -0
  27. package/dist/development/src/json-schema/keywords.cjs +57 -0
  28. package/dist/development/src/json-schema/keywords.cjs.map +1 -0
  29. package/dist/development/src/json-schema/keywords.js +57 -0
  30. package/dist/development/src/json-schema/keywords.js.map +1 -0
  31. package/dist/development/src/lax/LaxSchemaConverter.cjs.map +1 -1
  32. package/dist/development/src/lax/LaxSchemaConverter.js.map +1 -1
  33. package/dist/development/src/model/modelUtils.cjs.map +1 -1
  34. package/dist/development/src/model/modelUtils.js.map +1 -1
  35. package/dist/development/src/model/resolveDefaultColumnType.cjs +2 -0
  36. package/dist/development/src/model/resolveDefaultColumnType.cjs.map +1 -1
  37. package/dist/development/src/model/resolveDefaultColumnType.js +2 -0
  38. package/dist/development/src/model/resolveDefaultColumnType.js.map +1 -1
  39. package/dist/development/src/schemaNormalizer.cjs.map +1 -1
  40. package/dist/development/src/schemaNormalizer.js.map +1 -1
  41. package/dist/development/src/type-schema/acl.cjs.map +1 -1
  42. package/dist/development/src/type-schema/acl.js.map +1 -1
  43. package/dist/development/src/type-schema/condition.cjs +6 -1
  44. package/dist/development/src/type-schema/condition.cjs.map +1 -1
  45. package/dist/development/src/type-schema/condition.js +6 -1
  46. package/dist/development/src/type-schema/condition.js.map +1 -1
  47. package/dist/development/src/type-schema/model.cjs +14 -2
  48. package/dist/development/src/type-schema/model.cjs.map +1 -1
  49. package/dist/development/src/type-schema/model.js +14 -2
  50. package/dist/development/src/type-schema/model.js.map +1 -1
  51. package/dist/development/src/type-schema/settings.cjs +1 -0
  52. package/dist/development/src/type-schema/settings.cjs.map +1 -1
  53. package/dist/development/src/type-schema/settings.js +1 -0
  54. package/dist/development/src/type-schema/settings.js.map +1 -1
  55. package/dist/development/src/type-schema/where.cjs.map +1 -1
  56. package/dist/development/src/type-schema/where.js.map +1 -1
  57. package/dist/development/src/validation/AclValidator.cjs.map +1 -1
  58. package/dist/development/src/validation/AclValidator.js.map +1 -1
  59. package/dist/development/src/validation/ActionsValidator.cjs.map +1 -1
  60. package/dist/development/src/validation/ActionsValidator.js.map +1 -1
  61. package/dist/development/src/validation/MembershipResolver.cjs.map +1 -1
  62. package/dist/development/src/validation/MembershipResolver.js.map +1 -1
  63. package/dist/development/src/validation/ModelValidator.cjs +66 -0
  64. package/dist/development/src/validation/ModelValidator.cjs.map +1 -1
  65. package/dist/development/src/validation/ModelValidator.js +66 -0
  66. package/dist/development/src/validation/ModelValidator.js.map +1 -1
  67. package/dist/development/src/validation/SchemaValidator.cjs.map +1 -1
  68. package/dist/development/src/validation/SchemaValidator.js.map +1 -1
  69. package/dist/development/src/validation/ValidationValidator.cjs.map +1 -1
  70. package/dist/development/src/validation/ValidationValidator.js.map +1 -1
  71. package/dist/development/src/validation/errors.cjs.map +1 -1
  72. package/dist/development/src/validation/errors.js.map +1 -1
  73. package/dist/production/index.cjs +11 -0
  74. package/dist/production/index.cjs.map +1 -1
  75. package/dist/production/index.js +11 -0
  76. package/dist/production/index.js.map +1 -1
  77. package/dist/production/src/acl/PredicateDefinitionProcessor.cjs.map +1 -1
  78. package/dist/production/src/acl/PredicateDefinitionProcessor.js.map +1 -1
  79. package/dist/production/src/dataUtils.cjs +1 -0
  80. package/dist/production/src/dataUtils.cjs.map +1 -1
  81. package/dist/production/src/dataUtils.js +1 -0
  82. package/dist/production/src/dataUtils.js.map +1 -1
  83. package/dist/production/src/definition-generator/AclDefinitionCodeGenerator.cjs.map +1 -1
  84. package/dist/production/src/definition-generator/AclDefinitionCodeGenerator.js.map +1 -1
  85. package/dist/production/src/definition-generator/DefinitionCodeGenerator.cjs +37 -2
  86. package/dist/production/src/definition-generator/DefinitionCodeGenerator.cjs.map +1 -1
  87. package/dist/production/src/definition-generator/DefinitionCodeGenerator.js +37 -2
  88. package/dist/production/src/definition-generator/DefinitionCodeGenerator.js.map +1 -1
  89. package/dist/production/src/definition-generator/TsDefinitionGenerator.cjs.map +1 -1
  90. package/dist/production/src/definition-generator/TsDefinitionGenerator.js.map +1 -1
  91. package/dist/production/src/json-schema/collectUnsupportedKeywords.cjs +45 -0
  92. package/dist/production/src/json-schema/collectUnsupportedKeywords.cjs.map +1 -0
  93. package/dist/production/src/json-schema/collectUnsupportedKeywords.js +45 -0
  94. package/dist/production/src/json-schema/collectUnsupportedKeywords.js.map +1 -0
  95. package/dist/production/src/json-schema/jsonSchemaToTsType.cjs +138 -0
  96. package/dist/production/src/json-schema/jsonSchemaToTsType.cjs.map +1 -0
  97. package/dist/production/src/json-schema/jsonSchemaToTsType.js +138 -0
  98. package/dist/production/src/json-schema/jsonSchemaToTsType.js.map +1 -0
  99. package/dist/production/src/json-schema/keywords.cjs +57 -0
  100. package/dist/production/src/json-schema/keywords.cjs.map +1 -0
  101. package/dist/production/src/json-schema/keywords.js +57 -0
  102. package/dist/production/src/json-schema/keywords.js.map +1 -0
  103. package/dist/production/src/lax/LaxSchemaConverter.cjs.map +1 -1
  104. package/dist/production/src/lax/LaxSchemaConverter.js.map +1 -1
  105. package/dist/production/src/model/modelUtils.cjs.map +1 -1
  106. package/dist/production/src/model/modelUtils.js.map +1 -1
  107. package/dist/production/src/model/resolveDefaultColumnType.cjs +2 -0
  108. package/dist/production/src/model/resolveDefaultColumnType.cjs.map +1 -1
  109. package/dist/production/src/model/resolveDefaultColumnType.js +2 -0
  110. package/dist/production/src/model/resolveDefaultColumnType.js.map +1 -1
  111. package/dist/production/src/schemaNormalizer.cjs.map +1 -1
  112. package/dist/production/src/schemaNormalizer.js.map +1 -1
  113. package/dist/production/src/type-schema/acl.cjs.map +1 -1
  114. package/dist/production/src/type-schema/acl.js.map +1 -1
  115. package/dist/production/src/type-schema/condition.cjs +6 -1
  116. package/dist/production/src/type-schema/condition.cjs.map +1 -1
  117. package/dist/production/src/type-schema/condition.js +6 -1
  118. package/dist/production/src/type-schema/condition.js.map +1 -1
  119. package/dist/production/src/type-schema/model.cjs +14 -2
  120. package/dist/production/src/type-schema/model.cjs.map +1 -1
  121. package/dist/production/src/type-schema/model.js +14 -2
  122. package/dist/production/src/type-schema/model.js.map +1 -1
  123. package/dist/production/src/type-schema/settings.cjs +1 -0
  124. package/dist/production/src/type-schema/settings.cjs.map +1 -1
  125. package/dist/production/src/type-schema/settings.js +1 -0
  126. package/dist/production/src/type-schema/settings.js.map +1 -1
  127. package/dist/production/src/type-schema/where.cjs.map +1 -1
  128. package/dist/production/src/type-schema/where.js.map +1 -1
  129. package/dist/production/src/validation/AclValidator.cjs.map +1 -1
  130. package/dist/production/src/validation/AclValidator.js.map +1 -1
  131. package/dist/production/src/validation/ActionsValidator.cjs.map +1 -1
  132. package/dist/production/src/validation/ActionsValidator.js.map +1 -1
  133. package/dist/production/src/validation/MembershipResolver.cjs.map +1 -1
  134. package/dist/production/src/validation/MembershipResolver.js.map +1 -1
  135. package/dist/production/src/validation/ModelValidator.cjs +66 -0
  136. package/dist/production/src/validation/ModelValidator.cjs.map +1 -1
  137. package/dist/production/src/validation/ModelValidator.js +66 -0
  138. package/dist/production/src/validation/ModelValidator.js.map +1 -1
  139. package/dist/production/src/validation/SchemaValidator.cjs.map +1 -1
  140. package/dist/production/src/validation/SchemaValidator.js.map +1 -1
  141. package/dist/production/src/validation/ValidationValidator.cjs.map +1 -1
  142. package/dist/production/src/validation/ValidationValidator.js.map +1 -1
  143. package/dist/production/src/validation/errors.cjs.map +1 -1
  144. package/dist/production/src/validation/errors.js.map +1 -1
  145. package/dist/tests/cases/unit/jsonSchemaKeywords.test.d.ts +2 -0
  146. package/dist/tests/cases/unit/jsonSchemaKeywords.test.d.ts.map +1 -0
  147. package/dist/tests/cases/unit/jsonSchemaToTsType.test.d.ts +2 -0
  148. package/dist/tests/cases/unit/jsonSchemaToTsType.test.d.ts.map +1 -0
  149. package/dist/tests/cases/unit/numericColumnCodeGenerator.test.d.ts +2 -0
  150. package/dist/tests/cases/unit/numericColumnCodeGenerator.test.d.ts.map +1 -0
  151. package/dist/tests/tsconfig.tsbuildinfo +1 -1
  152. package/dist/types/acl/index.d.ts +3 -3
  153. package/dist/types/acl/index.d.ts.map +1 -1
  154. package/dist/types/dataUtils.d.ts.map +1 -1
  155. package/dist/types/definition-generator/AclDefinitionCodeGenerator.d.ts +1 -1
  156. package/dist/types/definition-generator/AclDefinitionCodeGenerator.d.ts.map +1 -1
  157. package/dist/types/definition-generator/DefinitionCodeGenerator.d.ts +3 -3
  158. package/dist/types/definition-generator/DefinitionCodeGenerator.d.ts.map +1 -1
  159. package/dist/types/definition-generator/TsDefinitionGenerator.d.ts +1 -1
  160. package/dist/types/definition-generator/TsDefinitionGenerator.d.ts.map +1 -1
  161. package/dist/types/definition-generator/index.d.ts +4 -4
  162. package/dist/types/definition-generator/index.d.ts.map +1 -1
  163. package/dist/types/index.d.ts +11 -10
  164. package/dist/types/index.d.ts.map +1 -1
  165. package/dist/types/json-schema/collectUnsupportedKeywords.d.ts +20 -0
  166. package/dist/types/json-schema/collectUnsupportedKeywords.d.ts.map +1 -0
  167. package/dist/types/json-schema/index.d.ts +4 -0
  168. package/dist/types/json-schema/index.d.ts.map +1 -0
  169. package/dist/types/json-schema/jsonSchemaToTsType.d.ts +24 -0
  170. package/dist/types/json-schema/jsonSchemaToTsType.d.ts.map +1 -0
  171. package/dist/types/json-schema/keywords.d.ts +28 -0
  172. package/dist/types/json-schema/keywords.d.ts.map +1 -0
  173. package/dist/types/lax/LaxSchemaConverter.d.ts +2 -2
  174. package/dist/types/lax/LaxSchemaConverter.d.ts.map +1 -1
  175. package/dist/types/lax/index.d.ts +2 -2
  176. package/dist/types/lax/index.d.ts.map +1 -1
  177. package/dist/types/model/index.d.ts +3 -3
  178. package/dist/types/model/index.d.ts.map +1 -1
  179. package/dist/types/model/resolveDefaultColumnType.d.ts.map +1 -1
  180. package/dist/types/tsconfig.tsbuildinfo +1 -1
  181. package/dist/types/type-schema/index.d.ts +5 -5
  182. package/dist/types/type-schema/index.d.ts.map +1 -1
  183. package/dist/types/type-schema/model.d.ts.map +1 -1
  184. package/dist/types/type-schema/settings.d.ts +3 -0
  185. package/dist/types/type-schema/settings.d.ts.map +1 -1
  186. package/dist/types/utils/index.d.ts +3 -3
  187. package/dist/types/utils/index.d.ts.map +1 -1
  188. package/dist/types/validation/AclValidator.d.ts +1 -1
  189. package/dist/types/validation/AclValidator.d.ts.map +1 -1
  190. package/dist/types/validation/ActionsValidator.d.ts +1 -1
  191. package/dist/types/validation/ActionsValidator.d.ts.map +1 -1
  192. package/dist/types/validation/ModelValidator.d.ts +3 -1
  193. package/dist/types/validation/ModelValidator.d.ts.map +1 -1
  194. package/dist/types/validation/SchemaValidator.d.ts +1 -1
  195. package/dist/types/validation/SchemaValidator.d.ts.map +1 -1
  196. package/dist/types/validation/ValidationValidator.d.ts +1 -1
  197. package/dist/types/validation/ValidationValidator.d.ts.map +1 -1
  198. package/dist/types/validation/errors.d.ts +1 -1
  199. package/dist/types/validation/errors.d.ts.map +1 -1
  200. package/dist/types/validation/index.d.ts +6 -6
  201. package/dist/types/validation/index.d.ts.map +1 -1
  202. package/package.json +4 -4
  203. package/src/acl/PredicateDefinitionProcessor.ts +1 -1
  204. package/src/acl/index.ts +3 -3
  205. package/src/dataUtils.ts +1 -0
  206. package/src/definition-generator/AclDefinitionCodeGenerator.ts +3 -3
  207. package/src/definition-generator/DefinitionCodeGenerator.ts +44 -7
  208. package/src/definition-generator/TsDefinitionGenerator.ts +2 -2
  209. package/src/definition-generator/index.ts +4 -4
  210. package/src/index.ts +14 -13
  211. package/src/json-schema/collectUnsupportedKeywords.ts +74 -0
  212. package/src/json-schema/index.ts +3 -0
  213. package/src/json-schema/jsonSchemaToTsType.ts +188 -0
  214. package/src/json-schema/keywords.ts +79 -0
  215. package/src/lax/LaxSchemaConverter.ts +3 -3
  216. package/src/lax/index.ts +2 -2
  217. package/src/model/index.ts +3 -3
  218. package/src/model/modelUtils.ts +1 -1
  219. package/src/model/resolveDefaultColumnType.ts +3 -1
  220. package/src/schemaNormalizer.ts +1 -1
  221. package/src/type-schema/acl.ts +1 -1
  222. package/src/type-schema/condition.ts +5 -0
  223. package/src/type-schema/index.ts +5 -5
  224. package/src/type-schema/model.ts +12 -0
  225. package/src/type-schema/settings.ts +1 -0
  226. package/src/type-schema/where.ts +2 -2
  227. package/src/utils/index.ts +3 -3
  228. package/src/validation/AclValidator.ts +4 -4
  229. package/src/validation/ActionsValidator.ts +4 -4
  230. package/src/validation/MembershipResolver.ts +3 -3
  231. package/src/validation/ModelValidator.ts +80 -2
  232. package/src/validation/SchemaValidator.ts +5 -5
  233. package/src/validation/ValidationValidator.ts +2 -2
  234. package/src/validation/errors.ts +2 -0
  235. package/src/validation/index.ts +6 -6
  236. package/tests/cases/unit/conditionSchema.test.ts +1 -1
  237. package/tests/cases/unit/deepCompare.test.ts +1 -1
  238. package/tests/cases/unit/jsonSchemaKeywords.test.ts +91 -0
  239. package/tests/cases/unit/jsonSchemaToTsType.test.ts +55 -0
  240. package/tests/cases/unit/laxSchemaConverter.test.ts +1 -1
  241. package/tests/cases/unit/membershipResolver.test.ts +1 -1
  242. package/tests/cases/unit/modelSchema.test.ts +1 -1
  243. package/tests/cases/unit/modelValidator.test.ts +246 -1
  244. package/tests/cases/unit/numericColumnCodeGenerator.test.ts +69 -0
  245. package/tests/cases/unit/printJsvalue.test.ts +1 -1
  246. package/tests/cases/unit/tsDefinitionGenerator.test.ts +46 -9
  247. package/tests/cases/unit/whereSchema.test.ts +2 -2
@@ -0,0 +1,91 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import { collectUnsupportedJsonSchemaKeywords } from '../../../src/index.js'
3
+
4
+ describe('collectUnsupportedJsonSchemaKeywords', () => {
5
+ test('accepts a fully supported schema', () => {
6
+ expect(collectUnsupportedJsonSchemaKeywords({
7
+ type: 'object',
8
+ properties: {
9
+ name: { type: 'string', minLength: 1, maxLength: 10, pattern: '^.+$' },
10
+ age: { type: 'integer', minimum: 0, maximum: 120 },
11
+ tags: { type: 'array', items: { type: 'string' }, minItems: 1, maxItems: 5, uniqueItems: true },
12
+ kind: { enum: ['a', 'b'] },
13
+ flag: { const: true },
14
+ },
15
+ required: ['name'],
16
+ additionalProperties: false,
17
+ minProperties: 1,
18
+ maxProperties: 10,
19
+ })).toStrictEqual([])
20
+ })
21
+
22
+ test('accepts combinators', () => {
23
+ expect(collectUnsupportedJsonSchemaKeywords({
24
+ allOf: [{ type: 'object' }],
25
+ anyOf: [{ type: 'string' }, { type: 'number' }],
26
+ oneOf: [{ const: 1 }, { const: 2 }],
27
+ not: { type: 'null' },
28
+ })).toStrictEqual([])
29
+ })
30
+
31
+ test('accepts annotation keywords', () => {
32
+ expect(collectUnsupportedJsonSchemaKeywords({
33
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
34
+ $id: 'https://example.com/x',
35
+ $comment: 'note',
36
+ title: 'Title',
37
+ description: 'Desc',
38
+ examples: [1, 2],
39
+ default: 0,
40
+ readOnly: true,
41
+ writeOnly: false,
42
+ deprecated: false,
43
+ type: 'integer',
44
+ })).toStrictEqual([])
45
+ })
46
+
47
+ test.each([
48
+ '$ref',
49
+ '$defs',
50
+ 'format',
51
+ 'patternProperties',
52
+ 'propertyNames',
53
+ 'dependentRequired',
54
+ 'dependentSchemas',
55
+ 'if',
56
+ 'then',
57
+ 'else',
58
+ 'contains',
59
+ 'prefixItems',
60
+ ])('rejects unsupported keyword %s at the root', keyword => {
61
+ expect(collectUnsupportedJsonSchemaKeywords({ [keyword]: {} })).toStrictEqual([{ keyword, path: '' }])
62
+ })
63
+
64
+ test('descends into properties, items, additionalProperties and reports a path', () => {
65
+ expect(collectUnsupportedJsonSchemaKeywords({
66
+ type: 'object',
67
+ properties: {
68
+ a: { type: 'string', format: 'email' },
69
+ },
70
+ additionalProperties: { type: 'array', items: { contains: { type: 'number' } } },
71
+ })).toStrictEqual([
72
+ { keyword: 'format', path: '/properties/a' },
73
+ { keyword: 'contains', path: '/additionalProperties/items' },
74
+ ])
75
+ })
76
+
77
+ test('descends into combinators', () => {
78
+ expect(collectUnsupportedJsonSchemaKeywords({
79
+ anyOf: [{ type: 'string' }, { type: 'object', propertyNames: { pattern: '^x' } }],
80
+ not: { $ref: '#/x' },
81
+ })).toStrictEqual([
82
+ { keyword: 'propertyNames', path: '/anyOf/1' },
83
+ { keyword: '$ref', path: '/not' },
84
+ ])
85
+ })
86
+
87
+ test('boolean schema is valid', () => {
88
+ expect(collectUnsupportedJsonSchemaKeywords(true)).toStrictEqual([])
89
+ expect(collectUnsupportedJsonSchemaKeywords(false)).toStrictEqual([])
90
+ })
91
+ })
@@ -0,0 +1,55 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import { jsonSchemaToTsType } from '../../../src/index.js'
3
+
4
+ describe('jsonSchemaToTsType', () => {
5
+ test('scalars', () => {
6
+ expect(jsonSchemaToTsType({ type: 'string' })).toBe('string')
7
+ expect(jsonSchemaToTsType({ type: 'integer' })).toBe('number')
8
+ expect(jsonSchemaToTsType({ type: 'number' })).toBe('number')
9
+ expect(jsonSchemaToTsType({ type: 'boolean' })).toBe('boolean')
10
+ expect(jsonSchemaToTsType({ type: 'null' })).toBe('null')
11
+ })
12
+
13
+ test('type union', () => {
14
+ expect(jsonSchemaToTsType({ type: ['string', 'null'] })).toBe('string | null')
15
+ })
16
+
17
+ test('const and enum', () => {
18
+ expect(jsonSchemaToTsType({ const: 'x' })).toBe('"x"')
19
+ expect(jsonSchemaToTsType({ const: 5 })).toBe('5')
20
+ expect(jsonSchemaToTsType({ enum: ['a', 'b', 1] })).toBe('"a" | "b" | 1')
21
+ })
22
+
23
+ test('object with required and optional properties', () => {
24
+ expect(jsonSchemaToTsType({
25
+ type: 'object',
26
+ properties: {
27
+ name: { type: 'string' },
28
+ age: { type: 'integer' },
29
+ },
30
+ required: ['name'],
31
+ })).toBe('{ name: string; age?: number }')
32
+ })
33
+
34
+ test('array of strings', () => {
35
+ expect(jsonSchemaToTsType({ type: 'array', items: { type: 'string' } })).toBe('readonly (string)[]')
36
+ })
37
+
38
+ test('combinators', () => {
39
+ expect(jsonSchemaToTsType({ anyOf: [{ type: 'string' }, { type: 'number' }] })).toBe('string | number')
40
+ expect(jsonSchemaToTsType({ allOf: [{ type: 'object', properties: { a: { type: 'string' } }, required: ['a'] }] }))
41
+ .toBe('{ a: string }')
42
+ })
43
+
44
+ test('falls back to JSONValue for unexpressible constructs', () => {
45
+ expect(jsonSchemaToTsType({})).toBe('JSONValue')
46
+ expect(jsonSchemaToTsType(true)).toBe('JSONValue')
47
+ expect(jsonSchemaToTsType({ not: { type: 'string' } })).toBe('JSONValue')
48
+ // object with no declared properties stays JSONValue rather than emitting `{}`
49
+ expect(jsonSchemaToTsType({ type: 'object' })).toBe('JSONValue')
50
+ // const of a non-primitive
51
+ expect(jsonSchemaToTsType({ const: { a: 1 } })).toBe('JSONValue')
52
+ // unsupported nested item type degrades to JSONValue element rather than failing whole array
53
+ expect(jsonSchemaToTsType({ type: 'array', items: { not: {} } })).toBe('readonly (JSONValue)[]')
54
+ })
55
+ })
@@ -1,5 +1,5 @@
1
1
  import { expect, test } from 'bun:test'
2
- import { LaxSchemaConverter } from '../../../src'
2
+ import { LaxSchemaConverter } from '../../../src/index.js'
3
3
 
4
4
  test('lax schema converter basic', () => {
5
5
  const converter = new LaxSchemaConverter(
@@ -1,5 +1,5 @@
1
1
  import { expect, test } from 'bun:test'
2
- import { MembershipResolver, MembershipValidationErrorType } from '../../../src'
2
+ import { MembershipResolver, MembershipValidationErrorType } from '../../../src/index.js'
3
3
  import { Acl } from '@contember/schema'
4
4
 
5
5
  const id1 = 'bff057b3-11f7-4bc7-abe3-1f2ef266824d'
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
- import { modelSchema } from '../../../src/type-schema'
2
+ import { modelSchema } from '../../../src/type-schema/index.js'
3
3
  import { Model } from '@contember/schema'
4
4
 
5
5
  describe('model schema', () => {
@@ -1,6 +1,6 @@
1
1
  import { expect, test } from 'bun:test'
2
2
  import { Model } from '@contember/schema'
3
- import { ModelValidator } from '../../../src'
3
+ import { ModelValidator } from '../../../src/index.js'
4
4
  import { c, createSchema } from '@contember/schema-definition'
5
5
 
6
6
  test('"meta" collision', () => {
@@ -92,6 +92,167 @@ test('column name collision', () => {
92
92
  ])
93
93
  })
94
94
 
95
+ namespace CoveringIndexKeyOverlap {
96
+ @c.Index({ fields: ['title'], include: ['title'] })
97
+ export class Article {
98
+ title = c.stringColumn()
99
+ }
100
+ }
101
+
102
+ test('covering index include column overlaps key column', () => {
103
+ const schema = createSchema(CoveringIndexKeyOverlap)
104
+ const validator = new ModelValidator(schema.model)
105
+ expect(validator.validate()).toStrictEqual([
106
+ {
107
+ code: 'MODEL_INVALID_INDEX',
108
+ message: 'Field title cannot be both an index key and an included (covering) column',
109
+ path: ['entities', 'Article', 'indexes'],
110
+ },
111
+ ])
112
+ })
113
+
114
+ namespace IndexColumnOptionsNonKey {
115
+ @c.Index('title')
116
+ export class Article {
117
+ title = c.stringColumn()
118
+ rank = c.intColumn()
119
+ }
120
+ }
121
+
122
+ test('index column options reference a non-key column', () => {
123
+ const schema = createSchema(IndexColumnOptionsNonKey)
124
+ const article = schema.model.entities.Article
125
+ // `columnOptions` keys are always index key fields when built through the DSL; inject an invalid
126
+ // one directly to exercise the guard against hand-written/imported models.
127
+ const model: Model.Schema = {
128
+ ...schema.model,
129
+ entities: {
130
+ ...schema.model.entities,
131
+ Article: {
132
+ ...article,
133
+ indexes: [{ fields: ['title'], columnOptions: { rank: { order: 'desc' } } }],
134
+ },
135
+ },
136
+ }
137
+ const validator = new ModelValidator(model)
138
+ expect(validator.validate()).toStrictEqual([
139
+ {
140
+ code: 'MODEL_INVALID_INDEX',
141
+ message: 'Column options reference field rank, which is not a key column of the index',
142
+ path: ['entities', 'Article', 'indexes'],
143
+ },
144
+ ])
145
+ })
146
+
147
+ namespace IndexDuplicateKeyColumn {
148
+ @c.Index({ fields: [{ field: 'title' }, { field: 'title', order: 'desc' }] })
149
+ export class Article {
150
+ title = c.stringColumn()
151
+ }
152
+ }
153
+
154
+ test('index lists a key column more than once', () => {
155
+ const schema = createSchema(IndexDuplicateKeyColumn)
156
+ const validator = new ModelValidator(schema.model)
157
+ expect(validator.validate()).toStrictEqual([
158
+ {
159
+ code: 'MODEL_INVALID_INDEX',
160
+ message: 'Field title is listed more than once as an index key column',
161
+ path: ['entities', 'Article', 'indexes'],
162
+ },
163
+ ])
164
+ })
165
+
166
+ namespace IndexSortOptionsOnNonBtree {
167
+ @c.Index({ fields: [{ field: 'title', order: 'desc' }], method: 'gin' })
168
+ export class Article {
169
+ title = c.stringColumn()
170
+ }
171
+ }
172
+
173
+ test('index sort order on a non-btree method', () => {
174
+ const schema = createSchema(IndexSortOptionsOnNonBtree)
175
+ const validator = new ModelValidator(schema.model)
176
+ expect(validator.validate()).toStrictEqual([
177
+ {
178
+ code: 'MODEL_INVALID_INDEX',
179
+ message: 'Column options (order/nulls) on field title are only supported for btree indexes, not "gin"',
180
+ path: ['entities', 'Article', 'indexes'],
181
+ },
182
+ ])
183
+ })
184
+
185
+ namespace IndexInvalidOpClass {
186
+ @c.Index({ fields: ['title'], opClass: 'gin_trgm_ops); DROP TABLE article; --' })
187
+ export class Article {
188
+ title = c.stringColumn()
189
+ }
190
+ }
191
+
192
+ test('index operator class with an invalid (injection-shaped) value', () => {
193
+ const schema = createSchema(IndexInvalidOpClass)
194
+ const validator = new ModelValidator(schema.model)
195
+ expect(validator.validate()).toStrictEqual([
196
+ {
197
+ code: 'MODEL_INVALID_INDEX',
198
+ message:
199
+ 'Invalid index operator class "gin_trgm_ops); DROP TABLE article; --": expected an identifier, optionally schema-qualified (e.g. "public.gin_trgm_ops")',
200
+ path: ['entities', 'Article', 'indexes'],
201
+ },
202
+ ])
203
+ })
204
+
205
+ namespace IndexSchemaQualifiedOpClass {
206
+ @c.Index({ fields: ['title'], method: 'gin', opClass: 'public.gin_trgm_ops' })
207
+ export class Article {
208
+ title = c.stringColumn()
209
+ }
210
+ }
211
+
212
+ test('index operator class may be schema-qualified', () => {
213
+ const schema = createSchema(IndexSchemaQualifiedOpClass)
214
+ const validator = new ModelValidator(schema.model)
215
+ expect(validator.validate()).toStrictEqual([])
216
+ })
217
+
218
+ namespace PartialIndexEmptyPredicate {
219
+ @c.Index({ fields: ['title'], where: ' ' })
220
+ export class Article {
221
+ title = c.stringColumn()
222
+ }
223
+ }
224
+
225
+ test('partial index with empty predicate', () => {
226
+ const schema = createSchema(PartialIndexEmptyPredicate)
227
+ const validator = new ModelValidator(schema.model)
228
+ expect(validator.validate()).toStrictEqual([
229
+ {
230
+ code: 'MODEL_INVALID_INDEX',
231
+ message: 'Index predicate (where) must not be empty.',
232
+ path: ['entities', 'Article', 'indexes'],
233
+ },
234
+ ])
235
+ })
236
+
237
+ namespace PartialIndexSemicolonPredicate {
238
+ @c.Index({ fields: ['title'], where: 'title IS NOT NULL); DROP TABLE article; --' })
239
+ export class Article {
240
+ title = c.stringColumn()
241
+ }
242
+ }
243
+
244
+ test('partial index predicate with a statement terminator', () => {
245
+ const schema = createSchema(PartialIndexSemicolonPredicate)
246
+ const validator = new ModelValidator(schema.model)
247
+ expect(validator.validate()).toStrictEqual([
248
+ {
249
+ code: 'MODEL_INVALID_INDEX',
250
+ message: 'Index predicate (where) must not contain ";".',
251
+ path: ['entities', 'Article', 'indexes'],
252
+ },
253
+ ])
254
+ })
255
+
95
256
  namespace ViewRelations {
96
257
  @c.View('SELECT 1')
97
258
  export class Foo {
@@ -109,3 +270,87 @@ test('view relations', () => {
109
270
  const validator = new ModelValidator(schema.model)
110
271
  expect(validator.validate()).toStrictEqual([])
111
272
  })
273
+
274
+ namespace SupportedJsonSchema {
275
+ export class Foo {
276
+ data = c.jsonColumn().schema({
277
+ type: 'object',
278
+ title: 'Some data',
279
+ description: 'annotation keywords are allowed',
280
+ properties: {
281
+ name: { type: 'string', minLength: 1 },
282
+ tags: { type: 'array', items: { type: 'string' }, uniqueItems: true },
283
+ },
284
+ required: ['name'],
285
+ additionalProperties: false,
286
+ })
287
+ }
288
+ }
289
+
290
+ test('json schema with supported keywords passes', () => {
291
+ const schema = createSchema(SupportedJsonSchema)
292
+ const validator = new ModelValidator(schema.model)
293
+ expect(validator.validate()).toStrictEqual([])
294
+ })
295
+
296
+ namespace UnsupportedJsonSchemaKeyword {
297
+ export class Foo {
298
+ data = c.jsonColumn().schema({
299
+ type: 'string',
300
+ format: 'email',
301
+ })
302
+ }
303
+ }
304
+
305
+ test('json schema with an unsupported keyword fails', () => {
306
+ const schema = createSchema(UnsupportedJsonSchemaKeyword)
307
+ const validator = new ModelValidator(schema.model)
308
+ const errors = validator.validate()
309
+ expect(errors).toHaveLength(1)
310
+ expect(errors[0].code).toBe('MODEL_INVALID_JSON_SCHEMA')
311
+ expect(errors[0].path).toStrictEqual(['entities', 'Foo', 'data'])
312
+ expect(errors[0].message).toContain('unsupported keyword "format"')
313
+ expect(errors[0].message).toContain('at the schema root')
314
+ })
315
+
316
+ namespace UnsupportedJsonSchemaKeywordNested {
317
+ export class Foo {
318
+ data = c.jsonColumn().schema({
319
+ type: 'object',
320
+ properties: {
321
+ ref: { $ref: '#/definitions/Bar' },
322
+ },
323
+ })
324
+ }
325
+ }
326
+
327
+ test('json schema reports unsupported keyword nested in properties with a path', () => {
328
+ const schema = createSchema(UnsupportedJsonSchemaKeywordNested)
329
+ const validator = new ModelValidator(schema.model)
330
+ const errors = validator.validate()
331
+ expect(errors).toHaveLength(1)
332
+ expect(errors[0].code).toBe('MODEL_INVALID_JSON_SCHEMA')
333
+ expect(errors[0].message).toContain('unsupported keyword "$ref"')
334
+ expect(errors[0].message).toContain('at "/properties/ref"')
335
+ })
336
+
337
+ namespace UnsupportedJsonSchemaKeywordInCombinator {
338
+ export class Foo {
339
+ data = c.jsonColumn().schema({
340
+ anyOf: [
341
+ { type: 'string' },
342
+ { type: 'object', patternProperties: { '^x': { type: 'number' } } },
343
+ ],
344
+ })
345
+ }
346
+ }
347
+
348
+ test('json schema reports unsupported keyword inside a combinator', () => {
349
+ const schema = createSchema(UnsupportedJsonSchemaKeywordInCombinator)
350
+ const validator = new ModelValidator(schema.model)
351
+ const errors = validator.validate()
352
+ expect(errors).toHaveLength(1)
353
+ expect(errors[0].code).toBe('MODEL_INVALID_JSON_SCHEMA')
354
+ expect(errors[0].message).toContain('unsupported keyword "patternProperties"')
355
+ expect(errors[0].message).toContain('at "/anyOf/1"')
356
+ })
@@ -0,0 +1,69 @@
1
+ import { c, createSchema } from '@contember/schema-definition'
2
+ import { expect, test } from 'bun:test'
3
+ import { Model, Schema } from '@contember/schema'
4
+ import { DefinitionCodeGenerator } from '../../../src/definition-generator/DefinitionCodeGenerator.js'
5
+
6
+ namespace NumericModel {
7
+ export class Product {
8
+ price = c.numericColumn(20, 9).notNull()
9
+ discount = c.numericColumn(5, 2)
10
+ }
11
+ }
12
+
13
+ // Immutably overrides the stored columnType of a single Product column, so we can
14
+ // exercise the regex / fallback branches without writing to the readonly fields map.
15
+ const withColumnType = (schema: Schema, field: string, columnType: string): Schema => {
16
+ const entity = schema.model.entities.Product
17
+ const column = entity.fields[field] as Model.AnyColumn
18
+ return {
19
+ ...schema,
20
+ model: {
21
+ ...schema.model,
22
+ entities: {
23
+ ...schema.model.entities,
24
+ Product: {
25
+ ...entity,
26
+ fields: {
27
+ ...entity.fields,
28
+ [field]: { ...column, columnType },
29
+ },
30
+ },
31
+ },
32
+ },
33
+ }
34
+ }
35
+
36
+ test('reverse codegen round-trips numericColumn(precision, scale)', () => {
37
+ const schema = createSchema(NumericModel)
38
+ const generated = new DefinitionCodeGenerator().generate(schema)
39
+
40
+ expect(generated).toContain('price = c.numericColumn(20, 9).notNull()')
41
+ expect(generated).toContain('discount = c.numericColumn(5, 2)')
42
+ })
43
+
44
+ test('reverse codegen parses arbitrary whitespace/casing in numeric(p, s)', () => {
45
+ // An equivalent-but-unusual form must still be parsed by the regex.
46
+ const schema = withColumnType(createSchema(NumericModel), 'price', 'NUMERIC ( 30 , 10 )')
47
+
48
+ const generated = new DefinitionCodeGenerator().generate(schema)
49
+ expect(generated).toContain('price = c.numericColumn(30, 10).notNull()')
50
+ })
51
+
52
+ test('reverse codegen falls back when columnType is not parseable', () => {
53
+ // An unparseable columnType (e.g. a domain type) must fall back to a default
54
+ // numericColumn(...) plus an explicit columnType(...).
55
+ const schema = withColumnType(createSchema(NumericModel), 'price', 'my_money_domain')
56
+
57
+ const generated = new DefinitionCodeGenerator().generate(schema)
58
+ expect(generated).toContain("price = c.numericColumn(30, 10).columnType('my_money_domain').notNull()")
59
+ })
60
+
61
+ test('reverse codegen fallback omits columnType when it equals the default', () => {
62
+ // A bare "numeric" is not matched by the precision/scale regex, but equals the default
63
+ // column type, so no redundant columnType(...) should be emitted.
64
+ const schema = withColumnType(createSchema(NumericModel), 'discount', 'numeric')
65
+
66
+ const generated = new DefinitionCodeGenerator().generate(schema)
67
+ expect(generated).toContain('discount = c.numericColumn(30, 10)')
68
+ expect(generated).not.toContain("columnType('numeric')")
69
+ })
@@ -1,5 +1,5 @@
1
1
  import { beforeAll, describe, expect, test } from 'bun:test'
2
- import { printJsValue } from '../../../src/utils/printJsValue'
2
+ import { printJsValue } from '../../../src/utils/printJsValue.js'
3
3
 
4
4
  describe('printJsValue', () => {
5
5
  test('string', () => {
@@ -1,15 +1,15 @@
1
- import { createSchema } from '@contember/schema-definition'
1
+ import { c, createSchema } from '@contember/schema-definition'
2
2
  import { expect, test } from 'bun:test'
3
- import * as basic from './schemas/basic'
4
- import * as complex from './schemas/complex'
5
- import * as relations from './schemas/relations'
6
- import * as unique from './schemas/unique'
7
- import * as enum_ from './schemas/enum'
8
- import * as acl from './schemas/acl'
9
- import * as view from './schemas/view'
3
+ import * as basic from './schemas/basic.js'
4
+ import * as complex from './schemas/complex.js'
5
+ import * as relations from './schemas/relations.js'
6
+ import * as unique from './schemas/unique.js'
7
+ import * as enum_ from './schemas/enum.js'
8
+ import * as acl from './schemas/acl.js'
9
+ import * as view from './schemas/view.js'
10
10
  import { readFile, writeFile } from 'fs/promises'
11
11
  import { join } from 'path'
12
- import { DefinitionCodeGenerator } from '../../../src/definition-generator/DefinitionCodeGenerator'
12
+ import { DefinitionCodeGenerator } from '../../../src/definition-generator/DefinitionCodeGenerator.js'
13
13
  import { dirname } from 'node:path'
14
14
  import { fileURLToPath } from 'node:url'
15
15
 
@@ -36,3 +36,40 @@ for (const [name, def] of tests) {
36
36
  }
37
37
  })
38
38
  }
39
+
40
+ namespace IndexOptionsSchema {
41
+ @c.Index({ fields: ['title'], opClass: 'text_pattern_ops' })
42
+ @c.Index({ fields: ['title'], method: 'gin', opClass: 'gin_trgm_ops' })
43
+ @c.Index({ fields: ['title'], include: ['content'], where: 'content IS NOT NULL' })
44
+ export class Article {
45
+ title = c.stringColumn()
46
+ content = c.stringColumn()
47
+ }
48
+ }
49
+
50
+ test('definition generator preserves index opClass, include and where options', () => {
51
+ const generator = new DefinitionCodeGenerator()
52
+ const generated = generator.generate(createSchema(IndexOptionsSchema))
53
+ // regression (ARCH-1): opClass used to be dropped on regeneration, silently degrading e.g. trigram search
54
+ expect(generated).toContain(`@c.Index({ fields: ['title'], opClass: 'text_pattern_ops' })`)
55
+ expect(generated).toContain(`@c.Index({ fields: ['title'], method: 'gin', opClass: 'gin_trgm_ops' })`)
56
+ expect(generated).toContain(`@c.Index({ fields: ['title'], include: ['content'], where: 'content IS NOT NULL' })`)
57
+ // opClass alone must force the options form, never the positional short form that would drop it
58
+ expect(generated).not.toContain(`@c.Index('title')`)
59
+ })
60
+
61
+ namespace IndexColumnOptionsGenSchema {
62
+ @c.Index({ fields: ['title', { field: 'rank', order: 'desc', nulls: 'last' }] })
63
+ @c.Index({ fields: [{ field: 'title', opClass: 'text_pattern_ops' }] })
64
+ export class Article {
65
+ title = c.stringColumn()
66
+ rank = c.intColumn()
67
+ }
68
+ }
69
+
70
+ test('definition generator round-trips per-column index options', () => {
71
+ const generator = new DefinitionCodeGenerator()
72
+ const generated = generator.generate(createSchema(IndexColumnOptionsGenSchema))
73
+ expect(generated).toContain(`@c.Index({ fields: ['title', { field: 'rank', order: 'desc', nulls: 'last' }] })`)
74
+ expect(generated).toContain(`@c.Index({ fields: [{ field: 'title', opClass: 'text_pattern_ops' }] })`)
75
+ })
@@ -1,8 +1,8 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
- import { conditionSchema } from '../../../src/type-schema'
2
+ import { conditionSchema } from '../../../src/type-schema/index.js'
3
3
  import { Model } from '@contember/schema'
4
4
  import { createSchema, SchemaDefinition as def } from '@contember/schema-definition'
5
- import { whereSchema } from '../../../src/type-schema/where'
5
+ import { whereSchema } from '../../../src/type-schema/where.js'
6
6
 
7
7
  namespace MyModel {
8
8
  export class Article {