teamplay 0.1.14 → 0.1.16
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 +112 -2
- package/connect/index.js +2 -5
- package/index.js +2 -17
- package/package.json +8 -8
- package/react/universalSub.js +4 -0
- package/react/useSub.js +13 -0
package/README.md
CHANGED
|
@@ -63,8 +63,118 @@ If you need the subscriptions to reactively update data whenever it changes (the
|
|
|
63
63
|
|
|
64
64
|
## Usage
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
### Introduction to teamplay ORM
|
|
67
|
+
|
|
68
|
+
teamplay is a powerful and easy-to-use ORM (Object-Relational Mapping) that allows you to work with your data in a natural, dot-notation style. It's designed to make data management in your app seamless and intuitive.
|
|
69
|
+
|
|
70
|
+
#### The Big Idea: Deep Signals
|
|
71
|
+
|
|
72
|
+
At the heart of teamplay is the concept of "deep signals." Think of your entire data structure as a big tree. With teamplay, you can navigate this tree using simple dot notation, just like you would access properties in a JavaScript object.
|
|
73
|
+
|
|
74
|
+
For example, to access a user's name, you might write:
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
$.users[userId].name
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
This creates a "signal" pointing to that specific piece of data. Signals are smart pointers that know how to get and set data, and they automatically update your app when the data changes.
|
|
81
|
+
|
|
82
|
+
#### Public and Private Collections
|
|
83
|
+
|
|
84
|
+
In teamplay, data is organized into collections. There are two types:
|
|
85
|
+
|
|
86
|
+
1. **Public Collections**: These are shared across all users of your app. They typically start with a lowercase letter (e.g., `users`, `posts`).
|
|
87
|
+
|
|
88
|
+
2. **Private Collections**: These are specific to each user or session. They start with an underscore or dollar sign (e.g., `_session`, `$page`).
|
|
89
|
+
|
|
90
|
+
### Basic Operations on Signals
|
|
91
|
+
|
|
92
|
+
Every signal in teamplay comes with a set of useful methods:
|
|
93
|
+
|
|
94
|
+
- `.get()`: Retrieves the current value of the signal.
|
|
95
|
+
- `.set(value)`: Updates the value of the signal.
|
|
96
|
+
- `.del()`: Deletes the value (or removes an item from an array).
|
|
97
|
+
|
|
98
|
+
Example:
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
// Get a user's name
|
|
102
|
+
const name = $.users[userId].name.get()
|
|
103
|
+
|
|
104
|
+
// Update a user's name
|
|
105
|
+
$.users[userId].name.set('Alice')
|
|
106
|
+
|
|
107
|
+
// Delete a user's profile picture
|
|
108
|
+
$.users[userId].profilePicture.del()
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### The `$()` Function: Creating Local Signals
|
|
112
|
+
|
|
113
|
+
The `$()` function is a powerful tool for creating local, reactive values:
|
|
114
|
+
|
|
115
|
+
1. Creating a simple value:
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
const $count = $(0)
|
|
119
|
+
console.log($count.get()) // Outputs: 0
|
|
120
|
+
$count.set(5)
|
|
121
|
+
console.log($count.get()) // Outputs: 5
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
2. Creating a computed value (similar to a calculated spreadsheet cell):
|
|
125
|
+
|
|
126
|
+
```javascript
|
|
127
|
+
const $firstName = $('John')
|
|
128
|
+
const $lastName = $('Doe')
|
|
129
|
+
const $fullName = $(() => $firstName.get() + ' ' + $lastName.get())
|
|
130
|
+
|
|
131
|
+
console.log($fullName.get()) // Outputs: "John Doe"
|
|
132
|
+
$firstName.set('Jane')
|
|
133
|
+
console.log($fullName.get()) // Outputs: "Jane Doe"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### The `sub()` Function: Subscribing to Data
|
|
137
|
+
|
|
138
|
+
The `sub()` function is used to subscribe to data from the server:
|
|
139
|
+
|
|
140
|
+
1. Subscribing to a single document:
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
const $user = await sub($.users[userId])
|
|
144
|
+
console.log($user.name.get())
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
2. Subscribing to a query (multiple documents):
|
|
148
|
+
|
|
149
|
+
```javascript
|
|
150
|
+
const $activeUsers = await sub($.users, { status: 'active' })
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
#### Working with Query Signals
|
|
154
|
+
|
|
155
|
+
Query signals are special. They behave like a collection signal, but they're also iterable:
|
|
156
|
+
|
|
157
|
+
```javascript
|
|
158
|
+
// Iterate over active users
|
|
159
|
+
for (const $user of $activeUsers) {
|
|
160
|
+
console.log($user.name.get())
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Or use array methods
|
|
164
|
+
const names = $activeUsers.map($user => $user.name.get())
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Each `$user` in the loop is a scoped signal for that specific user document.
|
|
168
|
+
|
|
169
|
+
### Reactivity: Keeping Your App in Sync
|
|
170
|
+
|
|
171
|
+
teamplay's reactivity system ensures that whenever data changes, any part of your app using that data updates automatically. This happens behind the scenes, so you don't have to manually track and update data dependencies.
|
|
172
|
+
|
|
173
|
+
For example, if you're displaying a user's name in your app and that name changes in the database, teamplay will automatically update your app's UI to reflect the new name.
|
|
174
|
+
|
|
175
|
+
This reactivity works for both public and private collections, local signals created with `$()`, and subscribed data from `sub()`.
|
|
176
|
+
|
|
177
|
+
By using these tools and concepts, you can build powerful, real-time applications with ease using teamplay!
|
|
68
178
|
|
|
69
179
|
## Examples
|
|
70
180
|
|
package/connect/index.js
CHANGED
|
@@ -2,11 +2,8 @@ import Socket from '@teamplay/channel'
|
|
|
2
2
|
import Connection from './sharedbConnection.cjs'
|
|
3
3
|
import { connection, setConnection } from '../orm/connection.js'
|
|
4
4
|
|
|
5
|
-
export default function connect ({
|
|
6
|
-
baseUrl,
|
|
7
|
-
...options
|
|
8
|
-
} = {}) {
|
|
5
|
+
export default function connect (options) {
|
|
9
6
|
if (connection) return
|
|
10
|
-
const socket = new Socket(
|
|
7
|
+
const socket = new Socket(options)
|
|
11
8
|
setConnection(new Connection(socket))
|
|
12
9
|
}
|
package/index.js
CHANGED
|
@@ -13,7 +13,8 @@ export { default as signal } from './orm/getSignal.js'
|
|
|
13
13
|
export { GLOBAL_ROOT_ID } from './orm/Root.js'
|
|
14
14
|
export const $ = _getRootSignal({ rootId: GLOBAL_ROOT_ID, rootFunction: universal$ })
|
|
15
15
|
export default $
|
|
16
|
-
export { default as sub } from './
|
|
16
|
+
export { default as sub } from './orm/sub.js'
|
|
17
|
+
export { default as useSub } from './react/useSub.js'
|
|
17
18
|
export { default as observer } from './react/observer.js'
|
|
18
19
|
export { connection, setConnection, getConnection, fetchOnly, setFetchOnly, publicOnly, setPublicOnly } from './orm/connection.js'
|
|
19
20
|
export { GUID_PATTERN, hasMany, hasOne, hasManyFlags, belongsTo, pickFormFields } from '@teamplay/schema'
|
|
@@ -25,19 +26,3 @@ export function getRootSignal (options) {
|
|
|
25
26
|
...options
|
|
26
27
|
})
|
|
27
28
|
}
|
|
28
|
-
|
|
29
|
-
// the following are react-specific hook alternatives to $() and sub() functions.
|
|
30
|
-
// In future we might want to expose them, but at the current time they are not needed
|
|
31
|
-
// and instead just the regular $() and sub() functions are used since they are universal
|
|
32
|
-
//
|
|
33
|
-
// export function use$ (value) {
|
|
34
|
-
// // TODO: maybe replace all non-letter/digit characters with underscores
|
|
35
|
-
// const id = useId() // eslint-disable-line react-hooks/rules-of-hooks
|
|
36
|
-
// return $(value, id)
|
|
37
|
-
// }
|
|
38
|
-
|
|
39
|
-
// export function useSub (...args) {
|
|
40
|
-
// const promiseOrSignal = sub(...args)
|
|
41
|
-
// if (promiseOrSignal.then) throw promiseOrSignal
|
|
42
|
-
// return promiseOrSignal
|
|
43
|
-
// }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "teamplay",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"description": "Full-stack signals ORM with multiplayer",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -23,12 +23,12 @@
|
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@nx-js/observer-util": "^4.1.3",
|
|
26
|
-
"@teamplay/backend": "^0.1.
|
|
27
|
-
"@teamplay/cache": "^0.1.
|
|
28
|
-
"@teamplay/channel": "^0.1.
|
|
29
|
-
"@teamplay/debug": "^0.1.
|
|
30
|
-
"@teamplay/schema": "^0.1.
|
|
31
|
-
"@teamplay/utils": "^0.1.
|
|
26
|
+
"@teamplay/backend": "^0.1.16",
|
|
27
|
+
"@teamplay/cache": "^0.1.16",
|
|
28
|
+
"@teamplay/channel": "^0.1.16",
|
|
29
|
+
"@teamplay/debug": "^0.1.16",
|
|
30
|
+
"@teamplay/schema": "^0.1.16",
|
|
31
|
+
"@teamplay/utils": "^0.1.16",
|
|
32
32
|
"diff-match-patch": "^1.0.5",
|
|
33
33
|
"events": "^3.3.0",
|
|
34
34
|
"json0-ot-diff": "^1.1.2",
|
|
@@ -63,5 +63,5 @@
|
|
|
63
63
|
]
|
|
64
64
|
},
|
|
65
65
|
"license": "MIT",
|
|
66
|
-
"gitHead": "
|
|
66
|
+
"gitHead": "f6a404f330ec3b88d498fa08fbcdc65cb80e8bb4"
|
|
67
67
|
}
|
package/react/universalSub.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// NOTE: this is not used currently since using an explicit useSub()
|
|
2
|
+
// hook is easier to understand in a React context.
|
|
3
|
+
// Having the same sub() function working with either await or without it
|
|
4
|
+
// is confusing. It's better to have a separate function for the hook.
|
|
1
5
|
import { useRef } from 'react'
|
|
2
6
|
import sub from '../orm/sub.js'
|
|
3
7
|
import executionContextTracker from './executionContextTracker.js'
|
package/react/useSub.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useRef } from 'react'
|
|
2
|
+
import sub from '../orm/sub.js'
|
|
3
|
+
|
|
4
|
+
// version of sub() which works as a react hook and throws promise for Suspense
|
|
5
|
+
export default function useSub (...args) {
|
|
6
|
+
const promiseOrSignal = sub(...args)
|
|
7
|
+
// 1. if it's a promise, throw it so that Suspense can catch it and wait for subscription to finish
|
|
8
|
+
if (promiseOrSignal.then) throw promiseOrSignal
|
|
9
|
+
// 2. if it's a signal, we save it into ref to make sure it's not garbage collected while component exists
|
|
10
|
+
const $signalRef = useRef() // eslint-disable-line react-hooks/rules-of-hooks
|
|
11
|
+
if ($signalRef.current !== promiseOrSignal) $signalRef.current = promiseOrSignal
|
|
12
|
+
return promiseOrSignal
|
|
13
|
+
}
|