sof-mssql 1.0.1 → 2.0.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 (35) hide show
  1. package/README.md +175 -35
  2. package/dist/fhirpath/transpiler.d.ts +40 -2
  3. package/dist/fhirpath/transpiler.d.ts.map +1 -1
  4. package/dist/fhirpath/transpiler.js +85 -15
  5. package/dist/fhirpath/transpiler.js.map +1 -1
  6. package/dist/loader/tables.d.ts.map +1 -1
  7. package/dist/loader/tables.js +7 -0
  8. package/dist/loader/tables.js.map +1 -1
  9. package/dist/parser.d.ts +12 -0
  10. package/dist/parser.d.ts.map +1 -1
  11. package/dist/parser.js +66 -9
  12. package/dist/parser.js.map +1 -1
  13. package/dist/queryGenerator/WhereClauseBuilder.d.ts +1 -0
  14. package/dist/queryGenerator/WhereClauseBuilder.d.ts.map +1 -1
  15. package/dist/queryGenerator/WhereClauseBuilder.js +6 -2
  16. package/dist/queryGenerator/WhereClauseBuilder.js.map +1 -1
  17. package/dist/queryGenerator.d.ts.map +1 -1
  18. package/dist/queryGenerator.js +13 -8
  19. package/dist/queryGenerator.js.map +1 -1
  20. package/dist/tests/utils/generator.js +11 -11
  21. package/dist/tests/utils/generator.js.map +1 -1
  22. package/dist/tests/utils/sqlOnFhir.d.ts.map +1 -1
  23. package/dist/tests/utils/sqlOnFhir.js +39 -4
  24. package/dist/tests/utils/sqlOnFhir.js.map +1 -1
  25. package/dist/tests/utils/testContext.d.ts +3 -6
  26. package/dist/tests/utils/testContext.d.ts.map +1 -1
  27. package/dist/tests/utils/testContext.js +5 -9
  28. package/dist/tests/utils/testContext.js.map +1 -1
  29. package/dist/types.d.ts +12 -0
  30. package/dist/types.d.ts.map +1 -1
  31. package/dist/validation.d.ts +66 -0
  32. package/dist/validation.d.ts.map +1 -0
  33. package/dist/validation.js +449 -0
  34. package/dist/validation.js.map +1 -0
  35. package/package.json +3 -2
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # SQL on FHIR views for Microsoft SQL Server
1
+ # 🔥 MSSQL on FHIR 🔥
2
2
 
3
3
  A TypeScript library and CLI tool for loading FHIR into Microsoft SQL Server,
4
4
  and transpiling [SQL on FHIR](https://sql-on-fhir.org/) view definitions into
@@ -209,59 +209,76 @@ WHERE [r].[resource_type] = 'Patient'
209
209
 
210
210
  ### Basic usage
211
211
 
212
- ```javascript
212
+ ```typescript
213
213
  import {SqlOnFhir} from 'sof-mssql';
214
214
 
215
- // Create an instance with default configuration
216
215
  const sqlOnFhir = new SqlOnFhir();
216
+ ```
217
217
 
218
- // Transpile a ViewDefinition to T-SQL
219
- const viewDefinition = {
220
- resourceType: 'ViewDefinition',
221
- resource: 'Patient',
222
- name: 'patient_demographics',
223
- select: [
218
+ Create a ViewDefinition to transpile:
219
+
220
+ ```json
221
+ {
222
+ "resourceType": "ViewDefinition",
223
+ "resource": "Patient",
224
+ "name": "patient_demographics",
225
+ "select": [
224
226
  {
225
- column: [
227
+ "column": [
226
228
  {
227
- name: 'id',
228
- path: 'id',
229
- type: 'id'
229
+ "name": "id",
230
+ "path": "id",
231
+ "type": "id"
230
232
  },
231
233
  {
232
- name: 'family_name',
233
- path: 'name.family',
234
- type: 'string'
234
+ "name": "family_name",
235
+ "path": "name.family",
236
+ "type": "string"
235
237
  },
236
238
  {
237
- name: 'birth_date',
238
- path: 'birthDate',
239
- type: 'date'
239
+ "name": "birth_date",
240
+ "path": "birthDate",
241
+ "type": "date"
240
242
  }
241
243
  ]
242
244
  }
243
245
  ]
244
- };
246
+ }
247
+ ```
248
+
249
+ Transpile the ViewDefinition to T-SQL:
245
250
 
251
+ ```typescript
246
252
  const result = sqlOnFhir.transpile(viewDefinition);
247
253
 
248
254
  console.log(result.sql);
249
- // Output:
250
- // SELECT
251
- // r.id AS [id],
252
- // JSON_VALUE(r.json, '$.name[0].family') AS [family_name],
253
- // CAST(JSON_VALUE(r.json, '$.birthDate') AS DATETIME2) AS [birth_date]
254
- // FROM [dbo].[fhir_resources] AS [r]
255
- // WHERE [r].[resource_type] = 'Patient'
256
-
257
- // Access column metadata
255
+ ```
256
+
257
+ Generated SQL output:
258
+
259
+ ```sql
260
+ SELECT
261
+ r.id AS [id],
262
+ JSON_VALUE(r.json, '$.name[0].family') AS [family_name],
263
+ CAST(JSON_VALUE(r.json, '$.birthDate') AS DATETIME2) AS [birth_date]
264
+ FROM [dbo].[fhir_resources] AS [r]
265
+ WHERE [r].[resource_type] = 'Patient'
266
+ ```
267
+
268
+ Access column metadata:
269
+
270
+ ```typescript
258
271
  console.log(result.columns);
259
- // Output:
260
- // [
261
- // { name: 'id', type: 'NVARCHAR(64)', nullable: true },
262
- // { name: 'family_name', type: 'NVARCHAR(MAX)', nullable: true },
263
- // { name: 'birth_date', type: 'DATETIME2', nullable: true }
264
- // ]
272
+ ```
273
+
274
+ Column metadata output:
275
+
276
+ ```json
277
+ [
278
+ { "name": "id", "type": "VARCHAR(64)", "nullable": true },
279
+ { "name": "family_name", "type": "NVARCHAR(MAX)", "nullable": true },
280
+ { "name": "birth_date", "type": "VARCHAR(10)", "nullable": true }
281
+ ]
265
282
  ```
266
283
 
267
284
  ### Custom table configuration
@@ -292,8 +309,131 @@ const fhirResource = {
292
309
  // ... rest of ViewDefinition
293
310
  };
294
311
  const result2 = sqlOnFhir.transpile(fhirResource);
312
+ ````
313
+
314
+ ### Type mappings and type hints
315
+
316
+ #### Default type mappings
317
+
318
+ By default, FHIR primitive types are mapped to the following T-SQL types:
319
+
320
+ | FHIR Type | Default T-SQL Type | Rationale |
321
+ |-----------------------------------------|--------------------|-------------------------------------------|
322
+ | `id` | `VARCHAR(64)` | ASCII-only, fixed max length |
323
+ | `boolean` | `BIT` | Native boolean |
324
+ | `integer`, `positiveint`, `unsignedint` | `INT` | 32-bit integer |
325
+ | `integer64` | `BIGINT` | 64-bit integer |
326
+ | `decimal` | `VARCHAR(MAX)` | Preserves arbitrary precision |
327
+ | `date` | `VARCHAR(10)` | Preserves partial dates (e.g., "2024-01") |
328
+ | `datetime` | `VARCHAR(50)` | Preserves partial datetimes and timezones |
329
+ | `instant` | `VARCHAR(50)` | Preserves full ISO 8601 format |
330
+ | `time` | `VARCHAR(20)` | Preserves partial times |
331
+ | `string`, `markdown`, `code` | `NVARCHAR(MAX)` | Unicode-capable text |
332
+ | `uri`, `url`, `canonical` | `NVARCHAR(MAX)` | Can contain Unicode (IRIs) |
333
+ | `uuid` | `VARCHAR(100)` | ASCII UUID format |
334
+ | `oid` | `VARCHAR(255)` | ASCII OID format |
335
+ | `base64binary` | `VARBINARY(MAX)` | Binary data |
336
+
337
+ **Design principle:** Default mappings use `VARCHAR` for temporal and numeric types to preserve FHIR semantics (partial dates, arbitrary precision decimals) rather than forcing conversion to SQL native types.
338
+
339
+ #### Using type hints
340
+
341
+ You can override default type mappings using the `tag` array on column definitions. Two tag types are supported:
342
+
343
+ **`tsql/type` - Direct T-SQL type specification:**
344
+
345
+ ```json
346
+ {
347
+ "name": "birth_date",
348
+ "path": "birthDate",
349
+ "type": "date",
350
+ "tag": [
351
+ { "name": "tsql/type", "value": "DATE" }
352
+ ]
353
+ }
295
354
  ```
296
355
 
356
+ This generates a CAST expression: `CAST(JSON_VALUE(r.json, '$.birthDate') AS DATE) AS [birth_date]`
357
+
358
+ **`ansi/type` - ANSI/ISO SQL standard types (automatically converted to T-SQL):**
359
+
360
+ ```json
361
+ {
362
+ "name": "age",
363
+ "path": "age",
364
+ "type": "integer",
365
+ "tag": [
366
+ { "name": "ansi/type", "value": "INTEGER" }
367
+ ]
368
+ }
369
+ ```
370
+
371
+ The ANSI type `INTEGER` is automatically converted to T-SQL `INT`.
372
+
373
+ ```json
374
+ {
375
+ "name": "active",
376
+ "path": "active",
377
+ "type": "boolean",
378
+ "tag": [
379
+ { "name": "ansi/type", "value": "BOOLEAN" }
380
+ ]
381
+ }
382
+ ```
383
+
384
+ The ANSI type `BOOLEAN` is automatically converted to T-SQL `BIT`.
385
+
386
+ **Type precedence:** `tsql/type` > `ansi/type` > FHIR type defaults
387
+
388
+ **Supported ANSI types:**
389
+ - Character: `CHARACTER`, `CHARACTER VARYING`, `NATIONAL CHARACTER VARYING`
390
+ - Numeric: `INTEGER`, `SMALLINT`, `BIGINT`, `DECIMAL`, `NUMERIC`, `FLOAT`, `REAL`, `DOUBLE PRECISION`
391
+ - Temporal: `DATE`, `TIME`, `TIMESTAMP` (converted to `DATETIME2`)
392
+ - Boolean: `BOOLEAN` (converted to `BIT`)
393
+
394
+ **Example with multiple columns:**
395
+
396
+ This example demonstrates how different type hints affect the resulting SQL types:
397
+
398
+ ```json
399
+ {
400
+ "resourceType": "ViewDefinition",
401
+ "resource": "Patient",
402
+ "select": [
403
+ {
404
+ "column": [
405
+ {
406
+ "name": "id",
407
+ "path": "id",
408
+ "type": "id"
409
+ },
410
+ {
411
+ "name": "birth_date",
412
+ "path": "birthDate",
413
+ "type": "date",
414
+ "tag": [
415
+ { "name": "tsql/type", "value": "DATE" }
416
+ ]
417
+ },
418
+ {
419
+ "name": "deceased",
420
+ "path": "deceasedBoolean",
421
+ "type": "boolean",
422
+ "tag": [
423
+ { "name": "ansi/type", "value": "BOOLEAN" }
424
+ ]
425
+ }
426
+ ]
427
+ }
428
+ ]
429
+ }
430
+ ```
431
+
432
+ Type behaviour for each column:
433
+ - `id` - Uses default FHIR type mapping: `VARCHAR(64)`
434
+ - `birth_date` - Overrides default `VARCHAR(10)` with T-SQL `DATE` type
435
+ - `deceased` - Uses ANSI `BOOLEAN` type, automatically converted to T-SQL `BIT`
436
+
297
437
  ## Database setup
298
438
 
299
439
  ### Table structure
@@ -2,6 +2,7 @@
2
2
  * FHIRPath expression transpiler to T-SQL.
3
3
  * Converts FHIRPath expressions to equivalent T-SQL expressions for MS SQL Server.
4
4
  */
5
+ import type { ViewDefinitionColumnTag } from "../types.js";
5
6
  import { TranspilerContext } from "./visitor";
6
7
  export { TranspilerContext } from "./visitor";
7
8
  export declare class Transpiler {
@@ -11,8 +12,45 @@ export declare class Transpiler {
11
12
  static transpile(expression: string, context: TranspilerContext): string;
12
13
  private static parseExpression;
13
14
  /**
14
- * Get the SQL data type for a FHIRPath expression result.
15
+ * Get the SQL data type for a FHIR type, with optional tag-based override.
16
+ *
17
+ * Default mappings are conservative to accommodate ALL valid FHIR data,
18
+ * using MAX sizes where needed and NVARCHAR for any fields that could
19
+ * potentially contain Unicode characters.
20
+ *
21
+ * Design principles:
22
+ * - Use NVARCHAR for fields that may contain Unicode (string, code, uri/url/canonical)
23
+ * - Use VARCHAR for ASCII-only fields (id, uuid, oid, decimal, dates/times)
24
+ * - Use MAX or generous fixed sizes to prevent truncation
25
+ * - Preserve FHIR semantics (partial dates, arbitrary precision decimals)
26
+ *
27
+ * Users can optimise storage using type tags:
28
+ * - 'tsql/type' - Direct T-SQL type (e.g., 'DATE', 'VARCHAR(50)')
29
+ * - 'ansi/type' - ANSI/ISO SQL standard type (e.g., 'INTEGER', 'CHARACTER(50)', 'BOOLEAN')
30
+ *
31
+ * Type precedence: tsql/type > ansi/type > FHIR type defaults
32
+ *
33
+ * Example tag usage:
34
+ * - { "name": "tsql/type", "value": "DATE" } - Use T-SQL DATE type
35
+ * - { "name": "ansi/type", "value": "INTEGER" } - Use ANSI INTEGER (converted to T-SQL INT)
36
+ * - { "name": "ansi/type", "value": "BOOLEAN" } - Use ANSI BOOLEAN (converted to T-SQL BIT)
37
+ *
38
+ * @param fhirType - FHIR primitive type name (e.g., 'string', 'integer')
39
+ * @param tags - Optional array of column tags for type hints
40
+ * @returns MS SQL Server type specification
15
41
  */
16
- static inferSqlType(fhirType?: string): string;
42
+ static inferSqlType(fhirType?: string, tags?: ViewDefinitionColumnTag[]): string;
43
+ /**
44
+ * Get type override from tsql/type or ansi/type tag if present.
45
+ *
46
+ * Precedence order:
47
+ * 1. tsql/type - Direct T-SQL type specification
48
+ * 2. ansi/type - ANSI/ISO SQL standard type (converted to T-SQL equivalent)
49
+ */
50
+ private static getTagTypeOverride;
51
+ /**
52
+ * Get default MS SQL Server type mapping for a FHIR primitive type.
53
+ */
54
+ private static getDefaultFhirTypeMapping;
17
55
  }
18
56
  //# sourceMappingURL=transpiler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"transpiler.d.ts","sourceRoot":"","sources":["../../src/fhirpath/transpiler.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,OAAO,EAAyB,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAGrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAE9C,qBAAa,UAAU;IACrB;;OAEG;IACH,MAAM,CAAC,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,MAAM;IAkBxE,OAAO,CAAC,MAAM,CAAC,eAAe;IA4B9B;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM;CA8B/C"}
1
+ {"version":3,"file":"transpiler.d.ts","sourceRoot":"","sources":["../../src/fhirpath/transpiler.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAE3D,OAAO,EAAyB,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAGrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAE9C,qBAAa,UAAU;IACrB;;OAEG;IACH,MAAM,CAAC,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,MAAM;IAkBxE,OAAO,CAAC,MAAM,CAAC,eAAe;IA4B9B;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,MAAM,CAAC,YAAY,CACjB,QAAQ,CAAC,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE,uBAAuB,EAAE,GAC/B,MAAM;IAWT;;;;;;OAMG;IACH,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAuBjC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,yBAAyB;CA0CzC"}
@@ -8,6 +8,7 @@ exports.Transpiler = void 0;
8
8
  const antlr4ts_1 = require("antlr4ts");
9
9
  const fhirpathLexer_1 = require("../generated/grammar/fhirpathLexer");
10
10
  const fhirpathParser_1 = require("../generated/grammar/fhirpathParser");
11
+ const validation_js_1 = require("../validation.js");
11
12
  const visitor_1 = require("./visitor");
12
13
  class Transpiler {
13
14
  /**
@@ -47,29 +48,98 @@ class Transpiler {
47
48
  return { success: true, tree };
48
49
  }
49
50
  /**
50
- * Get the SQL data type for a FHIRPath expression result.
51
+ * Get the SQL data type for a FHIR type, with optional tag-based override.
52
+ *
53
+ * Default mappings are conservative to accommodate ALL valid FHIR data,
54
+ * using MAX sizes where needed and NVARCHAR for any fields that could
55
+ * potentially contain Unicode characters.
56
+ *
57
+ * Design principles:
58
+ * - Use NVARCHAR for fields that may contain Unicode (string, code, uri/url/canonical)
59
+ * - Use VARCHAR for ASCII-only fields (id, uuid, oid, decimal, dates/times)
60
+ * - Use MAX or generous fixed sizes to prevent truncation
61
+ * - Preserve FHIR semantics (partial dates, arbitrary precision decimals)
62
+ *
63
+ * Users can optimise storage using type tags:
64
+ * - 'tsql/type' - Direct T-SQL type (e.g., 'DATE', 'VARCHAR(50)')
65
+ * - 'ansi/type' - ANSI/ISO SQL standard type (e.g., 'INTEGER', 'CHARACTER(50)', 'BOOLEAN')
66
+ *
67
+ * Type precedence: tsql/type > ansi/type > FHIR type defaults
68
+ *
69
+ * Example tag usage:
70
+ * - { "name": "tsql/type", "value": "DATE" } - Use T-SQL DATE type
71
+ * - { "name": "ansi/type", "value": "INTEGER" } - Use ANSI INTEGER (converted to T-SQL INT)
72
+ * - { "name": "ansi/type", "value": "BOOLEAN" } - Use ANSI BOOLEAN (converted to T-SQL BIT)
73
+ *
74
+ * @param fhirType - FHIR primitive type name (e.g., 'string', 'integer')
75
+ * @param tags - Optional array of column tags for type hints
76
+ * @returns MS SQL Server type specification
51
77
  */
52
- static inferSqlType(fhirType) {
78
+ static inferSqlType(fhirType, tags) {
79
+ // Check for tsql/type tag override.
80
+ const tagOverride = this.getTagTypeOverride(tags);
81
+ if (tagOverride) {
82
+ return tagOverride;
83
+ }
84
+ // Use default FHIR type mapping.
85
+ return this.getDefaultFhirTypeMapping(fhirType);
86
+ }
87
+ /**
88
+ * Get type override from tsql/type or ansi/type tag if present.
89
+ *
90
+ * Precedence order:
91
+ * 1. tsql/type - Direct T-SQL type specification
92
+ * 2. ansi/type - ANSI/ISO SQL standard type (converted to T-SQL equivalent)
93
+ */
94
+ static getTagTypeOverride(tags) {
95
+ if (!tags) {
96
+ return null;
97
+ }
98
+ // Check for tsql/type tag first (highest precedence)
99
+ const tsqlTypeTag = tags.find((tag) => tag.name === "tsql/type");
100
+ if (tsqlTypeTag) {
101
+ (0, validation_js_1.validateMsSqlType)(tsqlTypeTag.value);
102
+ return tsqlTypeTag.value;
103
+ }
104
+ // Check for ansi/type tag (lower precedence)
105
+ const ansiTypeTag = tags.find((tag) => tag.name === "ansi/type");
106
+ if (ansiTypeTag) {
107
+ return (0, validation_js_1.validateAnsiSqlType)(ansiTypeTag.value);
108
+ }
109
+ return null;
110
+ }
111
+ /**
112
+ * Get default MS SQL Server type mapping for a FHIR primitive type.
113
+ */
114
+ static getDefaultFhirTypeMapping(fhirType) {
115
+ // Conservative default type mappings based on FHIR R4 constraints.
116
+ // Sized to accommodate ALL valid FHIR data.
117
+ // Uses NVARCHAR for potential Unicode, VARCHAR for ASCII-only.
53
118
  const typeMap = {
54
- id: "NVARCHAR(MAX)",
119
+ // ASCII-only types with fixed constraints
120
+ id: "VARCHAR(64)",
121
+ boolean: "BIT",
122
+ integer: "INT",
123
+ positiveint: "INT",
124
+ unsignedint: "INT",
125
+ integer64: "BIGINT",
126
+ // ASCII-only structured formats
127
+ uuid: "VARCHAR(100)",
128
+ oid: "VARCHAR(255)",
129
+ decimal: "VARCHAR(MAX)",
130
+ date: "VARCHAR(10)",
131
+ datetime: "VARCHAR(50)",
132
+ instant: "VARCHAR(50)",
133
+ time: "VARCHAR(20)",
134
+ // Unicode-capable text types
55
135
  string: "NVARCHAR(MAX)",
56
136
  markdown: "NVARCHAR(MAX)",
57
137
  code: "NVARCHAR(MAX)",
138
+ // URIs (can be IRIs with Unicode)
58
139
  uri: "NVARCHAR(MAX)",
59
140
  url: "NVARCHAR(MAX)",
60
141
  canonical: "NVARCHAR(MAX)",
61
- uuid: "NVARCHAR(MAX)",
62
- oid: "NVARCHAR(MAX)",
63
- boolean: "BIT",
64
- integer: "INT",
65
- positiveint: "INT",
66
- unsignedint: "INT",
67
- integer64: "BIGINT",
68
- decimal: "DECIMAL(18,6)",
69
- date: "DATETIME2",
70
- datetime: "DATETIME2",
71
- instant: "DATETIME2",
72
- time: "TIME",
142
+ // Binary data
73
143
  base64binary: "VARBINARY(MAX)",
74
144
  };
75
145
  if (!fhirType) {
@@ -1 +1 @@
1
- {"version":3,"file":"transpiler.js","sourceRoot":"","sources":["../../src/fhirpath/transpiler.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,uCAA0D;AAC1D,sEAAmE;AACnE,wEAG6C;AAC7C,uCAAqE;AAKrE,MAAa,UAAU;IACrB;;OAEG;IACH,MAAM,CAAC,SAAS,CAAC,UAAkB,EAAE,OAA0B;QAC7D,sDAAsD;QACtD,MAAM,WAAW,GAAG,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;QACrD,IAAI,CAAC,WAAW,CAAC,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;YAC9C,MAAM,IAAI,KAAK,CAAC,wCAAwC,UAAU,GAAG,CAAC,CAAC;QACzE,CAAC;QAED,IAAI,CAAC;YACH,0CAA0C;YAC1C,MAAM,OAAO,GAAG,IAAI,+BAAqB,CAAC,OAAO,CAAC,CAAC;YACnD,OAAO,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QACzC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CACb,4CAA4C,UAAU,MAAM,KAAK,EAAE,CACpE,CAAC;QACJ,CAAC;IACH,CAAC;IAEO,MAAM,CAAC,eAAe,CAAC,UAAkB;QAI/C,4BAA4B;QAC5B,MAAM,WAAW,GAAG,sBAAW,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;QAEvD,eAAe;QACf,MAAM,KAAK,GAAG,IAAI,6BAAa,CAAC,WAAW,CAAC,CAAC;QAC7C,MAAM,WAAW,GAAG,IAAI,4BAAiB,CAAC,KAAK,CAAC,CAAC;QAEjD,gBAAgB;QAChB,MAAM,MAAM,GAAG,IAAI,+BAAc,CAAC,WAAW,CAAC,CAAC;QAE/C,yDAAyD;QACzD,MAAM,CAAC,oBAAoB,EAAE,CAAC;QAE9B,8BAA8B;QAC9B,MAAM,IAAI,GAAG,MAAM,CAAC,gBAAgB,EAAE,CAAC;QAEvC,yBAAyB;QACzB,IAAI,MAAM,CAAC,oBAAoB,GAAG,CAAC,EAAE,CAAC;YACpC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QACxC,CAAC;QAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACjC,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,QAAiB;QACnC,MAAM,OAAO,GAA2B;YACtC,EAAE,EAAE,eAAe;YACnB,MAAM,EAAE,eAAe;YACvB,QAAQ,EAAE,eAAe;YACzB,IAAI,EAAE,eAAe;YACrB,GAAG,EAAE,eAAe;YACpB,GAAG,EAAE,eAAe;YACpB,SAAS,EAAE,eAAe;YAC1B,IAAI,EAAE,eAAe;YACrB,GAAG,EAAE,eAAe;YACpB,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,KAAK;YACd,WAAW,EAAE,KAAK;YAClB,WAAW,EAAE,KAAK;YAClB,SAAS,EAAE,QAAQ;YACnB,OAAO,EAAE,eAAe;YACxB,IAAI,EAAE,WAAW;YACjB,QAAQ,EAAE,WAAW;YACrB,OAAO,EAAE,WAAW;YACpB,IAAI,EAAE,MAAM;YACZ,YAAY,EAAE,gBAAgB;SAC/B,CAAC;QAEF,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,OAAO,eAAe,CAAC;QACzB,CAAC;QAED,OAAO,OAAO,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,IAAI,eAAe,CAAC;IAC5D,CAAC;CACF;AAnFD,gCAmFC"}
1
+ {"version":3,"file":"transpiler.js","sourceRoot":"","sources":["../../src/fhirpath/transpiler.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../src/loader/tables.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAY,EAAE,KAAK,cAAc,EAAE,MAAM,OAAO,CAAC;AAEjD;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,cAAc,EACpB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAWlB;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,cAAc,EACpB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAef;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,cAAc,EACpB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,cAAc,EACpB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,QAAQ,GAAE,OAAe,GACxB,OAAO,CAAC,IAAI,CAAC,CAUf"}
1
+ {"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../src/loader/tables.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAY,EAAE,KAAK,cAAc,EAAE,MAAM,OAAO,CAAC;AAGjD;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,cAAc,EACpB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAWlB;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,cAAc,EACpB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAmBf;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,cAAc,EACpB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAMf;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,cAAc,EACpB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,QAAQ,GAAE,OAAe,GACxB,OAAO,CAAC,IAAI,CAAC,CAUf"}
@@ -14,6 +14,7 @@ exports.createTable = createTable;
14
14
  exports.truncateTable = truncateTable;
15
15
  exports.ensureTable = ensureTable;
16
16
  const mssql_1 = __importDefault(require("mssql"));
17
+ const validation_js_1 = require("../validation.js");
17
18
  /**
18
19
  * Check if a table exists in the database.
19
20
  *
@@ -42,6 +43,9 @@ async function tableExists(pool, schemaName, tableName) {
42
43
  * @param tableName - Name of the table to create.
43
44
  */
44
45
  async function createTable(pool, schemaName, tableName) {
46
+ // Validate identifiers to prevent SQL injection
47
+ (0, validation_js_1.validateSqlServerIdentifier)(schemaName, "Schema name");
48
+ (0, validation_js_1.validateSqlServerIdentifier)(tableName, "Table name");
45
49
  // Create the table.
46
50
  await pool.request().query(`
47
51
  CREATE TABLE [${schemaName}].[${tableName}] (
@@ -64,6 +68,9 @@ async function createTable(pool, schemaName, tableName) {
64
68
  * @param tableName - Name of the table to truncate.
65
69
  */
66
70
  async function truncateTable(pool, schemaName, tableName) {
71
+ // Validate identifiers to prevent SQL injection
72
+ (0, validation_js_1.validateSqlServerIdentifier)(schemaName, "Schema name");
73
+ (0, validation_js_1.validateSqlServerIdentifier)(tableName, "Table name");
67
74
  await pool.request().query(`TRUNCATE TABLE [${schemaName}].[${tableName}]`);
68
75
  }
69
76
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"tables.js","sourceRoot":"","sources":["../../src/loader/tables.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;;;AAYH,kCAeC;AAUD,kCAmBC;AASD,sCAMC;AAUD,kCAeC;AA9FD,kDAAiD;AAEjD;;;;;;;GAOG;AACI,KAAK,UAAU,WAAW,CAC/B,IAAoB,EACpB,UAAkB,EAClB,SAAiB;IAEjB,MAAM,MAAM,GAAG,MAAM,IAAI;SACtB,OAAO,EAAE;SACT,KAAK,CAAC,YAAY,EAAE,eAAG,CAAC,QAAQ,EAAE,UAAU,CAAC;SAC7C,KAAK,CAAC,WAAW,EAAE,eAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,KAAK,CAAC;;;;KAIlD,CAAC,CAAC;IAEL,OAAO,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC;AACvC,CAAC;AAED;;;;;;;GAOG;AACI,KAAK,UAAU,WAAW,CAC/B,IAAoB,EACpB,UAAkB,EAClB,SAAiB;IAEjB,oBAAoB;IACpB,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC;oBACT,UAAU,MAAM,SAAS;;;;;GAK1C,CAAC,CAAC;IAEH,6EAA6E;IAC7E,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC;uBACN,SAAS;UACtB,UAAU,MAAM,SAAS;GAChC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;GAMG;AACI,KAAK,UAAU,aAAa,CACjC,IAAoB,EACpB,UAAkB,EAClB,SAAiB;IAEjB,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,mBAAmB,UAAU,MAAM,SAAS,GAAG,CAAC,CAAC;AAC9E,CAAC;AAED;;;;;;;GAOG;AACI,KAAK,UAAU,WAAW,CAC/B,IAAoB,EACpB,UAAkB,EAClB,SAAiB,EACjB,WAAoB,KAAK;IAEzB,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;IAE9D,IAAI,MAAM,EAAE,CAAC;QACX,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,aAAa,CAAC,IAAI,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;SAAM,CAAC;QACN,MAAM,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;IACjD,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"tables.js","sourceRoot":"","sources":["../../src/loader/tables.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;;;AAaH,kCAeC;AAUD,kCAuBC;AASD,sCAUC;AAUD,kCAeC;AAvGD,kDAAiD;AACjD,oDAA+D;AAE/D;;;;;;;GAOG;AACI,KAAK,UAAU,WAAW,CAC/B,IAAoB,EACpB,UAAkB,EAClB,SAAiB;IAEjB,MAAM,MAAM,GAAG,MAAM,IAAI;SACtB,OAAO,EAAE;SACT,KAAK,CAAC,YAAY,EAAE,eAAG,CAAC,QAAQ,EAAE,UAAU,CAAC;SAC7C,KAAK,CAAC,WAAW,EAAE,eAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,KAAK,CAAC;;;;KAIlD,CAAC,CAAC;IAEL,OAAO,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC;AACvC,CAAC;AAED;;;;;;;GAOG;AACI,KAAK,UAAU,WAAW,CAC/B,IAAoB,EACpB,UAAkB,EAClB,SAAiB;IAEjB,gDAAgD;IAChD,IAAA,2CAA2B,EAAC,UAAU,EAAE,aAAa,CAAC,CAAC;IACvD,IAAA,2CAA2B,EAAC,SAAS,EAAE,YAAY,CAAC,CAAC;IAErD,oBAAoB;IACpB,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC;oBACT,UAAU,MAAM,SAAS;;;;;GAK1C,CAAC,CAAC;IAEH,6EAA6E;IAC7E,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC;uBACN,SAAS;UACtB,UAAU,MAAM,SAAS;GAChC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;GAMG;AACI,KAAK,UAAU,aAAa,CACjC,IAAoB,EACpB,UAAkB,EAClB,SAAiB;IAEjB,gDAAgD;IAChD,IAAA,2CAA2B,EAAC,UAAU,EAAE,aAAa,CAAC,CAAC;IACvD,IAAA,2CAA2B,EAAC,SAAS,EAAE,YAAY,CAAC,CAAC;IAErD,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,mBAAmB,UAAU,MAAM,SAAS,GAAG,CAAC,CAAC;AAC9E,CAAC;AAED;;;;;;;GAOG;AACI,KAAK,UAAU,WAAW,CAC/B,IAAoB,EACpB,UAAkB,EAClB,SAAiB,EACjB,WAAoB,KAAK;IAEzB,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;IAE9D,IAAI,MAAM,EAAE,CAAC;QACX,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,aAAa,CAAC,IAAI,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;SAAM,CAAC;QACN,MAAM,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;IACjD,CAAC;AACH,CAAC"}
package/dist/parser.d.ts CHANGED
@@ -15,6 +15,10 @@ export declare class ViewDefinitionParser {
15
15
  * Validate and narrow a ViewDefinition structure using type predicate.
16
16
  */
17
17
  private static isValidViewDefinition;
18
+ /**
19
+ * Validate constant names match SQL on FHIR specification.
20
+ */
21
+ private static validateConstants;
18
22
  /**
19
23
  * Validate select element using type predicate.
20
24
  */
@@ -43,6 +47,14 @@ export declare class ViewDefinitionParser {
43
47
  * Validate column using type predicate.
44
48
  */
45
49
  private static isValidColumn;
50
+ /**
51
+ * Validate column tag structure.
52
+ */
53
+ private static validateColumnTags;
54
+ /**
55
+ * Validate a single tag object.
56
+ */
57
+ private static validateSingleTag;
46
58
  /**
47
59
  * Validate collection property constraints.
48
60
  */
@@ -1 +1 @@
1
- {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,SAAS,EAIT,cAAc,EAGf,MAAM,YAAY,CAAC;AAEpB,qBAAa,oBAAoB;IAC/B;;OAEG;IACH,MAAM,CAAC,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,cAAc;IAWjE;;OAEG;IACH,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAUvD;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,qBAAqB;IA8BpC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,aAAa;IAa5B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,uBAAuB;IAQtC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,yBAAyB;IAUxC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAWpC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAWpC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAY/B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,aAAa;IAyB5B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,6BAA6B;IAiC5C;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,uBAAuB;IA0CtC;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,wBAAwB;CA+BxC"}
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,SAAS,EAIT,cAAc,EAGf,MAAM,YAAY,CAAC;AAEpB,qBAAa,oBAAoB;IAC/B;;OAEG;IACH,MAAM,CAAC,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,cAAc;IAWjE;;OAEG;IACH,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAUvD;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAqCpC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAqBhC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,aAAa;IAa5B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,uBAAuB;IAQtC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,yBAAyB;IAYxC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAWpC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAWpC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAY/B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,aAAa;IA6B5B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAcjC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,iBAAiB;IA0BhC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,6BAA6B;IAiC5C;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,uBAAuB;IA0CtC;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,wBAAwB;CA+BxC"}
package/dist/parser.js CHANGED
@@ -30,12 +30,12 @@ class ViewDefinitionParser {
30
30
  */
31
31
  static isValidViewDefinition(data) {
32
32
  if (!data.resource || typeof data.resource !== "string") {
33
- throw new Error("ViewDefinition must specify a resource type.");
33
+ throw new TypeError("ViewDefinition must specify a resource type.");
34
34
  }
35
35
  if (!data.select ||
36
36
  !Array.isArray(data.select) ||
37
37
  data.select.length === 0) {
38
- throw new Error("ViewDefinition must have at least one select element.");
38
+ throw new TypeError("ViewDefinition must have at least one select element.");
39
39
  }
40
40
  // Status is optional for test cases, but recommended for production use
41
41
  // The SQL-on-FHIR spec requires status, but test cases may omit it
@@ -47,8 +47,30 @@ class ViewDefinitionParser {
47
47
  return false;
48
48
  }
49
49
  }
50
+ // Validate constants if present
51
+ if (data.constant) {
52
+ this.validateConstants(data.constant);
53
+ }
50
54
  return true;
51
55
  }
56
+ /**
57
+ * Validate constant names match SQL on FHIR specification.
58
+ */
59
+ static validateConstants(constants) {
60
+ for (const constant of constants) {
61
+ if (!constant ||
62
+ typeof constant !== "object" ||
63
+ !("name" in constant) ||
64
+ typeof constant.name !== "string") {
65
+ throw new TypeError("Constant must have a valid name.");
66
+ }
67
+ // Validate constant name matches SQL on FHIR specification pattern
68
+ // Pattern: must start with a letter, followed by letters, digits, or underscores
69
+ if (!/^[A-Za-z]\w*$/.test(constant.name)) {
70
+ throw new Error(`Constant name '${constant.name}' does not match SQL on FHIR specification. Must start with a letter, followed by letters, digits, or underscores.`);
71
+ }
72
+ }
73
+ }
52
74
  /**
53
75
  * Validate select element using type predicate.
54
76
  */
@@ -72,10 +94,10 @@ class ViewDefinitionParser {
72
94
  */
73
95
  static validateSelectExpressions(select) {
74
96
  if (select.forEach && typeof select.forEach !== "string") {
75
- throw new Error("forEach must be a string FHIRPath expression.");
97
+ throw new TypeError("forEach must be a string FHIRPath expression.");
76
98
  }
77
99
  if (select.forEachOrNull && typeof select.forEachOrNull !== "string") {
78
- throw new Error("forEachOrNull must be a string FHIRPath expression.");
100
+ throw new TypeError("forEachOrNull must be a string FHIRPath expression.");
79
101
  }
80
102
  }
81
103
  /**
@@ -123,19 +145,54 @@ class ViewDefinitionParser {
123
145
  */
124
146
  static isValidColumn(column, selectContext) {
125
147
  if (!column.name || typeof column.name !== "string") {
126
- throw new Error("Column must have a valid name.");
148
+ throw new TypeError("Column must have a valid name.");
127
149
  }
128
150
  if (!column.path || typeof column.path !== "string") {
129
- throw new Error("Column must have a valid FHIRPath expression.");
151
+ throw new TypeError("Column must have a valid FHIRPath expression.");
130
152
  }
131
- // Validate column name is database-friendly
132
- if (!/^[a-zA-Z_]\w*$/.test(column.name)) {
133
- throw new Error(`Column name '${column.name}' is not database-friendly. Use alphanumeric and underscores only.`);
153
+ // Validate column name matches SQL on FHIR specification pattern
154
+ // Pattern: must start with a letter, followed by letters, digits, or underscores
155
+ if (!/^[A-Za-z]\w*$/.test(column.name)) {
156
+ throw new Error(`Column name '${column.name}' does not match SQL on FHIR specification. Must start with a letter, followed by letters, digits, or underscores.`);
134
157
  }
135
158
  // Validate collection constraints
136
159
  this.validateCollectionConstraints(column, selectContext);
160
+ // Validate tag structure if present
161
+ this.validateColumnTags(column);
137
162
  return true;
138
163
  }
164
+ /**
165
+ * Validate column tag structure.
166
+ */
167
+ static validateColumnTags(column) {
168
+ if (column.tag === undefined) {
169
+ return;
170
+ }
171
+ if (!Array.isArray(column.tag)) {
172
+ throw new TypeError(`Column '${column.name}' tag must be an array.`);
173
+ }
174
+ for (const tag of column.tag) {
175
+ this.validateSingleTag(column.name, tag);
176
+ }
177
+ }
178
+ /**
179
+ * Validate a single tag object.
180
+ */
181
+ static validateSingleTag(columnName, tag) {
182
+ if (typeof tag !== "object" || tag === null) {
183
+ throw new TypeError(`Column '${columnName}' tag entry must be an object.`);
184
+ }
185
+ if (!("name" in tag) ||
186
+ typeof tag.name !== "string" ||
187
+ tag.name.trim().length === 0) {
188
+ throw new TypeError(`Column '${columnName}' tag must have a non-empty 'name' string.`);
189
+ }
190
+ if (!("value" in tag) ||
191
+ typeof tag.value !== "string" ||
192
+ tag.value.trim().length === 0) {
193
+ throw new TypeError(`Column '${columnName}' tag must have a non-empty 'value' string.`);
194
+ }
195
+ }
139
196
  /**
140
197
  * Validate collection property constraints.
141
198
  */