sof-mssql 2.3.0 → 2.4.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 (57) hide show
  1. package/README.md +88 -0
  2. package/dist/fhirpath/jsonSafeEmptiness.test.d.ts +19 -0
  3. package/dist/fhirpath/jsonSafeEmptiness.test.d.ts.map +1 -0
  4. package/dist/fhirpath/jsonSafeEmptiness.test.js +80 -0
  5. package/dist/fhirpath/jsonSafeEmptiness.test.js.map +1 -0
  6. package/dist/fhirpath/visitor.d.ts.map +1 -1
  7. package/dist/fhirpath/visitor.js +20 -12
  8. package/dist/fhirpath/visitor.js.map +1 -1
  9. package/dist/load.d.ts +9 -0
  10. package/dist/load.d.ts.map +1 -1
  11. package/dist/load.js +4 -0
  12. package/dist/load.js.map +1 -1
  13. package/dist/loader/feedback.integration.test.d.ts +14 -0
  14. package/dist/loader/feedback.integration.test.d.ts.map +1 -0
  15. package/dist/loader/feedback.integration.test.js +76 -0
  16. package/dist/loader/feedback.integration.test.js.map +1 -0
  17. package/dist/loader/index.d.ts.map +1 -1
  18. package/dist/loader/index.js +19 -6
  19. package/dist/loader/index.js.map +1 -1
  20. package/dist/loader/jsonColumn.integration.test.d.ts +13 -0
  21. package/dist/loader/jsonColumn.integration.test.d.ts.map +1 -0
  22. package/dist/loader/jsonColumn.integration.test.js +86 -0
  23. package/dist/loader/jsonColumn.integration.test.js.map +1 -0
  24. package/dist/loader/tables.d.ts +96 -3
  25. package/dist/loader/tables.d.ts.map +1 -1
  26. package/dist/loader/tables.js +182 -16
  27. package/dist/loader/tables.js.map +1 -1
  28. package/dist/loader/tables.test.d.ts +12 -0
  29. package/dist/loader/tables.test.d.ts.map +1 -0
  30. package/dist/loader/tables.test.js +130 -0
  31. package/dist/loader/tables.test.js.map +1 -0
  32. package/dist/loader/types.d.ts +9 -0
  33. package/dist/loader/types.d.ts.map +1 -1
  34. package/dist/tests/load.test.d.ts +11 -0
  35. package/dist/tests/load.test.d.ts.map +1 -0
  36. package/dist/tests/load.test.js +37 -0
  37. package/dist/tests/load.test.js.map +1 -0
  38. package/dist/tests/utils/database.d.ts.map +1 -1
  39. package/dist/tests/utils/database.js +51 -3
  40. package/dist/tests/utils/database.js.map +1 -1
  41. package/dist/tests/utils/loaderIntegration.d.ts +59 -0
  42. package/dist/tests/utils/loaderIntegration.d.ts.map +1 -0
  43. package/dist/tests/utils/loaderIntegration.js +137 -0
  44. package/dist/tests/utils/loaderIntegration.js.map +1 -0
  45. package/dist/tests/utils/serverCapabilities.d.ts +23 -0
  46. package/dist/tests/utils/serverCapabilities.d.ts.map +1 -0
  47. package/dist/tests/utils/serverCapabilities.js +35 -0
  48. package/dist/tests/utils/serverCapabilities.js.map +1 -0
  49. package/dist/tests/validation.test.d.ts +13 -0
  50. package/dist/tests/validation.test.d.ts.map +1 -0
  51. package/dist/tests/validation.test.js +81 -0
  52. package/dist/tests/validation.test.js.map +1 -0
  53. package/dist/validation.d.ts +40 -0
  54. package/dist/validation.d.ts.map +1 -1
  55. package/dist/validation.js +56 -0
  56. package/dist/validation.js.map +1 -1
  57. package/package.json +1 -1
@@ -9,12 +9,119 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
9
9
  return (mod && mod.__esModule) ? mod : { "default": mod };
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.buildCreateTableStatements = buildCreateTableStatements;
13
+ exports.resolveColumnJsonDataType = resolveColumnJsonDataType;
14
+ exports.buildJsonTypeMismatchWarning = buildJsonTypeMismatchWarning;
12
15
  exports.tableExists = tableExists;
16
+ exports.getExistingJsonColumnType = getExistingJsonColumnType;
17
+ exports.warnIfJsonTypeMismatch = warnIfJsonTypeMismatch;
13
18
  exports.createTable = createTable;
14
19
  exports.truncateTable = truncateTable;
15
20
  exports.ensureTable = ensureTable;
16
21
  const mssql_1 = __importDefault(require("mssql"));
17
22
  const validation_js_1 = require("../validation.js");
