Skip to content

Commit f7c36a7

Browse files
committed
feat: initial commit
0 parents  commit f7c36a7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+7008
-0
lines changed

.github/workflows/publish.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: Publish to npm
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
8+
permissions:
9+
id-token: write # Required for OIDC trusted publishing
10+
contents: write # Required to push version bumps and tags
11+
12+
jobs:
13+
publish:
14+
runs-on: ubuntu-latest
15+
environment: production
16+
17+
steps:
18+
- name: Checkout code
19+
uses: actions/checkout@v4
20+
with:
21+
fetch-depth: 0
22+
23+
- name: Setup Node.js
24+
uses: actions/setup-node@v4
25+
with:
26+
node-version: '20'
27+
registry-url: 'https://registry.npmjs.org'
28+
cache: 'npm'
29+
30+
- name: Update npm to latest
31+
run: npm install -g npm@latest
32+
33+
- name: Install dependencies
34+
run: npm ci
35+
36+
- name: Run tests
37+
run: npm test
38+
39+
- name: Build
40+
run: npm run build
41+
42+
- name: Publish to npm
43+
run: npm publish
44+
# No NODE_AUTH_TOKEN needed! OIDC handles authentication automatically
45+

.github/workflows/test.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: Test
2+
3+
on:
4+
pull_request:
5+
branches: [main]
6+
push:
7+
branches: [main]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout code
15+
uses: actions/checkout@v4
16+
17+
- name: Setup Node.js
18+
uses: actions/setup-node@v4
19+
with:
20+
node-version: '20'
21+
cache: 'npm'
22+
23+
- name: Install dependencies
24+
run: npm ci
25+
26+
- name: Run tests
27+
run: npm test
28+
29+
- name: Build
30+
run: npm run build
31+
32+
- name: Run coverage
33+
run: npm run test:coverage
34+

.gitignore

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Dependencies
2+
node_modules/
3+
.pnp
4+
.pnp.js
5+
6+
# Testing
7+
coverage/
8+
9+
# Production
10+
dist/
11+
build/
12+
13+
# Misc
14+
.DS_Store
15+
*.log
16+
npm-debug.log*
17+
yarn-debug.log*
18+
yarn-error.log*
19+
20+
# IDE
21+
.vscode/
22+
.idea/
23+
*.swp
24+
*.swo
25+
*~
26+
27+
# Environment
28+
.env
29+
.env.local
30+
.env.development.local
31+
.env.test.local
32+
.env.production.local

.npmignore

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Source files
2+
src/
3+
4+
# Tests
5+
*.spec.ts
6+
*.test.ts
7+
vitest.ts
8+
9+
# Config files
10+
tsconfig.json
11+
.gitignore
12+
13+
# Coverage
14+
coverage/
15+
16+
# Development
17+
node_modules/
18+
.DS_Store
19+
*.log
20+
21+
# IDE
22+
.vscode/
23+
.idea/
24+
*.swp
25+
*.swo
26+

LICENSE

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
ISC License
2+
3+
Copyright (c) 2025, RegiByte
4+
5+
Permission to use, copy, modify, and/or distribute this software for any
6+
purpose with or without fee is hereby granted, provided that the above
7+
copyright notice and this permission notice appear in all copies.
8+
9+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16+

