@medplum/mock 0.9.0 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,6 +8,38 @@ For example:
8
8
  - `GET fhir/R4/Practitioner/123` returns Dr. Alice Smith
9
9
  - `GET fhir/R4/Organization/123` returns Test Organization
10
10
 
11
+ ## Installation
12
+
13
+ Add as a dependency:
14
+
15
+ ```bash
16
+ npm install @medplum/mock
17
+ ```
18
+
19
+ Note the following peer dependencies:
20
+ - [@medplum/core](https://www.npmjs.com/package/@medplum/core)
21
+ - [@medplum/fhirpath](https://www.npmjs.com/package/@medplum/fhirpath)
22
+
23
+ ## Usage
24
+
25
+ Create a new mock client:
26
+
27
+ ```ts
28
+ const client = new MockClient();
29
+ ```
30
+
31
+ Read a `Patient` resource:
32
+
33
+ ```ts
34
+ const patient = await client.readResource('Patient', '123');
35
+ ```
36
+
37
+ `MockClient` is API compatible with `MedplumClient` in [@medplum/core](https://www.npmjs.com/package/@medplum/core). Please refer to `MedplumClient` for full documentation on client capabilities.
38
+
39
+ ## About Medplum
40
+
41
+ Medplum is a healthcare platform that helps you quickly develop high-quality compliant applications. Medplum includes a FHIR server, React component library, and developer app.
42
+
11
43
  ## License
12
44
 
13
45
  Apache 2.0. Copyright © Medplum 2022
package/dist/cjs/index.js CHANGED
@@ -444,6 +444,7 @@
444
444
  },
445
445
  ],
446
446
  specimen: [core.createReference(HomerSimpsonSpecimen)],
447
+ authoredOn: '2020-01-01T12:00:00Z',
447
448
  };
448
449
  const BartSimpson = {
449
450
  resourceType: 'Patient',
@@ -6216,6 +6217,136 @@
6216
6217
  }
6217
6218
  ]
6218
6219
  }
6220
+ },
6221
+ {
6222
+ resourceType: "StructureDefinition",
6223
+ id: "Bot",
6224
+ name: "Bot",
6225
+ description: "Bot account for automated actions.",
6226
+ snapshot: {
6227
+ element: [
6228
+ {
6229
+ id: "Bot.id",
6230
+ path: "Bot.id",
6231
+ definition: "The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.",
6232
+ min: 0,
6233
+ max: "1",
6234
+ type: [
6235
+ {
6236
+ code: "string"
6237
+ }
6238
+ ]
6239
+ },
6240
+ {
6241
+ id: "Bot.meta",
6242
+ path: "Bot.meta",
6243
+ definition: "The metadata about the resource. This is content that is maintained by the infrastructure. Changes to the content might not always be associated with version changes to the resource.",
6244
+ min: 0,
6245
+ max: "1",
6246
+ type: [
6247
+ {
6248
+ code: "Meta"
6249
+ }
6250
+ ]
6251
+ },
6252
+ {
6253
+ id: "Bot.implicitRules",
6254
+ path: "Bot.implicitRules",
6255
+ definition: "A reference to a set of rules that were followed when the resource was constructed, and which must be understood when processing the content. Often, this is a reference to an implementation guide that defines the special rules along with other profiles etc.",
6256
+ min: 0,
6257
+ max: "1",
6258
+ type: [
6259
+ {
6260
+ code: "uri"
6261
+ }
6262
+ ]
6263
+ },
6264
+ {
6265
+ id: "Bot.language",
6266
+ path: "Bot.language",
6267
+ definition: "The base language in which the resource is written.",
6268
+ min: 0,
6269
+ max: "1",
6270
+ type: [
6271
+ {
6272
+ code: "code"
6273
+ }
6274
+ ]
6275
+ },
6276
+ {
6277
+ id: "Bot.name",
6278
+ path: "Bot.name",
6279
+ definition: "A name associated with the Bot.",
6280
+ min: 0,
6281
+ max: "1",
6282
+ type: [
6283
+ {
6284
+ code: "string"
6285
+ }
6286
+ ]
6287
+ },
6288
+ {
6289
+ id: "Bot.description",
6290
+ path: "Bot.description",
6291
+ definition: "A summary, characterization or explanation of the Bot.",
6292
+ min: 0,
6293
+ max: "1",
6294
+ type: [
6295
+ {
6296
+ code: "string"
6297
+ }
6298
+ ]
6299
+ },
6300
+ {
6301
+ id: "Bot.runtimeVersion",
6302
+ path: "Bot.runtimeVersion",
6303
+ definition: "The identifier of the bot runtime environment (i.e., vmcontext, awslambda, etc).",
6304
+ min: 0,
6305
+ max: "1",
6306
+ type: [
6307
+ {
6308
+ code: "string"
6309
+ }
6310
+ ]
6311
+ },
6312
+ {
6313
+ id: "Bot.photo",
6314
+ path: "Bot.photo",
6315
+ definition: "Image of the bot.",
6316
+ min: 0,
6317
+ max: "1",
6318
+ type: [
6319
+ {
6320
+ code: "Attachment"
6321
+ }
6322
+ ]
6323
+ },
6324
+ {
6325
+ id: "Bot.code",
6326
+ path: "Bot.code",
6327
+ definition: "Bot logic script.",
6328
+ min: 0,
6329
+ max: "1",
6330
+ type: [
6331
+ {
6332
+ code: "string"
6333
+ }
6334
+ ]
6335
+ },
6336
+ {
6337
+ id: "Bot.runAsUser",
6338
+ path: "Bot.runAsUser",
6339
+ definition: "Optional flag to indicate that the bot should be run as the user.",
6340
+ min: 0,
6341
+ max: "1",
6342
+ type: [
6343
+ {
6344
+ code: "boolean"
6345
+ }
6346
+ ]
6347
+ }
6348
+ ]
6349
+ }
6219
6350
  }
