@platforma-sdk/ui-vue 1.61.2 → 1.62.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/.oxfmtrc.json +1 -1
- package/.turbo/turbo-build.log +13 -12
- 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 +35 -0
- package/dist/components/PlAnnotations/components/PlAnnotations.vue2.js.map +1 -1
- package/dist/defineApp.d.ts +6 -6
- package/dist/defineApp.d.ts.map +1 -1
- package/dist/defineApp.js.map +1 -1
- package/dist/internal/createAppV3.d.ts +4 -2
- package/dist/internal/createAppV3.d.ts.map +1 -1
- package/dist/internal/createAppV3.js +85 -82
- package/dist/internal/createAppV3.js.map +1 -1
- package/dist/internal/service_factories.d.ts +10 -0
- package/dist/internal/service_factories.d.ts.map +1 -0
- package/dist/internal/service_factories.js +19 -0
- package/dist/internal/service_factories.js.map +1 -0
- package/dist/usePlugin.d.ts +5 -4
- package/dist/usePlugin.d.ts.map +1 -1
- package/dist/usePlugin.js.map +1 -1
- package/package.json +5 -3
- package/src/defineApp.ts +30 -14
- package/src/internal/createAppV3.test.ts +95 -0
- package/src/internal/createAppV3.ts +38 -15
- package/src/internal/service_factories.ts +23 -0
- package/src/usePlugin.ts +8 -2
|
@@ -165,6 +165,11 @@ function createMockApiV3<
|
|
|
165
165
|
async dispose(): Promise<ResultOrError<void>> {
|
|
166
166
|
return { value: undefined };
|
|
167
167
|
},
|
|
168
|
+
serviceDispatch: {
|
|
169
|
+
getServiceNames: () => [],
|
|
170
|
+
getServiceMethods: () => [],
|
|
171
|
+
callServiceMethod: () => undefined,
|
|
172
|
+
},
|
|
168
173
|
//
|
|
169
174
|
blobDriver: undefined as any,
|
|
170
175
|
//
|
|
@@ -521,6 +526,96 @@ describe("createAppV3", { timeout: 20_000 }, () => {
|
|
|
521
526
|
app.closedRef = true;
|
|
522
527
|
});
|
|
523
528
|
|
|
529
|
+
it("should default stable to true on plugin withStatus outputs when stable is absent", async () => {
|
|
530
|
+
type F = PluginFactory<PluginData, undefined, { pFrame: OutputWithStatus<string | undefined> }>;
|
|
531
|
+
const pluginId = "graphMaker" as PluginHandle<F>;
|
|
532
|
+
const pluginOutputName = pluginOutputKey(pluginId, "pFrame");
|
|
533
|
+
|
|
534
|
+
const outputsWithoutStable = {
|
|
535
|
+
...defaultOutputs(),
|
|
536
|
+
[pluginOutputName]: { ok: true, value: undefined } as OutputWithStatus<string | undefined>,
|
|
537
|
+
} as Outputs;
|
|
538
|
+
|
|
539
|
+
const state = new BlockStateV3Mock<Data, Outputs>(defaultData(), outputsWithoutStable, "/", {
|
|
540
|
+
[pluginId]: defaultPluginData(),
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
const blockModelInfo: BlockModelInfo = {
|
|
544
|
+
outputs: {
|
|
545
|
+
doubled: { withStatus: false },
|
|
546
|
+
[pluginOutputName]: { withStatus: true },
|
|
547
|
+
},
|
|
548
|
+
pluginIds: [pluginId],
|
|
549
|
+
featureFlags: {},
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
const platforma = createMockApiV3<Data, Args, Outputs>(state, blockModelInfo);
|
|
553
|
+
const initialState = await platforma.loadBlockState();
|
|
554
|
+
if ("error" in initialState) throw initialState.error;
|
|
555
|
+
|
|
556
|
+
const { pluginAccess } = createAppV3<Data, Args, Outputs>(initialState.value!, platforma, {
|
|
557
|
+
debug: false,
|
|
558
|
+
debounceSpan: 10,
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
const pluginState = pluginAccess.getOrCreatePluginState(pluginId);
|
|
562
|
+
|
|
563
|
+
const pFrame = pluginState.model.outputs["pFrame"] as Extract<
|
|
564
|
+
OutputWithStatus<string | undefined>,
|
|
565
|
+
{ ok: true }
|
|
566
|
+
>;
|
|
567
|
+
expect(pFrame).toBeDefined();
|
|
568
|
+
expect(pFrame.ok).toBe(true);
|
|
569
|
+
expect(pFrame.stable).toBe(true);
|
|
570
|
+
expect(pFrame.value).toBeUndefined();
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
it("should preserve stable=false on plugin withStatus outputs when explicitly set", async () => {
|
|
574
|
+
type F = PluginFactory<PluginData, undefined, { pFrame: OutputWithStatus<string | undefined> }>;
|
|
575
|
+
const pluginId = "graphMaker" as PluginHandle<F>;
|
|
576
|
+
const pluginOutputName = pluginOutputKey(pluginId, "pFrame");
|
|
577
|
+
|
|
578
|
+
const outputsUnstable = {
|
|
579
|
+
...defaultOutputs(),
|
|
580
|
+
[pluginOutputName]: { ok: true, value: undefined, stable: false } as OutputWithStatus<
|
|
581
|
+
string | undefined
|
|
582
|
+
>,
|
|
583
|
+
} as Outputs;
|
|
584
|
+
|
|
585
|
+
const state = new BlockStateV3Mock<Data, Outputs>(defaultData(), outputsUnstable, "/", {
|
|
586
|
+
[pluginId]: defaultPluginData(),
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
const blockModelInfo: BlockModelInfo = {
|
|
590
|
+
outputs: {
|
|
591
|
+
doubled: { withStatus: false },
|
|
592
|
+
[pluginOutputName]: { withStatus: true },
|
|
593
|
+
},
|
|
594
|
+
pluginIds: [pluginId],
|
|
595
|
+
featureFlags: {},
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
const platforma = createMockApiV3<Data, Args, Outputs>(state, blockModelInfo);
|
|
599
|
+
const initialState = await platforma.loadBlockState();
|
|
600
|
+
if ("error" in initialState) throw initialState.error;
|
|
601
|
+
|
|
602
|
+
const { pluginAccess } = createAppV3<Data, Args, Outputs>(initialState.value!, platforma, {
|
|
603
|
+
debug: false,
|
|
604
|
+
debounceSpan: 10,
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
const pluginState = pluginAccess.getOrCreatePluginState(pluginId);
|
|
608
|
+
|
|
609
|
+
const pFrame = pluginState.model.outputs["pFrame"] as Extract<
|
|
610
|
+
OutputWithStatus<string | undefined>,
|
|
611
|
+
{ ok: true }
|
|
612
|
+
>;
|
|
613
|
+
expect(pFrame).toBeDefined();
|
|
614
|
+
expect(pFrame.ok).toBe(true);
|
|
615
|
+
expect(pFrame.stable).toBe(false);
|
|
616
|
+
expect(pFrame.value).toBeUndefined();
|
|
617
|
+
});
|
|
618
|
+
|
|
524
619
|
it("should navigate to href", async () => {
|
|
525
620
|
const state = createDefaultState();
|
|
526
621
|
const platforma = createMockApiV3<Data, Args, Outputs>(state, defaultBlockModelInfo());
|
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
PluginHandle,
|
|
13
13
|
InferFactoryData,
|
|
14
14
|
InferFactoryOutputs,
|
|
15
|
+
InferFactoryUiServices,
|
|
15
16
|
PluginFactoryLike,
|
|
16
17
|
} from "@platforma-sdk/model";
|
|
17
18
|
import {
|
|
@@ -21,9 +22,13 @@ import {
|
|
|
21
22
|
getPluginData,
|
|
22
23
|
isPluginOutputKey,
|
|
23
24
|
pluginOutputPrefix,
|
|
25
|
+
createNodeServiceProxy,
|
|
26
|
+
buildServices,
|
|
24
27
|
} from "@platforma-sdk/model";
|
|
28
|
+
import { type UiServices as AllUiServices } from "@milaboratories/pl-model-common";
|
|
29
|
+
import { createUiServiceRegistry } from "./service_factories";
|
|
25
30
|
import type { Ref } from "vue";
|
|
26
|
-
import { reactive, computed, ref } from "vue";
|
|
31
|
+
import { reactive, computed, ref, markRaw } from "vue";
|
|
27
32
|
import type { OutputValues, OutputErrors, AppSettings } from "../types";
|
|
28
33
|
import { parseQuery } from "../urls";
|
|
29
34
|
import { ensureOutputHasStableFlag, MultiError } from "../utils";
|
|
@@ -35,10 +40,11 @@ import type { PluginState, PluginAccess } from "../usePlugin";
|
|
|
35
40
|
export const patchPoolingDelay = 150;
|
|
36
41
|
|
|
37
42
|
/** Internal per-plugin state with reconciliation support. */
|
|
38
|
-
interface InternalPluginState<
|
|
39
|
-
Data,
|
|
40
|
-
Outputs
|
|
41
|
-
|
|
43
|
+
interface InternalPluginState<
|
|
44
|
+
Data = unknown,
|
|
45
|
+
Outputs = unknown,
|
|
46
|
+
Services = Record<string, unknown>,
|
|
47
|
+
> extends PluginState<Data, Outputs, Services> {
|
|
42
48
|
readonly ignoreUpdates: (fn: () => void) => void;
|
|
43
49
|
}
|
|
44
50
|
|
|
@@ -75,9 +81,10 @@ export function createAppV3<
|
|
|
75
81
|
Outputs extends BlockOutputsBase = BlockOutputsBase,
|
|
76
82
|
Href extends `/${string}` = `/${string}`,
|
|
77
83
|
Plugins extends Record<string, unknown> = Record<string, unknown>,
|
|
84
|
+
UiServices extends Partial<AllUiServices> = Partial<AllUiServices>,
|
|
78
85
|
>(
|
|
79
86
|
state: ValueWithUTag<BlockStateV3<Data, Outputs, Href>>,
|
|
80
|
-
platforma: PlatformaExtended<PlatformaV3<Data, Args, Outputs, Href, Plugins>>,
|
|
87
|
+
platforma: PlatformaExtended<PlatformaV3<Data, Args, Outputs, Href, Plugins, UiServices>>,
|
|
81
88
|
settings: AppSettings,
|
|
82
89
|
) {
|
|
83
90
|
const debug = (msg: string, ...rest: unknown[]) => {
|
|
@@ -223,12 +230,11 @@ export function createAppV3<
|
|
|
223
230
|
(async () => {
|
|
224
231
|
window.addEventListener("beforeunload", () => {
|
|
225
232
|
closedRef.value = true;
|
|
226
|
-
platforma
|
|
227
|
-
|
|
228
|
-
.then(unwrapResult)
|
|
229
|
-
.catch((err) => {
|
|
233
|
+
Promise.allSettled([uiRegistry.dispose(), platforma.dispose().then(unwrapResult)]).catch(
|
|
234
|
+
(err) => {
|
|
230
235
|
error("error in dispose", err);
|
|
231
|
-
}
|
|
236
|
+
},
|
|
237
|
+
);
|
|
232
238
|
});
|
|
233
239
|
|
|
234
240
|
while (!closedRef.value) {
|
|
@@ -328,6 +334,10 @@ export function createAppV3<
|
|
|
328
334
|
},
|
|
329
335
|
};
|
|
330
336
|
|
|
337
|
+
const proxy = createNodeServiceProxy(platforma.serviceDispatch);
|
|
338
|
+
const uiRegistry = createUiServiceRegistry({ proxy });
|
|
339
|
+
const services = buildServices<UiServices>(platforma.serviceDispatch, uiRegistry);
|
|
340
|
+
|
|
331
341
|
/** Creates a lazily-cached per-plugin reactive state. */
|
|
332
342
|
const createPluginState = <F extends PluginFactoryLike>(
|
|
333
343
|
handle: PluginHandle<F>,
|
|
@@ -342,7 +352,9 @@ export function createAppV3<
|
|
|
342
352
|
if (!key.startsWith(prefix)) continue;
|
|
343
353
|
const outputKey = key.slice(prefix.length);
|
|
344
354
|
if (platforma.blockModelInfo.outputs[key]?.withStatus) {
|
|
345
|
-
result[outputKey] = outputWithStatus
|
|
355
|
+
result[outputKey] = outputWithStatus
|
|
356
|
+
? ensureOutputHasStableFlag(outputWithStatus)
|
|
357
|
+
: undefined;
|
|
346
358
|
} else {
|
|
347
359
|
result[outputKey] =
|
|
348
360
|
outputWithStatus.ok && outputWithStatus.value !== undefined
|
|
@@ -385,6 +397,7 @@ export function createAppV3<
|
|
|
385
397
|
|
|
386
398
|
return {
|
|
387
399
|
model: pluginModel,
|
|
400
|
+
services: markRaw(services),
|
|
388
401
|
ignoreUpdates,
|
|
389
402
|
};
|
|
390
403
|
};
|
|
@@ -394,11 +407,19 @@ export function createAppV3<
|
|
|
394
407
|
getOrCreatePluginState<F extends PluginFactoryLike>(handle: PluginHandle<F>) {
|
|
395
408
|
const existing = pluginStates.get(handle);
|
|
396
409
|
if (existing) {
|
|
397
|
-
return existing as unknown as PluginState<
|
|
410
|
+
return existing as unknown as PluginState<
|
|
411
|
+
InferFactoryData<F>,
|
|
412
|
+
InferFactoryOutputs<F>,
|
|
413
|
+
InferFactoryUiServices<F>
|
|
414
|
+
>;
|
|
398
415
|
}
|
|
399
416
|
const state = createPluginState(handle);
|
|
400
417
|
pluginStates.set(handle, state);
|
|
401
|
-
return state
|
|
418
|
+
return state as unknown as PluginState<
|
|
419
|
+
InferFactoryData<F>,
|
|
420
|
+
InferFactoryOutputs<F>,
|
|
421
|
+
InferFactoryUiServices<F>
|
|
422
|
+
>;
|
|
402
423
|
},
|
|
403
424
|
};
|
|
404
425
|
|
|
@@ -410,6 +431,7 @@ export function createAppV3<
|
|
|
410
431
|
closedRef,
|
|
411
432
|
snapshot,
|
|
412
433
|
plugins,
|
|
434
|
+
services: markRaw(services),
|
|
413
435
|
queryParams: computed(() => parseQuery<Href>(snapshot.value.navigationState.href as Href)),
|
|
414
436
|
href: computed(() => snapshot.value.navigationState.href),
|
|
415
437
|
hasErrors: computed(() =>
|
|
@@ -433,4 +455,5 @@ export type BaseAppV3<
|
|
|
433
455
|
Outputs extends BlockOutputsBase = BlockOutputsBase,
|
|
434
456
|
Href extends `/${string}` = `/${string}`,
|
|
435
457
|
Plugins extends Record<string, unknown> = Record<string, unknown>,
|
|
436
|
-
> =
|
|
458
|
+
UiServices extends Partial<AllUiServices> = Partial<AllUiServices>,
|
|
459
|
+
> = ReturnType<typeof createAppV3<Data, Args, Outputs, Href, Plugins, UiServices>>["app"];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI service factories — add a factory for each new service here.
|
|
3
|
+
*
|
|
4
|
+
* Each entry maps a Services key to a factory function that creates the
|
|
5
|
+
* UI-side driver instance:
|
|
6
|
+
* - WASM services: instantiated directly (e.g. SpecDriver)
|
|
7
|
+
* - Node services: proxied via IPC using NodeServiceProxy
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Services, UiServiceRegistry } from "@milaboratories/pl-model-common";
|
|
11
|
+
import { SpecDriver } from "@milaboratories/pf-spec-driver";
|
|
12
|
+
import type { NodeServiceProxy } from "@platforma-sdk/model";
|
|
13
|
+
|
|
14
|
+
export type UiServiceOptions = {
|
|
15
|
+
proxy: NodeServiceProxy;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function createUiServiceRegistry(options: UiServiceOptions) {
|
|
19
|
+
return new UiServiceRegistry(Services, {
|
|
20
|
+
PFrameSpec: () => new SpecDriver(),
|
|
21
|
+
PFrame: () => options.proxy(Services.PFrame),
|
|
22
|
+
});
|
|
23
|
+
}
|
package/src/usePlugin.ts
CHANGED
|
@@ -4,11 +4,16 @@ import type {
|
|
|
4
4
|
PluginHandle,
|
|
5
5
|
InferFactoryData,
|
|
6
6
|
InferFactoryOutputs,
|
|
7
|
+
InferFactoryUiServices,
|
|
7
8
|
PluginFactoryLike,
|
|
8
9
|
} from "@platforma-sdk/model";
|
|
9
10
|
|
|
10
11
|
/** Per-plugin reactive model exposed to consumers via usePlugin(). */
|
|
11
|
-
export interface PluginState<
|
|
12
|
+
export interface PluginState<
|
|
13
|
+
Data = unknown,
|
|
14
|
+
Outputs = unknown,
|
|
15
|
+
Services = Record<string, unknown>,
|
|
16
|
+
> {
|
|
12
17
|
readonly model: Reactive<{
|
|
13
18
|
data: Data;
|
|
14
19
|
outputs: Outputs extends Record<string, unknown>
|
|
@@ -18,13 +23,14 @@ export interface PluginState<Data = unknown, Outputs = unknown> {
|
|
|
18
23
|
? { [K in keyof Outputs]?: Error }
|
|
19
24
|
: Record<string, Error | undefined>;
|
|
20
25
|
}>;
|
|
26
|
+
readonly services: Services;
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
/** Internal interface for plugin access — provided via Vue injection to usePlugin(). */
|
|
24
30
|
export interface PluginAccess {
|
|
25
31
|
getOrCreatePluginState<F extends PluginFactoryLike>(
|
|
26
32
|
handle: PluginHandle<F>,
|
|
27
|
-
): PluginState<InferFactoryData<F>, InferFactoryOutputs<F>>;
|
|
33
|
+
): PluginState<InferFactoryData<F>, InferFactoryOutputs<F>, InferFactoryUiServices<F>>;
|
|
28
34
|
}
|
|
29
35
|
|
|
30
36
|
/**
|