README.md

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
# 🧶 Braided React
2+
3+
> **React integration for [Braided](https://github.com/RegiByte/braided) - Bridge your stateful systems to React without giving up lifecycle control.**
4+
5+
React observes your system. React doesn't own it.
6+
7+
## Why Braided React?
8+
9+
Modern React apps often need to manage complex, long-lived resources that don't fit neatly into the react component lifecycle:
10+
11+
- **WebSockets & Real-time Feeds** (Chat, Multiplayer Games)
12+
- **Audio/Video Contexts** (WebRTC, Music Apps)
13+
- **Complex API Clients** (Authentication, Retries, Caching)
14+
- **Game Loops & Simulations**
15+
16+
Managing these inside `useEffect` often leads to "dependency hell," double-initialization in StrictMode, and race conditions.
17+
18+
**Braided React** solves this by letting you define your system *outside* React, and then bridging it *into* React as a fully-typed dependency injection layer.
19+
20+
## Features
21+
22+
- 🔌 **Dependency Injection:** Inject complex resources into any component.
23+
- 🛡️ **Lifecycle Safety:** Resources survive remounts and StrictMode.
24+
- 🎯 **Type Safety:** Fully inferred types from your system config to your hooks.
25+
- 🧩 **Observer Pattern:** React components observe the system; they don't drive it.
26+
-**Low-Cost Abstraction:** Just a thin wrapper around React Context.
27+
28+
## Installation
29+
30+
```bash
31+
npm install braided-react braided
32+
```
33+
34+
Peer dependencies: `react >= 18.0.0` and `braided >= 0.0.4`
35+
36+
## Quick Start
37+
38+
### 1. Define Your System (outside React)
39+
40+
```typescript
41+
// system.ts
42+
import { defineResource } from 'braided'
43+
import { createSystemHooks } from 'braided-react'
44+
45+
// A simple logger resource
46+
const logger = defineResource({
47+
start: () => ({
48+
log: (msg: string) => console.log(`[Log]: ${msg}`)
49+
})
50+
})
51+
52+
// An API client resource that depends on logger
53+
const apiClient = defineResource({
54+
dependencies: ['logger'],
55+
start: ({ logger }) => ({
56+
fetchUser: async (id: string) => {
57+
logger.log(`Fetching user: ${id}`)
58+
return fetch(`/api/users/${id}`).then(r => r.json())
59+
}
60+
})
61+
})
62+
63+
export const systemConfig = { logger, apiClient }
64+
65+
// Create your typed hooks!
66+
export const { SystemBridge, useResource } = createSystemHooks<typeof systemConfig>()
67+
```
68+
69+
### 2. Initialize & Bridge
70+
71+
```typescript
72+
// main.tsx
73+
import { startSystem } from 'braided'
74+
import { SystemBridge, systemConfig } from './system'
75+
76+
// Start the system *before* React mounts
77+
const { system } = await startSystem(systemConfig)
78+
79+
ReactDOM.createRoot(document.getElementById('root')!).render(
80+
<SystemBridge system={system}>
81+
<App />
82+
</SystemBridge>
83+
)
84+
```
85+
86+
### 3. Use in Components
87+
88+
```typescript
89+
// App.tsx
90+
import { useResource } from './system'
91+
92+
function UserProfile({ id }) {
93+
// Fully typed! TypeScript knows 'apiClient' has a fetchUser method.
94+
const api = useResource('apiClient')
95+
96+
const handleLoad = async () => {
97+
const user = await api.fetchUser(id)
98+
console.log(user)
99+
}
100+
101+
return <button onClick={handleLoad}>Load User</button>
102+
}
103+
```
104+
105+
---
106+
107+
## Examples
108+
109+
### Basic Example
110+
[`examples/basic/`](./examples/basic/)
111+
112+
Pre-started system with counter and logger. Shows the recommended pattern.
113+
114+
```bash
115+
cd examples/basic
116+
npm install && npm run dev
117+
```
118+
119+
### Singleton Manager Example
120+
[`examples/singleton-manager/`](./examples/singleton-manager/)
121+
122+
Role-based systems (host vs player) with lazy initialization.
123+
124+
```bash
125+
cd examples/singleton-manager
126+
npm install && npm run dev
127+
```
128+
129+
### Lazy Start Example
130+
[`examples/lazy-start/`](./examples/lazy-start/)
131+
132+
System starts on mount with loading states and async resources.
133+
134+
```bash
135+
cd examples/lazy-start
136+
npm install && npm run dev
137+
```
138+
139+
## Reactivity & State Management
140+
141+
**Important:** `braided-react` is a Dependency Injection (DI) library, **not** a state management library.
142+
143+
When you call `useResource('counter')`, you get the *instance* of the counter. If properties on that instance change, your component **will not re-render** automatically.
144+
145+
### Recommended Pattern: Zustand Integration
146+
147+
To make your UI reactive, we recommend using a state manager like [Zustand](https://github.com/pmndrs/zustand) alongside your Braided resources.
148+
149+
1. **Resource:** Holds the *business logic* and *connections* (e.g., WebSocket).
150+
2. **Store:** Holds the *reactive state* (e.g., current messages).
151+
3. **Component:** Subscribes to the store and calls methods on the resource.
152+
153+
```typescript
154+
// 1. Define Store
155+
const useChatStore = create((set) => ({
156+
messages: [],
157+
addMessage: (msg) => set((state) => ({ messages: [...state.messages, msg] }))
158+
}))
159+
160+
// 2. Define Resource (connects to store)
161+
const chatClient = defineResource({
162+
start: () => {
163+
const socket = new WebSocket('wss://api.chat.com')
164+
socket.onmessage = (event) => {
165+
// Update the store directly!
166+
useChatStore.getState().addMessage(event.data)
167+
}
168+
return {
169+
sendMessage: (msg) => socket.send(msg)
170+
}
171+
}
172+
})
173+
174+
// 3. Component
175+
function ChatRoom() {
176+
const chat = useResource('chatClient') // Get the imperative resource
177+
const messages = useChatStore((state) => state.messages) // Get the reactive state
178+
179+
return (
180+
<div>
181+
{messages.map(m => <div key={m}>{m}</div>)}
182+
<button onClick={() => chat.sendMessage('Hello!')}>Send</button>
183+
</div>
184+
)
185+
}
186+
```
187+
188+
This separation of concerns (imperative logic vs. reactive state) is extremely powerful for scaling complex apps.
189+
190+
## Advanced Usage
191+
192+
### Lazy Initialization
193+
194+
If you don't want to block your app startup, use `createSystemManager` and `LazySystemBridge`.
195+
196+
```typescript
197+
import { createSystemManager, LazySystemBridge } from 'braided-react'
198+
199+
// Create a manager singleton
200+
const apiManager = createSystemManager(apiConfig)
201+
202+
function App() {
203+
return (
204+
<LazySystemBridge
205+
manager={apiManager}
206+
SystemBridge={SystemBridge}
207+
fallback={<div>Connecting to API...</div>}
208+
>
209+
<Dashboard />
210+
</LazySystemBridge>
211+
)
212+
}
213+
```
214+
215+
### Lifecycle Ownership
216+
217+
- **Pre-started (Recommended):** `startSystem()` before `root.render()`. System lives until page reload.
218+
- **Manager:** `createSystemManager()`. System starts on first demand, persists across remounts.
219+
- **Lazy Bridge:** `LazySystemBridge` with `onUnmount`. System starts on mount, halts on unmount (optional).
220+
221+
## License
222+
223+
ISC

0 commit comments

Comments
 (0)