6220
6351
  ];
6221
6352
 
@@ -6311,6 +6442,14 @@
6311
6442
  expression: 'ServiceRequest.subject',
6312
6443
  target: ['Group', 'Device', 'Patient', 'Location'],
6313
6444
  },
6445
+ {
6446
+ resourceType: 'SearchParameter',
6447
+ id: 'ServiceRequest-authored',
6448
+ code: 'authored',
6449
+ base: ['ServiceRequest'],
6450
+ type: 'date',
6451
+ expression: 'ServiceRequest.authoredOn',
6452
+ },
6314
6453
  {
6315
6454
  resourceType: 'SearchParameter',
6316
6455
  id: 'Observation-value-quantity',
@@ -6348,7 +6487,7 @@
6348
6487
  },
6349
6488
  };
6350
6489
 
6351
- /*! *****************************************************************************
6490
+ /******************************************************************************
6352
6491
  Copyright (c) Microsoft Corporation.
6353
6492
 
6354
6493
  Permission to use, copy, modify, and/or distribute this software for any
@@ -6376,7 +6515,7 @@
6376
6515
  return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6377
6516
  }
6378
6517
 
6379
- /*! *****************************************************************************
6518
+ /******************************************************************************
6380
6519
  Copyright (c) Microsoft Corporation.
6381
6520
 
6382
6521
  Permission to use, copy, modify, and/or distribute this software for any
@@ -6968,6 +7107,23 @@
6968
7107
  eval(context) {
6969
7108
  return this.impl(ensureArray(context), ...this.args);
6970
7109
  }
7110
+ }
7111
+ class IndexerAtom {
7112
+ constructor(left, expr) {
7113
+ this.left = left;
7114
+ this.expr = expr;
7115
+ }
7116
+ eval(context) {
7117
+ const index = this.expr.eval(context);
7118
+ if (typeof index !== 'number') {
7119
+ throw new Error(`Invalid indexer expression: should return integer}`);
7120
+ }
7121
+ const leftResult = this.left.eval(context);
7122
+ if (!(index in leftResult)) {
7123
+ return [];
7124
+ }
7125
+ return leftResult[index];
7126
+ }
6971
7127
  }
6972
7128
 
6973
7129
  function parseDateString(str) {
@@ -7230,7 +7386,7 @@
7230
7386
  * See: http://hl7.org/fhirpath/#selectprojection-expression-collection
7231
7387
  */
7232
7388
  function select(input, criteria) {
7233
- return ensureArray(criteria.eval(input));
7389
+ return ensureArray(input.map((e) => criteria.eval(e)).flat());
7234
7390
  }
