plusui-native-core 0.1.101 → 0.1.103

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.
@@ -1,160 +1,76 @@
1
1
  /**
2
- * PlusUI Connect API - Custom User Channels
3
- *
4
- * This is for user-defined channels generated by `plusui connect` tool.
5
- * Built-in features (window, clipboard, etc.) use direct methods only.
6
- *
7
- * Usage:
8
- * import { createChannel, connect } from '@plusui/api';
9
- *
10
- * // Create a custom channel
11
- * const search = createChannel('search');
12
- * search.emit({ query: 'test' });
13
- * search.on((result) => { ... });
14
- *
15
- * // Or use connect directly
16
- * connect.emit('myEvent', { data: 123 });
17
- * connect.on('myEvent', (data) => { ... });
18
- *
19
- * The `plusui connect` CLI tool scans your codebase for:
20
- * - TypeScript: name.on() and name.emit()
21
- * - C++: name.on() and name.emit()
22
- * And generates typed channel objects for both frontend and backend.
2
+ * PlusUI Connect API Semantic Frontend ↔ Backend Communication
3
+ *
4
+ * ZERO CONFIG. SEMANTIC SYNTAX. ALL 5 PATTERNS.
5
+ *
6
+ * IMPORTANT: `connect` is for CUSTOM user-defined communication only!
7
+ * Built-in features (window, clipboard, app, etc.) use direct imports:
8
+ * import plusui from 'plusui';
9
+ * plusui.window.minimize();
10
+ * plusui.clipboard.setText('hello');
11
+ *
12
+ * For custom communication, use `connect.namespace.method()`:
13
+ *
14
+ * import { connect } from 'plusui';
15
+ *
16
+ * // Request/Response await a call to the backend
17
+ * const user = await connect.user.fetch(123);
18
+ *
19
+ * // Fire & Forget one-way notification
20
+ * connect.files.upload({ file: myFile });
21
+ *
22
+ * // Event Listener listen for backend events
23
+ * connect.app.onNotify((msg) => console.log(msg));
24
+ *
25
+ * // Register a handler — backend can call frontend
26
+ * connect.ui.handlePrompt = async (data) => {
27
+ * return await Dialog.confirm(data.msg);
28
+ * };
29
+ *
30
+ * Then run `plusui connect` to generate typed bindings.
31
+ *
32
+ * Pattern detection is automatic:
33
+ * - `await connect.x.y()` → CALL (Request/Response)
34
+ * - `connect.x.onY(cb)` → EVENT (Subscribe)
35
+ * - `connect.x.handleY = fn` → HANDLER (Backend calls frontend)
36
+ * - `connect.x.y()` → FIRE (Simplex, one-way)
23
37
  */
24
38
 
25
- import { _client } from '../Connection/connect';
39
+ import { connect, _client, createFeatureConnect } from '../Connection/connect';
40
+ import type { FeatureConnect, ConnectionKind, ConnectionEnvelope } from '../Connection/connect';
26
41
 
27
42
  /**
28
- * Channel object returned by createChannel()
29
- * Mirrors the C++ Connect::Channel API
30
- */
31
- export interface Channel<T = unknown> {
32
- /**
33
- * Listen for messages on this channel
34
- * @param callback - Function called when message is received
35
- * @returns Unsubscribe function
36
- */
37
- on(callback: (data: T) => void): () => void;
38
-
39
- /**
40
- * Send a message on this channel
41
- * @param data - Data to send
42
- */
43
- emit(data: T): void;
44
-
45
- /**
46
- * Channel name
47
- */
48
- readonly name: string;
49
- }
50
-
51
- /**
52
- * Create a named channel object
53
- *
54
- * @param name - Channel name (must match backend)
55
- * @returns Channel object with .on() and .emit()
56
- *
57
- * @example
58
- * const download = createChannel<DownloadProgress>('download');
59
- * download.emit({ url: 'https://example.com/file.zip' });
60
- * download.on((progress) => console.log(progress.percent));
43
+ * connect Semantic channel proxy
44
+ *
45
+ * Usage:
46
+ * connect.namespace.method(...args)
47
+ *
48
+ * The proxy routes to the correct wire format automatically:
49
+ * connect.user.fetch(123) → _client.call('user.fetch', 123)
50
+ * connect.app.onNotify(cb) → _client.on('app.onNotify', cb)
51
+ * connect.ui.handlePrompt = fn → _client.handle('ui.handlePrompt', fn)
52
+ * connect.system.minimize() _client.fire('system.minimize', {})
53
+ *
54
+ * Works dynamically even before running `plusui connect`.
55
+ * After running `plusui connect`, you get IDE autocomplete.
61
56
  */
