Skip to content

Commit 903dc4e

Browse files
author
mochatek
committed
feat: RepeatedFieldStream (init)
0 parents  commit 903dc4e

File tree

16 files changed

+3822
-0
lines changed

16 files changed

+3822
-0
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# This workflow will build the publish the protobuf-array-stream package to NPM when a version-tagged commit is pushed
2+
3+
name: Publish protobuf-array-stream library to NPM
4+
5+
on:
6+
push:
7+
tags:
8+
- "v[0-9]+.[0-9]+.[0-9]+"
9+
10+
permissions:
11+
contents: read
12+
13+
jobs:
14+
build-and-publish:
15+
name: Build dist from source and publish the artifacts to NPM
16+
runs-on: ubuntu-latest
17+
18+
steps:
19+
- uses: actions/checkout@master
20+
- name: Setup Node.js
21+
uses: actions/setup-node@v2
22+
with:
23+
node-version: "18.x"
24+
registry-url: "https://registry.npmjs.org"
25+
- name: Install dependencies and build
26+
run: npm ci && npm run build
27+
- name: Publish package to NPM
28+
run: npm publish
29+
env:
30+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

.gitignore

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Dependencies
2+
node_modules/
3+
4+
# Build output
5+
dist/
6+
7+
# IDE and editor files
8+
.idea/
9+
.vscode/
10+
*.swp
11+
*.swo
12+
13+
# OS files
14+
.DS_Store
15+
Thumbs.db
16+
17+
# Test coverage
18+
coverage/
19+
20+
# Logs
21+
logs/
22+
*.log
23+
npm-debug.log*
24+
yarn-debug.log*
25+
yarn-error.log*
26+
27+
# Environment variables
28+
.env
29+
.env.local
30+
.env.*.local
31+
32+
# Proto files (if needed)
33+
proto/
34+
35+
# Binary files
36+
bin/

.husky/pre-commit

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
npm run format:check && npm test

.npmrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package-lock=true
2+
save-exact=true
3+
engine-strict=true

.prettierignore

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Dependencies
2+
node_modules
3+
package-lock.json
4+
5+
# Build outputs
6+
dist
7+
8+
# Environment and config
9+
.env
10+
.env.*
11+
!.env.example
12+
13+
# Logs
14+
*.log
15+
npm-debug.log*
16+
17+
# IDE and editor files
18+
.idea
19+
.vscode
20+
21+
# Git hooks
22+
.husky
23+
24+
# OS generated files
25+
.DS_Store

.prettierrc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"arrowParens": "always",
3+
"bracketSpacing": true,
4+
"printWidth": 80,
5+
"semi": true,
6+
"singleQuote": false,
7+
"tabWidth": 2,
8+
"trailingComma": "all",
9+
"useTabs": false
10+
}

LICENSE

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
MIT License
2+
3+
Copyright (c) 2024 Akash S Panickar
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6+
7+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8+
9+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

