• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

project-slippi / slippi-js / 20257242476

16 Dec 2025 05:10AM UTC coverage: 80.865% (+0.05%) from 80.813%
20257242476

push

github

web-flow
refactor: prefer undefined over null (#158)

* find and replace null with undefined

* compare against null non type check for checking existence

* simplify undefined declarations

* update tests

* allow Aerials to still have nullable iasa frame data

* make string types more restrictive

* revert back to null in metadata type

* make aerial frame data undefined

* remove unnecessary undefined in optional types

* fix _write() method prototype

* refer T[] over new Array<T>

* revert some typing changes

* revert

* revert type change

* simplify

* remove some comments

690 of 921 branches covered (74.92%)

Branch coverage included in aggregate %.

70 of 90 new or added lines in 21 files covered. (77.78%)

1 existing line in 1 file now uncovered.

1854 of 2225 relevant lines covered (83.33%)

125903.11 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.71
/src/node/utils/slpFileWriter.ts
1
import { format } from "date-fns";
12✔
2
import path from "path";
12✔
3
import type { WritableOptions } from "stream";
4
import { Writable } from "stream";
12✔
5

6
import { Command } from "../../common/types";
12✔
7
import type { SlpRawEventPayload, SlpStreamSettings } from "../../common/utils/slpStream";
8
import { SlpStream, SlpStreamEvent } from "../../common/utils/slpStream";
12✔
9
import { SlpFile } from "./slpFile";
12✔
10

11
/**
12
 * The default function to use for generating new SLP files.
13
 */
14
function getNewFilePath(folder: string, date: Date): string {
15
  return path.join(folder, `Game_${format(date, "yyyyMMdd")}T${format(date, "HHmmss")}.slp`);
3✔
16
}
17

18
export type SlpFileWriterOptions = Partial<SlpStreamSettings> & {
19
  outputFiles: boolean;
20
  folderPath: string;
21
  consoleNickname: string;
22
  newFilename: (folder: string, startTime: Date) => string;
23
};
24

25
const defaultSettings: SlpFileWriterOptions = {
12✔
26
  outputFiles: true,
27
  folderPath: ".",
28
  consoleNickname: "unknown",
29
  newFilename: getNewFilePath,
30
};
31

32
export enum SlpFileWriterEvent {
12✔
33
  NEW_FILE = "new-file",
12✔
34
  FILE_COMPLETE = "file-complete",
12✔
35
}
36

37
/**
38
 * SlpFileWriter lets us not only emit events as an SlpStream but also
39
 * writes the data that is being passed in to an SLP file. Use this if
40
 * you want to process Slippi data in real time but also want to be able
41
 * to write out the data to an SLP file.
42
 *
43
 * @export
44
 * @class SlpFileWriter
45
 * @extends {Writable}
46
 */
47
export class SlpFileWriter extends Writable {
12✔
48
  private currentFile?: SlpFile;
49
  private options: SlpFileWriterOptions;
50
  private processor: SlpStream;
51

52
  /**
53
   * Creates an instance of SlpFileWriter.
54
   */
55
  public constructor(options?: Partial<SlpFileWriterOptions>, opts?: WritableOptions) {
56
    super(opts);
3✔
57
    this.options = Object.assign({}, defaultSettings, options);
3✔
58
    this.processor = new SlpStream(options);
3✔
59
    this._setupListeners();
3✔
60
  }
61

62
  /**
63
   * Access the underlying SlpStream processor for event listening
64
   */
65
  public getProcessor(): SlpStream {
66
    return this.processor;
×
67
  }
68

69
  // Implement _write to handle incoming data
70
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
71
  public override _write(chunk: Buffer, _encoding: string, callback: (error?: Error | null) => void): void {
72
    try {
336,372✔
73
      this.processor.process(new Uint8Array(chunk));
336,372✔
74
      callback();
336,372✔
75
    } catch (err) {
76
      callback(err instanceof Error ? err : new Error(String(err)));
×
77
    }
78
  }
79

80
  private _writePayload(payload: Uint8Array): void {
81
    // Write data to the current file
82
    if (this.currentFile) {
336,372✔
83
      // Convert Uint8Array to Buffer for Node.js fs operations
84
      const buffer = Buffer.from(payload);
336,372✔
85
      this.currentFile.write(buffer);
336,372✔
86
    }
87
  }
88

89
  private _setupListeners(): void {
90
    this.processor.on(SlpStreamEvent.RAW, (data: SlpRawEventPayload) => {
3✔
91
      const { command, payload } = data;
336,372✔
92
      switch (command) {
336,372✔
93
        case Command.MESSAGE_SIZES:
94
          // Create the new game first before writing the payload
95
          this._handleNewGame();
3✔
96
          this._writePayload(payload);
3✔
97
          break;
3✔
98
        case Command.GAME_END:
99
          // Write payload first before ending the game
100
          this._writePayload(payload);
3✔
101
          this._handleEndGame();
3✔
102
          break;
3✔
103
        default:
104
          this._writePayload(payload);
336,366✔
105
          break;
336,366✔
106
      }
107
    });
108
  }
109

110
  /**
111
   * Return the name of the SLP file currently being written or undefined if
112
   * no file is being written to currently.
113
   *
114
   * @returns {(string | undefined)}
115
   * @memberof SlpFileWriter
116
   */
117
  public getCurrentFilename(): string | undefined {
118
    if (this.currentFile != null) {
3✔
119
      return path.resolve(this.currentFile.path());
3✔
120
    }
NEW
121
    return undefined;
×
122
  }
123

124
  /**
125
   * Ends the current file being written to.
126
   *
127
   * @returns {(string | undefined)}
128
   * @memberof SlpFileWriter
129
   */
130
  public endCurrentFile(): void {
131
    this._handleEndGame();
×
132
  }
133

134
  /**
135
   * Updates the settings to be the desired ones passed in.
136
   *
137
   * @param {Partial<SlpFileWriterOptions>} settings
138
   * @memberof SlpFileWriter
139
   */
140
  public updateSettings(settings: Partial<SlpFileWriterOptions>): void {
141
    this.options = Object.assign({}, this.options, settings);
×
142
  }
143

144
  private _handleNewGame(): void {
145
    // Only create a new file if we're outputting files
146
    if (this.options.outputFiles) {
3✔
147
      const filePath = this.options.newFilename(this.options.folderPath, new Date());
3✔
148
      // Pass the processor to SlpFile so it can listen to events
149
      this.currentFile = new SlpFile(filePath, this.processor);
3✔
150
      // console.log(`Creating new file at: ${filePath}`);
151
      this.emit(SlpFileWriterEvent.NEW_FILE, filePath);
3✔
152
    }
153
  }
154

155
  private _handleEndGame(): void {
156
    // End the stream
157
    if (this.currentFile) {
3✔
158
      const filePath = this.currentFile.path();
3✔
159

160
      // Set the console nickname
161
      this.currentFile.setMetadata({
3✔
162
        consoleNickname: this.options.consoleNickname,
163
      });
164

165
      // Wait for the file to actually finish writing before emitting FILE_COMPLETE
166
      this.currentFile.once("finish", () => {
3✔
167
        this.emit(SlpFileWriterEvent.FILE_COMPLETE, filePath);
3✔
168
      });
169

170
      this.currentFile.end();
3✔
171

172
      // Clear current file
173
      this.currentFile = undefined;
3✔
174
    }
175
  }
176
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc