@platforma-sdk/ui-vue 1.54.13 → 1.55.0
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/.turbo/turbo-build.log +10 -9
- package/.turbo/turbo-formatter$colon$check.log +2 -2
- package/.turbo/turbo-linter$colon$check.log +2 -2
- package/.turbo/turbo-types$colon$check.log +1 -1
- package/CHANGELOG.md +12 -0
- package/dist/defineApp.d.ts +11 -11
- package/dist/defineApp.d.ts.map +1 -1
- package/dist/defineApp.js +90 -58
- package/dist/defineApp.js.map +1 -1
- package/dist/index.js +58 -54
- package/dist/index.js.map +1 -1
- package/dist/internal/createAppV3.d.ts +45 -22
- package/dist/internal/createAppV3.d.ts.map +1 -1
- package/dist/internal/createAppV3.js +103 -70
- package/dist/internal/createAppV3.js.map +1 -1
- package/dist/lib.d.ts +1 -0
- package/dist/lib.d.ts.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/usePluginData.d.ts +30 -0
- package/dist/usePluginData.d.ts.map +1 -0
- package/dist/usePluginData.js +22 -0
- package/dist/usePluginData.js.map +1 -0
- package/package.json +5 -5
- package/src/defineApp.ts +116 -87
- package/src/internal/createAppV3.ts +84 -12
- package/src/internal/v1.static-test.ts +2 -2
- package/src/internal/v2.static-test.ts +2 -2
- package/src/lib.ts +2 -0
- package/src/types.ts +1 -1
- package/src/usePluginData.ts +63 -0
package/src/defineApp.ts
CHANGED
|
@@ -9,10 +9,9 @@ import {
|
|
|
9
9
|
getPlatformaApiVersion,
|
|
10
10
|
unwrapResult,
|
|
11
11
|
type BlockOutputsBase,
|
|
12
|
-
type Platforma,
|
|
13
12
|
type BlockModelInfo,
|
|
14
13
|
} from "@platforma-sdk/model";
|
|
15
|
-
import type { Component, Reactive } from "vue";
|
|
14
|
+
import type { App as VueApp, Component, Reactive } from "vue";
|
|
16
15
|
import { inject, markRaw, reactive } from "vue";
|
|
17
16
|
import { createAppV1, type BaseAppV1 } from "./internal/createAppV1";
|
|
18
17
|
import { createAppV2, type BaseAppV2 } from "./internal/createAppV2";
|
|
@@ -21,6 +20,7 @@ import type { AppSettings, ExtendSettings, Routes } from "./types";
|
|
|
21
20
|
import { activateAgGrid } from "./aggrid";
|
|
22
21
|
|
|
23
22
|
const pluginKey = Symbol("sdk-vue");
|
|
23
|
+
export const pluginDataKey = Symbol("plugin-data-access");
|
|
24
24
|
|
|
25
25
|
export function useSdkPlugin(): SdkPlugin {
|
|
26
26
|
return inject(pluginKey)!;
|
|
@@ -57,39 +57,28 @@ export function defineApp<
|
|
|
57
57
|
export function defineApp<
|
|
58
58
|
Args = unknown,
|
|
59
59
|
Outputs extends BlockOutputsBase = BlockOutputsBase,
|
|
60
|
-
|
|
60
|
+
UiState = unknown,
|
|
61
61
|
Href extends `/${string}` = `/${string}`,
|
|
62
62
|
Extend extends ExtendSettings<Href> = ExtendSettings<Href>,
|
|
63
63
|
>(
|
|
64
|
-
platforma:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
extendApp: (app: BaseAppV3<Args, Outputs, Data, Href>) => Extend,
|
|
68
|
-
settings?: AppSettings,
|
|
69
|
-
): SdkPluginV3<Args, Outputs, Data, Href, Extend>;
|
|
64
|
+
platforma: PlatformaExtended<
|
|
65
|
+
PlatformaV1<Args, Outputs, UiState, Href> | PlatformaV2<Args, Outputs, UiState, Href>
|
|
66
|
+
>,
|
|
70
67
|
|
|
71
|
-
export function defineApp<
|
|
72
|
-
Args = unknown,
|
|
73
|
-
Outputs extends BlockOutputsBase = BlockOutputsBase,
|
|
74
|
-
UiStateOrData = unknown,
|
|
75
|
-
Href extends `/${string}` = `/${string}`,
|
|
76
|
-
Extend extends ExtendSettings<Href> = ExtendSettings<Href>,
|
|
77
|
-
>(
|
|
78
|
-
platforma: PlatformaExtended<Platforma<Args, Outputs, UiStateOrData, Href>>,
|
|
79
68
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
80
69
|
extendApp: (app: any) => Extend,
|
|
81
70
|
settings: AppSettings = {},
|
|
82
|
-
):
|
|
71
|
+
):
|
|
72
|
+
| SdkPluginV1<Args, Outputs, UiState, Href, Extend>
|
|
73
|
+
| SdkPluginV2<Args, Outputs, UiState, Href, Extend> {
|
|
83
74
|
let app:
|
|
84
|
-
|
|
|
85
|
-
|
|
|
86
|
-
|
|
|
87
|
-
| AppV3<Args, Outputs, UiStateOrData, Href, Extend> = undefined;
|
|
75
|
+
| AppV1<Args, Outputs, UiState, Href, Extend>
|
|
76
|
+
| AppV2<Args, Outputs, UiState, Href, Extend>
|
|
77
|
+
| undefined = undefined;
|
|
88
78
|
|
|
89
79
|
activateAgGrid();
|
|
90
80
|
|
|
91
81
|
const runtimeApiVersion = platforma.apiVersion ?? 1; // undefined means 1 (backward compatibility)
|
|
92
|
-
|
|
93
82
|
const blockRequestedApiVersion = getPlatformaApiVersion();
|
|
94
83
|
|
|
95
84
|
const loadApp = async () => {
|
|
@@ -101,7 +90,7 @@ export function defineApp<
|
|
|
101
90
|
if (platforma.apiVersion === undefined || platforma.apiVersion === 1) {
|
|
102
91
|
await platforma.loadBlockState().then((state) => {
|
|
103
92
|
plugin.loaded = true;
|
|
104
|
-
const baseApp = createAppV1<Args, Outputs,
|
|
93
|
+
const baseApp = createAppV1<Args, Outputs, UiState, Href>(state, platforma, settings);
|
|
105
94
|
|
|
106
95
|
const localState = extendApp(baseApp);
|
|
107
96
|
|
|
@@ -117,13 +106,13 @@ export function defineApp<
|
|
|
117
106
|
getRoute(href: Href): Component | undefined {
|
|
118
107
|
return routes[href];
|
|
119
108
|
},
|
|
120
|
-
} as unknown as AppV1<Args, Outputs,
|
|
109
|
+
} as unknown as AppV1<Args, Outputs, UiState, Href, Extend>);
|
|
121
110
|
});
|
|
122
111
|
} else if (platforma.apiVersion === 2) {
|
|
123
112
|
await platforma.loadBlockState().then((stateOrError) => {
|
|
124
113
|
const state = unwrapResult(stateOrError);
|
|
125
114
|
plugin.loaded = true;
|
|
126
|
-
const baseApp = createAppV2<Args, Outputs,
|
|
115
|
+
const baseApp = createAppV2<Args, Outputs, UiState, Href>(state, platforma, settings);
|
|
127
116
|
|
|
128
117
|
const localState = extendApp(baseApp);
|
|
129
118
|
|
|
@@ -139,50 +128,106 @@ export function defineApp<
|
|
|
139
128
|
getRoute(href: Href): Component | undefined {
|
|
140
129
|
return routes[href];
|
|
141
130
|
},
|
|
142
|
-
} as unknown as AppV2<Args, Outputs,
|
|
131
|
+
} as unknown as AppV2<Args, Outputs, UiState, Href, Extend>);
|
|
143
132
|
});
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const state = unwrapResult(stateOrError);
|
|
147
|
-
plugin.loaded = true;
|
|
148
|
-
const baseApp = createAppV3<Args, Outputs, UiStateOrData, Href>(state, platforma, settings);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
149
135
|
|
|
150
|
-
|
|
136
|
+
const plugin = reactive({
|
|
137
|
+
apiVersion: platforma.apiVersion ?? 1,
|
|
138
|
+
loaded: false,
|
|
139
|
+
error: undefined as unknown,
|
|
140
|
+
useApp<PageHref extends Href = Href>() {
|
|
141
|
+
return notEmpty(app, "App is not loaded") as
|
|
142
|
+
| AppV1<Args, Outputs, UiState, PageHref, Extend>
|
|
143
|
+
| AppV2<Args, Outputs, UiState, PageHref, Extend>;
|
|
144
|
+
},
|
|
145
|
+
install(app: VueApp) {
|
|
146
|
+
app.provide(pluginKey, this);
|
|
147
|
+
loadApp().catch((err) => {
|
|
148
|
+
console.error("load initial state error", err);
|
|
149
|
+
plugin.error = err;
|
|
150
|
+
});
|
|
151
|
+
},
|
|
152
|
+
});
|
|
151
153
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}),
|
|
157
|
-
);
|
|
154
|
+
return plugin as
|
|
155
|
+
| SdkPluginV1<Args, Outputs, UiState, Href, Extend>
|
|
156
|
+
| SdkPluginV2<Args, Outputs, UiState, Href, Extend>;
|
|
157
|
+
}
|
|
158
158
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
159
|
+
export function defineAppV3<
|
|
160
|
+
Data = unknown,
|
|
161
|
+
Args = unknown,
|
|
162
|
+
Outputs extends BlockOutputsBase = BlockOutputsBase,
|
|
163
|
+
Href extends `/${string}` = `/${string}`,
|
|
164
|
+
Extend extends ExtendSettings<Href> = ExtendSettings<Href>,
|
|
165
|
+
>(
|
|
166
|
+
platforma: PlatformaV3<Data, Args, Outputs, Href> & {
|
|
167
|
+
blockModelInfo: BlockModelInfo;
|
|
168
|
+
},
|
|
169
|
+
extendApp: (app: BaseAppV3<Data, Args, Outputs, Href>) => Extend,
|
|
170
|
+
settings: AppSettings = {},
|
|
171
|
+
): SdkPluginV3<Data, Args, Outputs, Href, Extend> {
|
|
172
|
+
let app: AppV3<Data, Args, Outputs, Href, Extend> | undefined = undefined;
|
|
173
|
+
|
|
174
|
+
// Captured during install() so V3 can provide plugin data access after async load
|
|
175
|
+
let vueAppInstance: VueApp | undefined;
|
|
176
|
+
|
|
177
|
+
activateAgGrid();
|
|
178
|
+
|
|
179
|
+
const runtimeApiVersion = 3;
|
|
180
|
+
const blockRequestedApiVersion = getPlatformaApiVersion();
|
|
181
|
+
|
|
182
|
+
const loadApp = async () => {
|
|
183
|
+
if (blockRequestedApiVersion !== runtimeApiVersion) {
|
|
184
|
+
throw new Error(`Block requested API version ${blockRequestedApiVersion} but runtime API version is ${runtimeApiVersion}.
|
|
185
|
+
Please update the desktop app to use the latest API version.`);
|
|
166
186
|
}
|
|
187
|
+
|
|
188
|
+
await platforma.loadBlockState().then((stateOrError) => {
|
|
189
|
+
const state = unwrapResult(stateOrError);
|
|
190
|
+
plugin.loaded = true;
|
|
191
|
+
const { app: baseApp, pluginAccess } = createAppV3<Data, Args, Outputs, Href>(
|
|
192
|
+
state,
|
|
193
|
+
platforma,
|
|
194
|
+
settings,
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
if (!vueAppInstance) {
|
|
198
|
+
throw new Error(
|
|
199
|
+
"Plugin data injection failed: Vue app instance not captured during install()",
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
vueAppInstance.provide(pluginDataKey, pluginAccess);
|
|
203
|
+
|
|
204
|
+
const localState = extendApp(baseApp);
|
|
205
|
+
|
|
206
|
+
const routes = Object.fromEntries(
|
|
207
|
+
Object.entries(localState.routes as Routes<Href>).map(([href, component]) => {
|
|
208
|
+
const c = typeof component === "function" ? component() : component;
|
|
209
|
+
return [href, markRaw(c as Component)];
|
|
210
|
+
}),
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
app = Object.assign(baseApp, {
|
|
214
|
+
...localState,
|
|
215
|
+
getRoute(href: Href): Component | undefined {
|
|
216
|
+
return routes[href];
|
|
217
|
+
},
|
|
218
|
+
} as unknown as AppV3<Data, Args, Outputs, Href, Extend>);
|
|
219
|
+
});
|
|
167
220
|
};
|
|
168
221
|
|
|
169
222
|
const plugin = reactive({
|
|
170
|
-
apiVersion:
|
|
223
|
+
apiVersion: 3,
|
|
171
224
|
loaded: false,
|
|
172
225
|
error: undefined as unknown,
|
|
173
|
-
// Href to get typed query parameters for a specific route
|
|
174
226
|
useApp<PageHref extends Href = Href>() {
|
|
175
|
-
return notEmpty(app, "App is not loaded") as
|
|
176
|
-
Args,
|
|
177
|
-
Outputs,
|
|
178
|
-
UiStateOrData,
|
|
179
|
-
PageHref,
|
|
180
|
-
Extend
|
|
181
|
-
>;
|
|
227
|
+
return notEmpty(app, "App is not loaded") as AppV3<Data, Args, Outputs, PageHref, Extend>;
|
|
182
228
|
},
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
install(app: any) {
|
|
229
|
+
install(app: VueApp) {
|
|
230
|
+
vueAppInstance = app;
|
|
186
231
|
app.provide(pluginKey, this);
|
|
187
232
|
loadApp().catch((err) => {
|
|
188
233
|
console.error("load initial state error", err);
|
|
@@ -191,7 +236,7 @@ export function defineApp<
|
|
|
191
236
|
},
|
|
192
237
|
});
|
|
193
238
|
|
|
194
|
-
return plugin as
|
|
239
|
+
return plugin as SdkPluginV3<Data, Args, Outputs, Href, Extend>;
|
|
195
240
|
}
|
|
196
241
|
|
|
197
242
|
export type AppV1<
|
|
@@ -213,24 +258,17 @@ export type AppV2<
|
|
|
213
258
|
Reactive<Omit<Local, "routes">> & { getRoute(href: Href): Component | undefined };
|
|
214
259
|
|
|
215
260
|
export type AppV3<
|
|
216
|
-
Args = unknown,
|
|
217
|
-
Outputs extends BlockOutputsBase = BlockOutputsBase,
|
|
218
261
|
Data = unknown,
|
|
262
|
+
Args = unknown,
|
|
263
|
+
Outputs extends BlockOutputsBase = NonNullable<unknown>,
|
|
219
264
|
Href extends `/${string}` = `/${string}`,
|
|
220
265
|
Local extends ExtendSettings<Href> = ExtendSettings<Href>,
|
|
221
|
-
> = BaseAppV3<Args, Outputs,
|
|
266
|
+
> = BaseAppV3<Data, Args, Outputs, Href> &
|
|
222
267
|
Reactive<Omit<Local, "routes">> & { getRoute(href: Href): Component | undefined };
|
|
223
268
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
UiState = unknown,
|
|
228
|
-
Href extends `/${string}` = `/${string}`,
|
|
229
|
-
Local extends ExtendSettings<Href> = ExtendSettings<Href>,
|
|
230
|
-
> =
|
|
231
|
-
| AppV1<Args, Outputs, UiState, Href, Local>
|
|
232
|
-
| AppV2<Args, Outputs, UiState, Href, Local>
|
|
233
|
-
| AppV3<Args, Outputs, UiState, Href, Local>;
|
|
269
|
+
// ---------------------------------------------------------------------------
|
|
270
|
+
// SdkPlugin types
|
|
271
|
+
// ---------------------------------------------------------------------------
|
|
234
272
|
|
|
235
273
|
export type SdkPluginV1<
|
|
236
274
|
Args = unknown,
|
|
@@ -243,7 +281,7 @@ export type SdkPluginV1<
|
|
|
243
281
|
loaded: boolean;
|
|
244
282
|
error: unknown;
|
|
245
283
|
useApp<PageHref extends Href = Href>(): AppV1<Args, Outputs, UiState, PageHref, Local>;
|
|
246
|
-
install(app:
|
|
284
|
+
install(app: VueApp): void;
|
|
247
285
|
};
|
|
248
286
|
|
|
249
287
|
export type SdkPluginV2<
|
|
@@ -257,30 +295,21 @@ export type SdkPluginV2<
|
|
|
257
295
|
loaded: boolean;
|
|
258
296
|
error: unknown;
|
|
259
297
|
useApp<PageHref extends Href = Href>(): AppV2<Args, Outputs, UiState, PageHref, Local>;
|
|
260
|
-
install(app:
|
|
298
|
+
install(app: VueApp): void;
|
|
261
299
|
};
|
|
262
300
|
|
|
263
301
|
export type SdkPluginV3<
|
|
302
|
+
Data = unknown,
|
|
264
303
|
Args = unknown,
|
|
265
304
|
Outputs extends BlockOutputsBase = BlockOutputsBase,
|
|
266
|
-
Data = unknown,
|
|
267
305
|
Href extends `/${string}` = `/${string}`,
|
|
268
306
|
Local extends ExtendSettings<Href> = ExtendSettings<Href>,
|
|
269
307
|
> = {
|
|
270
308
|
apiVersion: 3;
|
|
271
309
|
loaded: boolean;
|
|
272
310
|
error: unknown;
|
|
273
|
-
useApp<PageHref extends Href = Href>(): AppV3<Args, Outputs,
|
|
274
|
-
install(app:
|
|
311
|
+
useApp<PageHref extends Href = Href>(): AppV3<Data, Args, Outputs, PageHref, Local>;
|
|
312
|
+
install(app: VueApp): void;
|
|
275
313
|
};
|
|
276
314
|
|
|
277
|
-
export type SdkPlugin
|
|
278
|
-
Args = unknown,
|
|
279
|
-
Outputs extends BlockOutputsBase = BlockOutputsBase,
|
|
280
|
-
UiState = unknown,
|
|
281
|
-
Href extends `/${string}` = `/${string}`,
|
|
282
|
-
Local extends ExtendSettings<Href> = ExtendSettings<Href>,
|
|
283
|
-
> =
|
|
284
|
-
| SdkPluginV1<Args, Outputs, UiState, Href, Local>
|
|
285
|
-
| SdkPluginV2<Args, Outputs, UiState, Href, Local>
|
|
286
|
-
| SdkPluginV3<Args, Outputs, UiState, Href, Local>;
|
|
315
|
+
export type SdkPlugin = SdkPluginV1 | SdkPluginV2 | SdkPluginV3;
|
|
@@ -7,19 +7,33 @@ import type {
|
|
|
7
7
|
PlatformaV3,
|
|
8
8
|
ValueWithUTag,
|
|
9
9
|
AuthorMarker,
|
|
10
|
+
PlatformaExtended,
|
|
11
|
+
} from "@platforma-sdk/model";
|
|
12
|
+
import {
|
|
13
|
+
hasAbortError,
|
|
14
|
+
unwrapResult,
|
|
15
|
+
deriveDataFromStorage,
|
|
16
|
+
getPluginData,
|
|
17
|
+
normalizeBlockStorage,
|
|
10
18
|
} from "@platforma-sdk/model";
|
|
11
|
-
import { hasAbortError, unwrapResult, deriveDataFromStorage } from "@platforma-sdk/model";
|
|
12
19
|
import type { Ref } from "vue";
|
|
13
20
|
import { reactive, computed, ref } from "vue";
|
|
14
21
|
import type { OutputValues, OutputErrors, AppSettings } from "../types";
|
|
15
22
|
import { parseQuery } from "../urls";
|
|
16
|
-
import { MultiError } from "../utils";
|
|
23
|
+
import { ensureOutputHasStableFlag, MultiError } from "../utils";
|
|
17
24
|
import { applyPatch } from "fast-json-patch";
|
|
18
25
|
import { UpdateSerializer } from "./UpdateSerializer";
|
|
19
26
|
import { watchIgnorable } from "@vueuse/core";
|
|
20
27
|
|
|
21
28
|
export const patchPoolingDelay = 150;
|
|
22
29
|
|
|
30
|
+
/** Internal interface for plugin data access — injected separately from the app. */
|
|
31
|
+
export interface PluginDataAccess {
|
|
32
|
+
readonly pluginDataMap: Record<string, unknown>;
|
|
33
|
+
setPluginData(pluginId: string, value: unknown): Promise<boolean>;
|
|
34
|
+
initPluginDataSlot(pluginId: string): void;
|
|
35
|
+
}
|
|
36
|
+
|
|
23
37
|
export const createNextAuthorMarker = (marker: AuthorMarker | undefined): AuthorMarker => ({
|
|
24
38
|
authorId: marker?.authorId ?? uniqueId(),
|
|
25
39
|
localVersion: (marker?.localVersion ?? 0) + 1,
|
|
@@ -48,13 +62,13 @@ const stringifyForDebug = (v: unknown) => {
|
|
|
48
62
|
* @returns A reactive application object with methods, getters, and state.
|
|
49
63
|
*/
|
|
50
64
|
export function createAppV3<
|
|
65
|
+
Data = unknown,
|
|
51
66
|
Args = unknown,
|
|
52
67
|
Outputs extends BlockOutputsBase = BlockOutputsBase,
|
|
53
|
-
Data = unknown,
|
|
54
68
|
Href extends `/${string}` = `/${string}`,
|
|
55
69
|
>(
|
|
56
|
-
state: ValueWithUTag<BlockStateV3<
|
|
57
|
-
platforma: PlatformaV3<Args, Outputs,
|
|
70
|
+
state: ValueWithUTag<BlockStateV3<Data, Outputs, Href>>,
|
|
71
|
+
platforma: PlatformaExtended<PlatformaV3<Data, Args, Outputs, Href>>,
|
|
58
72
|
settings: AppSettings,
|
|
59
73
|
) {
|
|
60
74
|
const debug = (msg: string, ...rest: unknown[]) => {
|
|
@@ -98,7 +112,19 @@ export function createAppV3<
|
|
|
98
112
|
const debounceSpan = settings.debounceSpan ?? 200;
|
|
99
113
|
|
|
100
114
|
const setDataQueue = new UpdateSerializer({ debounceSpan });
|
|
115
|
+
const pluginDataQueues = new Map<string, UpdateSerializer>();
|
|
116
|
+
const getPluginDataQueue = (pluginId: string): UpdateSerializer => {
|
|
117
|
+
let queue = pluginDataQueues.get(pluginId);
|
|
118
|
+
if (!queue) {
|
|
119
|
+
queue = new UpdateSerializer({ debounceSpan });
|
|
120
|
+
pluginDataQueues.set(pluginId, queue);
|
|
121
|
+
}
|
|
122
|
+
return queue;
|
|
123
|
+
};
|
|
101
124
|
const setNavigationStateQueue = new UpdateSerializer({ debounceSpan });
|
|
125
|
+
|
|
126
|
+
/** Reactive map of plugin data keyed by pluginId. Optimistic state for plugin components. */
|
|
127
|
+
const pluginDataMap = reactive<Record<string, unknown>>({});
|
|
102
128
|
/**
|
|
103
129
|
* Reactive snapshot of the application state, including args, outputs, UI state, and navigation state.
|
|
104
130
|
*/
|
|
@@ -113,7 +139,20 @@ export function createAppV3<
|
|
|
113
139
|
}>;
|
|
114
140
|
|
|
115
141
|
const updateData = async (value: Data) => {
|
|
116
|
-
return platforma.mutateStorage({ operation: "update-data", value }, nextAuthorMarker());
|
|
142
|
+
return platforma.mutateStorage({ operation: "update-block-data", value }, nextAuthorMarker());
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const updatePluginData = async (pluginId: string, value: unknown) => {
|
|
146
|
+
return platforma.mutateStorage(
|
|
147
|
+
{ operation: "update-plugin-data", pluginId, value },
|
|
148
|
+
nextAuthorMarker(),
|
|
149
|
+
);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/** Derives plugin data for a given pluginId from the current snapshot. */
|
|
153
|
+
const derivePluginDataFromSnapshot = (pluginId: string): unknown => {
|
|
154
|
+
const storage = normalizeBlockStorage(snapshot.value.blockStorage);
|
|
155
|
+
return getPluginData(storage, pluginId);
|
|
117
156
|
};
|
|
118
157
|
|
|
119
158
|
const setNavigationState = async (state: NavigationState<Href>) => {
|
|
@@ -122,7 +161,15 @@ export function createAppV3<
|
|
|
122
161
|
|
|
123
162
|
const outputs = computed<OutputValues<Outputs>>(() => {
|
|
124
163
|
const entries = Object.entries(snapshot.value.outputs as Partial<Readonly<Outputs>>).map(
|
|
125
|
-
([k,
|
|
164
|
+
([k, outputWithStatus]) =>
|
|
165
|
+
platforma.blockModelInfo.outputs[k]?.withStatus
|
|
166
|
+
? [k, ensureOutputHasStableFlag(outputWithStatus)]
|
|
167
|
+
: [
|
|
168
|
+
k,
|
|
169
|
+
outputWithStatus.ok && outputWithStatus.value !== undefined
|
|
170
|
+
? outputWithStatus.value
|
|
171
|
+
: undefined,
|
|
172
|
+
],
|
|
126
173
|
);
|
|
127
174
|
return Object.fromEntries(entries);
|
|
128
175
|
});
|
|
@@ -205,6 +252,10 @@ export function createAppV3<
|
|
|
205
252
|
ignoreUpdates(() => {
|
|
206
253
|
snapshot.value = applyPatch(snapshot.value, patches.value, false, false).newDocument;
|
|
207
254
|
updateAppModel({ data: deriveDataFromStorage<Data>(snapshot.value.blockStorage) });
|
|
255
|
+
// Reconcile plugin data from external source
|
|
256
|
+
for (const pluginId of Object.keys(pluginDataMap)) {
|
|
257
|
+
pluginDataMap[pluginId] = derivePluginDataFromSnapshot(pluginId);
|
|
258
|
+
}
|
|
208
259
|
data.isExternalSnapshot = isAuthorChanged;
|
|
209
260
|
});
|
|
210
261
|
} else {
|
|
@@ -261,7 +312,28 @@ export function createAppV3<
|
|
|
261
312
|
},
|
|
262
313
|
async allSettled() {
|
|
263
314
|
await delay(0);
|
|
264
|
-
|
|
315
|
+
const allQueues = [
|
|
316
|
+
setDataQueue.allSettled(),
|
|
317
|
+
...Array.from(pluginDataQueues.values()).map((q) => q.allSettled()),
|
|
318
|
+
];
|
|
319
|
+
await Promise.all(allQueues);
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
/** Plugin internals — provided via separate injection key, not exposed on useApp(). */
|
|
324
|
+
const pluginAccess: PluginDataAccess = {
|
|
325
|
+
pluginDataMap,
|
|
326
|
+
setPluginData(pluginId: string, value: unknown): Promise<boolean> {
|
|
327
|
+
pluginDataMap[pluginId] = value;
|
|
328
|
+
debug("setPluginData", pluginId, value);
|
|
329
|
+
return getPluginDataQueue(pluginId).run(() =>
|
|
330
|
+
updatePluginData(pluginId, value).then(unwrapResult),
|
|
331
|
+
);
|
|
332
|
+
},
|
|
333
|
+
initPluginDataSlot(pluginId: string): void {
|
|
334
|
+
if (!(pluginId in pluginDataMap)) {
|
|
335
|
+
pluginDataMap[pluginId] = derivePluginDataFromSnapshot(pluginId);
|
|
336
|
+
}
|
|
265
337
|
},
|
|
266
338
|
};
|
|
267
339
|
|
|
@@ -275,19 +347,19 @@ export function createAppV3<
|
|
|
275
347
|
),
|
|
276
348
|
};
|
|
277
349
|
|
|
278
|
-
const app = reactive(Object.assign(appModel,
|
|
350
|
+
const app = Object.assign(reactive(Object.assign(appModel, getters)), methods);
|
|
279
351
|
|
|
280
352
|
if (settings.debug) {
|
|
281
353
|
// @ts-expect-error (to inspect in console in debug mode)
|
|
282
354
|
globalThis.__block_app__ = app;
|
|
283
355
|
}
|
|
284
356
|
|
|
285
|
-
return app;
|
|
357
|
+
return { app, pluginAccess };
|
|
286
358
|
}
|
|
287
359
|
|
|
288
360
|
export type BaseAppV3<
|
|
361
|
+
Data = unknown,
|
|
289
362
|
Args = unknown,
|
|
290
363
|
Outputs extends BlockOutputsBase = BlockOutputsBase,
|
|
291
|
-
Data = unknown,
|
|
292
364
|
Href extends `/${string}` = `/${string}`,
|
|
293
|
-
> = ReturnType<typeof createAppV3<Args, Outputs,
|
|
365
|
+
> = ReturnType<typeof createAppV3<Data, Args, Outputs, Href>>["app"];
|
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
Platforma,
|
|
10
10
|
} from "@platforma-sdk/model";
|
|
11
11
|
import type { BaseAppV1, createAppV1 } from "./createAppV1";
|
|
12
|
-
import { type
|
|
12
|
+
import { type AppV1 } from "../defineApp";
|
|
13
13
|
import { computed, type Component } from "vue";
|
|
14
14
|
|
|
15
15
|
declare function __createModel<M, V = unknown>(options: ModelOptions<M, V>): Model<M>;
|
|
@@ -84,7 +84,7 @@ const _local = () => {
|
|
|
84
84
|
};
|
|
85
85
|
};
|
|
86
86
|
|
|
87
|
-
type ExtApp =
|
|
87
|
+
type ExtApp = AppV1<1, BlockOutputsBase, unknown, "/", ReturnType<typeof _local>>;
|
|
88
88
|
|
|
89
89
|
type _UpdateArgsParams = Parameters<Parameters<_App1["updateArgs"]>[0]>[0];
|
|
90
90
|
|
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
Platforma,
|
|
10
10
|
} from "@platforma-sdk/model";
|
|
11
11
|
import type { BaseAppV2, createAppV2 } from "./createAppV2";
|
|
12
|
-
import { type
|
|
12
|
+
import { type AppV2 } from "../defineApp";
|
|
13
13
|
import { computed, type Component } from "vue";
|
|
14
14
|
|
|
15
15
|
declare function __createModel<M, V = unknown>(options: ModelOptions<M, V>): Model<M>;
|
|
@@ -84,7 +84,7 @@ const _local = () => {
|
|
|
84
84
|
};
|
|
85
85
|
};
|
|
86
86
|
|
|
87
|
-
type ExtApp =
|
|
87
|
+
type ExtApp = AppV2<1, BlockOutputsBase, unknown, "/", ReturnType<typeof _local>>;
|
|
88
88
|
|
|
89
89
|
type _UpdateArgsParams = Parameters<Parameters<_App1["updateArgs"]>[0]>[0];
|
|
90
90
|
|
package/src/lib.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { computed, inject } from "vue";
|
|
2
|
+
import { deepClone } from "@milaboratories/helpers";
|
|
3
|
+
import { pluginDataKey } from "./defineApp";
|
|
4
|
+
import type { PluginDataAccess } from "./internal/createAppV3";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Composable for accessing and updating plugin-specific data.
|
|
8
|
+
*
|
|
9
|
+
* Plugin components are self-contained: they use this composable to read/write
|
|
10
|
+
* their own data slice from BlockStorage without knowing about the parent block's data.
|
|
11
|
+
*
|
|
12
|
+
* Requires a V3 block (BlockModelV3). Throws if used in a V1/V2 block.
|
|
13
|
+
*
|
|
14
|
+
* @param pluginId - The plugin instance ID (must match the ID used in BlockModelV3.plugin()).
|
|
15
|
+
* Must be a static value; changing it after mount will not re-bind the composable.
|
|
16
|
+
* @returns `{ data, updateData }` where `data` is a reactive ref to the plugin's data,
|
|
17
|
+
* and `updateData` returns a promise resolving to `true` if the mutation was sent,
|
|
18
|
+
* or `false` if data is not yet available.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```vue
|
|
22
|
+
* <script setup lang="ts">
|
|
23
|
+
* const { data, updateData } = usePluginData<CounterData>('counter');
|
|
24
|
+
*
|
|
25
|
+
* function increment() {
|
|
26
|
+
* updateData((d) => ({ ...d, count: d.count + 1 }));
|
|
27
|
+
* }
|
|
28
|
+
* </script>
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function usePluginData<Data>(pluginId: string) {
|
|
32
|
+
const access = inject<PluginDataAccess>(pluginDataKey);
|
|
33
|
+
|
|
34
|
+
if (!access) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
"usePluginData requires a V3 block (BlockModelV3). " +
|
|
37
|
+
"Make sure the block uses apiVersion 3 and the plugin is installed.",
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Initialize the plugin data slot from snapshot if not already done
|
|
42
|
+
access.initPluginDataSlot(pluginId);
|
|
43
|
+
|
|
44
|
+
// Reactive reference to the plugin's data in the shared optimistic map
|
|
45
|
+
const data = computed<Data | undefined>(() => {
|
|
46
|
+
return access.pluginDataMap[pluginId] as Data | undefined;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Update plugin data with optimistic feedback.
|
|
51
|
+
*
|
|
52
|
+
* @param cb - Callback that receives a deep clone of current data and returns new data.
|
|
53
|
+
* @returns Promise that resolves to true when the mutation is sent
|
|
54
|
+
*/
|
|
55
|
+
const updateData = (cb: (current: Data) => Data): Promise<boolean> => {
|
|
56
|
+
const current = data.value;
|
|
57
|
+
if (current === undefined) return Promise.resolve(false);
|
|
58
|
+
const newValue = cb(deepClone(current));
|
|
59
|
+
return access.setPluginData(pluginId, newValue);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return { data, updateData };
|
|
63
|
+
}
|