@iankibetsh/vue-streamline 1.3.0 → 1.3.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 +398 -52
- package/package.json +1 -1
- package/src/App.vue +2 -0
- package/src/SimpleGetActionUrl.vue +11 -0
- package/src/composables/getActionUrl.js +7 -0
- package/src/index.js +2 -0
- package/src/plugins/streamline.js +1 -1
package/README.md
CHANGED
|
@@ -1,91 +1,437 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Streamline Vue Plugin
|
|
2
2
|
|
|
3
|
-
Vue
|
|
3
|
+
A robust Vue 3 plugin designed for seamless integration with Streamline backend services. It delivers reactive state management, intelligent caching, and dynamic action invocation.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
|
-
```
|
|
7
|
+
```bash
|
|
8
8
|
npm install @iankibetsh/vue-streamline
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Setup
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
### 1. Register the Plugin
|
|
14
14
|
|
|
15
|
-
```
|
|
16
|
-
import {
|
|
17
|
-
|
|
15
|
+
```javascript
|
|
16
|
+
import { createApp } from 'vue';
|
|
17
|
+
import { streamline } from '@iankibetsh/vue-streamline';
|
|
18
|
+
import App from './App.vue';
|
|
19
|
+
|
|
20
|
+
const app = createApp(App);
|
|
21
|
+
|
|
22
|
+
// Define authentication headers
|
|
23
|
+
const streamlineHeaders = {
|
|
24
|
+
Authorization: `Bearer ${localStorage.getItem('access_token')}`
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Construct the Streamline endpoint URL
|
|
28
|
+
const streamlineUrl = `${import.meta.env.VITE_APP_API_URL}streamline`;
|
|
18
29
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
30
|
+
app.use(streamline, {
|
|
31
|
+
streamlineHeaders,
|
|
32
|
+
streamlineUrl,
|
|
33
|
+
enableCache: true // Optional: enables local storage caching
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
app.mount('#app');
|
|
26
37
|
```
|
|
27
38
|
|
|
39
|
+
### 2. Use in Components
|
|
40
|
+
|
|
41
|
+
#### Option A: Full Streamline Integration
|
|
28
42
|
|
|
29
|
-
|
|
43
|
+
```vue
|
|
44
|
+
<script setup>
|
|
45
|
+
import { useStreamline } from '@iankibetsh/vue-streamline';
|
|
46
|
+
|
|
47
|
+
const { service, loading, props, getActionUrl } = useStreamline('users', 1);
|
|
48
|
+
</script>
|
|
49
|
+
```
|
|
30
50
|
|
|
31
|
-
|
|
32
|
-
import { useStreamline } from '@iankibet/vue-streamline'
|
|
51
|
+
#### Option B: Standalone `getActionUrl`
|
|
33
52
|
|
|
53
|
+
If you only need to generate action URLs without the full reactive service:
|
|
34
54
|
|
|
35
|
-
|
|
55
|
+
```vue
|
|
56
|
+
<script setup>
|
|
57
|
+
import { getActionUrl } from '@iankibetsh/vue-streamline';
|
|
36
58
|
|
|
59
|
+
// Use directly in template or script
|
|
60
|
+
const downloadUrl = getActionUrl('reports:download', reportId, 'pdf');
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<template>
|
|
64
|
+
<a :href="getActionUrl('users:export', userId, 'csv')">Export User</a>
|
|
65
|
+
<h3>Action URL: {{ getActionUrl('users:listUsers', 'admin', 'active') }}</h3>
|
|
66
|
+
</template>
|
|
37
67
|
```
|
|
38
68
|
|
|
39
|
-
##
|
|
69
|
+
## API Reference
|
|
70
|
+
|
|
71
|
+
### `useStreamline(stream, ...initialArgs)`
|
|
72
|
+
|
|
73
|
+
Primary composable for interfacing with Streamline services.
|
|
74
|
+
|
|
75
|
+
#### Parameters
|
|
76
|
+
|
|
77
|
+
- **`stream`** (`string`): Name of the target stream or service.
|
|
78
|
+
- **`...initialArgs`** (`any`): Optional arguments passed to the stream’s `onMounted` action.
|
|
40
79
|
|
|
41
|
-
|
|
42
|
-
```js
|
|
43
|
-
const res = await tasksService.getTasks()
|
|
80
|
+
#### Returns
|
|
44
81
|
|
|
45
|
-
|
|
82
|
+
An object with the following reactive properties and utilities:
|
|
46
83
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
84
|
+
| Property | Type | Description |
|
|
85
|
+
|------------------|-----------------------|-------------|
|
|
86
|
+
| `service` | Reactive Proxy | Proxy for invoking stream actions dynamically. |
|
|
87
|
+
| `loading` | `ref<boolean>` | Indicates ongoing operations. |
|
|
88
|
+
| `props` | Reactive Proxy | Holds properties fetched from the stream. |
|
|
89
|
+
| `getActionUrl` | `Function` | Generates URLs for specific actions. |
|
|
90
|
+
| `confirmAction` | `Function` | Displays confirmation dialogs before actions. |
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
### `getActionUrl(action, ...args)`
|
|
95
|
+
|
|
96
|
+
Standalone function for generating action URLs without reactive features.
|
|
97
|
+
|
|
98
|
+
#### Parameters
|
|
99
|
+
|
|
100
|
+
- **`action`** (`string`): Action name, optionally prefixed with stream name (e.g., `'stream:action'`).
|
|
101
|
+
- **`...args`** (`any`): Arguments to pass as URL parameters.
|
|
102
|
+
|
|
103
|
+
#### Returns
|
|
104
|
+
|
|
105
|
+
- **`string`**: Fully qualified URL for the specified action.
|
|
106
|
+
|
|
107
|
+
#### Usage
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
import { getActionUrl } from '@iankibetsh/vue-streamline';
|
|
111
|
+
|
|
112
|
+
// Simple action URL
|
|
113
|
+
const url = getActionUrl('download', fileId);
|
|
114
|
+
|
|
115
|
+
// Cross-stream action
|
|
116
|
+
const analyticsUrl = getActionUrl('analytics:track', eventName, userId);
|
|
117
|
+
|
|
118
|
+
// Multiple parameters
|
|
119
|
+
const reportUrl = getActionUrl('reports:generate', reportId, 'pdf', '2024');
|
|
50
120
|
```
|
|
51
|
-
A swal popup asking the user to confirm the action will be shown before the action is called
|
|
52
121
|
|
|
53
|
-
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Features
|
|
125
|
+
|
|
126
|
+
### 1. Dynamic Action Calling
|
|
54
127
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
128
|
+
Invoke backend actions via the `service` proxy:
|
|
129
|
+
|
|
130
|
+
```javascript
|
|
131
|
+
const { service, loading } = useStreamline('users');
|
|
132
|
+
|
|
133
|
+
// Standard CRUD operations
|
|
134
|
+
await service.fetchAll();
|
|
135
|
+
await service.create({ name: 'John', email: 'john@example.com' });
|
|
136
|
+
await service.update(1, { name: 'Jane' });
|
|
137
|
+
await service.delete(5);
|
|
138
|
+
|
|
139
|
+
// Custom actions with multiple arguments
|
|
140
|
+
await service.customAction(arg1, arg2, arg3);
|
|
58
141
|
```
|
|
59
142
|
|
|
143
|
+
### 2. Reactive Properties
|
|
60
144
|
|
|
61
|
-
|
|
145
|
+
Properties are fetched automatically upon component mount or first access:
|
|
62
146
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
getActionUrl('getTasks', 'active')
|
|
66
|
-
```
|
|
147
|
+
```javascript
|
|
148
|
+
const { props, loading } = useStreamline('dashboard', userId);
|
|
67
149
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
```js
|
|
73
|
-
const title = computed(() => props.title)
|
|
150
|
+
// Properties trigger fetch on access if not yet loaded
|
|
151
|
+
console.log(props.statistics);
|
|
152
|
+
console.log(props.userInfo);
|
|
153
|
+
console.log(props.settings);
|
|
74
154
|
```
|
|
75
|
-
|
|
76
|
-
|
|
155
|
+
|
|
156
|
+
**Auto-fetch triggers:**
|
|
157
|
+
- Component `onMounted` (when `initialArgs` are provided)
|
|
158
|
+
- First property access on the `props` proxy
|
|
159
|
+
|
|
160
|
+
### 3. Loading States
|
|
161
|
+
|
|
162
|
+
Monitor operation status reactively:
|
|
163
|
+
|
|
164
|
+
```vue
|
|
77
165
|
<template>
|
|
78
|
-
|
|
79
|
-
|
|
166
|
+
<div>
|
|
167
|
+
<div v-if="loading">Loading...</div>
|
|
168
|
+
<div v-else>
|
|
169
|
+
<button @click="service.fetchData()">Fetch Data</button>
|
|
80
170
|
</div>
|
|
171
|
+
</div>
|
|
172
|
+
</template>
|
|
173
|
+
|
|
174
|
+
<script setup>
|
|
175
|
+
import { useStreamline } from '@iankibetsh/vue-streamline';
|
|
176
|
+
|
|
177
|
+
const { service, loading } = useStreamline('data');
|
|
178
|
+
</script>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### 4. Local Storage Caching
|
|
182
|
+
|
|
183
|
+
Enable persistent caching across sessions:
|
|
184
|
+
|
|
185
|
+
```javascript
|
|
186
|
+
app.use(streamline, {
|
|
187
|
+
streamlineUrl: 'https://your-api.com/streamline',
|
|
188
|
+
enableCache: true
|
|
189
|
+
});
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Behavior when enabled:**
|
|
193
|
+
- Data is stored in `localStorage` using a unique key (stream + arguments).
|
|
194
|
+
- Cached data loads instantly on mount.
|
|
195
|
+
- Cache updates after successful fetch operations.
|
|
196
|
+
- Keys are deterministic and scoped per stream and arguments.
|
|
197
|
+
|
|
198
|
+
### 5. Manual Refresh
|
|
199
|
+
|
|
200
|
+
Force reload of stream properties:
|
|
201
|
+
|
|
202
|
+
```javascript
|
|
203
|
+
const { service } = useStreamline('products', categoryId);
|
|
204
|
+
|
|
205
|
+
// Refresh properties
|
|
206
|
+
await service.refresh(); // or service.reload()
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### 6. Confirmation Dialogs
|
|
210
|
+
|
|
211
|
+
Prompt users before destructive actions:
|
|
212
|
+
|
|
213
|
+
```javascript
|
|
214
|
+
const { service } = useStreamline('users');
|
|
215
|
+
|
|
216
|
+
// Default confirmation message
|
|
217
|
+
await service.confirm().delete(userId);
|
|
218
|
+
|
|
219
|
+
// Custom message
|
|
220
|
+
await service.confirm('Are you sure you want to delete this user?').delete(userId);
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
> Uses `shRepo.runPlainRequest` from the SH Framework for native confirmation dialogs.
|
|
224
|
+
|
|
225
|
+
### 7. Action URLs
|
|
226
|
+
|
|
227
|
+
Generate fully qualified action URLs:
|
|
228
|
+
|
|
229
|
+
#### Using `getActionUrl` from `useStreamline`
|
|
230
|
+
|
|
231
|
+
```javascript
|
|
232
|
+
const { getActionUrl } = useStreamline('reports');
|
|
233
|
+
|
|
234
|
+
const downloadUrl = getActionUrl('download', reportId, 'pdf');
|
|
235
|
+
// → https://your-api.com/streamline?action=download&stream=reports¶ms=reportId,pdf
|
|
236
|
+
|
|
237
|
+
// Cross-stream actions
|
|
238
|
+
const analyticsUrl = getActionUrl('analytics:trackEvent', eventName, eventData);
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
#### Using Standalone `getActionUrl`
|
|
242
|
+
|
|
243
|
+
For scenarios where you only need URL generation without reactive state management:
|
|
244
|
+
|
|
245
|
+
```vue
|
|
246
|
+
<script setup>
|
|
247
|
+
import { getActionUrl } from '@iankibetsh/vue-streamline';
|
|
248
|
+
|
|
249
|
+
// Generate URLs directly
|
|
250
|
+
const exportUrl = getActionUrl('users:export', userId, 'csv');
|
|
251
|
+
const reportUrl = getActionUrl('reports:generate', reportId, 'pdf');
|
|
252
|
+
</script>
|
|
253
|
+
|
|
254
|
+
<template>
|
|
255
|
+
<!-- Use in templates -->
|
|
256
|
+
<a :href="getActionUrl('users:export', userId, 'csv')" download>
|
|
257
|
+
Export User
|
|
258
|
+
</a>
|
|
259
|
+
|
|
260
|
+
<!-- Dynamic URLs -->
|
|
261
|
+
<div>
|
|
262
|
+
<h3>API Endpoint: {{ getActionUrl('users:listUsers', 'admin', 'active') }}</h3>
|
|
263
|
+
</div>
|
|
81
264
|
</template>
|
|
82
265
|
```
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
266
|
+
|
|
267
|
+
**Benefits of Standalone Import:**
|
|
268
|
+
- Lighter weight when you don't need reactive features
|
|
269
|
+
- Can be used in utility files or non-component contexts
|
|
270
|
+
- Still respects the global `streamlineUrl` configuration
|
|
271
|
+
- Supports all the same features (cross-stream notation, multiple parameters)
|
|
272
|
+
|
|
273
|
+
### 8. Cross-Stream Actions
|
|
274
|
+
|
|
275
|
+
Execute actions on different streams using colon notation:
|
|
276
|
+
|
|
277
|
+
```javascript
|
|
278
|
+
const { service } = useStreamline('users');
|
|
279
|
+
|
|
280
|
+
await service['analytics:trackEvent'](eventName, eventData);
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## Advanced Usage
|
|
284
|
+
|
|
285
|
+
### Immediate Property Access
|
|
286
|
+
|
|
287
|
+
Access properties before loading — fetch triggers automatically:
|
|
288
|
+
|
|
289
|
+
```javascript
|
|
290
|
+
const { props } = useStreamline('dashboard', userId);
|
|
291
|
+
|
|
292
|
+
watchEffect(() => {
|
|
293
|
+
console.log(props.stats); // Triggers fetch if needed
|
|
294
|
+
});
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Error Handling
|
|
298
|
+
|
|
299
|
+
All action calls return promises:
|
|
300
|
+
|
|
301
|
+
```javascript
|
|
302
|
+
const { service } = useStreamline('users');
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
const result = await service.create(userData);
|
|
306
|
+
console.log('User created:', result);
|
|
307
|
+
} catch (error) {
|
|
308
|
+
console.error('Creation failed:', error);
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Form Data Integration
|
|
313
|
+
|
|
314
|
+
Pass structured data to actions:
|
|
315
|
+
|
|
316
|
+
```javascript
|
|
317
|
+
const { service } = useStreamline('posts');
|
|
318
|
+
|
|
319
|
+
const formData = {
|
|
320
|
+
title: 'My Post',
|
|
321
|
+
content: 'Post content',
|
|
322
|
+
tags: ['vue', 'javascript']
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
await service.create(formData);
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Complete Example
|
|
329
|
+
|
|
330
|
+
```vue
|
|
86
331
|
<template>
|
|
87
|
-
|
|
88
|
-
|
|
332
|
+
<div class="user-management">
|
|
333
|
+
<div v-if="loading" class="loading">Loading...</div>
|
|
334
|
+
|
|
335
|
+
<div v-else>
|
|
336
|
+
<h1>Total Users: {{ props.totalUsers }}</h1>
|
|
337
|
+
|
|
338
|
+
<div class="users-list">
|
|
339
|
+
<div v-for="user in props.users" :key="user.id" class="user-card">
|
|
340
|
+
<h3>{{ user.name }}</h3>
|
|
341
|
+
<p>{{ user.email }}</p>
|
|
342
|
+
|
|
343
|
+
<button @click="editUser(user)">Edit</button>
|
|
344
|
+
<button @click="deleteUser(user.id)">Delete</button>
|
|
345
|
+
|
|
346
|
+
<a :href="getActionUrl('exportUser', user.id)" target="_blank" rel="noopener">
|
|
347
|
+
Export Profile
|
|
348
|
+
</a>
|
|
349
|
+
</div>
|
|
350
|
+
</div>
|
|
351
|
+
|
|
352
|
+
<button @click="refreshData">Refresh</button>
|
|
353
|
+
<button @click="addNewUser">Add User</button>
|
|
89
354
|
</div>
|
|
355
|
+
</div>
|
|
90
356
|
</template>
|
|
357
|
+
|
|
358
|
+
<script setup>
|
|
359
|
+
import { useStreamline } from '@iankibetsh/vue-streamline';
|
|
360
|
+
|
|
361
|
+
const { service, loading, props, getActionUrl } = useStreamline('users', 'active');
|
|
362
|
+
|
|
363
|
+
const editUser = async (user) => {
|
|
364
|
+
try {
|
|
365
|
+
const result = await service.update(user.id, {
|
|
366
|
+
name: user.name,
|
|
367
|
+
email: user.email
|
|
368
|
+
});
|
|
369
|
+
console.log('User updated:', result);
|
|
370
|
+
} catch (error) {
|
|
371
|
+
console.error('Update failed:', error);
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
const deleteUser = async (userId) => {
|
|
376
|
+
try {
|
|
377
|
+
await service.confirm('Delete this user permanently?').delete(userId);
|
|
378
|
+
await service.refresh();
|
|
379
|
+
} catch (error) {
|
|
380
|
+
console.error('Delete failed:', error);
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
const refreshData = () => service.refresh();
|
|
385
|
+
|
|
386
|
+
const addNewUser = async () => {
|
|
387
|
+
const newUser = { name: 'New User', email: 'newuser@example.com' };
|
|
388
|
+
await service.create(newUser);
|
|
389
|
+
await service.refresh();
|
|
390
|
+
};
|
|
391
|
+
</script>
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
## Best Practices
|
|
395
|
+
|
|
396
|
+
1. **Use descriptive stream names** aligned with backend services.
|
|
397
|
+
2. **Enable caching** for infrequently updated data.
|
|
398
|
+
3. **Handle errors gracefully** in component logic.
|
|
399
|
+
4. **Display loading states** for better user experience.
|
|
400
|
+
5. **Use confirmation dialogs** for irreversible actions.
|
|
401
|
+
6. **Leverage `getActionUrl`** for links and downloads.
|
|
402
|
+
7. **Call `refresh()`** after mutations to synchronize UI.
|
|
403
|
+
|
|
404
|
+
## `getActionUrl` Function Details
|
|
405
|
+
|
|
406
|
+
```javascript
|
|
407
|
+
const getActionUrl = (action, ...args) => {
|
|
408
|
+
let targetStream = stream;
|
|
409
|
+
let targetAction = action;
|
|
410
|
+
|
|
411
|
+
if (action.includes(':')) {
|
|
412
|
+
[targetStream, targetAction] = action.split(':');
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const payload = {
|
|
416
|
+
action: targetAction,
|
|
417
|
+
stream: targetStream,
|
|
418
|
+
params: args
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
return `${streamlineUrl}?${new URLSearchParams(payload).toString()}`;
|
|
422
|
+
};
|
|
91
423
|
```
|
|
424
|
+
|
|
425
|
+
**Key Capabilities:**
|
|
426
|
+
- Serializes arguments into query parameters.
|
|
427
|
+
- Supports cross-stream routing via `stream:action`.
|
|
428
|
+
- Produces ready-to-use, fully qualified URLs.
|
|
429
|
+
|
|
430
|
+
## Dependencies
|
|
431
|
+
|
|
432
|
+
- **Vue 3**: `reactive`, `ref`, `inject`, `onMounted`
|
|
433
|
+
- **@iankibetsh/shframework**: `shApis`, `shRepo`
|
|
434
|
+
|
|
435
|
+
## License
|
|
436
|
+
|
|
437
|
+
MIT
|
package/package.json
CHANGED
package/src/App.vue
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import useStreamline from './composables/useStreamline.js'
|
|
3
3
|
import { onMounted, ref, toRefs } from 'vue'
|
|
4
|
+
import SimpleGetActionUrl from './SimpleGetActionUrl.vue'
|
|
4
5
|
|
|
5
6
|
const {loading, service:paybillService, getActionUrl,props,confirmAction} = useStreamline('mpesa/paybill',true)
|
|
6
7
|
|
|
@@ -40,6 +41,7 @@ const refresh = ()=>{
|
|
|
40
41
|
<h1 @click="refresh">Refresh</h1>
|
|
41
42
|
{{ foundPaybill }}
|
|
42
43
|
</div>
|
|
44
|
+
<simple-get-action-url/>
|
|
43
45
|
</template>
|
|
44
46
|
|
|
45
47
|
<style scoped>
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const streamline = {
|
|
2
2
|
install(app, options) {
|
|
3
|
-
app.provide('streamlineUrl', options.streamlineUrl)
|
|
3
|
+
app.provide('streamlineUrl', options.streamlineUrl ?? '/api/streamline')
|
|
4
4
|
// app.provide('streamlineHeaders', options.streamlineHeaders)
|
|
5
5
|
app.provide('enableCache', options.enableCache)
|
|
6
6
|
}
|