62
- export function createChannel<T = unknown>(name: string): Channel<T> {
63
- return {
64
- name,
65
- on: (callback: (data: T) => void): (() => void) => {
66
- return _client.on<T>(name, callback);
67
- },
68
- emit: (data: T): void => {
69
- _client.fire(name, data);
70
- }
71
- };
72
- }
57
+ export { connect };
73
58
 
74
59
  /**
75
- * Direct connect API for custom channels
76
- *
77
- * Use this for quick one-off events, or use createChannel()
78
- * for typed, reusable channel objects.
60
+ * _client Low-level connection client
61
+ *
62
+ * Direct access to the underlying client for advanced use cases.
63
+ * Prefer using `connect.namespace.method()` for normal communication.
79
64
  */
80
- export const connect = {
81
- /**
82
- * Send a message on a channel
83
- * @param name - Channel name
84
- * @param data - Data to send
85
- */
86
- emit: <T = unknown>(name: string, data: T): void => {
87
- _client.fire(name, data);
88
- },
89
-
90
- /**
91
- * Listen for messages on a channel
92
- * @param name - Channel name
93
- * @param callback - Function called when message is received
94
- * @returns Unsubscribe function
95
- */
96
- on: <T = unknown>(name: string, callback: (data: T) => void): (() => void) => {
97
- return _client.on<T>(name, callback);
98
- },
99
-
100
- /**
101
- * Make a call and wait for response
102
- * @param name - Channel name
103
- * @param data - Request data
104
- * @returns Promise with response
105
- */
106
- call: <TResponse = unknown, TRequest = unknown>(name: string, data: TRequest): Promise<TResponse> => {
107
- return _client.call<TResponse, TRequest>(name, data);
108
- },
109
-
110
- /**
111
- * Create a typed channel object
112
- * @param name - Channel name
113
- * @returns Channel object with .on() and .emit()
114
- */
115
- channel: <T = unknown>(name: string): Channel<T> => {
116
- return createChannel<T>(name);
117
- }
118
- };
65
+ export { _client };
119
66
 
120
67
  /**
121
- * Feature-scoped connect
122
- *
123
- * Creates a connect API that automatically prefixes all channel names
124
- * with a scope. Useful for organizing channels by feature.
125
- *
126
- * @param scope - Feature scope (e.g., 'auth', 'data', 'ui')
127
- * @returns Scoped connect API
128
- *
129
- * @example
130
- * const authConnect = createFeatureConnect('auth');
131
- * authConnect.emit('login', { user: 'john' }); // sends to 'auth.login'
132
- * authConnect.on('logout', () => {}); // listens to 'auth.logout'
68
+ * createFeatureConnect — Scoped connect for framework internals
69
+ *
70
+ * Creates a connect API scoped to a feature namespace.
71
+ * Used internally by PlusUI features like window, clipboard, etc.
133
72
  */
134
- export function createFeatureConnect(scope: string) {
135
- const scopedName = (name: string): string => {
136
- const prefix = `${scope}.`;
137
- if (name.startsWith(prefix)) return name;
138
- return prefix + name;
139
- };
140
-
141
- return {
142
- emit: <T = unknown>(name: string, data: T): void => {
143
- _client.fire(scopedName(name), data);
144
- },
145
-
146
- on: <T = unknown>(name: string, callback: (data: T) => void): (() => void) => {
147
- return _client.on<T>(scopedName(name), callback);
148
- },
149
-
150
- call: <TResponse = unknown, TRequest = unknown>(name: string, data: TRequest): Promise<TResponse> => {
151
- return _client.call<TResponse, TRequest>(scopedName(name), data);
152
- },
153
-
154
- channel: <T = unknown>(name: string): Channel<T> => {
155
- return createChannel<T>(scopedName(name));
156
- }
157
- };
158
- }
73
+ export { createFeatureConnect };
159
74
 
75
+ export type { FeatureConnect, ConnectionKind, ConnectionEnvelope };
160
76
  export default connect;
