@iankibetsh/vue-streamline 1.3.3 β 1.3.6
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 +67 -361
- package/package.json +3 -3
- package/src/App.vue +10 -4
- package/src/composables/useStreamline.js +31 -34
- package/src/main.js +5 -1
- package/src/plugins/streamline.js +2 -2
- package/src/utils/cache.js +113 -0
package/README.md
CHANGED
|
@@ -2,71 +2,61 @@
|
|
|
2
2
|
|
|
3
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
|
+
## π Key Features
|
|
6
|
+
|
|
7
|
+
- **Zero-Config Actions**: Invoke backend methods directly via a JavaScript Proxy.
|
|
8
|
+
- **Stateful Streams**: Initial arguments are persisted across all subsequent action calls.
|
|
9
|
+
- **Object Argument Mapping**: Pass objects that automatically populate backend `request()` data and map to method parameters.
|
|
10
|
+
- **Reactive State**: Automatically syncs public properties from your backend Stream classes.
|
|
11
|
+
- **Smart Caching**: IndexedDB caching for instant data availability (enabled by default).
|
|
12
|
+
- **Security & Privacy**: Automatically filters out internal methods and properties from the response payload.
|
|
13
|
+
|
|
14
|
+
## π¦ Installation
|
|
6
15
|
|
|
7
16
|
```bash
|
|
8
17
|
npm install @iankibetsh/vue-streamline
|
|
9
18
|
```
|
|
10
19
|
|
|
11
|
-
## Setup
|
|
20
|
+
## βοΈ Setup
|
|
12
21
|
|
|
13
22
|
### 1. Register the Plugin
|
|
14
23
|
|
|
15
24
|
```javascript
|
|
16
|
-
import { createApp } from
|
|
17
|
-
import { streamline } from
|
|
18
|
-
import App from
|
|
25
|
+
import { createApp } from "vue";
|
|
26
|
+
import { streamline } from "@iankibetsh/vue-streamline";
|
|
27
|
+
import App from "./App.vue";
|
|
19
28
|
|
|
20
29
|
const app = createApp(App);
|
|
21
30
|
|
|
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`;
|
|
29
|
-
|
|
30
31
|
app.use(streamline, {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
enableCache: true // Optional: enables local storage caching
|
|
32
|
+
streamlineUrl: "https://your-api.com/api/streamline",
|
|
33
|
+
enableCache: true, // Enabled by default, set to false to disable
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
-
app.mount(
|
|
36
|
+
app.mount("#app");
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
> [!NOTE]
|
|
40
|
+
> Caching uses **IndexedDB** (`streamline_cache` database) for persistence, offering higher storage limits and better performance than standard local storage.
|
|
40
41
|
|
|
41
|
-
|
|
42
|
+
### 2. Use in Components
|
|
42
43
|
|
|
43
44
|
```vue
|
|
44
45
|
<script setup>
|
|
45
|
-
import { useStreamline } from
|
|
46
|
+
import { useStreamline } from "@iankibetsh/vue-streamline";
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
// Pass initial arguments (e.g., ID 42)
|
|
49
|
+
// These are PERSISTED for all future actions on this service
|
|
50
|
+
const { service, loading, props } = useStreamline("project", 42);
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
```vue
|
|
56
|
-
<script setup>
|
|
57
|
-
import { getActionUrl } from '@iankibetsh/vue-streamline';
|
|
58
|
-
|
|
59
|
-
// Use directly in template or script
|
|
60
|
-
const downloadUrl = getActionUrl('reports:download', reportId, 'pdf');
|
|
52
|
+
const update = async () => {
|
|
53
|
+
// Backend receives taskId=42 in constructor automatically
|
|
54
|
+
await service.updateStatus("active");
|
|
55
|
+
};
|
|
61
56
|
</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>
|
|
67
57
|
```
|
|
68
58
|
|
|
69
|
-
## API Reference
|
|
59
|
+
## π API Reference
|
|
70
60
|
|
|
71
61
|
### `useStreamline(stream, ...initialArgs)`
|
|
72
62
|
|
|
@@ -74,364 +64,80 @@ Primary composable for interfacing with Streamline services.
|
|
|
74
64
|
|
|
75
65
|
#### Parameters
|
|
76
66
|
|
|
77
|
-
- **`stream`** (`string`): Name of the target stream
|
|
78
|
-
- **`...initialArgs`** (`any`):
|
|
67
|
+
- **`stream`** (`string`): Name of the target stream class on the backend.
|
|
68
|
+
- **`...initialArgs`** (`any`): Arguments passed to the backend constructor. **These are persisted for every subsequent action call.**
|
|
79
69
|
|
|
80
70
|
#### Returns
|
|
81
71
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
|
85
|
-
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
| `getActionUrl` | `Function` | Generates URLs for specific actions. |
|
|
90
|
-
| `confirmAction` | `Function` | Displays confirmation dialogs before actions. |
|
|
72
|
+
| Property | Type | Description |
|
|
73
|
+
| --------------- | -------------- | -------------------------------------------------------------- |
|
|
74
|
+
| `service` | Reactive Proxy | Proxy for invoking backend methods. |
|
|
75
|
+
| `loading` | `ref<boolean>` | `true` when a request is in flight. |
|
|
76
|
+
| `props` | Reactive Proxy | Holds public properties fetched from the backend. |
|
|
77
|
+
| `getActionUrl` | `Function` | Generates URLs for actions (downloads, exports). |
|
|
78
|
+
| `confirmAction` | `Function` | Wrapper for triggering a confirmation dialog before an action. |
|
|
91
79
|
|
|
92
80
|
---
|
|
93
81
|
|
|
94
|
-
|
|
82
|
+
## π§ Advanced Features
|
|
95
83
|
|
|
96
|
-
|
|
84
|
+
### 1. Persistent State
|
|
97
85
|
|
|
98
|
-
|
|
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
|
|
86
|
+
Unlike traditional APIs where you send the ID every time, Streamline remembers:
|
|
108
87
|
|
|
109
88
|
```javascript
|
|
110
|
-
|
|
89
|
+
const { service } = useStreamline("user", 123);
|
|
111
90
|
|
|
112
|
-
//
|
|
113
|
-
|
|
91
|
+
// Backend runs: new UserStream(123)->changePassword('secret')
|
|
92
|
+
await service.changePassword("secret");
|
|
114
93
|
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
// Multiple parameters
|
|
119
|
-
const reportUrl = getActionUrl('reports:generate', reportId, 'pdf', '2024');
|
|
94
|
+
// Backend still runs: new UserStream(123)->getLogs()
|
|
95
|
+
await service.getLogs();
|
|
120
96
|
```
|
|
121
97
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
## Features
|
|
98
|
+
### 2. Method Object Mapping
|
|
125
99
|
|
|
126
|
-
|
|
100
|
+
You can pass objects to methods. Streamline will:
|
|
127
101
|
|
|
128
|
-
|
|
102
|
+
1. Merge the object into the backend `request()` data.
|
|
103
|
+
2. Automatically map object keys to specific method parameters if they match by name.
|
|
129
104
|
|
|
130
105
|
```javascript
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
await service.customAction(arg1, arg2, arg3);
|
|
106
|
+
// Frontend
|
|
107
|
+
service.updateProfile(123, { bio: 'New bio', age: 30 });
|
|
108
|
+
|
|
109
|
+
// Backend
|
|
110
|
+
public function updateProfile($userId, $bio) {
|
|
111
|
+
// $userId = 123
|
|
112
|
+
// $bio = 'New bio' (auto-mapped from object)
|
|
113
|
+
// request('age') = 30
|
|
114
|
+
}
|
|
141
115
|
```
|
|
142
116
|
|
|
143
|
-
###
|
|
117
|
+
### 3. Confirmation Dialogs
|
|
144
118
|
|
|
145
|
-
|
|
119
|
+
Prompt users before destructive actions:
|
|
146
120
|
|
|
147
121
|
```javascript
|
|
148
|
-
|
|
149
|
-
|
|
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);
|
|
122
|
+
await service.confirm("Delete this user?").delete();
|
|
154
123
|
```
|
|
155
124
|
|
|
156
|
-
|
|
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:
|
|
125
|
+
### 4. Cross-Stream Actions
|
|
163
126
|
|
|
164
|
-
|
|
165
|
-
<template>
|
|
166
|
-
<div>
|
|
167
|
-
<div v-if="loading">Loading...</div>
|
|
168
|
-
<div v-else>
|
|
169
|
-
<button @click="service.fetchData()">Fetch Data</button>
|
|
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:
|
|
127
|
+
Execute actions on different streams using colon notation:
|
|
184
128
|
|
|
185
129
|
```javascript
|
|
186
|
-
|
|
187
|
-
streamlineUrl: 'https://your-api.com/streamline',
|
|
188
|
-
enableCache: true
|
|
189
|
-
});
|
|
130
|
+
await service["logs:clear"]();
|
|
190
131
|
```
|
|
191
132
|
|
|
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
133
|
### 5. Manual Refresh
|
|
199
134
|
|
|
200
135
|
Force reload of stream properties:
|
|
201
136
|
|
|
202
137
|
```javascript
|
|
203
|
-
const { service } = useStreamline('products', categoryId);
|
|
204
|
-
|
|
205
|
-
// Refresh properties
|
|
206
138
|
await service.refresh(); // or service.reload()
|
|
207
139
|
```
|
|
208
140
|
|
|
209
|
-
|
|
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>
|
|
264
|
-
</template>
|
|
265
|
-
```
|
|
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
|
|
331
|
-
<template>
|
|
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>
|
|
354
|
-
</div>
|
|
355
|
-
</div>
|
|
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
|
-
};
|
|
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
|
|
141
|
+
## π License
|
|
436
142
|
|
|
437
|
-
MIT
|
|
143
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iankibetsh/vue-streamline",
|
|
3
|
-
|
|
3
|
+
"version": "1.3.6",
|
|
4
4
|
"description": "Vue library for streamlining laravel backend services with @iankibet/streamline composer package",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"author": "Iankibet",
|
|
16
16
|
"license": "ISC",
|
|
17
17
|
"devDependencies": {
|
|
18
|
-
"@vitejs/plugin-vue": "^
|
|
19
|
-
"vite": "^
|
|
18
|
+
"@vitejs/plugin-vue": "^6",
|
|
19
|
+
"vite": "^7"
|
|
20
20
|
},
|
|
21
21
|
"peerDependencies": {
|
|
22
22
|
"@iankibetsh/shframework": "^5",
|
package/src/App.vue
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import useStreamline from './composables/useStreamline.js'
|
|
3
|
-
import { onMounted, ref, toRefs } from 'vue'
|
|
3
|
+
import { onMounted, ref, toRefs,computed } from 'vue'
|
|
4
4
|
import SimpleGetActionUrl from './SimpleGetActionUrl.vue'
|
|
5
5
|
|
|
6
|
-
const {loading, service:paybillService, getActionUrl,props,confirmAction} = useStreamline('mpesa/paybill',
|
|
7
|
-
|
|
6
|
+
const {loading, service:paybillService, getActionUrl,props,confirmAction} = useStreamline('mpesa/paybill',32)
|
|
8
7
|
|
|
8
|
+
const existing = computed(()=>props.paybill)
|
|
9
9
|
const foundPaybill = ref(null)
|
|
10
10
|
|
|
11
11
|
onMounted(()=>{
|
|
@@ -18,7 +18,7 @@ const findPaybill = async ()=>{
|
|
|
18
18
|
// res.getPaybill(32).then(res=>{
|
|
19
19
|
// foundPaybill.value = res
|
|
20
20
|
// })
|
|
21
|
-
foundPaybill.value = await paybillService.confirm().getPaybill(32)
|
|
21
|
+
foundPaybill.value = await paybillService.confirm().getPaybill(32, {name: 'ian'})
|
|
22
22
|
.catch(err=>{
|
|
23
23
|
console.log(err)
|
|
24
24
|
})
|
|
@@ -41,6 +41,12 @@ const refresh = ()=>{
|
|
|
41
41
|
<h1 @click="refresh">Refresh</h1>
|
|
42
42
|
{{ foundPaybill }}
|
|
43
43
|
</div>
|
|
44
|
+
<div class="alert alert-info">
|
|
45
|
+
<h2>Existing</h2>
|
|
46
|
+
<p>
|
|
47
|
+
{{ existing }}
|
|
48
|
+
</p>
|
|
49
|
+
</div>
|
|
44
50
|
<simple-get-action-url/>
|
|
45
51
|
</template>
|
|
46
52
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { inject, onMounted, reactive, ref } from 'vue'
|
|
2
2
|
import { shApis, shRepo } from '@iankibetsh/shframework'
|
|
3
|
+
import Cache from '../utils/cache'
|
|
3
4
|
|
|
4
5
|
const useStreamline = (stream, ...initialArgs) => {
|
|
5
6
|
let formData = {}
|
|
@@ -11,8 +12,8 @@ const useStreamline = (stream, ...initialArgs) => {
|
|
|
11
12
|
const cacheKey = `streamline_${stream}_${initialArgs.join('_')}`
|
|
12
13
|
|
|
13
14
|
// Inject headers and API endpoint
|
|
14
|
-
const streamlineUrl = inject('streamlineUrl',window.streamlineUrl)
|
|
15
|
-
const enableCache = inject('enableCache',window.enableCache)
|
|
15
|
+
const streamlineUrl = inject('streamlineUrl', window.streamlineUrl)
|
|
16
|
+
const enableCache = inject('enableCache', window.enableCache ?? true)
|
|
16
17
|
|
|
17
18
|
const originalProps = reactive({})
|
|
18
19
|
|
|
@@ -30,7 +31,7 @@ const useStreamline = (stream, ...initialArgs) => {
|
|
|
30
31
|
})
|
|
31
32
|
|
|
32
33
|
const fetchServiceProperties = async (force) => {
|
|
33
|
-
if(!force){
|
|
34
|
+
if (!force) {
|
|
34
35
|
if (loading.value || propertiesFetched.value) return
|
|
35
36
|
}
|
|
36
37
|
|
|
@@ -42,7 +43,9 @@ const useStreamline = (stream, ...initialArgs) => {
|
|
|
42
43
|
params: initialArgs
|
|
43
44
|
})
|
|
44
45
|
assignProperties(response.data)
|
|
45
|
-
|
|
46
|
+
if (enableCache) {
|
|
47
|
+
await Cache.set(cacheKey, response.data)
|
|
48
|
+
}
|
|
46
49
|
} catch (error) {
|
|
47
50
|
console.error(`Error fetching properties for stream ${stream}`, error)
|
|
48
51
|
throw error
|
|
@@ -64,10 +67,10 @@ const useStreamline = (stream, ...initialArgs) => {
|
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
return (...args) => {
|
|
67
|
-
if(prop === 'refresh' || prop === 'reload'){
|
|
70
|
+
if (prop === 'refresh' || prop === 'reload') {
|
|
68
71
|
return fetchServiceProperties(true)
|
|
69
72
|
}
|
|
70
|
-
if(prop === 'confirm'){
|
|
73
|
+
if (prop === 'confirm') {
|
|
71
74
|
return confirmAction(args[0] ?? 'Are you sure?')
|
|
72
75
|
}
|
|
73
76
|
let repo = null
|
|
@@ -75,10 +78,16 @@ const useStreamline = (stream, ...initialArgs) => {
|
|
|
75
78
|
action: prop,
|
|
76
79
|
stream,
|
|
77
80
|
...formData,
|
|
78
|
-
params: args
|
|
81
|
+
params: args,
|
|
82
|
+
initialParams: initialArgs
|
|
79
83
|
}
|
|
84
|
+
args.forEach(arg => {
|
|
85
|
+
if (arg && typeof arg === 'object' && !Array.isArray(arg)) {
|
|
86
|
+
Object.assign(data, arg)
|
|
87
|
+
}
|
|
88
|
+
})
|
|
80
89
|
if (confirmationMessage.value) {
|
|
81
|
-
|
|
90
|
+
repo = shRepo.runPlainRequest(streamlineUrl, null, confirmationMessage.value, data)
|
|
82
91
|
} else {
|
|
83
92
|
repo = shApis
|
|
84
93
|
.doPost(streamlineUrl, data);
|
|
@@ -87,13 +96,13 @@ const useStreamline = (stream, ...initialArgs) => {
|
|
|
87
96
|
loading.value = true
|
|
88
97
|
return repo.then((response) => {
|
|
89
98
|
loading.value = false
|
|
90
|
-
if(confirmationMessage.value){
|
|
99
|
+
if (confirmationMessage.value) {
|
|
91
100
|
confirmationMessage.value = null
|
|
92
101
|
|
|
93
|
-
if(!response.isConfirmed){
|
|
102
|
+
if (!response.isConfirmed) {
|
|
94
103
|
return
|
|
95
104
|
}
|
|
96
|
-
if(response.value?.success){
|
|
105
|
+
if (response.value?.success) {
|
|
97
106
|
return response.value.response
|
|
98
107
|
} else {
|
|
99
108
|
// throw error
|
|
@@ -102,8 +111,8 @@ const useStreamline = (stream, ...initialArgs) => {
|
|
|
102
111
|
|
|
103
112
|
}
|
|
104
113
|
|
|
105
|
-
|
|
106
|
-
|
|
114
|
+
return response.data
|
|
115
|
+
})
|
|
107
116
|
.catch((error) => {
|
|
108
117
|
loading.value = false
|
|
109
118
|
console.error(`Error calling ${prop} on stream ${stream}`, error)
|
|
@@ -114,28 +123,15 @@ const useStreamline = (stream, ...initialArgs) => {
|
|
|
114
123
|
}
|
|
115
124
|
const getActionUrl = (action, ...args) => {
|
|
116
125
|
let newStream = stream
|
|
117
|
-
if(action.includes(':')){
|
|
126
|
+
if (action.includes(':')) {
|
|
118
127
|
[newStream, action] = action.split(':')
|
|
119
128
|
}
|
|
120
129
|
const post = {
|
|
121
130
|
action,
|
|
122
|
-
stream:newStream,
|
|
131
|
+
stream: newStream,
|
|
123
132
|
params: args
|
|
124
133
|
}
|
|
125
134
|
return `${streamlineUrl}?${new URLSearchParams(post).toString()}`
|
|
126
|
-
|
|
127
|
-
// // Add each arg as a separate param
|
|
128
|
-
// args.forEach((arg, index) => {
|
|
129
|
-
// let value = arg;
|
|
130
|
-
// if (typeof arg === 'object' && !(arg instanceof Date)) {
|
|
131
|
-
// value = JSON.stringify(arg);
|
|
132
|
-
// } else if (arg instanceof Date) {
|
|
133
|
-
// value = arg.toISOString();
|
|
134
|
-
// }
|
|
135
|
-
// params.append(`params[${index}]`, value);
|
|
136
|
-
// })
|
|
137
|
-
|
|
138
|
-
// return `${streamlineUrl}?${params.toString()}`
|
|
139
135
|
}
|
|
140
136
|
|
|
141
137
|
const service = reactive({})
|
|
@@ -146,15 +142,16 @@ const useStreamline = (stream, ...initialArgs) => {
|
|
|
146
142
|
return new Proxy(service, handler)
|
|
147
143
|
}
|
|
148
144
|
|
|
149
|
-
onMounted(() => {
|
|
145
|
+
onMounted(async () => {
|
|
146
|
+
if (enableCache) {
|
|
147
|
+
const cachedData = await Cache.get(cacheKey)
|
|
148
|
+
if (cachedData) {
|
|
149
|
+
assignProperties(cachedData)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
150
152
|
if (initialArgs.length > 0) {
|
|
151
153
|
fetchServiceProperties()
|
|
152
154
|
}
|
|
153
|
-
if (!enableCache) return
|
|
154
|
-
const cachedData = localStorage.getItem(cacheKey)
|
|
155
|
-
if (cachedData) {
|
|
156
|
-
assignProperties(JSON.parse(cachedData))
|
|
157
|
-
}
|
|
158
155
|
})
|
|
159
156
|
|
|
160
157
|
return {
|
package/src/main.js
CHANGED
|
@@ -2,16 +2,20 @@ import { createApp } from 'vue'
|
|
|
2
2
|
import './style.css'
|
|
3
3
|
import App from './App.vue'
|
|
4
4
|
import streamline from './plugins/streamline.js'
|
|
5
|
+
import { ShFrontend } from '@iankibetsh/shframework'
|
|
5
6
|
|
|
6
7
|
const streamlineHeaders = {
|
|
7
8
|
Authorization: 'Bearer ' + localStorage.getItem('access_token')
|
|
8
9
|
}
|
|
9
10
|
const streamlineUrl = 'http://2022-sharasms.test/api/streamline'
|
|
10
11
|
const app = createApp(App)
|
|
12
|
+
app.use(ShFrontend, {
|
|
13
|
+
baseApiUrl: 'http://2022-sharasms.test/api'
|
|
14
|
+
})
|
|
11
15
|
app.use(streamline,{
|
|
12
16
|
streamlineHeaders,
|
|
13
17
|
streamlineUrl,
|
|
14
|
-
enableCache:
|
|
18
|
+
enableCache: true
|
|
15
19
|
})
|
|
16
20
|
|
|
17
21
|
app.mount('#app')
|
|
@@ -2,11 +2,11 @@ const streamline = {
|
|
|
2
2
|
install(app, options) {
|
|
3
3
|
app.provide('streamlineUrl', options.streamlineUrl ?? '/api/streamline')
|
|
4
4
|
// app.provide('streamlineHeaders', options.streamlineHeaders)
|
|
5
|
-
app.provide('enableCache', options.enableCache)
|
|
5
|
+
app.provide('enableCache', options.enableCache ?? true)
|
|
6
6
|
|
|
7
7
|
// put them on window
|
|
8
8
|
window.streamlineUrl = options.streamlineUrl ?? '/api/streamline'
|
|
9
|
-
window.enableCache = options.enableCache ??
|
|
9
|
+
window.enableCache = options.enableCache ?? true
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const DB_NAME = 'streamline_cache'
|
|
2
|
+
const STORE_NAME = 'properties'
|
|
3
|
+
const DB_VERSION = 1
|
|
4
|
+
|
|
5
|
+
const openDB = () => {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
const request = indexedDB.open(DB_NAME, DB_VERSION)
|
|
8
|
+
|
|
9
|
+
request.onupgradeneeded = (event) => {
|
|
10
|
+
const db = event.target.result
|
|
11
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
12
|
+
db.createObjectStore(STORE_NAME)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
request.onsuccess = (event) => {
|
|
17
|
+
resolve(event.target.result)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
request.onerror = (event) => {
|
|
21
|
+
reject(event.target.error)
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const Cache = {
|
|
27
|
+
async get(key) {
|
|
28
|
+
try {
|
|
29
|
+
const db = await openDB()
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
const transaction = db.transaction(STORE_NAME, 'readonly')
|
|
32
|
+
const store = transaction.objectStore(STORE_NAME)
|
|
33
|
+
const request = store.get(key)
|
|
34
|
+
|
|
35
|
+
request.onsuccess = () => {
|
|
36
|
+
resolve(request.result)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
request.onerror = () => {
|
|
40
|
+
reject(request.error)
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error('Error getting from IndexedDB cache', error)
|
|
45
|
+
return null
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
async set(key, value) {
|
|
50
|
+
try {
|
|
51
|
+
const db = await openDB()
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
const transaction = db.transaction(STORE_NAME, 'readwrite')
|
|
54
|
+
const store = transaction.objectStore(STORE_NAME)
|
|
55
|
+
const request = store.put(value, key)
|
|
56
|
+
|
|
57
|
+
request.onsuccess = () => {
|
|
58
|
+
resolve()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
request.onerror = () => {
|
|
62
|
+
reject(request.error)
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error('Error setting in IndexedDB cache', error)
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
async delete(key) {
|
|
71
|
+
try {
|
|
72
|
+
const db = await openDB()
|
|
73
|
+
return new Promise((resolve, reject) => {
|
|
74
|
+
const transaction = db.transaction(STORE_NAME, 'readwrite')
|
|
75
|
+
const store = transaction.objectStore(STORE_NAME)
|
|
76
|
+
const request = store.delete(key)
|
|
77
|
+
|
|
78
|
+
request.onsuccess = () => {
|
|
79
|
+
resolve()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
request.onerror = () => {
|
|
83
|
+
reject(request.error)
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error('Error deleting from IndexedDB cache', error)
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
async clear() {
|
|
92
|
+
try {
|
|
93
|
+
const db = await openDB()
|
|
94
|
+
return new Promise((resolve, reject) => {
|
|
95
|
+
const transaction = db.transaction(STORE_NAME, 'readwrite')
|
|
96
|
+
const store = transaction.objectStore(STORE_NAME)
|
|
97
|
+
const request = store.clear()
|
|
98
|
+
|
|
99
|
+
request.onsuccess = () => {
|
|
100
|
+
resolve()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
request.onerror = () => {
|
|
104
|
+
reject(request.error)
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error('Error clearing IndexedDB cache', error)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export default Cache
|