23
+ /**
24
+ * Build the DDL statements for the resources table and its index.
25
+ *
26
+ * This is a pure function so the generated SQL can be unit-tested without a
27
+ * database. The `json` column is typed with the resolved {@link
28
+ * ResourceJsonDataType}; with the default `NVARCHAR(MAX)` the output is
29
+ * byte-for-byte identical to earlier releases (SC-001). Identifiers are assumed
30
+ * to have been validated by the caller (see {@link createTable}).
31
+ *
32
+ * @param schemaName - Schema name (already validated).
33
+ * @param tableName - Table name (already validated).
34
+ * @param jsonType - Resolved canonical storage type for the `json` column.
35
+ * @returns The `CREATE TABLE` and `CREATE INDEX` statements.
36
+ */
37
+ function buildCreateTableStatements(schemaName, tableName, jsonType) {
38
+ // Only the json column's type varies; every other part of the DDL is held
39
+ // constant so the default path is unchanged from earlier releases.
40
+ const createTable = `
41
+ CREATE TABLE [${schemaName}].[${tableName}] (
42
+ [id] INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
43
+ [resource_type] NVARCHAR(64) NOT NULL,
44
+ [json] ${jsonType} NOT NULL
45
+ )
46
+ `;
47
+ const createIndex = `
48
+ CREATE INDEX [IX_${tableName}_resource_type]
49
+ ON [${schemaName}].[${tableName}] ([resource_type])
50
+ `;
51
+ return { createTable, createIndex };
52
+ }
53
+ /**
54
+ * Render an INFORMATION_SCHEMA column type for a diagnostic message.
55
+ *
56
+ * Length-bearing types are shown with their declared length, with the `-1`
57
+ * sentinel rendered as `MAX`; types reported without a length (such as the
58
+ * native `JSON` type) are shown as the bare type name.
59
+ *
60
+ * @param dataType - The INFORMATION_SCHEMA DATA_TYPE.
61
+ * @param characterMaximumLength - The INFORMATION_SCHEMA CHARACTER_MAXIMUM_LENGTH.
62
+ * @returns A readable type such as `VARCHAR(100)`, `NVARCHAR(MAX)` or `JSON`.
63
+ */
64
+ function formatColumnType(dataType, characterMaximumLength) {
65
+ const baseType = dataType.trim().toUpperCase();
66
+ if (characterMaximumLength === null) {
67
+ return baseType;
68
+ }
69
+ const length = characterMaximumLength === -1 ? "MAX" : String(characterMaximumLength);
70
+ return `${baseType}(${length})`;
71
+ }
72
+ /**
73
+ * Resolve an INFORMATION_SCHEMA column description to a canonical json type.
74
+ *
75
+ * Only two column shapes can faithfully hold a serialised FHIR resource: the
76
+ * native type (`data_type = 'json'`) and `NVARCHAR(MAX)` (`data_type =
77
+ * 'nvarchar'` with `character_maximum_length = -1`). Any other shape - a bounded
78
+ * `NVARCHAR(64)`, a non-Unicode `VARCHAR`, `TEXT`, and so on - is rejected here
79
+ * rather than silently coerced to `NVARCHAR(MAX)`. Coercion would let the
80
+ * mismatch check pass and the loader write into a column that cannot hold the
81
+ * data, surfacing later as a `String or binary data would be truncated` error
82
+ * or, under non-Unicode `VARCHAR`, silent character corruption. Failing fast
83
+ * turns that late, data-dependent failure into an early, actionable
84
+ * configuration error (Constitution Principle IV).
85
+ *
86
+ * @param dataType - The INFORMATION_SCHEMA DATA_TYPE.
87
+ * @param characterMaximumLength - The INFORMATION_SCHEMA CHARACTER_MAXIMUM_LENGTH.
88
+ * @returns The canonical resource json data type (`JSON` or `NVARCHAR(MAX)`).
89
+ * @throws Error if the column is neither native `JSON` nor `NVARCHAR(MAX)`. The
90
+ * message names the offending type and the two acceptable types.
91
+ */
92
+ function resolveColumnJsonDataType(dataType, characterMaximumLength) {
93
+ const normalised = dataType.trim().toLowerCase();
94
+ if (normalised === "json") {
95
+ return "JSON";
96
+ }
97
+ // NVARCHAR(MAX) is the only nvarchar form that can hold an arbitrarily long
98
+ // resource; bounded lengths would truncate, so the length is required here.
99
+ if (normalised === "nvarchar" && characterMaximumLength === -1) {
100
+ return "NVARCHAR(MAX)";
101
+ }
102
+ throw new Error(`Existing [json] column is ` +
103
+ `${formatColumnType(dataType, characterMaximumLength)}, which cannot ` +
104
+ `safely hold serialised FHIR resources. Expected NVARCHAR(MAX) or JSON. ` +
105
+ `Alter or drop the column before loading.`);
106
+ }
107
+ /**
108
+ * Build a warning for an existing table whose json column type differs from the
109
+ * requested type.
110
+ *
111
+ * @param schemaName - Schema name.
112
+ * @param tableName - Table name.
113
+ * @param existingType - The table's current json column type.
114
+ * @param requestedType - The requested json column type.
115
+ * @returns A warning message naming both types, or null when they match.
116
+ */
117
+ function buildJsonTypeMismatchWarning(schemaName, tableName, existingType, requestedType) {
118
+ if (existingType === requestedType) {
119
+ return null;
120
+ }
121
+ return (`Warning: table [${schemaName}].[${tableName}] already exists with a json ` +
122
+ `column of type ${existingType}, but ${requestedType} was requested. The ` +
123
+ `existing table is left unaltered and loading continues into it.`);
124
+ }
18
125
  /**
19
126
  * Check if a table exists in the database.
20
127
  *
@@ -34,31 +141,82 @@ async function tableExists(pool, schemaName, tableName) {
34
141
  `);
35
142
  return result.recordset[0].count > 0;
36
143
  }
144
+ /**
145
+ * Read the effective json column type for an existing table.
146
+ *
147
+ * @param pool - Database connection pool.
148
+ * @param schemaName - Schema name.
149
+ * @param tableName - Name of the table.
150
+ * @returns The canonical json column type, or null if the table or its `json`
151
+ * column does not exist.
152
+ * @throws Error if the column exists but is neither native `JSON` nor
153
+ * `NVARCHAR(MAX)` (see {@link resolveColumnJsonDataType}).
154
+ */
155
+ async function getExistingJsonColumnType(pool, schemaName, tableName) {
156
+ const result = await pool
157
+ .request()
158
+ .input("schemaName", mssql_1.default.NVarChar, schemaName)
159
+ .input("tableName", mssql_1.default.NVarChar, tableName).query(`
160
+ SELECT DATA_TYPE, CHARACTER_MAXIMUM_LENGTH
161
+ FROM INFORMATION_SCHEMA.COLUMNS
162
+ WHERE TABLE_SCHEMA = @schemaName
163
+ AND TABLE_NAME = @tableName
164
+ AND COLUMN_NAME = 'json'
165
+ `);
166
+ const row = result.recordset[0];
167
+ if (!row) {
168
+ return null;
169
+ }
170
+ return resolveColumnJsonDataType(row.DATA_TYPE, row.CHARACTER_MAXIMUM_LENGTH);
171
+ }
172
+ /**
173
+ * Emit a warning if an existing table's json column type differs from the
174
+ * requested type. The table is never altered; this only surfaces the mismatch
175
+ * so it is visible rather than silently ignored (FR-008, SC-005).
176
+ *
177
+ * An existing column that is neither native `JSON` nor `NVARCHAR(MAX)` cannot
178
+ * hold a serialised FHIR resource, so it is rejected outright rather than
179
+ * warned about: the error is raised here before any rows are loaded.
180
+ *
181
+ * @param pool - Database connection pool.
182
+ * @param schemaName - Schema name.
183
+ * @param tableName - Name of the table.
184
+ * @param requestedType - The requested json column type.
185
+ * @throws Error if the existing `json` column is neither native `JSON` nor
186
+ * `NVARCHAR(MAX)` (see {@link resolveColumnJsonDataType}).
187
+ */
188
+ async function warnIfJsonTypeMismatch(pool, schemaName, tableName, requestedType) {
189
+ const existingType = await getExistingJsonColumnType(pool, schemaName, tableName);
190
+ if (existingType === null) {
191
+ return;
192
+ }
193
+ const warning = buildJsonTypeMismatchWarning(schemaName, tableName, existingType, requestedType);
194
+ if (warning !== null) {
195
+ // The warning is emitted regardless of quiet mode so the misconfiguration
196
+ // is always visible (SC-005).
197
+ console.warn(warning);
198
+ }
199
+ }
37
200
  /**
38
201
  * Create the fhir_resources table with an index on resource_type.
39
- * Table schema: id (INT IDENTITY PRIMARY KEY), resource_type (NVARCHAR(64)), json (NVARCHAR(MAX))
202
+ * Table schema: id (INT IDENTITY PRIMARY KEY), resource_type (NVARCHAR(64)),
203
+ * json (the configured storage type, NVARCHAR(MAX) by default).
40
204
  *
41
205
  * @param pool - Database connection pool.
42
206
  * @param schemaName - Schema name.
43
207
  * @param tableName - Name of the table to create.
208
+ * @param jsonType - Storage type for the `json` column (default `NVARCHAR(MAX)`).
44
209
  */