@@ -14,7 +14,7 @@ export { fileDrop, formatFileSize, createDropZone } from './filedrop-api';
14
14
  export { menu } from './menu-api';
15
15
  export { gpu, GPUBufferUsage, GPUTextureUsage, GPUMapMode, GPUShaderStage, GPUColorWrite } from './webgpu-api';
16
16
 
17
- export { connect, createChannel, type Channel } from './Connect_API';
17
+ export { connect } from './Connect_API';
18
18
  export { _client } from '../Connection/connect';
19
19
 
20
20
  export type { WindowSize, WindowPosition, WindowRect } from './window-api';
@@ -40,7 +40,7 @@ import { display } from './display-api';
40
40
  import { fileDrop, formatFileSize, createDropZone } from './filedrop-api';
41
41
  import { menu } from './menu-api';
42
42
  import { gpu, GPUBufferUsage, GPUTextureUsage, GPUMapMode, GPUShaderStage, GPUColorWrite } from './webgpu-api';
43
- import { connect, createChannel } from './Connect_API';
43
+ import { connect } from './Connect_API';
44
44
 
45
45
  const plusui = {
46
46
  window,
@@ -58,7 +58,6 @@ const plusui = {
58
58
  menu,
59
59
  gpu,
60
60
  connect,
61
- createChannel,
62
61
  formatFileSize,
63
62
  KeyCode,
64
63
  KeyMod,
@@ -1,66 +1,158 @@
1
- # PlusUI Connection
1
+ # PlusUI Connect
2
2
 
3
- ## Two methods. Five primitives. Same syntax.
3
+ ## Semantic syntax. Zero config. All 5 patterns.
4
4
 
5
5
  ```typescript
6
- // TypeScript
7
- myEvent.on((data) => { ... });
8
- myEvent.emit({ value: 123 });
6
+ // TypeScript — just write what you mean
7
+ const user = await connect.user.fetch(123);
8
+ connect.app.onNotify((msg) => console.log(msg));
9
+ connect.system.minimize();
9
10
  ```
10
11
 
11
12
  ```cpp
12
- // C++ (identical!)
13
- myEvent.on([&](const json& p) { ... });
14
- myEvent.emit({{"value", 123}});
13
+ // C++ identical semantic style
14
+ connect::user.handleFetch = [](const json& p) -> json {
15
+ return database.getUser(p["id"]);
16
+ };
17
+ connect::app.notify({{"msg", "Hello!"}});
15
18
  ```
16
19
 
17
20
  ## Quick Start
18
21
 
19
- ### 1. Write your code
22
+ ### 1. Write your code — anywhere
20
23
 
24
+ **Frontend (TypeScript):**
21
25
  ```typescript
22
- // frontend/src/app.ts
23
- import { download } from '../Connections/connections.gen';
26
+ import { connect } from 'plusui';
27
+
28
+ // Call the backend and get a response
29
+ const user = await connect.database.getUser(123);
24
30
 
25
- download.emit({ url: 'https://example.com/file.zip' });
26
- download.on((data) => {
27
- progressBar.value = data.progress;
31
+ // Listen for events from the backend
32
+ connect.app.onNotify((msg) => {
33
+ toast.success(msg);
28
34
  });
35
+
36
+ // Fire a one-way command
37
+ connect.system.minimize();
29
38
  ```
30
39
 
40
+ **Backend (C++):**
31
41
  ```cpp
32
- // main.cpp
42
+ #include <plusui/plusui>
33
43
  #include "Connections/connections.gen.hpp"
34
44
 
35
- initChannels(connect);
45
+ // Handle calls from the frontend
46
+ connect::database.handleGetUser = [](const json& p) -> json {
47
+ auto id = p.get<int>();
48
+ return {{"name", "Dannan"}, {"id", id}};
49
+ };
36
50
 
37
- download.on([&](const json& p) {
38
- auto url = p.value("url", "");
39
- download.emit({{"progress", 50}});
40
- });
51
+ // Send events to the frontend
52
+ connect::app.notify({{"msg", "Backend ready!"}});
53
+
54
+ // Listen for one-way commands from the frontend
55
+ connect::system.onMinimize = [](const json&) {
56
+ window.minimize();
57
+ };
41
58
  ```
42
59
 
43
- ### 2. Generate channels
60
+ ### 2. Generate bindings
44
61
 
45
62
  ```bash
46
63
  plusui connect
47
64
  ```
48
65
 
49
- Scans your `.ts`, `.tsx`, `.cpp`, `.hpp` files for `.on()` and `.emit()` calls, generates:
50
- - `Connections/connections.gen.ts`
51
- - `Connections/connections.gen.hpp`
66
+ Scans your `.ts`, `.tsx`, `.cpp`, `.hpp` files for `connect.namespace.method()` patterns and generates:
67
+ - `Connections/connections.gen.ts` — Typed semantic API for frontend
68
+ - `Connections/connections.gen.hpp` — Typed semantic API for backend
69
+
70
+ ### 3. That's it.
71
+
72
+ No schemas. No config files. No imports to manage (beyond `plusui`).
73
+ Write your code, run `plusui connect`, done.
74
+
75
+ ---
76
+
77
+ ## The 5 Communication Patterns
78
+
79
+ All 5 patterns work **bidirectionally** — frontend ↔ backend. The pattern used depends on which side is sending vs receiving.
80
+
81
+ ### 1. Request-Response (Call)
82
+
83
+ A request is sent, processed, and a response is returned. Works both ways.
52
84
 
53
- ## The 5 Primitives
85
+ | Sender | Receiver |
86
+ |--------|----------|
87
+ | `await connect.user.fetch(123)` | `connect::user.handleFetch = [](json p) -> json { ... }` |
88
+ | `connect.call("ui.prompt", data)` | `connect.ui.handlePrompt = async (data) => { ... }` |
54
89
 
55
- | Pattern | Direction | Use Case |
56
- |---------|-----------|----------|
57
- | **EVENT** | One way | Notifications, commands |
58
- | **CALL** | Two ways ↔ | Request/response |
59
- | **STREAM** | Continuous | Real-time updates |
60
- | **QUERY** | Many responses ↔↔↔ | Search, pagination |
61
- | **STATE** | Both ways | Synced values |
90
+ ### 2. Simplex (Fire & Forget)
91
+
92
+ One-way message. Sender doesn't wait for a response. Works both ways.
93
+
94
+ | Sender | Receiver |
95
+ |--------|----------|
96
+ | `connect.files.upload({ file })` | `connect::files.onUpload = [](json) { ... }` |
97
+ | `connect::app.notify(data)` | `connect.app.onNotify((msg) => { ... })` |
98
+
99
+ ### 3. Publish-Subscribe (One-to-Many)
100
+
101
+ Broadcast to multiple listeners. Works both ways.
102
+
103
+ | Sender | Subscribers |
104
+ |--------|-------------|
105
+ | `connect::sensors.publishTemp(data)` | `connect.sensors.onTemp(cb)` |
106
+ | `connect.auth.login(data)` | `connect::auth.onLogin = [](json) { ... }` |
107
+
108
+ ### 4. Full-Duplex (Bidirectional Stream)
109
+
110
+ Both sides send and receive continuously. Works both ways.
111
+
112
+ ```typescript
113
+ // Frontend: stream to backend
114
+ connect.files.onChunk(chunk);
115
+ connect.files.onProgress((p) => console.log(p));
116
+ ```
117
+
118
+ ```cpp
119
+ // Backend: receive and stream back
120
+ connect::files.onChunk = [](json chunk) { saveFile(chunk); };
121
+ connect::files.progress({{"percent", 50}});
122
+ ```
62
123
 
63
- All handled with just `.on()` and `.emit()`.
124
+ ### 5. Half-Duplex (State Sync)
125
+
126
+ Both sides can update, but one at a time. Works both ways.
127
+
128
+ ```typescript
129
+ // Frontend updates and listens
130
+ connect.settings.setTheme("dark");
131
+ connect.settings.onThemeChange((t) => applyTheme(t));
132
+ ```
133
+
134
+ ```cpp
135
+ // Backend updates and listens
136
+ connect::settings.onSetTheme = [](json p) { applyNativeTheme(p); };
137
+ connect::settings.themeChange({{"mode", "dark"}});
138
+ ```
139
+
140
+ ---
141
+
142
+ ## How Patterns Are Detected
143
+
144
+ The `plusui connect` tool automatically infers the communication pattern from your code:
145
+
146
+ | Your Code | Detected Pattern |
147
+ |-----------|-----------------|
148
+ | `await connect.x.y()` | **Call** (Request/Response) |
149
+ | `connect.x.onY(callback)` | **Event** (Listener/Subscribe) |
150
+ | `connect::x.handleY = fn` | **Call Handler** (Backend responds) |
151
+ | `connect.x.y()` (no await) | **Fire** (Simplex/One-way) |
152
+
153
+ You never need to specify the pattern. Just write natural, semantic code.
154
+
155
+ ---
64
156
 
65
157
  ## Generated Files
66
158
 
@@ -69,12 +161,17 @@ All handled with just `.on()` and `.emit()`.
69
161
  ```typescript
70
162
  import { _client } from 'plusui';
71
163
 
72
- export const download = {
73
- on: <T = unknown>(cb: (data: T) => void) => _client.on('download', cb),
74
- emit: (data: unknown) => _client.fire('download', data)
164
+ export const user = {
165
+ fetch: <T = unknown>(...args: unknown[]) => _client.call<T>('user.fetch', args[0]),
75
166
  };
76
167
 
77
- export const CONNECT_CHANNELS = ['download'] as const;
168
+ export const app = {
169
+ onNotify: <T = unknown>(cb: (data: T) => void) => _client.on<T>('app.onNotify', cb),
170
+ };
171
+
172
+ export const system = {
173
+ minimize: (...args: unknown[]) => _client.fire('system.minimize', args[0]),
174
+ };
78
175
  ```
79
176
 
80
177
  ### C++ (`connections.gen.hpp`)
@@ -83,29 +180,56 @@ export const CONNECT_CHANNELS = ['download'] as const;
83
180
  #pragma once
84
181
  #include <plusui/plusui.hpp>
85
182
 
86
- namespace channels {
87
- inline plusui::Connect* _connect = nullptr;
88
- inline plusui::Connect::Channel download;
89
- }
183
+ namespace connect {
184
+ inline plusui::Connect* _conn = nullptr;
90
185
 
91
- inline void initChannels(plusui::Connect& c) {
92
- channels::_connect = &c;
93
- channels::download = c.channel("download");
186
+ inline struct user_t {
187
+ std::function<nlohmann::json(const nlohmann::json&)> handleFetch;
188
+ } user;
189
+
190
+ inline struct app_t {
191
+ void notify(const nlohmann::json& p = nlohmann::json::object()) {
192
+ if (_conn) _conn->emit("app.notify", p);
193
+ }
194
+ } app;
195
+
196
+ inline struct system_t {
197
+ std::function<void(const nlohmann::json&)> onMinimize;
198
+ } system;
94
199
  }
95
200
 
96
- // Direct access (same as TypeScript)
97
- using channels::download;
201
+ inline void initConnect(plusui::Connect& c) {
202
+ connect::_conn = &c;
203
+ c.onCall("user.handleFetch", [](const nlohmann::json& p) -> nlohmann::json {
204
+ if (connect::user.handleFetch) return connect::user.handleFetch(p);
205
+ return nlohmann::json::object();
206
+ });
207
+ c.on("system.onMinimize", [](const nlohmann::json& p) {
208
+ if (connect::system.onMinimize) connect::system.onMinimize(p);
209
+ });
210
+ }
98
211
  ```
99
212
 
100
- ## Built-in Channels
213
+ ---
214
+
215
+ ## Built-in Features
216
+
217
+ Built-in features use direct imports from `plusui` — they're not custom channels:
101
218
 
102
219
  ```typescript
103
- import { win, clipboard, app } from 'plusui';
220
+ import plusui from 'plusui';
104
221
 
105
- win.on((data) => console.log(data.width));
106
- clipboard.emit({ text: 'copied!' });
222
+ plusui.window.minimize();
223
+ plusui.clipboard.write('copied!');
107
224
  ```
108
225
 
109
- ## That's it.
226
+ ## Legacy Support
227
+
228
+ The old flat `name.on()` / `name.emit()` pattern still works for backward compatibility:
229
+
230
+ ```typescript
231
+ download.on((data) => console.log(data.progress));
232
+ download.emit({ url: 'https://example.com' });
233
+ ```
110
234
 
111
- No schemas. No configuration. Write `.on()` and `.emit()`, run `plusui connect`.
235
+ These are detected alongside the new semantic patterns.