api-ape 2.0.0 → 2.2.2
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 +203 -124
- package/client/README.md +37 -30
- package/client/browser.js +10 -8
- package/client/connectSocket.js +662 -381
- package/client/index.js +171 -0
- package/client/transports/streaming.js +240 -0
- package/dist/ape.js +2 -699
- package/dist/ape.js.map +7 -0
- package/dist/api-ape.min.js +2 -0
- package/dist/api-ape.min.js.map +7 -0
- package/index.d.ts +71 -18
- package/package.json +50 -15
- package/server/README.md +99 -13
- package/server/lib/broadcast.js +25 -8
- package/server/lib/bun.js +122 -0
- package/server/lib/longPolling.js +226 -0
- package/server/lib/main.js +381 -38
- package/server/lib/wiring.js +19 -12
- package/server/lib/ws/adapters/bun.js +225 -0
- package/server/lib/ws/adapters/deno.js +186 -0
- package/server/lib/ws/frames.js +217 -0
- package/server/lib/ws/index.js +15 -0
- package/server/lib/ws/server.js +109 -0
- package/server/lib/ws/socket.js +222 -0
- package/server/lib/wsProvider.js +135 -0
- package/server/security/origin.js +16 -4
- package/server/socket/receive.js +14 -1
- package/server/socket/send.js +6 -6
- package/server/utils/deepRequire.js +25 -10
- package/server/utils/parseUserAgent.js +286 -0
- package/example/Bun/README.md +0 -74
- package/example/Bun/api/message.ts +0 -11
- package/example/Bun/index.html +0 -76
- package/example/Bun/package.json +0 -9
- package/example/Bun/server.ts +0 -59
- package/example/Bun/styles.css +0 -128
- package/example/ExpressJs/README.md +0 -95
- package/example/ExpressJs/api/message.js +0 -11
- package/example/ExpressJs/backend.js +0 -39
- package/example/ExpressJs/index.html +0 -88
- package/example/ExpressJs/package-lock.json +0 -834
- package/example/ExpressJs/package.json +0 -10
- package/example/ExpressJs/styles.css +0 -128
- package/example/NextJs/.dockerignore +0 -29
- package/example/NextJs/Dockerfile +0 -52
- package/example/NextJs/Dockerfile.dev +0 -27
- package/example/NextJs/README.md +0 -113
- package/example/NextJs/ape/client.js +0 -66
- package/example/NextJs/ape/embed.js +0 -12
- package/example/NextJs/ape/index.js +0 -23
- package/example/NextJs/ape/logic/chat.js +0 -62
- package/example/NextJs/ape/onConnect.js +0 -69
- package/example/NextJs/ape/onDisconnect.js +0 -13
- package/example/NextJs/ape/onError.js +0 -9
- package/example/NextJs/ape/onReceive.js +0 -15
- package/example/NextJs/ape/onSend.js +0 -15
- package/example/NextJs/api/message.js +0 -44
- package/example/NextJs/docker-compose.yml +0 -22
- package/example/NextJs/next-env.d.ts +0 -5
- package/example/NextJs/next.config.js +0 -8
- package/example/NextJs/package-lock.json +0 -6400
- package/example/NextJs/package.json +0 -24
- package/example/NextJs/pages/Info.tsx +0 -153
- package/example/NextJs/pages/_app.tsx +0 -6
- package/example/NextJs/pages/index.tsx +0 -275
- package/example/NextJs/public/favicon.ico +0 -0
- package/example/NextJs/public/vercel.svg +0 -4
- package/example/NextJs/server.js +0 -36
- package/example/NextJs/styles/Chat.module.css +0 -448
- package/example/NextJs/styles/Home.module.css +0 -129
- package/example/NextJs/styles/globals.css +0 -26
- package/example/NextJs/tsconfig.json +0 -20
- package/example/README.md +0 -117
- package/example/Vite/README.md +0 -68
- package/example/Vite/ape/client.ts +0 -66
- package/example/Vite/ape/onConnect.ts +0 -52
- package/example/Vite/api/message.ts +0 -57
- package/example/Vite/index.html +0 -16
- package/example/Vite/package.json +0 -19
- package/example/Vite/server.ts +0 -62
- package/example/Vite/src/App.vue +0 -170
- package/example/Vite/src/components/Info.vue +0 -352
- package/example/Vite/src/main.ts +0 -5
- package/example/Vite/src/style.css +0 -200
- package/example/Vite/src/vite-env.d.ts +0 -7
- package/example/Vite/vite.config.ts +0 -20
- package/todo.md +0 -85
- package/utils/jss.test.js +0 -261
- package/utils/messageHash.test.js +0 -56
|
@@ -1,352 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* Info component - explains how api-ape works
|
|
4
|
-
*/
|
|
5
|
-
</script>
|
|
6
|
-
|
|
7
|
-
<template>
|
|
8
|
-
<div class="code-section">
|
|
9
|
-
<h3 class="code-title">📚 How api-ape Works</h3>
|
|
10
|
-
|
|
11
|
-
<div class="grid-container">
|
|
12
|
-
<div class="grid-layout">
|
|
13
|
-
<!-- Top Left: Key Concepts -->
|
|
14
|
-
<div>
|
|
15
|
-
<h4 class="section-heading">💡 Key Concepts</h4>
|
|
16
|
-
<pre class="code">• Proxy Pattern: api.message() → api/message.js
|
|
17
|
-
• Auto-wiring: Drop files in api/ folder, they become endpoints
|
|
18
|
-
• Promises: All calls return Promises automatically
|
|
19
|
-
• Broadcasts: Use this.broadcast() or this.broadcastOthers()
|
|
20
|
-
• Context: this.broadcast, this.hostId, this.req available in controllers
|
|
21
|
-
• Auto-reconnect: Client reconnects automatically on disconnect</pre>
|
|
22
|
-
</div>
|
|
23
|
-
|
|
24
|
-
<!-- Top Right: Data Flow -->
|
|
25
|
-
<div>
|
|
26
|
-
<h4 class="section-heading-large">🔄 Data Flow</h4>
|
|
27
|
-
<div class="data-flow-grid">
|
|
28
|
-
<!-- Column Headers -->
|
|
29
|
-
<div class="column-header-client">Client</div>
|
|
30
|
-
<div class="grid-cell"></div>
|
|
31
|
-
<div class="column-header-server">Server</div>
|
|
32
|
-
|
|
33
|
-
<!-- Step 1: Client sends -->
|
|
34
|
-
<div class="client-box-span3">api.message(data)</div>
|
|
35
|
-
<div class="arrow-container-row2">
|
|
36
|
-
<div class="arrow-line-send"></div>
|
|
37
|
-
<span class="arrow-label-blue">Send</span>
|
|
38
|
-
<div class="arrow-head-right"></div>
|
|
39
|
-
</div>
|
|
40
|
-
<div class="empty-grid-cell"></div>
|
|
41
|
-
|
|
42
|
-
<!-- Step 2: Server receives -->
|
|
43
|
-
<div class="empty-grid-cell-row3"></div>
|
|
44
|
-
<div class="arrow-container-row3">
|
|
45
|
-
<div class="arrow-head-left"></div>
|
|
46
|
-
<span class="arrow-label-green">Return</span>
|
|
47
|
-
<div class="arrow-line-return"></div>
|
|
48
|
-
</div>
|
|
49
|
-
<div class="server-box-span2">api/message.js</div>
|
|
50
|
-
|
|
51
|
-
<!-- Step 3: Server broadcasts -->
|
|
52
|
-
<div class="empty-grid-cell-row4"></div>
|
|
53
|
-
<div class="arrow-container-row4">
|
|
54
|
-
<div class="arrow-line-broadcast"></div>
|
|
55
|
-
<span class="arrow-label-green">Broadcast</span>
|
|
56
|
-
<div class="arrow-head-right"></div>
|
|
57
|
-
</div>
|
|
58
|
-
<div class="server-box-span3">Broadcast to others</div>
|
|
59
|
-
|
|
60
|
-
<!-- Step 4: Other clients receive -->
|
|
61
|
-
<div class="client-box-single">Other clients</div>
|
|
62
|
-
<div class="arrow-container-row5">
|
|
63
|
-
<div class="arrow-head-left-blue"></div>
|
|
64
|
-
<span class="arrow-label-blue">Broadcast</span>
|
|
65
|
-
<div class="arrow-line-broadcast-return"></div>
|
|
66
|
-
</div>
|
|
67
|
-
<div class="empty-grid-cell-row5"></div>
|
|
68
|
-
</div>
|
|
69
|
-
</div>
|
|
70
|
-
|
|
71
|
-
<!-- Bottom Left: Client-Side -->
|
|
72
|
-
<div>
|
|
73
|
-
<h4 class="section-heading">🔵 Client-Side (Browser)</h4>
|
|
74
|
-
<pre class="code">// 1. Initialize api-ape client
|
|
75
|
-
const client = await getApeClient()
|
|
76
|
-
const api = client.sender // Proxy object
|
|
77
|
-
|
|
78
|
-
// 2. Call server function - property name = file path
|
|
79
|
-
// api.message() → calls api/message.js
|
|
80
|
-
api.message({ user: 'Alice', text: 'Hello!' })
|
|
81
|
-
.then(response => {
|
|
82
|
-
// Server returned: { ok: true, message: {...} }
|
|
83
|
-
console.log('Response:', response)
|
|
84
|
-
})
|
|
85
|
-
.catch(err => {
|
|
86
|
-
// Server threw an error
|
|
87
|
-
console.error('Error:', err)
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
// 3. Listen for server broadcasts
|
|
91
|
-
client.setOnReciver('message', ({ data }) => {
|
|
92
|
-
// Server called: this.broadcastOthers('message', data)
|
|
93
|
-
// This fires for ALL clients except the sender
|
|
94
|
-
console.log('Broadcast received:', data.message)
|
|
95
|
-
})</pre>
|
|
96
|
-
</div>
|
|
97
|
-
|
|
98
|
-
<!-- Bottom Right: Server-Side -->
|
|
99
|
-
<div>
|
|
100
|
-
<h4 class="section-heading">🟢 Server-Side (api/message.js)</h4>
|
|
101
|
-
<pre class="code">// File: api/message.js
|
|
102
|
-
// This function is called when client does: api.message(data)
|
|
103
|
-
|
|
104
|
-
module.exports = function message(data) {
|
|
105
|
-
const { user, text } = data
|
|
106
|
-
|
|
107
|
-
// Validate input
|
|
108
|
-
if (!user || !text) {
|
|
109
|
-
throw new Error('Missing user or text')
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const msg = {
|
|
113
|
-
user,
|
|
114
|
-
text,
|
|
115
|
-
time: new Date().toISOString()
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Broadcast to ALL OTHER clients (not the sender)
|
|
119
|
-
this.broadcastOthers('message', { message: msg })
|
|
120
|
-
|
|
121
|
-
// Return response to sender (fulfills Promise)
|
|
122
|
-
return { ok: true, message: msg }
|
|
123
|
-
}</pre>
|
|
124
|
-
</div>
|
|
125
|
-
</div>
|
|
126
|
-
</div>
|
|
127
|
-
</div>
|
|
128
|
-
</template>
|
|
129
|
-
|
|
130
|
-
<style scoped>
|
|
131
|
-
.code-section {
|
|
132
|
-
margin-top: 2rem;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
.code-title {
|
|
136
|
-
margin-bottom: 0.5rem;
|
|
137
|
-
color: #0f0;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
.grid-container {
|
|
141
|
-
max-width: 1200px;
|
|
142
|
-
margin: 1.5rem auto 0;
|
|
143
|
-
padding: 0 1rem;
|
|
144
|
-
width: 100%;
|
|
145
|
-
box-sizing: border-box;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
.grid-layout {
|
|
149
|
-
display: grid;
|
|
150
|
-
grid-template-columns: 1fr;
|
|
151
|
-
gap: 2rem;
|
|
152
|
-
width: 100%;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
@media (min-width: 768px) {
|
|
156
|
-
.grid-layout {
|
|
157
|
-
grid-template-columns: 1fr 1fr;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
.section-heading {
|
|
162
|
-
margin-bottom: 0.5rem;
|
|
163
|
-
font-size: 0.9rem;
|
|
164
|
-
font-weight: bold;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
.section-heading-large {
|
|
168
|
-
margin-bottom: 1rem;
|
|
169
|
-
font-size: 0.9rem;
|
|
170
|
-
font-weight: bold;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
.code {
|
|
174
|
-
background: rgba(0, 0, 0, 0.4);
|
|
175
|
-
padding: 1.5rem;
|
|
176
|
-
border-radius: 12px;
|
|
177
|
-
font-size: 0.75rem;
|
|
178
|
-
color: #0f0;
|
|
179
|
-
overflow: auto;
|
|
180
|
-
white-space: pre-wrap;
|
|
181
|
-
font-family: monospace;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
.data-flow-grid {
|
|
185
|
-
display: grid;
|
|
186
|
-
grid-template-columns: 200px 1fr 200px;
|
|
187
|
-
grid-template-rows: auto auto auto auto auto;
|
|
188
|
-
gap: 1rem;
|
|
189
|
-
align-items: stretch;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
.column-header-client {
|
|
193
|
-
font-size: 0.8rem;
|
|
194
|
-
font-weight: bold;
|
|
195
|
-
text-align: center;
|
|
196
|
-
grid-row: 1;
|
|
197
|
-
grid-column: 1;
|
|
198
|
-
color: #00d2ff;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
.column-header-server {
|
|
202
|
-
font-size: 0.8rem;
|
|
203
|
-
font-weight: bold;
|
|
204
|
-
text-align: center;
|
|
205
|
-
grid-row: 1;
|
|
206
|
-
grid-column: 3;
|
|
207
|
-
color: #00e676;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
.client-box-span3 {
|
|
211
|
-
background: linear-gradient(135deg, #3a7bd5, #00d2ff);
|
|
212
|
-
padding: 0.75rem 1rem;
|
|
213
|
-
border-radius: 8px;
|
|
214
|
-
color: #fff;
|
|
215
|
-
font-size: 0.75rem;
|
|
216
|
-
font-weight: bold;
|
|
217
|
-
box-shadow: 0 4px 12px rgba(58, 123, 213, 0.4);
|
|
218
|
-
display: flex;
|
|
219
|
-
align-items: center;
|
|
220
|
-
justify-content: center;
|
|
221
|
-
grid-column: 1;
|
|
222
|
-
grid-row: 2 / 5;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
.client-box-single {
|
|
226
|
-
background: linear-gradient(135deg, #3a7bd5, #00d2ff);
|
|
227
|
-
padding: 0.75rem 1rem;
|
|
228
|
-
border-radius: 8px;
|
|
229
|
-
color: #fff;
|
|
230
|
-
font-size: 0.75rem;
|
|
231
|
-
font-weight: bold;
|
|
232
|
-
box-shadow: 0 4px 12px rgba(58, 123, 213, 0.4);
|
|
233
|
-
display: flex;
|
|
234
|
-
align-items: center;
|
|
235
|
-
justify-content: center;
|
|
236
|
-
grid-column: 1;
|
|
237
|
-
grid-row: 5;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
.server-box-span2 {
|
|
241
|
-
background: linear-gradient(135deg, #00c851, #00e676);
|
|
242
|
-
padding: 0.75rem 1rem;
|
|
243
|
-
border-radius: 8px;
|
|
244
|
-
color: #fff;
|
|
245
|
-
font-size: 0.75rem;
|
|
246
|
-
font-weight: bold;
|
|
247
|
-
box-shadow: 0 4px 12px rgba(0, 200, 81, 0.4);
|
|
248
|
-
display: flex;
|
|
249
|
-
align-items: center;
|
|
250
|
-
justify-content: center;
|
|
251
|
-
grid-column: 3;
|
|
252
|
-
grid-row: 2 / 4;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
.server-box-span3 {
|
|
256
|
-
background: linear-gradient(135deg, #00c851, #00e676);
|
|
257
|
-
padding: 0.75rem 1rem;
|
|
258
|
-
border-radius: 8px;
|
|
259
|
-
color: #fff;
|
|
260
|
-
font-size: 0.75rem;
|
|
261
|
-
font-weight: bold;
|
|
262
|
-
box-shadow: 0 4px 12px rgba(0, 200, 81, 0.4);
|
|
263
|
-
display: flex;
|
|
264
|
-
align-items: center;
|
|
265
|
-
justify-content: center;
|
|
266
|
-
grid-column: 3;
|
|
267
|
-
grid-row: 4 / 6;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
.arrow-container-row2,
|
|
271
|
-
.arrow-container-row3,
|
|
272
|
-
.arrow-container-row4,
|
|
273
|
-
.arrow-container-row5 {
|
|
274
|
-
display: flex;
|
|
275
|
-
align-items: center;
|
|
276
|
-
justify-content: center;
|
|
277
|
-
gap: 0.5rem;
|
|
278
|
-
grid-column: 2;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
.arrow-container-row2 { grid-row: 2; }
|
|
282
|
-
.arrow-container-row3 { grid-row: 3; }
|
|
283
|
-
.arrow-container-row4 { grid-row: 4; }
|
|
284
|
-
.arrow-container-row5 { grid-row: 5; }
|
|
285
|
-
|
|
286
|
-
.arrow-line-send {
|
|
287
|
-
flex: 1;
|
|
288
|
-
height: 2px;
|
|
289
|
-
background: linear-gradient(90deg, #00d2ff, #00e676);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
.arrow-line-return {
|
|
293
|
-
flex: 1;
|
|
294
|
-
height: 2px;
|
|
295
|
-
background: linear-gradient(90deg, #00e676, transparent);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
.arrow-line-broadcast {
|
|
299
|
-
flex: 1;
|
|
300
|
-
height: 2px;
|
|
301
|
-
background: linear-gradient(90deg, transparent, #00e676);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
.arrow-line-broadcast-return {
|
|
305
|
-
flex: 1;
|
|
306
|
-
height: 2px;
|
|
307
|
-
background: linear-gradient(90deg, #00d2ff, transparent);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
.arrow-label-blue {
|
|
311
|
-
font-size: 0.7rem;
|
|
312
|
-
white-space: nowrap;
|
|
313
|
-
padding: 0 0.5rem;
|
|
314
|
-
color: #00d2ff;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
.arrow-label-green {
|
|
318
|
-
font-size: 0.7rem;
|
|
319
|
-
white-space: nowrap;
|
|
320
|
-
padding: 0 0.5rem;
|
|
321
|
-
color: #00e676;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
.arrow-head-right {
|
|
325
|
-
width: 0;
|
|
326
|
-
height: 0;
|
|
327
|
-
border-top: 4px solid transparent;
|
|
328
|
-
border-bottom: 4px solid transparent;
|
|
329
|
-
border-left: 8px solid #00e676;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
.arrow-head-left {
|
|
333
|
-
width: 0;
|
|
334
|
-
height: 0;
|
|
335
|
-
border-top: 4px solid transparent;
|
|
336
|
-
border-bottom: 4px solid transparent;
|
|
337
|
-
border-right: 8px solid #00e676;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
.arrow-head-left-blue {
|
|
341
|
-
width: 0;
|
|
342
|
-
height: 0;
|
|
343
|
-
border-top: 4px solid transparent;
|
|
344
|
-
border-bottom: 4px solid transparent;
|
|
345
|
-
border-right: 8px solid #00d2ff;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
.empty-grid-cell { grid-row: 2; grid-column: 3; }
|
|
349
|
-
.empty-grid-cell-row3 { grid-row: 3; grid-column: 1; }
|
|
350
|
-
.empty-grid-cell-row4 { grid-row: 4; grid-column: 1; }
|
|
351
|
-
.empty-grid-cell-row5 { grid-row: 5; grid-column: 3; }
|
|
352
|
-
</style>
|
package/example/Vite/src/main.ts
DELETED
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
* {
|
|
2
|
-
box-sizing: border-box;
|
|
3
|
-
margin: 0;
|
|
4
|
-
padding: 0;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
.container {
|
|
8
|
-
min-height: 100vh;
|
|
9
|
-
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
|
|
10
|
-
color: #fff;
|
|
11
|
-
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
.main {
|
|
15
|
-
max-width: 600px;
|
|
16
|
-
margin: 0 auto;
|
|
17
|
-
padding: 2rem;
|
|
18
|
-
width: 100%;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.title {
|
|
22
|
-
font-size: 2.5rem;
|
|
23
|
-
text-align: center;
|
|
24
|
-
margin-bottom: 0.5rem;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
.gradient {
|
|
28
|
-
background: linear-gradient(90deg, #00d2ff, #3a7bd5);
|
|
29
|
-
-webkit-background-clip: text;
|
|
30
|
-
-webkit-text-fill-color: transparent;
|
|
31
|
-
background-clip: text;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
.subtitle {
|
|
35
|
-
text-align: center;
|
|
36
|
-
color: #0f0;
|
|
37
|
-
margin-bottom: 2rem;
|
|
38
|
-
font-weight: bold;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
.join-form {
|
|
42
|
-
display: flex;
|
|
43
|
-
gap: 1rem;
|
|
44
|
-
justify-content: center;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
.input {
|
|
48
|
-
padding: 1rem 1.5rem;
|
|
49
|
-
font-size: 1rem;
|
|
50
|
-
border: none;
|
|
51
|
-
border-radius: 50px;
|
|
52
|
-
background: rgba(255, 255, 255, 0.1);
|
|
53
|
-
color: #fff;
|
|
54
|
-
outline: none;
|
|
55
|
-
width: 250px;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
.input::placeholder {
|
|
59
|
-
color: rgba(255, 255, 255, 0.5);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
.button {
|
|
63
|
-
padding: 1rem 2rem;
|
|
64
|
-
font-size: 1rem;
|
|
65
|
-
border: none;
|
|
66
|
-
border-radius: 50px;
|
|
67
|
-
background: linear-gradient(90deg, #00d2ff, #3a7bd5);
|
|
68
|
-
color: #fff;
|
|
69
|
-
cursor: pointer;
|
|
70
|
-
font-weight: bold;
|
|
71
|
-
transition: opacity 0.2s;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
.button:hover {
|
|
75
|
-
opacity: 0.9;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
.button:disabled {
|
|
79
|
-
opacity: 0.5;
|
|
80
|
-
cursor: not-allowed;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
.chat-container {
|
|
84
|
-
background: rgba(255, 255, 255, 0.05);
|
|
85
|
-
border-radius: 20px;
|
|
86
|
-
overflow: hidden;
|
|
87
|
-
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
.header {
|
|
91
|
-
display: flex;
|
|
92
|
-
justify-content: space-between;
|
|
93
|
-
padding: 1rem 1.5rem;
|
|
94
|
-
background: rgba(255, 255, 255, 0.1);
|
|
95
|
-
font-weight: bold;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
.user-count {
|
|
99
|
-
font-size: 0.85rem;
|
|
100
|
-
color: #0f0;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
.messages {
|
|
104
|
-
height: 350px;
|
|
105
|
-
overflow-y: auto;
|
|
106
|
-
padding: 1rem;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
.messages::-webkit-scrollbar {
|
|
110
|
-
width: 6px;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
.messages::-webkit-scrollbar-track {
|
|
114
|
-
background: rgba(255, 255, 255, 0.05);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
.messages::-webkit-scrollbar-thumb {
|
|
118
|
-
background: rgba(255, 255, 255, 0.2);
|
|
119
|
-
border-radius: 3px;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
.empty-state {
|
|
123
|
-
text-align: center;
|
|
124
|
-
color: #666;
|
|
125
|
-
margin-top: 140px;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
.message {
|
|
129
|
-
display: flex;
|
|
130
|
-
flex-direction: column;
|
|
131
|
-
gap: 0.25rem;
|
|
132
|
-
padding: 0.75rem 1rem;
|
|
133
|
-
margin-bottom: 0.5rem;
|
|
134
|
-
background: rgba(255, 255, 255, 0.05);
|
|
135
|
-
border-radius: 12px;
|
|
136
|
-
border-left: 3px solid #3a7bd5;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
.my-message {
|
|
140
|
-
background: rgba(0, 210, 255, 0.15);
|
|
141
|
-
border-left-color: #00d2ff;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
.message .username {
|
|
145
|
-
color: #00d2ff;
|
|
146
|
-
font-size: 0.85rem;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
.message .time {
|
|
150
|
-
color: #666;
|
|
151
|
-
font-size: 0.7rem;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
.input-form {
|
|
155
|
-
display: flex;
|
|
156
|
-
gap: 0.5rem;
|
|
157
|
-
padding: 1rem;
|
|
158
|
-
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
.message-input {
|
|
162
|
-
flex: 1;
|
|
163
|
-
padding: 0.75rem 1rem;
|
|
164
|
-
font-size: 1rem;
|
|
165
|
-
border: none;
|
|
166
|
-
border-radius: 50px;
|
|
167
|
-
background: rgba(255, 255, 255, 0.1);
|
|
168
|
-
color: #fff;
|
|
169
|
-
outline: none;
|
|
170
|
-
transition: background 0.2s;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
.message-input::placeholder {
|
|
174
|
-
color: rgba(255, 255, 255, 0.5);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
.message-input:focus {
|
|
178
|
-
background: rgba(255, 255, 255, 0.15);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
.send-button {
|
|
182
|
-
padding: 0.75rem 1.5rem;
|
|
183
|
-
font-size: 1rem;
|
|
184
|
-
border: none;
|
|
185
|
-
border-radius: 50px;
|
|
186
|
-
background: #3a7bd5;
|
|
187
|
-
color: #fff;
|
|
188
|
-
cursor: pointer;
|
|
189
|
-
font-weight: bold;
|
|
190
|
-
transition: opacity 0.2s;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
.send-button:hover {
|
|
194
|
-
opacity: 0.9;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
.send-button:disabled {
|
|
198
|
-
opacity: 0.5;
|
|
199
|
-
cursor: not-allowed;
|
|
200
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from 'vite'
|
|
2
|
-
import vue from '@vitejs/plugin-vue'
|
|
3
|
-
|
|
4
|
-
export default defineConfig({
|
|
5
|
-
plugins: [vue()],
|
|
6
|
-
server: {
|
|
7
|
-
port: 5173,
|
|
8
|
-
proxy: {
|
|
9
|
-
'/api': {
|
|
10
|
-
target: 'http://localhost:3000',
|
|
11
|
-
ws: true,
|
|
12
|
-
changeOrigin: true
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
build: {
|
|
17
|
-
outDir: 'dist',
|
|
18
|
-
emptyOutDir: true
|
|
19
|
-
}
|
|
20
|
-
})
|
package/todo.md
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
# Query System - TODO
|
|
2
|
-
|
|
3
|
-
A fluent, chainable query API for api-ape with filtering, field selection, and real-time subscriptions.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Client API Design
|
|
8
|
-
```js
|
|
9
|
-
const petsReq = ape.pets.list(data, (item) => shouldSubscribe)
|
|
10
|
-
petsReq.filter`name ! ${undefined} AND bio.checkin > ${10} OR bio.type = ${"admin"}`
|
|
11
|
-
petsReq.fields("*", {bio: ["email"]})
|
|
12
|
-
petsReq.then(pets => ...).catch(err => ...)
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## Phase 1: Filter Parser
|
|
18
|
-
- [ ] Create `client/utils/filter.js` - tagged template parser
|
|
19
|
-
- [ ] Parse operators: `=`, `!` (not equal), `?` (exists), `>`, `<`
|
|
20
|
-
- [ ] Parse `AND` / `OR` with correct precedence
|
|
21
|
-
- [ ] Support nested paths (e.g., `bio.checkin`, `owner.type`)
|
|
22
|
-
- [ ] Return serializable filter object to send over wire
|
|
23
|
-
|
|
24
|
-
## Phase 2: Fields Selection
|
|
25
|
-
- [ ] Parse `fields("*", {relation: ["field1", "field2"]})`
|
|
26
|
-
- [ ] `"*"` = all root-level fields
|
|
27
|
-
- [ ] Object syntax for nested/related field selection
|
|
28
|
-
- [ ] Max depth limit (≤ 4)
|
|
29
|
-
|
|
30
|
-
## Phase 3: Query Builder (Client)
|
|
31
|
-
- [ ] Refactor `client/index.js` → proper query builder
|
|
32
|
-
- [ ] Chainable `.filter()` and `.fields()` methods
|
|
33
|
-
- [ ] Integrate with existing `connectSocket.js` sender
|
|
34
|
-
- [ ] Send query payload: `{ type, data, filter, fields, subscribe }`
|
|
35
|
-
|
|
36
|
-
## Phase 4: Subscription Callback
|
|
37
|
-
- [ ] Second arg: `(item) => boolean` subscription predicate
|
|
38
|
-
- [ ] Register listener via `setOnReciver` for matching type
|
|
39
|
-
- [ ] Filter incoming broadcasts client-side with predicate
|
|
40
|
-
|
|
41
|
-
## Phase 5: Server Query Execution
|
|
42
|
-
- [ ] Parse incoming `filter` object
|
|
43
|
-
- [ ] Apply filter to controller response data
|
|
44
|
-
- [ ] Respect `fields` selection (project/limit returned data)
|
|
45
|
-
- [ ] Track subscribed queryIds for broadcast targeting
|
|
46
|
-
|
|
47
|
-
## Phase 6: Skip/Have Optimization (Future)
|
|
48
|
-
- [ ] `.dont([id1, id2])` - skip items client already has
|
|
49
|
-
- [ ] Server tracks live refs per client
|
|
50
|
-
- [ ] Only send missing/changed items
|
|
51
|
-
|
|
52
|
-
---
|
|
53
|
-
|
|
54
|
-
## Open Questions
|
|
55
|
-
|
|
56
|
-
### Query Method Semantics
|
|
57
|
-
- `ape.pets.list(null, (pet) => { pet.owner == me })`:
|
|
58
|
-
- [ ] What does the first argument (`null`) represent? Initial filter data? Query options?
|
|
59
|
-
- [ ] What does the callback do? Live subscription filter? Server-side predicate?
|
|
60
|
-
|
|
61
|
-
### Filter Syntax
|
|
62
|
-
- `.filter\`name ! ${undefined} AND owner.checkin > ${10} OR owner.type = ${"nice"}\``:
|
|
63
|
-
- [ ] Confirm operators: `!` = not equal, `?` = exists, `>` `<` `=` = comparison
|
|
64
|
-
- [ ] Need additional operators? `>=`, `<=`, `LIKE`, `IN`, `BETWEEN`?
|
|
65
|
-
- [ ] Should `AND`/`OR` respect standard boolean precedence?
|
|
66
|
-
- [ ] Should `${10}DaysAgo` be a special relative time syntax?
|
|
67
|
-
|
|
68
|
-
### Fields Selection
|
|
69
|
-
- `.fields("*", {toys: ["type"]})`:
|
|
70
|
-
- [ ] `"*"` = all fields at root level?
|
|
71
|
-
- [ ] Object syntax `{relation: [...]}` for nested/related fields?
|
|
72
|
-
- [ ] Max depth limit? (code comment says ≤ 4)
|
|
73
|
-
- [ ] Support exclusion? (e.g., `"-password"`)
|
|
74
|
-
|
|
75
|
-
### Real-time Subscriptions
|
|
76
|
-
- [ ] How should live updates work? Push on any change to matching items?
|
|
77
|
-
- [ ] Can the subscription filter differ from the initial query filter?
|
|
78
|
-
- [ ] What events trigger updates? Create, Update, Delete?
|
|
79
|
-
- [ ] Broadcast filtering - server-side or client-side predicate eval?
|
|
80
|
-
|
|
81
|
-
### Additional Features (In Scope?)
|
|
82
|
-
- [ ] Pagination: `.limit()`, `.offset()`, `.cursor()`
|
|
83
|
-
- [ ] Sorting: `.orderBy()`
|
|
84
|
-
- [ ] Aggregations: `.count()`, `.sum()`
|
|
85
|
-
- [ ] Skip/Have optimization: `.dont([ids])` for delta updates
|