Skip to content

Commit

Permalink
feat: file batch
Browse files Browse the repository at this point in the history
- initial code for file batch'ing creation
- simple unit test passes with a single MSH segment

#16

[ci skip]
  • Loading branch information
Bugs5382 committed Dec 14, 2023
1 parent cb8d30d commit 30a32ef
Show file tree
Hide file tree
Showing 6 changed files with 265 additions and 11 deletions.
45 changes: 44 additions & 1 deletion __tests__/hl7.build.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {randomUUID} from "crypto";
import {EmptyNode} from "../src/builder/modules/emptyNode";
import {Batch, createHL7Date, isBatch, isFile, Message} from "../src";
import {FileBatch, Batch, Message, createHL7Date, isBatch, isFile} from "../src";
import { Node } from "../src/builder/interface/node";

describe('node hl7 client - builder tests', () => {
Expand Down Expand Up @@ -423,6 +423,49 @@ describe('node hl7 client - builder tests', () => {

})

describe('basic file basics', () => {

let file: FileBatch

beforeEach(async () => {
file = new FileBatch()
file.start()
})

test('...initial build', async() => {

file.set('FHS.7', '20081231')

let message: Message

message = new Message({
messageHeader: {
msh_9_1: "ADT",
msh_9_2: "A01",
msh_10: 'CONTROL_ID'
}
})
message.set('MSH.7', '20081231')

// add this message to the file
file.add(message)

// end making a file batch
file.end()

// unit checking
expect(file.toString()).toBe(
[
"FHS|^~\\&||||||20081231",
"MSH|^~\\&|||||20081231||ADT^A01^ADT_A01|CONTROL_ID||2.7",
"FTS|1"
].join("\r"))
})

})

describe('complex file generation', () => {})

describe('non standard tests', () => {

test('...returns true if specified path exists', () => {
Expand Down
2 changes: 1 addition & 1 deletion src/builder/batch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export class Batch extends RootBase {
* @since 1.0.0
*/
start (): void {
this.set('BSH.7', createHL7Date(new Date()))
this.set('BHS.7', createHL7Date(new Date()))
}

/** @internal */
Expand Down
198 changes: 198 additions & 0 deletions src/builder/fileBatch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import { HL7FatalError, HL7ParserError } from '../utils/exception.js'
import { ClientBuilderFileOptions, normalizedClientFileBuilderOptions } from '../utils/normalizedBuilder.js'
import { createHL7Date } from '../utils/utils'
import {Batch} from "./batch";
import { Node } from './interface/node.js'
import { Message } from './message.js'
import { RootBase } from './modules/rootBase.js'
import { Segment } from './modules/segment.js'
import { SegmentList } from './modules/segmentList.js'

/**
* File Class
* @since 1.0.0
* @extends RootBase
*/
export class FileBatch extends RootBase {
/** @internal **/
_opt: ReturnType<typeof normalizedClientFileBuilderOptions>
/** @internal */
_lines?: string[]
/** @internal */
_batchCount: number
/** @internal */
_messagesCount: number

/**
* @since 1.0.0
* @param props
*/
constructor (props?: ClientBuilderFileOptions) {
const opt = normalizedClientFileBuilderOptions(props)
super(opt)
this._opt = opt
this._batchCount = 0
this._messagesCount = 0

if (typeof opt.text !== 'undefined' && opt.parsing === true && opt.text !== '') {
this._lines = this._splitBatch(opt.text).filter(line => line.includes('MSH'))
}
}

/**
* Add a Message or a Batch to the File
* @since 1.0.0
* @param item
*/
add (item: Message | Batch): void {
// if we are adding a message to a file
if (item instanceof Message) {
// and we already added a batch segment, we need to add it to the batch segment since we can not add a batch and then a MSH segment.
// That would violate HL7 specification.
if (this._batchCount >= 1 ) {
// @todo add to first batch segment that is already in this.children
} else {
this.setDirty()
this._messagesCount = this._messagesCount + 1
this.children.push(item)
}
} else {
// if there are already messages added before a batch
if (this._messagesCount >= 1 ) {
throw new HL7ParserError(500, 'Unable to add a batch segment, since there is already messages added individually.')
}
this.setDirty()
this._batchCount = this._batchCount + 1
this.children.push(item)
}
}

/**
* End Batch
* @since 1.0.0
*/
end (): void {
const segment = this._addSegment('FTS')
segment.set('1', this._batchCount + this._messagesCount)
}

/**
* Get Messages
* @since 1.0.0
*/
messages (): Message[] {
if (typeof this._lines !== 'undefined' && typeof this._opt.newLine !== 'undefined') {
const message: Message[] = []
const re = new RegExp(`${this._opt.newLine}$`, 'g')
for (let i = 0; i < this._lines.length; i++) {
message.push(new Message({ text: this._lines[i].replace(re, '') }))
}
return message
}
throw new HL7ParserError(500, 'No messages inside batch')
}

/** @internal */
read (path: string[]): Node {
const segmentName = path.shift() as string
if (path.length === 0) {
const segments = this.children.filter(x => (x as Segment).name === segmentName) as Segment[]
if (segments.length > 0) {
return new SegmentList(this, segments) as Node
}
} else {
if (typeof segmentName === 'undefined') {
throw new HL7FatalError(500, 'segment name is not defined.')
}
const segment = this._getFirstSegment(segmentName)
if (typeof segment !== 'undefined') {
return segment.read(path)
}
}
throw new HL7FatalError(500, 'Unable to process the read function correctly.')
}

/**
* Start Batch
* @since 1.0.0
*/
start (): void {
this.set('FHS.7', createHL7Date(new Date()))
}

/** @internal */
protected writeCore (path: string[], value: string): Node {
const segmentName = path.shift() as string
if (typeof segmentName === 'undefined') {
throw new HL7FatalError(500, 'segment name is not defined.')
}
return this.writeAtIndex(path, value, 0, segmentName)
}

/** @internal */
protected createChild (text: string, _index: number): Node {
return new Segment(this, text.trim())
}

/** @internal **/
private _addSegment (path: string): Segment {
if (typeof path === 'undefined') {
throw new HL7FatalError(404, 'Missing segment path.')
}

const preparedPath = this.preparePath(path)
if (preparedPath.length !== 1) {
throw new HL7FatalError(500, `"Invalid segment ${path}."`)
}

return this.addChild(preparedPath[0]) as Segment
}

/** @internal */
private _getFirstSegment (name: string): Segment {
const children = this.children
for (let i = 0, l = children.length; i < l; i++) {
const segment = children[i] as Segment
if (segment.name === name) {
return segment
}
}
throw new HL7FatalError(500, 'Unable to process _getFirstSegment.')
}

/** @internal */
private _getSegIndexes (names: string[], data: string, list: string[] = []): string[] {
for (let i = 0; i < names.length; i++) {
const regexp = new RegExp(`(\n|\r\n|^|\r)${names[i]}\\|`, 'g'); let m
while ((m = regexp.exec(data)) != null) {
const s = m[0]
if (s.includes('\r\n')) {
m.index = m.index + 2
} else if (s.includes('\n')) {
m.index++
} else if (s.includes('\r')) {
m.index++
}
if (m.index !== null) {
list.push(m.index.toString())
}
}
}
return list
}

/** @internal */
private _splitBatch (data: string, batch: string[] = []): string[] {
const getSegIndex = this._getSegIndexes(['FHS', 'BHS', 'MSH', 'BTS', 'FTS'], data)
getSegIndex.sort((a, b) => parseInt(a) - parseInt(b))
for (let i = 0; i < getSegIndex.length; i++) {
const start = parseInt(getSegIndex[i])
let end = parseInt(getSegIndex[i + 1])
if (i + 1 === getSegIndex.length) {
end = data.length
}
batch.push(data.slice(start, end))
}
return batch
}
}
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import { HL7_2_7 } from './specification/2.7.js'
import { HL7_SPEC, HL7_SPEC_BASE } from './specification/specification.js'
import { SubComponent } from './builder/modules/subComponent.js'
import { Batch } from './builder/batch.js'
import { FileBatch } from "./builder/fileBatch.js";
import { ParserPlan } from './utils/parserPlan.js'
import { HL7Outbound } from './client/hl7Outbound.js'

export default Client
export { Client, HL7Outbound, ParserPlan, Batch, Message, Segment, SegmentList, Component, SubComponent, Field, FieldRepetition }
export { Client, HL7Outbound, ParserPlan, FileBatch, Batch, Message, Segment, SegmentList, Component, SubComponent, Field, FieldRepetition }
export { Delimiters, READY_STATE } from './utils/enum.js'
export * from './utils/utils.js'

Expand Down
18 changes: 15 additions & 3 deletions src/utils/normalizedBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,15 +144,27 @@ export function normalizedClientBatchBuilderOptions (raw?: ClientBuilderBatchOpt
export function normalizedClientFileBuilderOptions (raw?: ClientBuilderFileOptions): ClientBuilderFileOptions {
const props = { ...DEFAULT_CLIENT_BUILDER_OPTS, ...raw }

if (typeof props.fileHeader === 'undefined' && props.text !== '') {
throw new Error('fileHeader must be set if no HL7 message is being passed.')
} else if (props.text.slice(0, 3) !== 'FHS') {
if (typeof props.fileHeader === 'undefined' && typeof props.text !== 'undefined' && props.text !== '' && props.text.slice(0, 3) !== 'FHS') {
throw new Error('text must begin with the FHS segment.')
}

if ((typeof props.newLine !== 'undefined' && props.newLine === '\\r') || props.newLine === '\\n') {
throw new Error('newLine must be \r or \n')
}

if (props.text === '') {
props.text = `FHS${props.separatorField}${props.separatorComponent}${props.separatorRepetition}${props.separatorEscape}${props.separatorSubComponent}`
} else if (typeof props.text !== 'undefined') {
const plan: ParserPlan = new ParserPlan(props.text.slice(3, 8))
props.parsing = true
// check to make sure that we set the correct properties
props.newLine = props.text.includes('\r') ? '\r' : '\n'
props.separatorField = plan.separatorField
props.separatorComponent = plan.separatorComponent
props.separatorRepetition = plan.separatorRepetition
props.separatorEscape = plan.separatorEscape
props.separatorSubComponent = plan.separatorSubComponent
}

return props
}
10 changes: 5 additions & 5 deletions src/utils/parserPlan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
*/
export class ParserPlan {
/** @internal */
separatorField?: string
separatorField: string
/** @internal */
separatorComponent?: string
separatorComponent: string
/** @internal */
separatorRepetition?: string
separatorRepetition: string
/** @internal */
separatorEscape?: string
separatorEscape: string
/** @internal */
separatorSubComponent?: string
separatorSubComponent: string

/**
* @since 1.0.0
Expand Down

0 comments on commit 30a32ef

Please sign in to comment.