@jskit-ai/users-web 0.1.82 → 0.1.83
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/package.descriptor.mjs +7 -7
- package/package.json +7 -7
- package/src/client/composables/records/useCrudAddEdit.js +8 -0
- package/src/client/composables/records/useCrudList.js +6 -0
- package/src/client/composables/records/useCrudView.js +1 -0
- package/src/client/composables/records/useView.js +8 -2
- package/src/client/composables/runtime/useEndpointResource.js +8 -0
- package/src/client/composables/useRealtimeQueryInvalidation.js +26 -0
- package/test/requestTransportOptions.test.js +110 -1
package/package.descriptor.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { HOME_COG_OUTLET } from "./src/shared/toolsOutletContracts.js";
|
|
|
3
3
|
export default Object.freeze({
|
|
4
4
|
packageVersion: 1,
|
|
5
5
|
packageId: "@jskit-ai/users-web",
|
|
6
|
-
version: "0.1.
|
|
6
|
+
version: "0.1.83",
|
|
7
7
|
kind: "runtime",
|
|
8
8
|
description: "Users web module: account/profile UI plus shared users web widgets.",
|
|
9
9
|
dependsOn: [
|
|
@@ -278,12 +278,12 @@ export default Object.freeze({
|
|
|
278
278
|
dependencies: {
|
|
279
279
|
runtime: {
|
|
280
280
|
"@mdi/js": "^7.4.47",
|
|
281
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
282
|
-
"@jskit-ai/realtime": "0.1.
|
|
283
|
-
"@jskit-ai/kernel": "0.1.
|
|
284
|
-
"@jskit-ai/shell-web": "0.1.
|
|
285
|
-
"@jskit-ai/uploads-image-web": "0.1.
|
|
286
|
-
"@jskit-ai/users-core": "0.1.
|
|
281
|
+
"@jskit-ai/http-runtime": "0.1.67",
|
|
282
|
+
"@jskit-ai/realtime": "0.1.67",
|
|
283
|
+
"@jskit-ai/kernel": "0.1.68",
|
|
284
|
+
"@jskit-ai/shell-web": "0.1.67",
|
|
285
|
+
"@jskit-ai/uploads-image-web": "0.1.46",
|
|
286
|
+
"@jskit-ai/users-core": "0.1.78"
|
|
287
287
|
},
|
|
288
288
|
dev: {}
|
|
289
289
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/users-web",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.83",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -44,12 +44,12 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@mdi/js": "^7.4.47",
|
|
47
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
48
|
-
"@jskit-ai/kernel": "0.1.
|
|
49
|
-
"@jskit-ai/realtime": "0.1.
|
|
50
|
-
"@jskit-ai/shell-web": "0.1.
|
|
51
|
-
"@jskit-ai/uploads-image-web": "0.1.
|
|
52
|
-
"@jskit-ai/users-core": "0.1.
|
|
47
|
+
"@jskit-ai/http-runtime": "0.1.67",
|
|
48
|
+
"@jskit-ai/kernel": "0.1.68",
|
|
49
|
+
"@jskit-ai/realtime": "0.1.67",
|
|
50
|
+
"@jskit-ai/shell-web": "0.1.67",
|
|
51
|
+
"@jskit-ai/uploads-image-web": "0.1.46",
|
|
52
|
+
"@jskit-ai/users-core": "0.1.78"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@tanstack/vue-query": "^5.90.5",
|
|
@@ -6,6 +6,7 @@ import { useAddEdit } from "./useAddEdit.js";
|
|
|
6
6
|
import {
|
|
7
7
|
resolveCrudBoundValues,
|
|
8
8
|
} from "../crud/crudBindingSupport.js";
|
|
9
|
+
import { resolveOperationRealtimeOptions } from "../useRealtimeQueryInvalidation.js";
|
|
9
10
|
import {
|
|
10
11
|
normalizeCrudFormFields,
|
|
11
12
|
createCrudFormModel,
|
|
@@ -64,6 +65,12 @@ function useCrudAddEdit({
|
|
|
64
65
|
operationName
|
|
65
66
|
}
|
|
66
67
|
);
|
|
68
|
+
const resolvedRealtime = resolveOperationRealtimeOptions({
|
|
69
|
+
realtime: normalizedAddEditOptions.realtime,
|
|
70
|
+
fallbackRealtime: resolvedResource?.operations?.[operationName]?.realtime ||
|
|
71
|
+
resolvedResource?.operations?.list?.realtime ||
|
|
72
|
+
null
|
|
73
|
+
});
|
|
67
74
|
const saveSuccessOptions = normalizeSaveSuccessOptions(saveSuccess);
|
|
68
75
|
const defaultFieldErrorKeys = normalizedFields.map((field) => field.key);
|
|
69
76
|
const providedFieldErrorKeys = normalizeFieldErrorKeys(normalizedAddEditOptions.fieldErrorKeys);
|
|
@@ -185,6 +192,7 @@ function useCrudAddEdit({
|
|
|
185
192
|
input: resolvedInput,
|
|
186
193
|
buildRawPayload: resolveBuildRawPayload,
|
|
187
194
|
mapLoadedToModel: effectiveMapLoadedToModel,
|
|
195
|
+
realtime: resolvedRealtime,
|
|
188
196
|
onSaveSuccess: handleSaveSuccess
|
|
189
197
|
});
|
|
190
198
|
addEditRuntime = addEdit;
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
toRouteParamValue
|
|
13
13
|
} from "../support/routeTemplateHelpers.js";
|
|
14
14
|
import { asPlainObject } from "../support/scopeHelpers.js";
|
|
15
|
+
import { resolveOperationRealtimeOptions } from "../useRealtimeQueryInvalidation.js";
|
|
15
16
|
import { useList } from "./useList.js";
|
|
16
17
|
|
|
17
18
|
function resolveRequestQueryParamsInput(requestQueryParams, context = {}) {
|
|
@@ -49,6 +50,7 @@ function useCrudList({
|
|
|
49
50
|
parentBinding = null,
|
|
50
51
|
recordIdParam = "recordId",
|
|
51
52
|
route = null,
|
|
53
|
+
realtime = undefined,
|
|
52
54
|
...listOptions
|
|
53
55
|
} = {}) {
|
|
54
56
|
const sourceRoute = route && typeof route === "object" ? route : useRoute();
|
|
@@ -72,6 +74,10 @@ function useCrudList({
|
|
|
72
74
|
transport: resolveCrudJsonApiTransport(listOptions.transport, resource, {
|
|
73
75
|
mode: "list"
|
|
74
76
|
}),
|
|
77
|
+
realtime: resolveOperationRealtimeOptions({
|
|
78
|
+
realtime,
|
|
79
|
+
fallbackRealtime: resource?.operations?.list?.realtime || null
|
|
80
|
+
}),
|
|
75
81
|
recordIdParam,
|
|
76
82
|
requestQueryParams(context = {}) {
|
|
77
83
|
const baseRequestQueryParams = resolveRequestQueryParamsInput(requestQueryParams, context);
|
|
@@ -8,6 +8,7 @@ import { setupOperationErrorReporting } from "../runtime/operationUiHelpers.js";
|
|
|
8
8
|
import { createViewUiRuntime } from "../runtime/viewUiRuntime.js";
|
|
9
9
|
import { createRequestQueryRuntime } from "../support/requestQueryRuntimeSupport.js";
|
|
10
10
|
import { resolveRouteParamNamesInOrder } from "../support/routeTemplateHelpers.js";
|
|
11
|
+
import { resolveOperationRealtimeOptions } from "../useRealtimeQueryInvalidation.js";
|
|
11
12
|
|
|
12
13
|
function useView({
|
|
13
14
|
ownershipFilter = ROUTE_VISIBILITY_WORKSPACE,
|
|
@@ -33,7 +34,7 @@ function useView({
|
|
|
33
34
|
listUrlTemplate = "",
|
|
34
35
|
editUrlTemplate = "",
|
|
35
36
|
includeRecordIdInQueryKey = false,
|
|
36
|
-
realtime =
|
|
37
|
+
realtime = undefined,
|
|
37
38
|
adapter = null
|
|
38
39
|
} = {}) {
|
|
39
40
|
const route = useRoute();
|
|
@@ -63,7 +64,12 @@ function useView({
|
|
|
63
64
|
permissionSets: {
|
|
64
65
|
view: viewPermissions
|
|
65
66
|
},
|
|
66
|
-
realtime
|
|
67
|
+
realtime: resolveOperationRealtimeOptions({
|
|
68
|
+
realtime,
|
|
69
|
+
fallbackRealtime: resource?.operations?.view?.realtime ||
|
|
70
|
+
resource?.operations?.list?.realtime ||
|
|
71
|
+
null
|
|
72
|
+
})
|
|
67
73
|
});
|
|
68
74
|
const queryParamsContext = computed(() => {
|
|
69
75
|
return Object.freeze({
|
|
@@ -4,6 +4,7 @@ import { usersWebHttpClient } from "../../lib/httpClient.js";
|
|
|
4
4
|
import { asPlainObject } from "../support/scopeHelpers.js";
|
|
5
5
|
import { resolveEnabledRef, resolveTextRef } from "../support/refValueHelpers.js";
|
|
6
6
|
import { toQueryErrorMessage } from "../support/errorMessageHelpers.js";
|
|
7
|
+
import { useOperationRealtime } from "../useRealtimeQueryInvalidation.js";
|
|
7
8
|
import {
|
|
8
9
|
hasResolvedQueryData,
|
|
9
10
|
mergeQueryMeta
|
|
@@ -71,6 +72,7 @@ function useEndpointResource({
|
|
|
71
72
|
readQuery = null,
|
|
72
73
|
transport = null,
|
|
73
74
|
refreshOnPull = false,
|
|
75
|
+
realtime = null,
|
|
74
76
|
queryOptions = null,
|
|
75
77
|
mutationOptions = null,
|
|
76
78
|
fallbackLoadError = "Unable to load resource.",
|
|
@@ -108,6 +110,11 @@ function useEndpointResource({
|
|
|
108
110
|
})
|
|
109
111
|
: asPlainObject(queryOptions))
|
|
110
112
|
});
|
|
113
|
+
const realtimeBinding = useOperationRealtime({
|
|
114
|
+
realtime,
|
|
115
|
+
queryKey,
|
|
116
|
+
enabled: queryEnabled
|
|
117
|
+
});
|
|
111
118
|
|
|
112
119
|
const mutation = useMutation({
|
|
113
120
|
mutationFn: async (request = {}) => {
|
|
@@ -168,6 +175,7 @@ function useEndpointResource({
|
|
|
168
175
|
isSaving,
|
|
169
176
|
loadError,
|
|
170
177
|
saveError,
|
|
178
|
+
realtime: realtimeBinding,
|
|
171
179
|
reload,
|
|
172
180
|
save
|
|
173
181
|
});
|
|
@@ -14,6 +14,31 @@ function normalizeRealtimeOptions(value = {}) {
|
|
|
14
14
|
return value;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
function hasRealtimeEventConfig(value = {}) {
|
|
18
|
+
const source = normalizeRealtimeOptions(value);
|
|
19
|
+
return Object.hasOwn(source, "event") || Object.hasOwn(source, "events");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function resolveOperationRealtimeOptions({
|
|
23
|
+
realtime = undefined,
|
|
24
|
+
fallbackRealtime = null
|
|
25
|
+
} = {}) {
|
|
26
|
+
if (realtime === false) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const fallback = normalizeRealtimeOptions(fallbackRealtime);
|
|
31
|
+
const explicit = realtime == null ? {} : normalizeRealtimeOptions(realtime);
|
|
32
|
+
if (hasRealtimeEventConfig(explicit) || !hasRealtimeEventConfig(fallback)) {
|
|
33
|
+
return Object.keys(explicit).length > 0 ? explicit : null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return Object.freeze({
|
|
37
|
+
...fallback,
|
|
38
|
+
...explicit
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
17
42
|
function resolveEnabled(value) {
|
|
18
43
|
if (typeof value === "undefined") {
|
|
19
44
|
return true;
|
|
@@ -125,6 +150,7 @@ function useOperationRealtime({
|
|
|
125
150
|
}
|
|
126
151
|
|
|
127
152
|
export {
|
|
153
|
+
resolveOperationRealtimeOptions,
|
|
128
154
|
useRealtimeQueryInvalidation,
|
|
129
155
|
useOperationRealtime
|
|
130
156
|
};
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
|
+
import { QueryClient, VueQueryPlugin } from "@tanstack/vue-query";
|
|
4
|
+
import { createSSRApp, h } from "vue";
|
|
5
|
+
import { renderToString } from "vue/server-renderer";
|
|
3
6
|
|
|
4
7
|
import {
|
|
5
8
|
buildEndpointReadRequestOptions,
|
|
6
|
-
buildEndpointWriteRequestOptions
|
|
9
|
+
buildEndpointWriteRequestOptions,
|
|
10
|
+
useEndpointResource
|
|
7
11
|
} from "../src/client/composables/runtime/useEndpointResource.js";
|
|
8
12
|
import { buildListRequestOptions } from "../src/client/composables/runtime/useListCore.js";
|
|
13
|
+
import {
|
|
14
|
+
resolveOperationRealtimeOptions
|
|
15
|
+
} from "../src/client/composables/useRealtimeQueryInvalidation.js";
|
|
9
16
|
|
|
10
17
|
test("endpoint read request options include transport only when provided", () => {
|
|
11
18
|
assert.deepEqual(
|
|
@@ -140,3 +147,105 @@ test("list request options preserve explicit limit values", () => {
|
|
|
140
147
|
}
|
|
141
148
|
);
|
|
142
149
|
});
|
|
150
|
+
|
|
151
|
+
test("operation realtime options use fallback events unless explicitly overridden", () => {
|
|
152
|
+
const fallbackRealtime = {
|
|
153
|
+
events: ["contacts.record.changed"]
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
assert.deepEqual(
|
|
157
|
+
resolveOperationRealtimeOptions({
|
|
158
|
+
fallbackRealtime
|
|
159
|
+
}),
|
|
160
|
+
{
|
|
161
|
+
events: ["contacts.record.changed"]
|
|
162
|
+
}
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
assert.deepEqual(
|
|
166
|
+
resolveOperationRealtimeOptions({
|
|
167
|
+
realtime: {
|
|
168
|
+
enabled: false
|
|
169
|
+
},
|
|
170
|
+
fallbackRealtime
|
|
171
|
+
}),
|
|
172
|
+
{
|
|
173
|
+
events: ["contacts.record.changed"],
|
|
174
|
+
enabled: false
|
|
175
|
+
}
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
assert.deepEqual(
|
|
179
|
+
resolveOperationRealtimeOptions({
|
|
180
|
+
realtime: {
|
|
181
|
+
event: "contacts.custom.changed"
|
|
182
|
+
},
|
|
183
|
+
fallbackRealtime
|
|
184
|
+
}),
|
|
185
|
+
{
|
|
186
|
+
event: "contacts.custom.changed"
|
|
187
|
+
}
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
assert.equal(
|
|
191
|
+
resolveOperationRealtimeOptions({
|
|
192
|
+
realtime: false,
|
|
193
|
+
fallbackRealtime
|
|
194
|
+
}),
|
|
195
|
+
null
|
|
196
|
+
);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("endpoint resources invalidate their query key from configured realtime events", async () => {
|
|
200
|
+
const queryClient = new QueryClient();
|
|
201
|
+
const invalidations = [];
|
|
202
|
+
const socketHandlers = new Map();
|
|
203
|
+
const socket = {
|
|
204
|
+
on(eventName, handler) {
|
|
205
|
+
socketHandlers.set(eventName, handler);
|
|
206
|
+
},
|
|
207
|
+
off(eventName, handler) {
|
|
208
|
+
if (socketHandlers.get(eventName) === handler) {
|
|
209
|
+
socketHandlers.delete(eventName);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
queryClient.invalidateQueries = async (options = {}) => {
|
|
214
|
+
invalidations.push(options);
|
|
215
|
+
};
|
|
216
|
+
const app = createSSRApp({
|
|
217
|
+
setup() {
|
|
218
|
+
useEndpointResource({
|
|
219
|
+
queryKey: ["today-workout-detail", "/api/today/workouts/2026-05-06"],
|
|
220
|
+
path: "/api/today/workouts/2026-05-06",
|
|
221
|
+
client: {
|
|
222
|
+
async request() {
|
|
223
|
+
return {};
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
realtime: {
|
|
227
|
+
event: "workout_set_logs.record.changed"
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
return () => h("div");
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
app.use(VueQueryPlugin, {
|
|
234
|
+
queryClient
|
|
235
|
+
});
|
|
236
|
+
app.provide("jskit.realtime.runtime.client.socket", socket);
|
|
237
|
+
await renderToString(app);
|
|
238
|
+
|
|
239
|
+
const handler = socketHandlers.get("workout_set_logs.record.changed");
|
|
240
|
+
assert.equal(typeof handler, "function");
|
|
241
|
+
handler({
|
|
242
|
+
entityId: "42"
|
|
243
|
+
});
|
|
244
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
245
|
+
|
|
246
|
+
assert.deepEqual(invalidations, [
|
|
247
|
+
{
|
|
248
|
+
queryKey: ["today-workout-detail", "/api/today/workouts/2026-05-06"]
|
|
249
|
+
}
|
|
250
|
+
]);
|
|
251
|
+
});
|