README.md

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
# protobuf-array-stream
2+
3+
[![npm version](https://img.shields.io/npm/v/protobuf-array-stream.svg)](https://www.npmjs.com/package/protobuf-array-stream)
4+
[![npm downloads](https://img.shields.io/npm/dm/protobuf-array-stream.svg)](https://www.npmjs.com/package/protobuf-array-stream)
5+
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6+
7+
Stream and decode Protocol Buffer arrays without memory issues. While other libraries need the entire message in memory, this one processes array elements directly from the source stream - making it perfect for large datasets.
8+
9+
## Why Use This?
10+
11+
- 💾 **Memory Efficient**: No need to load entire message in memory
12+
- 🌊 **Streaming**: Process array elements directly from source (file, network, etc.)
13+
- 🎯 **Specialized**: Built for messages with a repeated field (array)
14+
-**Scalable**: Works with arrays of any size, memory usage stays constant
15+
16+
## Installation
17+
18+
```bash
19+
npm install protobuf-array-stream
20+
```
21+
22+
## Usage
23+
24+
### Loading Protocol Definitions
25+
26+
First, you need to load your Protocol Buffer definitions. The library exposes these methods directly from protobufjs for convenience:
27+
28+
```typescript
29+
import RepeatedFieldStream from "protobuf-array-stream";
30+
import { createReadStream } from "fs";
31+
32+
// 1. Load from a .proto file (async)
33+
const root1 = await RepeatedFieldStream.loadRootFromFile(
34+
"path/to/messages.proto",
35+
);
36+
37+
// 2. Load from a .proto file (sync)
38+
const root2 = RepeatedFieldStream.loadRootFromFileSync(
39+
"path/to/messages.proto",
40+
);
41+
42+
// 3. Load from a proto definition string
43+
const root3 = RepeatedFieldStream.loadRootFromSource(`
44+
syntax = "proto3";
45+
46+
message NumberList {
47+
repeated int32 numbers = 1;
48+
}
49+
`);
50+
51+
// 4. Load from JSON schema
52+
const root4 = RepeatedFieldStream.loadRootFromJSON({
53+
nested: {
54+
NumberList: {
55+
fields: {
56+
numbers: {
57+
id: 1,
58+
rule: "repeated",
59+
type: "int32",
60+
},
61+
},
62+
},
63+
},
64+
});
65+
```
66+
67+
### Real-World Example
68+
69+
You're building an anti-cheat system for your massively multiplayer game. Players are generating TONS of data:
70+
71+
```protobuf
72+
syntax = "proto3";
73+
74+
message GameLog {
75+
repeated PlayerAction actions = 1;
76+
}
77+
78+
message PlayerAction {
79+
int64 timestamp = 1;
80+
string player_id = 2;
81+
string action = 3;
82+
Position position = 4;
83+
}
84+
85+
message Position {
86+
float x = 1;
87+
float y = 2;
88+
float z = 3;
89+
}
90+
```
91+
92+
Process large game session logs efficiently:
93+
94+
```typescript
95+
const sessionStream = createReadStream("game_session.bin");
96+
97+
const decodeStream = new RepeatedFieldStream({
98+
root,
99+
messageName: "GameLog",
100+
});
101+
102+
const analyzeStream = new Transform({
103+
objectMode: true,
104+
transform(action, _, callback) {
105+
const { timestamp, playerId, action: move, position } = action;
106+
107+
if (move === "shoot" && position.y > 500) {
108+
console.log(
109+
`Detected: ${playerId} at height ${position.y} on ${new Date(timestamp.toNumber())}`,
110+
);
111+
}
112+
113+
callback(null, action);
114+
},
115+
});
116+
117+
sessionStream.pipe(decodeStream).pipe(analyzeStream);
118+
```
119+
120+
### Error Handling
121+
122+
The stream emits errors in these cases:
123+
124+
- Message type not found in proto definitions
125+
- Message has no fields or multiple fields
126+
- Field is not a repeated field
127+
- Invalid protobuf encoding in input
128+
129+
```typescript
130+
stream.on("error", (error) => {
131+
console.error("Stream error:", error.message);
132+
});
133+
```
134+
135+
## API
136+
137+
### `new RepeatedFieldStream(options)`
138+
139+
Creates a new transform stream for processing Protocol Buffer repeated fields.
140+
141+
#### Options
142+
143+
- `root`: Root namespace from protobufjs containing message definitions
144+
- `messageName`: Name of the message type to decode
145+
146+
### Static Methods
147+
148+
#### `loadRootFromFile(path: string): Promise<Root>`
149+
150+
Asynchronously loads Protocol Buffer definitions from a .proto file.
151+
152+
- `path`: Path to the .proto file
153+
- Returns: Promise that resolves to the root namespace
154+
155+
#### `loadRootFromFileSync(path: string): Root`
156+
157+
Synchronously loads Protocol Buffer definitions from a .proto file.
158+
159+
- `path`: Path to the .proto file
160+
- Returns: Root namespace
161+
162+
#### `loadRootFromSource(source: string): Root`
163+
164+
Loads Protocol Buffer definitions from a string containing proto definitions.
165+
166+
- `source`: String containing the proto definitions
167+
- Returns: Root namespace
168+
169+
#### `loadRootFromJSON(json: object): Root`
170+
171+
Loads Protocol Buffer definitions from a JSON object.
172+
173+
- `json`: JSON object containing the proto definitions
174+
- Returns: Root namespace
175+
176+
## Requirements
177+
178+
- Node.js >= 16.0.0
179+
- Protocol Buffer messages must contain exactly one repeated field
180+
181+
## License
182+
183+
MIT License - see the [LICENSE](https://github.com/mochatek/protobuf-array-stream/blob/main/LICENSE) file for details

0 commit comments

Comments
 (0)