45
- async function createTable(pool, schemaName, tableName) {
46
- // Validate identifiers to prevent SQL injection
210
+ async function createTable(pool, schemaName, tableName, jsonType = "NVARCHAR(MAX)") {
211
+ // Validate identifiers to prevent SQL injection. The json type is already a
212
+ // canonical, allowlisted value, so it carries no injection risk.
47
213
  (0, validation_js_1.validateSqlServerIdentifier)(schemaName, "Schema name");
48
214
  (0, validation_js_1.validateSqlServerIdentifier)(tableName, "Table name");
215
+ const { createTable: createTableSql, createIndex: createIndexSql } = buildCreateTableStatements(schemaName, tableName, jsonType);
49
216
  // Create the table.
50
- await pool.request().query(`
51
- CREATE TABLE [${schemaName}].[${tableName}] (
52
- [id] INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
53
- [resource_type] NVARCHAR(64) NOT NULL,
54
- [json] NVARCHAR(MAX) NOT NULL
55
- )
56
- `);
217
+ await pool.request().query(createTableSql);
57
218
  // Create an index on resource_type for efficient filtering by resource type.
58
- await pool.request().query(`
59
- CREATE INDEX [IX_${tableName}_resource_type]
60
- ON [${schemaName}].[${tableName}] ([resource_type])
61
- `);
219
+ await pool.request().query(createIndexSql);
62
220
  }
63
221
  /**
64
222
  * Truncate a table (remove all rows).
@@ -76,20 +234,28 @@ async function truncateTable(pool, schemaName, tableName) {
76
234
  /**
77
235
  * Ensure the fhir_resources table exists, creating it if necessary.
78
236
  *
237
+ * When the table already exists it is never altered; the requested `json`
238
+ * column type only governs creation of a new table.
239
+ *
79
240
  * @param pool - Database connection pool.
80
241
  * @param schemaName - Schema name.
81
242
  * @param tableName - Name of the table.
82
243
  * @param truncate - Whether to truncate the table if it exists.
244
+ * @param jsonType - Storage type for the `json` column when creating the table
245
+ * (default `NVARCHAR(MAX)`).
83
246
  */
84
- async function ensureTable(pool, schemaName, tableName, truncate = false) {
247
+ async function ensureTable(pool, schemaName, tableName, truncate = false, jsonType = "NVARCHAR(MAX)") {
85
248
  const exists = await tableExists(pool, schemaName, tableName);
86
249
  if (exists) {
250
+ // The table already exists, so the requested type cannot take effect. Warn
251
+ // if it differs from the existing column type, then leave the table as is.
252
+ await warnIfJsonTypeMismatch(pool, schemaName, tableName, jsonType);
87
253
  if (truncate) {
88
254
  await truncateTable(pool, schemaName, tableName);
89
255
  }
90
256
  }
91
257
  else {
92
- await createTable(pool, schemaName, tableName);
258
+ await createTable(pool, schemaName, tableName, jsonType);
93
259
  }
94
260
  }
95
261
  //# sourceMappingURL=tables.js.map
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"tables.js","sourceRoot":"","sources":["../../src/loader/tables.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Unit tests for pure table DDL generation and existing-column-type resolution.
3
+ *
4
+ * These exercise the string-building and comparison logic without a database,
5
+ * so the byte-for-byte DDL contract (SC-001), the native JSON column type
6
+ * (FR-005) and the type-mismatch decision (FR-008) can be verified in
7
+ * isolation.
8
+ *
9
+ * @author John Grimes
10
+ */
11
+ export {};
12
+ //# sourceMappingURL=tables.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tables.test.d.ts","sourceRoot":"","sources":["../../src/loader/tables.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ /**
3
+ * Unit tests for pure table DDL generation and existing-column-type resolution.
4
+ *
5
+ * These exercise the string-building and comparison logic without a database,
6
+ * so the byte-for-byte DDL contract (SC-001), the native JSON column type
7
+ * (FR-005) and the type-mismatch decision (FR-008) can be verified in
8
+ * isolation.
9
+ *
10
+ * @author John Grimes
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ const vitest_1 = require("vitest");
14
+ const tables_1 = require("./tables");
15
+ (0, vitest_1.describe)("buildCreateTableStatements", () => {
16
+ (0, vitest_1.describe)("default NVARCHAR(MAX) json column", () => {
17
+ const statements = (0, tables_1.buildCreateTableStatements)("dbo", "fhir_resources", "NVARCHAR(MAX)");
18
+ (0, vitest_1.it)("types the json column as NVARCHAR(MAX) NOT NULL", () => {
19
+ (0, vitest_1.expect)(statements.createTable).toContain("[json] NVARCHAR(MAX) NOT NULL");
20
+ });
21
+ (0, vitest_1.it)("keeps the id column unchanged", () => {
22
+ (0, vitest_1.expect)(statements.createTable).toContain("[id] INT IDENTITY(1,1) NOT NULL PRIMARY KEY");
23
+ });
24
+ (0, vitest_1.it)("keeps the resource_type column unchanged", () => {
25
+ (0, vitest_1.expect)(statements.createTable).toContain("[resource_type] NVARCHAR(64) NOT NULL");
26
+ });
27
+ (0, vitest_1.it)("brackets the schema and table identifiers", () => {
28
+ (0, vitest_1.expect)(statements.createTable).toContain("[dbo].[fhir_resources]");
29
+ });
30
+ (0, vitest_1.it)("builds the resource_type index", () => {
31
+ (0, vitest_1.expect)(statements.createIndex).toContain("[IX_fhir_resources_resource_type]");
32
+ (0, vitest_1.expect)(statements.createIndex).toContain("[dbo].[fhir_resources] ([resource_type])");
33
+ });
34
+ });
35
+ (0, vitest_1.describe)("native JSON json column", () => {
36
+ const statements = (0, tables_1.buildCreateTableStatements)("dbo", "fhir_resources", "JSON");
37
+ (0, vitest_1.it)("types the json column as JSON NOT NULL", () => {
38
+ (0, vitest_1.expect)(statements.createTable).toContain("[json] JSON NOT NULL");
39
+ });
40
+ (0, vitest_1.it)("does not emit NVARCHAR(MAX) for the json column", () => {
41
+ (0, vitest_1.expect)(statements.createTable).not.toContain("[json] NVARCHAR(MAX)");
42
+ });
43
+ (0, vitest_1.it)("keeps the resource_type column unchanged", () => {
44
+ (0, vitest_1.expect)(statements.createTable).toContain("[resource_type] NVARCHAR(64) NOT NULL");
45
+ });
46
+ (0, vitest_1.it)("leaves the index statement identical to the default", () => {
47
+ const defaultStatements = (0, tables_1.buildCreateTableStatements)("dbo", "fhir_resources", "NVARCHAR(MAX)");
48
+ (0, vitest_1.expect)(statements.createIndex).toBe(defaultStatements.createIndex);
49
+ });
50
+ });
51
+ (0, vitest_1.describe)("identifier bracketing", () => {
52
+ (0, vitest_1.it)("brackets custom schema and table names", () => {
53
+ const statements = (0, tables_1.buildCreateTableStatements)("analytics", "resources", "JSON");
54
+ (0, vitest_1.expect)(statements.createTable).toContain("[analytics].[resources]");
55
+ (0, vitest_1.expect)(statements.createIndex).toContain("[IX_resources_resource_type]");
56
+ (0, vitest_1.expect)(statements.createIndex).toContain("[analytics].[resources] ([resource_type])");
57
+ });
58
+ });
59
+ });
60
+ (0, vitest_1.describe)("resolveColumnJsonDataType", () => {
61
+ (0, vitest_1.it)("resolves nvarchar with MAX length to NVARCHAR(MAX)", () => {
62
+ // NVARCHAR(MAX) appears in INFORMATION_SCHEMA as data_type 'nvarchar' with a
63
+ // character_maximum_length of -1.
64
+ (0, vitest_1.expect)((0, tables_1.resolveColumnJsonDataType)("nvarchar", -1)).toBe("NVARCHAR(MAX)");
65
+ });
66
+ (0, vitest_1.it)("resolves json to JSON", () => {
67
+ // The native type appears as data_type 'json' with a null maximum length.
68
+ (0, vitest_1.expect)((0, tables_1.resolveColumnJsonDataType)("json", null)).toBe("JSON");
69
+ });
70
+ (0, vitest_1.it)("is case-insensitive on the data type name", () => {
71
+ (0, vitest_1.expect)((0, tables_1.resolveColumnJsonDataType)("JSON", null)).toBe("JSON");
72
+ (0, vitest_1.expect)((0, tables_1.resolveColumnJsonDataType)("NVARCHAR", -1)).toBe("NVARCHAR(MAX)");
73
+ });
74
+ (0, vitest_1.it)("tolerates surrounding whitespace on the data type name", () => {
75
+ (0, vitest_1.expect)((0, tables_1.resolveColumnJsonDataType)(" json ", null)).toBe("JSON");
76
+ (0, vitest_1.expect)((0, tables_1.resolveColumnJsonDataType)(" nvarchar ", -1)).toBe("NVARCHAR(MAX)");
77
+ });
78
+ // An existing column that is neither native JSON nor NVARCHAR(MAX) cannot
79
+ // faithfully hold a serialised FHIR resource. Such a column must be rejected
80
+ // at the boundary, naming the offending type, rather than silently coerced to
81
+ // NVARCHAR(MAX) - coercion would let the loader write into it and lose data
82
+ // through truncation or, under non-Unicode VARCHAR, character corruption
83
+ // (Constitution Principle IV).
84
+ (0, vitest_1.describe)("rejects column types that cannot hold a FHIR resource", () => {
85
+ (0, vitest_1.it)("throws for a bounded nvarchar, naming the offending type", () => {
86
+ (0, vitest_1.expect)(() => (0, tables_1.resolveColumnJsonDataType)("nvarchar", 64)).toThrow(/NVARCHAR\(64\)/);
87
+ });
88
+ (0, vitest_1.it)("throws for varchar, naming the offending type", () => {
89
+ (0, vitest_1.expect)(() => (0, tables_1.resolveColumnJsonDataType)("varchar", 100)).toThrow(/VARCHAR\(100\)/);
90
+ });
91
+ (0, vitest_1.it)("throws for a max-length varchar, which is still not Unicode-safe", () => {
92
+ // VARCHAR(MAX) reports a length of -1 but is non-Unicode, so it can
93
+ // silently corrupt multi-byte characters and must still be rejected.
94
+ (0, vitest_1.expect)(() => (0, tables_1.resolveColumnJsonDataType)("varchar", -1)).toThrow(/VARCHAR\(MAX\)/);
95
+ });
96
+ (0, vitest_1.it)("throws for text, naming the offending type", () => {
97
+ (0, vitest_1.expect)(() => (0, tables_1.resolveColumnJsonDataType)("text", 2147483647)).toThrow(/TEXT/);
98
+ });
99
+ (0, vitest_1.it)("names both acceptable types in the error message", () => {
100
+ let message = "";
101
+ try {
102
+ (0, tables_1.resolveColumnJsonDataType)("varchar", 100);
103
+ }
104
+ catch (error) {
105
+ message = error.message;
106
+ }
107
+ (0, vitest_1.expect)(message).toContain("NVARCHAR(MAX)");
108
+ (0, vitest_1.expect)(message).toContain("JSON");
109
+ });
110
+ });
111
+ });
112
+ (0, vitest_1.describe)("buildJsonTypeMismatchWarning", () => {
113
+ (0, vitest_1.it)("returns null when the existing and requested types are equal", () => {
114
+ (0, vitest_1.expect)((0, tables_1.buildJsonTypeMismatchWarning)("dbo", "t", "NVARCHAR(MAX)", "NVARCHAR(MAX)")).toBeNull();
115
+ (0, vitest_1.expect)((0, tables_1.buildJsonTypeMismatchWarning)("dbo", "t", "JSON", "JSON")).toBeNull();
116
+ });
117
+ (0, vitest_1.it)("returns a warning naming both types and the table when they differ", () => {
118
+ const warning = (0, tables_1.buildJsonTypeMismatchWarning)("dbo", "fhir_resources", "NVARCHAR(MAX)", "JSON");
119
+ (0, vitest_1.expect)(warning).not.toBeNull();
120
+ (0, vitest_1.expect)(warning).toContain("NVARCHAR(MAX)");
121
+ (0, vitest_1.expect)(warning).toContain("JSON");
122
+ (0, vitest_1.expect)(warning).toContain("fhir_resources");
123
+ });
124
+ (0, vitest_1.it)("names both the existing and the requested type in either direction", () => {
125
+ const warning = (0, tables_1.buildJsonTypeMismatchWarning)("dbo", "t", "JSON", "NVARCHAR(MAX)");
126
+ (0, vitest_1.expect)(warning).toMatch(/JSON/);
127
+ (0, vitest_1.expect)(warning).toMatch(/NVARCHAR\(MAX\)/);
128
+ });
129
+ });
130
+ //# sourceMappingURL=tables.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tables.test.js","sourceRoot":"","sources":["../../src/loader/tables.test.ts"],"names":[],"mappings":"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"}
@@ -49,6 +49,15 @@ export interface LoaderOptions {
49
49
  tableName?: string;
50
50
  /** Schema name (default: dbo). */
51
51
  schemaName?: string;
52
+ /**
53
+ * Storage type for the resources table `json` column. One of `NVARCHAR(MAX)`
54
+ * (default) or `JSON`. Matching is case-insensitive and tolerant of
55
+ * surrounding whitespace. `JSON` selects SQL Server 2025's native JSON type
56
+ * and requires SQL Server 2025 or later; when omitted the column is created as
57
+ * `NVARCHAR(MAX)`, exactly as in earlier releases. The value is validated
58
+ * against the allowlist before any database connection is opened.
59
+ */
60
+ resourceJsonDataType?: string;
52
61
  /** Create table if it doesn't exist. */
53
62
  createTable?: boolean;
54
63
  /** Truncate table before loading. */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/loader/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/loader/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Unit tests for the `load` command option mapping.
3
+ *
4
+ * Verifies that the `--resource-json-data-type` CLI flag is mapped onto
5
+ * `LoaderOptions.resourceJsonDataType` and passed through untouched, leaving
6
+ * validation and normalisation to the loader (FR-007, cli-load contract).
7
+ *
8
+ * @author John Grimes
9
+ */
10
+ export {};
11
+ //# sourceMappingURL=load.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load.test.d.ts","sourceRoot":"","sources":["../../src/tests/load.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ /**
3
+ * Unit tests for the `load` command option mapping.
4
+ *
5
+ * Verifies that the `--resource-json-data-type` CLI flag is mapped onto
6
+ * `LoaderOptions.resourceJsonDataType` and passed through untouched, leaving
7
+ * validation and normalisation to the loader (FR-007, cli-load contract).
8
+ *
9
+ * @author John Grimes
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const vitest_1 = require("vitest");
13
+ const load_1 = require("../load");
14
+ (0, vitest_1.describe)("buildLoaderOptions", () => {
15
+ (0, vitest_1.it)("maps --resource-json-data-type onto resourceJsonDataType", () => {
16
+ // Dry-run avoids any need for database environment variables.
17
+ const options = (0, load_1.buildLoaderOptions)("./data", {
18
+ dryRun: true,
19
+ resourceJsonDataType: "JSON",
20
+ });
21
+ (0, vitest_1.expect)(options.resourceJsonDataType).toBe("JSON");
22
+ });
23
+ (0, vitest_1.it)("leaves resourceJsonDataType undefined when the flag is absent", () => {
24
+ const options = (0, load_1.buildLoaderOptions)("./data", { dryRun: true });
25
+ (0, vitest_1.expect)(options.resourceJsonDataType).toBeUndefined();
26
+ });
27
+ (0, vitest_1.it)("passes the raw value through without normalising", () => {
28
+ // Normalisation is the loader's responsibility; the CLI layer must not
29
+ // pre-empt it, so a lower-case value is forwarded verbatim.
30
+ const options = (0, load_1.buildLoaderOptions)("./data", {
31
+ dryRun: true,
32
+ resourceJsonDataType: "json",
33
+ });
34
+ (0, vitest_1.expect)(options.resourceJsonDataType).toBe("json");
35
+ });
36
+ });
37
+ //# sourceMappingURL=load.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load.test.js","sourceRoot":"","sources":["../../src/tests/load.test.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;AAEH,mCAA8C;AAC9C,kCAA6C;AAE7C,IAAA,iBAAQ,EAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,IAAA,WAAE,EAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,8DAA8D;QAC9D,MAAM,OAAO,GAAG,IAAA,yBAAkB,EAAC,QAAQ,EAAE;YAC3C,MAAM,EAAE,IAAI;YACZ,oBAAoB,EAAE,MAAM;SAC7B,CAAC,CAAC;QACH,IAAA,eAAM,EAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,+DAA+D,EAAE,GAAG,EAAE;QACvE,MAAM,OAAO,GAAG,IAAA,yBAAkB,EAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/D,IAAA,eAAM,EAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC,aAAa,EAAE,CAAC;IACvD,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,uEAAuE;QACvE,4DAA4D;QAC5D,MAAM,OAAO,GAAG,IAAA,yBAAkB,EAAC,QAAQ,EAAE;YAC3C,MAAM,EAAE,IAAI;YACZ,oBAAoB,EAAE,MAAM;SAC7B,CAAC,CAAC;QACH,IAAA,eAAM,EAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../../src/tests/utils/database.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,cAAc,EAAkC,MAAM,OAAO,CAAC;AA+CvE;;;GAGG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAuBnD;AAED;;;GAGG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAWrD;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,SAAS,EAAE,GAAG,EAAE,EAChB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAoDf;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAwCnE;AAwFD;;;GAGG;AACH,wBAAgB,eAAe,IAAI,cAAc,CAKhD"}
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../../src/tests/utils/database.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,cAAc,EAAkC,MAAM,OAAO,CAAC;AA4DvE;;;GAGG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAuBnD;AAED;;;GAGG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAWrD;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,SAAS,EAAE,GAAG,EAAE,EAChB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAoDf;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAwCnE;AAsID;;;GAGG;AACH,wBAAgB,eAAe,IAAI,cAAc,CAKhD"}
@@ -15,6 +15,7 @@ exports.cleanupTestData = cleanupTestData;
15
15
  exports.getDatabasePool = getDatabasePool;
16
16
  const lossless_json_1 = require("lossless-json");
17
17
  const mssql_1 = require("mssql");
18
+ const validation_1 = require("../../validation");
18
19
  // Global connection pool
19
20
  let globalPool = null;
20
21
  let isConnected = false;
@@ -44,12 +45,20 @@ const getDatabaseConfig = () => {
44
45
  /**
45
46
  * Get table configuration from environment variables.
46
47
  * Uses a test-specific table name to avoid conflicts with production data.
48
+ *
49
+ * The `json` column's storage type is governed by MSSQL_RESOURCE_JSON_DATA_TYPE
50
+ * (default `NVARCHAR(MAX)`); setting it to `JSON` exercises the native JSON type
51
+ * and requires a SQL Server 2025 instance. This lets the full conformance suite
52
+ * run against both column types (FR-011).
47
53
  */
48
54
  const getTableConfig = () => ({
49
55
  tableName: process.env.MSSQL_TEST_TABLE ?? "fhir_resources_test",
50
56
  schemaName: process.env.MSSQL_SCHEMA ?? "dbo",
51
57
  resourceIdColumn: "id",
52
58
  resourceJsonColumn: "json",
59
+ resourceJsonDataType: process.env.MSSQL_RESOURCE_JSON_DATA_TYPE
60
+ ? (0, validation_1.normaliseResourceJsonDataType)(process.env.MSSQL_RESOURCE_JSON_DATA_TYPE)
61
+ : "NVARCHAR(MAX)",
53
62
  });
54
63
  /**
55
64
  * Setup database connection and create the FHIR resources table.
@@ -215,6 +224,34 @@ async function clearTestTable() {
215
224
  throw new Error(`Failed to clear test table: ${error instanceof Error ? error.message : String(error)}`);
216
225
  }
217
226
  }
227
+ /**
228
+ * Determine the existing test table's json column type.
229
+ *
230
+ * Maps the INFORMATION_SCHEMA representation to a canonical type: `json` for the
231
+ * native type, otherwise `NVARCHAR(MAX)` (the test table only ever uses these
232
+ * two). Used to decide whether the persisted table needs recreating when the
233
+ * configured type changes.
234
+ *
235
+ * @param tableConfig - The resolved test table configuration.
236
+ * @returns The canonical existing json column type.
237
+ */
238
+ async function getExistingTestTableJsonType(tableConfig) {
239
+ if (!globalPool) {
240
+ throw new Error("Database not connected");
241
+ }
242
+ const request = new mssql_1.Request(globalPool);
243
+ request.input("schemaName", tableConfig.schemaName);
244
+ request.input("tableName", tableConfig.tableName);
245
+ request.input("columnName", tableConfig.resourceJsonColumn);
246
+ const result = await request.query(`
247
+ SELECT DATA_TYPE
248
+ FROM INFORMATION_SCHEMA.COLUMNS
249
+ WHERE TABLE_SCHEMA = @schemaName
250
+ AND TABLE_NAME = @tableName
251
+ AND COLUMN_NAME = @columnName
252
+ `);
253
+ return result.recordset[0]?.DATA_TYPE === "json" ? "JSON" : "NVARCHAR(MAX)";
254
+ }
218
255
  /**
219
256
  * Create the FHIR resources table if it doesn't exist.
220
257
  */
@@ -236,14 +273,25 @@ async function createTableIfNotExists() {
236
273
  const checkResult = await checkRequest.query(checkTableSql);
237
274
  const tableExists = checkResult.recordset[0]?.table_count > 0;
238
275
  if (tableExists) {
239
- return;
276
+ // The shared test table persists between runs. If its json column type no
277
+ // longer matches the configured type (e.g. switching
278
+ // MSSQL_RESOURCE_JSON_DATA_TYPE between NVARCHAR(MAX) and JSON), drop it so
279
+ // it is recreated with the right type; otherwise leave it as is.
280
+ const existingType = await getExistingTestTableJsonType(tableConfig);
281
+ if (existingType === tableConfig.resourceJsonDataType) {
282
+ return;
283
+ }
284
+ await new mssql_1.Request(globalPool).query(`DROP TABLE ${tableName}`);
240
285
  }
241
- // Create test table with test_id column for concurrent test isolation
286
+ // Create test table with test_id column for concurrent test isolation. The
287
+ // json column type is configurable so the same suite can prove conformance
288
+ // over both NVARCHAR(MAX) and the native JSON type. The type is a canonical,
289
+ // allowlisted value (see getTableConfig), so it carries no injection risk.
242
290
  const createTableSql = `
243
291
  CREATE TABLE ${tableName} (
244
292
  [${tableConfig.resourceIdColumn}] INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
245
293
  [resource_type] NVARCHAR(64) NOT NULL,
246
- [${tableConfig.resourceJsonColumn}] NVARCHAR(MAX) NOT NULL,
294
+ [${tableConfig.resourceJsonColumn}] ${tableConfig.resourceJsonDataType} NOT NULL,
247
295
  [test_id] NVARCHAR(255) NOT NULL
248
296
  )
249
297
  `;