7235
7391
  /**
7236
7392
  * A version of select that will repeat the projection and add it to the output
@@ -8685,7 +8841,7 @@
8685
8841
  prefix(tokenType, precedence, builder) {
8686
8842
  return this.registerPrefix(tokenType, {
8687
8843
  parse(parser, token) {
8688
- const right = parser.parse(precedence);
8844
+ const right = parser.consumeAndParse(precedence);
8689
8845
  return builder(token, right);
8690
8846
  },
8691
8847
  });
@@ -8693,7 +8849,7 @@
8693
8849
  infixLeft(tokenType, precedence, builder) {
8694
8850
  return this.registerInfix(tokenType, {
8695
8851
  parse(parser, left, token) {
8696
- const right = parser.parse(precedence);
8852
+ const right = parser.consumeAndParse(precedence);
8697
8853
  return builder(left, token, right);
8698
8854
  },
8699
8855
  precedence,
@@ -8722,7 +8878,7 @@
8722
8878
  __classPrivateFieldGet(this, _Parser_instances, "m", _Parser_consume).call(this);
8723
8879
  return true;
8724
8880
  }
8725
- parse(precedence = 100 /* MaximumPrecedence */) {
8881
+ consumeAndParse(precedence = 100 /* MaximumPrecedence */) {
8726
8882
  const token = __classPrivateFieldGet(this, _Parser_instances, "m", _Parser_consume).call(this);
8727
8883
  const prefix = __classPrivateFieldGet(this, _Parser_prefixParselets, "f")[token.id];
8728
8884
  if (!prefix) {
@@ -8757,13 +8913,23 @@
8757
8913
  };
8758
8914
  const PARENTHESES_PARSELET = {
8759
8915
  parse(parser) {
8760
- const expr = parser.parse();
8916
+ const expr = parser.consumeAndParse();
8761
8917
  if (!parser.match(')')) {
8762
8918
  throw new Error('Parse error: expected `)`');
8763
8919
  }
8764
8920
  return expr;
8765
8921
  },
8766
8922
  };
8923
+ const INDEXER_PARSELET = {
8924
+ parse(parser, left) {
8925
+ const expr = parser.consumeAndParse();
8926
+ if (!parser.match(']')) {
8927
+ throw new Error('Parse error: expected `]`');
8928
+ }
8929
+ return new IndexerAtom(left, expr);
8930
+ },
8931
+ precedence: 2 /* Indexer */,
8932
+ };
8767
8933
  const FUNCTION_CALL_PARSELET = {
8768
8934
  parse(parser, left) {
8769
8935
  if (!(left instanceof SymbolAtom)) {
@@ -8774,7 +8940,7 @@
8774
8940
  }
8775
8941
  const args = [];
8776
8942
  while (!parser.match(')')) {
8777
- args.push(parser.parse());
8943
+ args.push(parser.consumeAndParse());
8778
8944
  parser.match(',');
8779
8945
  }
8780
8946
  return new FunctionAtom(left.name, args, functions[left.name]);
@@ -8819,6 +8985,7 @@
8819
8985
  })
8820
8986
  .registerPrefix('{}', { parse: () => new EmptySetAtom() })
8821
8987
  .registerPrefix('(', PARENTHESES_PARSELET)
8988
+ .registerInfix('[', INDEXER_PARSELET)
8822
8989
  .registerInfix('(', FUNCTION_CALL_PARSELET)
8823
8990
  .prefix('+', 3 /* UnaryAdd */, (_, right) => new UnaryOperatorAtom(right, (x) => x))
8824
8991
  .prefix('-', 3 /* UnarySubtract */, (_, right) => new ArithemticOperatorAtom(right, right, (_, y) => -y))
@@ -8871,7 +9038,7 @@
8871
9038
  */
8872
9039
  function parseFhirPath(input) {
8873
9040
  try {
8874
- return new FhirPathAtom(input, parserBuilder.construct(input).parse());
9041
+ return new FhirPathAtom(input, parserBuilder.construct(input).consumeAndParse());
8875
9042
  }
8876
9043
  catch (error) {
8877
9044
  throw new Error(`FhirPathError on "${input}": ${error}`);
@@ -8968,7 +9135,7 @@
8968
9135
  }
8969
9136
  if (searchRequest.filters) {
8970
9137
  for (const filter of searchRequest.filters) {
8971
- if (!matchesSearchFilter(resource, searchRequest, filter)) {
9138
+ if (!matchesSearchFilter(resource, filter)) {
8972
9139
  return false;
8973
9140
  }
8974
9141
  }
@@ -8981,7 +9148,7 @@
8981
9148
  * @param filter One of the filters of a subscription criteria.
8982
9149
  * @returns True if the resource satisfies the search filter.
8983
9150
  */
8984
- function matchesSearchFilter(resource, searchRequest, filter) {
9151
+ function matchesSearchFilter(resource, filter) {
8985
9152
  const expression = filter.code.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
8986
9153
  const values = evalFhirPath(expression, resource);
8987
9154
  const result = values.some((value) => JSON.stringify(value).toLowerCase().includes(filter.value.toLowerCase()));
@@ -8994,19 +9161,15 @@
8994
9161
  baseUrl: 'https://example.com/',
8995
9162
  clientId: 'my-client-id',
8996
9163
  fetch: (url, options) => {
8997
- const method = options.method;
9164
+ const method = options.method || 'GET';
8998
9165
  const path = url.replace('https://example.com/', '');
8999
9166
  if (clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.debug) {
9000
9167
  console.log('MockClient', method, path);
9001
9168
  }
9002
- const result = mockHandler(method, path, options);
9003
- if ((clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.debug) && !result) {
9169
+ const response = mockHandler(method, path, options);
9170
+ if ((clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.debug) && !response) {
9004
9171
  console.log('MockClient: not found', method, path);
9005
9172
  }
9006
- const response = Object.assign({ request: {
9007
- url,
9008
- options,
9009
- } }, result);
9010
9173
  if (clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.debug) {
9011
9174
  console.log('MockClient', JSON.stringify(response, null, 2));
9012
9175
  }
@@ -9039,7 +9202,7 @@
9039
9202
  }
9040
9203
  return super.getActiveLogin();
9041
9204
  }
9042
- createBinary(data, filename, contentType) {
9205
+ createBinary(_data, filename, contentType) {
9043
9206
  if (filename.endsWith('.exe')) {
9044
9207
  return Promise.reject(core.badRequest('Invalid file type'));
9045
9208
  }
@@ -9065,7 +9228,7 @@
9065
9228
  return null;
9066
9229
  }
9067
9230
  }
9068
- function mockAdminHandler(method, path) {
9231
+ function mockAdminHandler(_method, path) {
9069
9232
  if (path.startsWith('admin/projects/123')) {
9070
9233
  return {
9071
9234
  project: { id: '123', name: 'Project 123' },
@@ -9095,9 +9258,14 @@
9095
9258
  if (path.startsWith('auth/resetpassword')) {
9096
9259
  return mockResetPasswordHandler(method, path, options);
9097
9260
  }
9261
+ if (path.startsWith('auth/me')) {
9262
+ return {
9263
+ profile: { reference: 'Practitioner/123' },
9264
+ };
9265
+ }
9098
9266
  return null;
9099
9267
  }
9100
- function mockChangePasswordHandler(method, path, options) {
9268
+ function mockChangePasswordHandler(_method, _path, options) {
9101
9269
  const { body } = options;
9102
9270
  const { oldPassword } = JSON.parse(body);
9103
9271
  if (oldPassword === 'orange') {
@@ -9117,7 +9285,7 @@
9117
9285
  };
9118
9286
  }
9119
9287
  }
9120
- function mockLoginHandler(method, path, options) {
9288
+ function mockLoginHandler(_method, _path, options) {
9121
9289
  const { body } = options;
9122
9290
  const { password } = JSON.parse(body);
9123
9291
  if (password === 'password') {
@@ -9139,7 +9307,7 @@
9139
9307
  };
9140
9308
  }
9141
9309
  }
9142
- function mockSetPasswordHandler(method, path, options) {
9310
+ function mockSetPasswordHandler(_method, _path, options) {
9143
9311
  const { body } = options;
9144
9312
  const { password } = JSON.parse(body);
9145
9313
  if (password === 'orange') {
@@ -9159,7 +9327,7 @@
9159
9327
  };
9160
9328
  }
9161
9329
  }
9162
- function mockRegisterHandler(method, path, options) {
9330
+ function mockRegisterHandler(_method, _path, options) {
9163
9331
  const { body } = options;
9164
9332
  const { email, password } = JSON.parse(body);
9165
9333
  if (email === 'george@example.com' && password === 'password') {
@@ -9178,7 +9346,7 @@
9178
9346
  };
9179
9347
  }
9180
9348
  }
9181
- function mockResetPasswordHandler(method, path, options) {
9349
+ function mockResetPasswordHandler(_method, _path, options) {
9182
9350
  const { body } = options;
9183
9351
  const { email } = JSON.parse(body);
9184
9352
  if (email === 'admin@example.com') {
@@ -9269,7 +9437,7 @@
9269
9437
  return mockRepo.createResource(JSON.parse(options.body));
9270
9438
  }
9271
9439
  }
9272
- function mockFhirBatchHandler(method, path, options) {
9440
+ function mockFhirBatchHandler(_method, _path, options) {
9273
9441
  var _a;
9274
9442
  const { body } = options;
9275
9443
  const request = JSON.parse(body);