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.
Files changed (89) hide show
  1. package/README.md +203 -124
  2. package/client/README.md +37 -30
  3. package/client/browser.js +10 -8
  4. package/client/connectSocket.js +662 -381
  5. package/client/index.js +171 -0
  6. package/client/transports/streaming.js +240 -0
  7. package/dist/ape.js +2 -699
  8. package/dist/ape.js.map +7 -0
  9. package/dist/api-ape.min.js +2 -0
  10. package/dist/api-ape.min.js.map +7 -0
  11. package/index.d.ts +71 -18
  12. package/package.json +50 -15
  13. package/server/README.md +99 -13
  14. package/server/lib/broadcast.js +25 -8
  15. package/server/lib/bun.js +122 -0
  16. package/server/lib/longPolling.js +226 -0
  17. package/server/lib/main.js +381 -38
  18. package/server/lib/wiring.js +19 -12
  19. package/server/lib/ws/adapters/bun.js +225 -0
  20. package/server/lib/ws/adapters/deno.js +186 -0
  21. package/server/lib/ws/frames.js +217 -0
  22. package/server/lib/ws/index.js +15 -0
  23. package/server/lib/ws/server.js +109 -0
  24. package/server/lib/ws/socket.js +222 -0
  25. package/server/lib/wsProvider.js +135 -0
  26. package/server/security/origin.js +16 -4
  27. package/server/socket/receive.js +14 -1
  28. package/server/socket/send.js +6 -6
  29. package/server/utils/deepRequire.js +25 -10
  30. package/server/utils/parseUserAgent.js +286 -0
  31. package/example/Bun/README.md +0 -74
  32. package/example/Bun/api/message.ts +0 -11
  33. package/example/Bun/index.html +0 -76
  34. package/example/Bun/package.json +0 -9
  35. package/example/Bun/server.ts +0 -59
  36. package/example/Bun/styles.css +0 -128
  37. package/example/ExpressJs/README.md +0 -95
  38. package/example/ExpressJs/api/message.js +0 -11
  39. package/example/ExpressJs/backend.js +0 -39
  40. package/example/ExpressJs/index.html +0 -88
  41. package/example/ExpressJs/package-lock.json +0 -834
  42. package/example/ExpressJs/package.json +0 -10
  43. package/example/ExpressJs/styles.css +0 -128
  44. package/example/NextJs/.dockerignore +0 -29
  45. package/example/NextJs/Dockerfile +0 -52
  46. package/example/NextJs/Dockerfile.dev +0 -27
  47. package/example/NextJs/README.md +0 -113
  48. package/example/NextJs/ape/client.js +0 -66
  49. package/example/NextJs/ape/embed.js +0 -12
  50. package/example/NextJs/ape/index.js +0 -23
  51. package/example/NextJs/ape/logic/chat.js +0 -62
  52. package/example/NextJs/ape/onConnect.js +0 -69
  53. package/example/NextJs/ape/onDisconnect.js +0 -13
  54. package/example/NextJs/ape/onError.js +0 -9
  55. package/example/NextJs/ape/onReceive.js +0 -15
  56. package/example/NextJs/ape/onSend.js +0 -15
  57. package/example/NextJs/api/message.js +0 -44
  58. package/example/NextJs/docker-compose.yml +0 -22
  59. package/example/NextJs/next-env.d.ts +0 -5
  60. package/example/NextJs/next.config.js +0 -8
  61. package/example/NextJs/package-lock.json +0 -6400
  62. package/example/NextJs/package.json +0 -24
  63. package/example/NextJs/pages/Info.tsx +0 -153
  64. package/example/NextJs/pages/_app.tsx +0 -6
  65. package/example/NextJs/pages/index.tsx +0 -275
  66. package/example/NextJs/public/favicon.ico +0 -0
  67. package/example/NextJs/public/vercel.svg +0 -4
  68. package/example/NextJs/server.js +0 -36
  69. package/example/NextJs/styles/Chat.module.css +0 -448
  70. package/example/NextJs/styles/Home.module.css +0 -129
  71. package/example/NextJs/styles/globals.css +0 -26
  72. package/example/NextJs/tsconfig.json +0 -20
  73. package/example/README.md +0 -117
  74. package/example/Vite/README.md +0 -68
  75. package/example/Vite/ape/client.ts +0 -66
  76. package/example/Vite/ape/onConnect.ts +0 -52
  77. package/example/Vite/api/message.ts +0 -57
  78. package/example/Vite/index.html +0 -16
  79. package/example/Vite/package.json +0 -19
  80. package/example/Vite/server.ts +0 -62
  81. package/example/Vite/src/App.vue +0 -170
  82. package/example/Vite/src/components/Info.vue +0 -352
  83. package/example/Vite/src/main.ts +0 -5
  84. package/example/Vite/src/style.css +0 -200
  85. package/example/Vite/src/vite-env.d.ts +0 -7
  86. package/example/Vite/vite.config.ts +0 -20
  87. package/todo.md +0 -85
  88. package/utils/jss.test.js +0 -261
  89. 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>
@@ -1,5 +0,0 @@
1
- import { createApp } from 'vue'
2
- import App from './App.vue'
3
- import './style.css'
4
-
5
- createApp(App).mount('#app')
@@ -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,7 +0,0 @@
1
- /// <reference types="vite/client" />
2
-
3
- declare module '*.vue' {
4
- import type { DefineComponent } from 'vue'
5
- const component: DefineComponent<{}, {}, any>
6
- export default component
7
- }
@@ -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