@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.
@@ -5,15 +5,16 @@ import "../lib/util/helpers/dist/index.js";
5
5
  import { MultiError as r, ensureOutputHasStableFlag as i } from "../utils.js";
6
6
  import { parseQuery as a } from "../urls.js";
7
7
  import { UpdateSerializer as o } from "./UpdateSerializer.js";
8
- import { computed as s, reactive as c, ref as l } from "vue";
9
- import { deriveDataFromStorage as u, getPluginData as d, hasAbortError as f, isPluginOutputKey as p, pluginOutputPrefix as m, unwrapResult as h } from "@platforma-sdk/model";
10
- import { watchIgnorable as g } from "@vueuse/core";
11
- import { applyPatch as _ } from "fast-json-patch";
12
- const v = (e) => ({
8
+ import { createUiServiceRegistry as s } from "./service_factories.js";
9
+ import { computed as c, markRaw as l, reactive as u, ref as d } from "vue";
10
+ import { buildServices as f, createNodeServiceProxy as p, deriveDataFromStorage as m, getPluginData as h, hasAbortError as g, isPluginOutputKey as _, pluginOutputPrefix as v, unwrapResult as y } from "@platforma-sdk/model";
11
+ import { watchIgnorable as b } from "@vueuse/core";
12
+ import { applyPatch as x } from "fast-json-patch";
13
+ const S = (e) => ({
13
14
  authorId: e?.authorId ?? t(),
14
15
  localVersion: (e?.localVersion ?? 0) + 1
15
16
  });
16
- var y = (e) => {
17
+ var C = (e) => {
17
18
  try {
18
19
  return JSON.stringify(e, null, 2);
19
20
  } catch (e) {
@@ -34,130 +35,132 @@ var y = (e) => {
34
35
  *
35
36
  * @returns A reactive application object with methods, getters, and state.
36
37
  */
37
- function b(b, x, S) {
38
- let C = (e, ...t) => {
39
- S.debug && console.log(`%c>>> %c${e}`, "color: orange; font-weight: bold", "color: orange", ...t.map((e) => y(e)));
40
- }, w = (e, ...t) => {
41
- console.error(`%c>>> %c${e}`, "color: red; font-weight: bold", "color: red", ...t.map((e) => y(e)));
42
- }, T = {
38
+ function w(w, T, E) {
39
+ let D = (e, ...t) => {
40
+ E.debug && console.log(`%c>>> %c${e}`, "color: orange; font-weight: bold", "color: orange", ...t.map((e) => C(e)));
41
+ }, O = (e, ...t) => {
42
+ console.error(`%c>>> %c${e}`, "color: red; font-weight: bold", "color: red", ...t.map((e) => C(e)));
43
+ }, k = {
43
44
  isExternalSnapshot: !1,
44
45
  author: {
45
46
  authorId: t(),
46
47
  localVersion: 0
47
48
  }
48
- }, E = () => (T.author = v(T.author), C("nextAuthorMarker", T.author), T.author), D = l(!1), O = l(b.uTag), k = S.debounceSpan ?? 200, A = new o({ debounceSpan: k }), j = /* @__PURE__ */ new Map(), M = (e) => {
49
- let t = j.get(e);
50
- return t || (t = new o({ debounceSpan: k }), j.set(e, t)), t;
51
- }, N = new o({ debounceSpan: k }), P = /* @__PURE__ */ new Map(), F = l(b.value), I = async (e) => x.mutateStorage({
49
+ }, A = () => (k.author = S(k.author), D("nextAuthorMarker", k.author), k.author), j = d(!1), M = d(w.uTag), N = E.debounceSpan ?? 200, P = new o({ debounceSpan: N }), F = /* @__PURE__ */ new Map(), I = (e) => {
50
+ let t = F.get(e);
51
+ return t || (t = new o({ debounceSpan: N }), F.set(e, t)), t;
52
+ }, L = new o({ debounceSpan: N }), R = /* @__PURE__ */ new Map(), z = d(w.value), B = async (e) => T.mutateStorage({
52
53
  operation: "update-block-data",
53
54
  value: e
54
- }, E()), L = async (e, t) => x.mutateStorage({
55
+ }, A()), V = async (e, t) => T.mutateStorage({
55
56
  operation: "update-plugin-data",
56
57
  pluginId: e,
57
58
  value: t
58
- }, E()), R = async (e) => x.setNavigationState(e), z = s(() => {
59
- let e = Object.entries(F.value.outputs).filter(([e]) => !p(e)).map(([e, t]) => x.blockModelInfo.outputs[e]?.withStatus ? [e, i(t)] : [e, t.ok && t.value !== void 0 ? t.value : void 0]);
59
+ }, A()), H = async (e) => T.setNavigationState(e), U = c(() => {
60
+ let e = Object.entries(z.value.outputs).filter(([e]) => !_(e)).map(([e, t]) => T.blockModelInfo.outputs[e]?.withStatus ? [e, i(t)] : [e, t.ok && t.value !== void 0 ? t.value : void 0]);
60
61
  return Object.fromEntries(e);
61
- }), B = s(() => {
62
- let e = Object.entries(F.value.outputs).filter(([e]) => !p(e)).map(([e, t]) => [e, t && t.ok === !1 ? new r(t.errors) : void 0]);
62
+ }), W = c(() => {
63
+ let e = Object.entries(z.value.outputs).filter(([e]) => !_(e)).map(([e, t]) => [e, t && t.ok === !1 ? new r(t.errors) : void 0]);
63
64
  return Object.fromEntries(e);
64
- }), V = c({
65
+ }), G = u({
65
66
  apiVersion: 3,
66
67
  error: "",
67
68
  model: {
68
- data: n(u(F.value.blockStorage)),
69
- outputs: z,
70
- outputErrors: B
69
+ data: n(m(z.value.blockStorage)),
70
+ outputs: U,
71
+ outputErrors: W
71
72
  }
72
- }), { ignoreUpdates: H } = g(() => V.model, (e) => {
73
+ }), { ignoreUpdates: K } = b(() => G.model, (e) => {
73
74
  let t = n(e);
74
- C("setDataQueue appModel.model, data", t.data), A.run(() => I(t.data).then(h));
75
- }, { deep: !0 }), U = (e) => {
76
- C("updateAppModel", e), V.model.data = n(e.data);
75
+ D("setDataQueue appModel.model, data", t.data), P.run(() => B(t.data).then(y));
76
+ }, { deep: !0 }), q = (e) => {
77
+ D("updateAppModel", e), G.model.data = n(e.data);
77
78
  };
78
79
  (async () => {
79
80
  for (window.addEventListener("beforeunload", () => {
80
- D.value = !0, x.dispose().then(h).catch((e) => {
81
- w("error in dispose", e);
81
+ j.value = !0, Promise.allSettled([Z.dispose(), T.dispose().then(y)]).catch((e) => {
82
+ O("error in dispose", e);
82
83
  });
83
- }); !D.value;) try {
84
- let e = await x.getPatches(O.value).then(h);
85
- if (C("patches.length", e.value.length), C("uTagRef.value", O.value), C("patches.uTag", e.uTag), C("patches.author", e.author), C("data.author", T.author), O.value = e.uTag, e.value.length === 0) {
84
+ }); !j.value;) try {
85
+ let e = await T.getPatches(M.value).then(y);
86
+ if (D("patches.length", e.value.length), D("uTagRef.value", M.value), D("patches.uTag", e.uTag), D("patches.author", e.author), D("data.author", k.author), M.value = e.uTag, e.value.length === 0) {
86
87
  await new Promise((e) => setTimeout(e, 150));
87
88
  continue;
88
89
  }
89
- let t = T.author?.authorId !== e.author?.authorId;
90
- t || T.isExternalSnapshot ? (C("got external changes, applying them to the snapshot", e.value), H(() => {
91
- F.value = _(F.value, e.value, !1, !1).newDocument, U({ data: u(F.value.blockStorage) });
92
- for (let [e, t] of P) t.ignoreUpdates(() => {
93
- t.model.data = n(d(F.value.blockStorage, e));
90
+ let t = k.author?.authorId !== e.author?.authorId;
91
+ t || k.isExternalSnapshot ? (D("got external changes, applying them to the snapshot", e.value), K(() => {
92
+ z.value = x(z.value, e.value, !1, !1).newDocument, q({ data: m(z.value.blockStorage) });
93
+ for (let [e, t] of R) t.ignoreUpdates(() => {
94
+ t.model.data = n(h(z.value.blockStorage, e));
94
95
  });
95
- T.isExternalSnapshot = t;
96
- })) : (C("outputs changed", e.value), H(() => {
97
- F.value = _(F.value, e.value).newDocument;
96
+ k.isExternalSnapshot = t;
97
+ })) : (D("outputs changed", e.value), K(() => {
98
+ z.value = x(z.value, e.value).newDocument;
98
99
  })), await new Promise((e) => setTimeout(e, 150));
99
100
  } catch (e) {
100
- f(e) ? (C("patches loop aborted"), D.value = !0) : (w("error in patches loop", e), await new Promise((e) => setTimeout(e, 1e3)));
101
+ g(e) ? (D("patches loop aborted"), j.value = !0) : (O("error in patches loop", e), await new Promise((e) => setTimeout(e, 1e3)));
101
102
  }
102
103
  })();
103
- let W = () => n(V.model.data), G = () => n(F.value.navigationState), K = {
104
- cloneData: W,
105
- cloneNavigationState: G,
104
+ let J = () => n(G.model.data), Y = () => n(z.value.navigationState), X = {
105
+ cloneData: J,
106
+ cloneNavigationState: Y,
106
107
  updateData(e) {
107
- let t = e(W());
108
- return C("updateData", t), V.model.data = t, A.run(() => I(t).then(h));
108
+ let t = e(J());
109
+ return D("updateData", t), G.model.data = t, P.run(() => B(t).then(y));
109
110
  },
110
111
  navigateTo(e) {
111
- let t = G();
112
- return t.href = e, N.run(() => R(t).then(h));
112
+ let t = Y();
113
+ return t.href = e, L.run(() => H(t).then(y));
113
114
  },
114
115
  async allSettled() {
115
116
  await e(0);
116
- let t = [A.allSettled(), ...Array.from(j.values()).map((e) => e.allSettled())];
117
+ let t = [P.allSettled(), ...Array.from(F.values()).map((e) => e.allSettled())];
117
118
  await Promise.all(t);
118
119
  }
119
- }, q = (e) => {
120
- let t = m(e), i = s(() => {
120
+ }, Z = s({ proxy: p(T.serviceDispatch) }), Q = f(T.serviceDispatch, Z), ee = (e) => {
121
+ let t = v(e), a = c(() => {
121
122
  let e = {};
122
- for (let [n, r] of Object.entries(F.value.outputs)) {
123
+ for (let [n, r] of Object.entries(z.value.outputs)) {
123
124
  if (!n.startsWith(t)) continue;
124
- let i = n.slice(t.length);
125
- x.blockModelInfo.outputs[n]?.withStatus ? e[i] = r ?? void 0 : e[i] = r.ok && r.value !== void 0 ? r.value : void 0;
125
+ let a = n.slice(t.length);
126
+ T.blockModelInfo.outputs[n]?.withStatus ? e[a] = r ? i(r) : void 0 : e[a] = r.ok && r.value !== void 0 ? r.value : void 0;
126
127
  }
127
128
  return e;
128
- }), a = s(() => {
129
+ }), o = c(() => {
129
130
  let e = {};
130
- for (let [n, i] of Object.entries(F.value.outputs)) n.startsWith(t) && (e[n.slice(t.length)] = i && i.ok === !1 ? new r(i.errors) : void 0);
131
+ for (let [n, i] of Object.entries(z.value.outputs)) n.startsWith(t) && (e[n.slice(t.length)] = i && i.ok === !1 ? new r(i.errors) : void 0);
131
132
  return e;
132
- }), o = c({
133
- data: n(d(F.value.blockStorage, e)),
134
- outputs: i,
135
- outputErrors: a
136
- }), { ignoreUpdates: l } = g(() => o.data, (t) => {
137
- t !== void 0 && (C("plugin setData", e, t), M(e).run(() => L(e, n(t)).then(h)));
133
+ }), s = u({
134
+ data: n(h(z.value.blockStorage, e)),
135
+ outputs: a,
136
+ outputErrors: o
137
+ }), { ignoreUpdates: d } = b(() => s.data, (t) => {
138
+ t !== void 0 && (D("plugin setData", e, t), I(e).run(() => V(e, n(t)).then(y)));
138
139
  }, { deep: !0 });
139
140
  return {
140
- model: o,
141
- ignoreUpdates: l
141
+ model: s,
142
+ services: l(Q),
143
+ ignoreUpdates: d
142
144
  };
143
- }, J = { getOrCreatePluginState(e) {
144
- let t = P.get(e);
145
+ }, te = { getOrCreatePluginState(e) {
146
+ let t = R.get(e);
145
147
  if (t) return t;
146
- let n = q(e);
147
- return P.set(e, n), n;
148
- } }, Y = {
149
- closedRef: D,
150
- snapshot: F,
151
- plugins: Object.fromEntries(x.blockModelInfo.pluginIds.map((e) => [e, { handle: e }])),
152
- queryParams: s(() => a(F.value.navigationState.href)),
153
- href: s(() => F.value.navigationState.href),
154
- hasErrors: s(() => Object.values(F.value.outputs).some((e) => !e?.ok))
155
- }, X = Object.assign(c(Object.assign(V, Y)), K);
156
- return S.debug && (globalThis.__block_app__ = X), {
157
- app: X,
158
- pluginAccess: J
148
+ let n = ee(e);
149
+ return R.set(e, n), n;
150
+ } }, ne = {
151
+ closedRef: j,
152
+ snapshot: z,
153
+ plugins: Object.fromEntries(T.blockModelInfo.pluginIds.map((e) => [e, { handle: e }])),
154
+ services: l(Q),
155
+ queryParams: c(() => a(z.value.navigationState.href)),
156
+ href: c(() => z.value.navigationState.href),
157
+ hasErrors: c(() => Object.values(z.value.outputs).some((e) => !e?.ok))
158
+ }, $ = Object.assign(u(Object.assign(G, ne)), X);
159
+ return E.debug && (globalThis.__block_app__ = $), {
160
+ app: $,
161
+ pluginAccess: te
159
162
  };
160
163
  }
161
- export { b as createAppV3 };
164
+ export { w as createAppV3 };
162
165
 
163
166
  //# sourceMappingURL=createAppV3.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"createAppV3.js","names":[],"sources":["../../src/internal/createAppV3.ts"],"sourcesContent":["import { deepClone, delay, uniqueId } from \"@milaboratories/helpers\";\nimport type { Mutable } from \"@milaboratories/helpers\";\nimport type {\n NavigationState,\n BlockOutputsBase,\n BlockStateV3,\n PlatformaV3,\n ValueWithUTag,\n AuthorMarker,\n PlatformaExtended,\n InferPluginHandles,\n PluginHandle,\n InferFactoryData,\n InferFactoryOutputs,\n PluginFactoryLike,\n} from \"@platforma-sdk/model\";\nimport {\n hasAbortError,\n unwrapResult,\n deriveDataFromStorage,\n getPluginData,\n isPluginOutputKey,\n pluginOutputPrefix,\n} from \"@platforma-sdk/model\";\nimport type { Ref } from \"vue\";\nimport { reactive, computed, ref } from \"vue\";\nimport type { OutputValues, OutputErrors, AppSettings } from \"../types\";\nimport { parseQuery } from \"../urls\";\nimport { ensureOutputHasStableFlag, MultiError } from \"../utils\";\nimport { applyPatch } from \"fast-json-patch\";\nimport { UpdateSerializer } from \"./UpdateSerializer\";\nimport { watchIgnorable } from \"@vueuse/core\";\nimport type { PluginState, PluginAccess } from \"../usePlugin\";\n\nexport const patchPoolingDelay = 150;\n\n/** Internal per-plugin state with reconciliation support. */\ninterface InternalPluginState<Data = unknown, Outputs = unknown> extends PluginState<\n Data,\n Outputs\n> {\n readonly ignoreUpdates: (fn: () => void) => void;\n}\n\nexport const createNextAuthorMarker = (marker: AuthorMarker | undefined): AuthorMarker => ({\n authorId: marker?.authorId ?? uniqueId(),\n localVersion: (marker?.localVersion ?? 0) + 1,\n});\n\nconst stringifyForDebug = (v: unknown) => {\n try {\n return JSON.stringify(v, null, 2);\n } catch (err) {\n return err instanceof Error ? err.message : String(err);\n }\n};\n\n/**\n * Creates an application instance with reactive state management, outputs, and methods for state updates and navigation.\n *\n * @template Args - The type of arguments used in the application.\n * @template Outputs - The type of block outputs extending `BlockOutputsBase`.\n * @template Data - The type of the block data.\n * @template Href - The type of navigation href, defaulting to a string starting with `/`.\n *\n * @param state - Initial state of the application, including args, outputs, UI state, and navigation state.\n * @param platforma - A platform interface for interacting with block states.\n * @param settings - Application settings, such as debug flags.\n *\n * @returns A reactive application object with methods, getters, and state.\n */\nexport function createAppV3<\n Data = unknown,\n Args = unknown,\n Outputs extends BlockOutputsBase = BlockOutputsBase,\n Href extends `/${string}` = `/${string}`,\n Plugins extends Record<string, unknown> = Record<string, unknown>,\n>(\n state: ValueWithUTag<BlockStateV3<Data, Outputs, Href>>,\n platforma: PlatformaExtended<PlatformaV3<Data, Args, Outputs, Href, Plugins>>,\n settings: AppSettings,\n) {\n const debug = (msg: string, ...rest: unknown[]) => {\n if (settings.debug) {\n console.log(\n `%c>>> %c${msg}`,\n \"color: orange; font-weight: bold\",\n \"color: orange\",\n ...rest.map((r) => stringifyForDebug(r)),\n );\n }\n };\n\n const error = (msg: string, ...rest: unknown[]) => {\n console.error(\n `%c>>> %c${msg}`,\n \"color: red; font-weight: bold\",\n \"color: red\",\n ...rest.map((r) => stringifyForDebug(r)),\n );\n };\n\n const data = {\n isExternalSnapshot: false,\n author: {\n authorId: uniqueId(),\n localVersion: 0,\n },\n };\n\n const nextAuthorMarker = () => {\n data.author = createNextAuthorMarker(data.author);\n debug(\"nextAuthorMarker\", data.author);\n return data.author;\n };\n\n const closedRef = ref(false);\n\n const uTagRef = ref(state.uTag);\n\n const debounceSpan = settings.debounceSpan ?? 200;\n\n const setDataQueue = new UpdateSerializer({ debounceSpan });\n const pluginDataQueues = new Map<PluginHandle, UpdateSerializer>();\n const getPluginDataQueue = (handle: PluginHandle): UpdateSerializer => {\n let queue = pluginDataQueues.get(handle);\n if (!queue) {\n queue = new UpdateSerializer({ debounceSpan });\n pluginDataQueues.set(handle, queue);\n }\n return queue;\n };\n const setNavigationStateQueue = new UpdateSerializer({ debounceSpan });\n\n /** Lazily-created per-plugin reactive states. */\n const pluginStates = new Map<PluginHandle, InternalPluginState>();\n /**\n * Reactive snapshot of the application state, including args, outputs, UI state, and navigation state.\n */\n const snapshot = ref<{\n outputs: Partial<Outputs>;\n blockStorage: unknown;\n navigationState: NavigationState<Href>;\n }>(state.value) as Ref<{\n outputs: Partial<Outputs>;\n blockStorage: unknown;\n navigationState: NavigationState<Href>;\n }>;\n\n const updateData = async (value: Data) => {\n return platforma.mutateStorage({ operation: \"update-block-data\", value }, nextAuthorMarker());\n };\n\n const updatePluginData = async (handle: PluginHandle, value: unknown) => {\n return platforma.mutateStorage(\n { operation: \"update-plugin-data\", pluginId: handle, value },\n nextAuthorMarker(),\n );\n };\n\n const setNavigationState = async (state: NavigationState<Href>) => {\n return platforma.setNavigationState(state);\n };\n\n const outputs = computed<OutputValues<Outputs>>(() => {\n const entries = Object.entries(snapshot.value.outputs as Partial<Readonly<Outputs>>)\n .filter(([k]) => !isPluginOutputKey(k))\n .map(([k, outputWithStatus]) =>\n platforma.blockModelInfo.outputs[k]?.withStatus\n ? [k, ensureOutputHasStableFlag(outputWithStatus)]\n : [\n k,\n outputWithStatus.ok && outputWithStatus.value !== undefined\n ? outputWithStatus.value\n : undefined,\n ],\n );\n return Object.fromEntries(entries);\n });\n\n const outputErrors = computed<OutputErrors<Outputs>>(() => {\n const entries = Object.entries(snapshot.value.outputs as Partial<Readonly<Outputs>>)\n .filter(([k]) => !isPluginOutputKey(k))\n .map(([k, vOrErr]) => [\n k,\n vOrErr && vOrErr.ok === false ? new MultiError(vOrErr.errors) : undefined,\n ]);\n return Object.fromEntries(entries);\n });\n\n const appModel = reactive({\n apiVersion: 3,\n error: \"\",\n model: {\n data: deepClone(deriveDataFromStorage<Data>(snapshot.value.blockStorage)) as Data,\n outputs,\n outputErrors,\n },\n }) as {\n error: string;\n model: {\n data: Data;\n outputs: OutputValues<Outputs>;\n outputErrors: OutputErrors<Outputs>;\n };\n };\n\n const { ignoreUpdates } = watchIgnorable(\n () => appModel.model,\n (_newData) => {\n const newData = deepClone(_newData);\n debug(\"setDataQueue appModel.model, data\", newData.data);\n setDataQueue.run(() => updateData(newData.data).then(unwrapResult));\n },\n { deep: true },\n );\n\n const updateAppModel = (newData: { data: Data }) => {\n debug(\"updateAppModel\", newData);\n appModel.model.data = deepClone(newData.data) as Data;\n };\n\n (async () => {\n window.addEventListener(\"beforeunload\", () => {\n closedRef.value = true;\n platforma\n .dispose()\n .then(unwrapResult)\n .catch((err) => {\n error(\"error in dispose\", err);\n });\n });\n\n while (!closedRef.value) {\n try {\n const patches = await platforma.getPatches(uTagRef.value).then(unwrapResult);\n\n debug(\"patches.length\", patches.value.length);\n debug(\"uTagRef.value\", uTagRef.value);\n debug(\"patches.uTag\", patches.uTag);\n debug(\"patches.author\", patches.author);\n debug(\"data.author\", data.author);\n\n uTagRef.value = patches.uTag;\n\n if (patches.value.length === 0) {\n await new Promise((resolve) => setTimeout(resolve, patchPoolingDelay));\n continue;\n }\n\n const isAuthorChanged = data.author?.authorId !== patches.author?.authorId;\n\n // Immutable behavior, apply external changes to the snapshot\n if (isAuthorChanged || data.isExternalSnapshot) {\n debug(\"got external changes, applying them to the snapshot\", patches.value);\n ignoreUpdates(() => {\n snapshot.value = applyPatch(snapshot.value, patches.value, false, false).newDocument;\n updateAppModel({ data: deriveDataFromStorage<Data>(snapshot.value.blockStorage) });\n // Reconcile plugin data from external source\n for (const [handle, pluginState] of pluginStates) {\n pluginState.ignoreUpdates(() => {\n pluginState.model.data = deepClone(\n getPluginData(snapshot.value.blockStorage, handle),\n );\n });\n }\n data.isExternalSnapshot = isAuthorChanged;\n });\n } else {\n // Mutable behavior\n debug(\"outputs changed\", patches.value);\n ignoreUpdates(() => {\n snapshot.value = applyPatch(snapshot.value, patches.value).newDocument;\n });\n }\n\n await new Promise((resolve) => setTimeout(resolve, patchPoolingDelay));\n } catch (err) {\n if (hasAbortError(err)) {\n debug(\"patches loop aborted\");\n closedRef.value = true;\n } else {\n error(\"error in patches loop\", err);\n await new Promise((resolve) => setTimeout(resolve, 1000));\n }\n }\n }\n })();\n\n const cloneData = () => deepClone(appModel.model.data) as Data;\n const cloneNavigationState = () =>\n deepClone(snapshot.value.navigationState) as Mutable<NavigationState<Href>>;\n\n const methods = {\n cloneData,\n cloneNavigationState,\n /**\n * Updates the UI state by applying a callback.\n *\n * @param cb - Callback to modify the current UI state.\n * @returns A promise resolving after the update is applied.\n * @todo Make it mutable since there is already an initial one\n */\n updateData(cb: (data: Data) => Data): Promise<boolean> {\n const newData = cb(cloneData());\n debug(\"updateData\", newData);\n appModel.model.data = newData;\n return setDataQueue.run(() => updateData(newData).then(unwrapResult));\n },\n /**\n * Navigates to a specific href by updating the navigation state.\n *\n * @param href - The target href to navigate to.\n * @returns A promise resolving after the navigation state is updated.\n */\n navigateTo(href: Href) {\n const newState = cloneNavigationState();\n newState.href = href;\n return setNavigationStateQueue.run(() => setNavigationState(newState).then(unwrapResult));\n },\n async allSettled() {\n await delay(0);\n const allQueues = [\n setDataQueue.allSettled(),\n ...Array.from(pluginDataQueues.values()).map((q) => q.allSettled()),\n ];\n await Promise.all(allQueues);\n },\n };\n\n /** Creates a lazily-cached per-plugin reactive state. */\n const createPluginState = <F extends PluginFactoryLike>(\n handle: PluginHandle<F>,\n ): InternalPluginState<InferFactoryData<F>, InferFactoryOutputs<F>> => {\n const prefix = pluginOutputPrefix(handle);\n\n const pluginOutputs = computed(() => {\n const result: Record<string, unknown> = {};\n for (const [key, outputWithStatus] of Object.entries(\n snapshot.value.outputs as Partial<Readonly<Outputs>>,\n )) {\n if (!key.startsWith(prefix)) continue;\n const outputKey = key.slice(prefix.length);\n if (platforma.blockModelInfo.outputs[key]?.withStatus) {\n result[outputKey] = outputWithStatus ?? undefined;\n } else {\n result[outputKey] =\n outputWithStatus.ok && outputWithStatus.value !== undefined\n ? outputWithStatus.value\n : undefined;\n }\n }\n return result;\n });\n\n const pluginOutputErrors = computed(() => {\n const result: Record<string, Error | undefined> = {};\n for (const [key, vOrErr] of Object.entries(\n snapshot.value.outputs as Partial<Readonly<Outputs>>,\n )) {\n if (!key.startsWith(prefix)) continue;\n result[key.slice(prefix.length)] =\n vOrErr && vOrErr.ok === false ? new MultiError(vOrErr.errors) : undefined;\n }\n return result;\n });\n\n const pluginModel = reactive({\n data: deepClone(getPluginData(snapshot.value.blockStorage, handle)),\n outputs: pluginOutputs,\n outputErrors: pluginOutputErrors,\n }) as InternalPluginState<InferFactoryData<F>, InferFactoryOutputs<F>>[\"model\"];\n\n const { ignoreUpdates } = watchIgnorable(\n () => pluginModel.data,\n (newData) => {\n if (newData === undefined) return;\n debug(\"plugin setData\", handle, newData);\n getPluginDataQueue(handle).run(() =>\n updatePluginData(handle, deepClone(newData)).then(unwrapResult),\n );\n },\n { deep: true },\n );\n\n return {\n model: pluginModel,\n ignoreUpdates,\n };\n };\n\n /** Plugin internals — provided via separate injection key, not exposed on useApp(). */\n const pluginAccess: PluginAccess = {\n getOrCreatePluginState<F extends PluginFactoryLike>(handle: PluginHandle<F>) {\n const existing = pluginStates.get(handle);\n if (existing) {\n return existing as unknown as PluginState<InferFactoryData<F>, InferFactoryOutputs<F>>;\n }\n const state = createPluginState(handle);\n pluginStates.set(handle, state);\n return state;\n },\n };\n\n const plugins = Object.fromEntries(\n platforma.blockModelInfo.pluginIds.map((id) => [id, { handle: id }]),\n ) as InferPluginHandles<Plugins>;\n\n const getters = {\n closedRef,\n snapshot,\n plugins,\n queryParams: computed(() => parseQuery<Href>(snapshot.value.navigationState.href as Href)),\n href: computed(() => snapshot.value.navigationState.href),\n hasErrors: computed(() =>\n Object.values(snapshot.value.outputs as Partial<Readonly<Outputs>>).some((v) => !v?.ok),\n ),\n };\n\n const app = Object.assign(reactive(Object.assign(appModel, getters)), methods);\n\n if (settings.debug) {\n // @ts-expect-error (to inspect in console in debug mode)\n globalThis.__block_app__ = app;\n }\n\n return { app, pluginAccess };\n}\n\nexport type BaseAppV3<\n Data = unknown,\n Args = unknown,\n Outputs extends BlockOutputsBase = BlockOutputsBase,\n Href extends `/${string}` = `/${string}`,\n Plugins extends Record<string, unknown> = Record<string, unknown>,\n> = ReturnType<typeof createAppV3<Data, Args, Outputs, Href, Plugins>>[\"app\"];\n"],"mappings":"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"}
1
+ {"version":3,"file":"createAppV3.js","names":[],"sources":["../../src/internal/createAppV3.ts"],"sourcesContent":["import { deepClone, delay, uniqueId } from \"@milaboratories/helpers\";\nimport type { Mutable } from \"@milaboratories/helpers\";\nimport type {\n NavigationState,\n BlockOutputsBase,\n BlockStateV3,\n PlatformaV3,\n ValueWithUTag,\n AuthorMarker,\n PlatformaExtended,\n InferPluginHandles,\n PluginHandle,\n InferFactoryData,\n InferFactoryOutputs,\n InferFactoryUiServices,\n PluginFactoryLike,\n} from \"@platforma-sdk/model\";\nimport {\n hasAbortError,\n unwrapResult,\n deriveDataFromStorage,\n getPluginData,\n isPluginOutputKey,\n pluginOutputPrefix,\n createNodeServiceProxy,\n buildServices,\n} from \"@platforma-sdk/model\";\nimport { type UiServices as AllUiServices } from \"@milaboratories/pl-model-common\";\nimport { createUiServiceRegistry } from \"./service_factories\";\nimport type { Ref } from \"vue\";\nimport { reactive, computed, ref, markRaw } from \"vue\";\nimport type { OutputValues, OutputErrors, AppSettings } from \"../types\";\nimport { parseQuery } from \"../urls\";\nimport { ensureOutputHasStableFlag, MultiError } from \"../utils\";\nimport { applyPatch } from \"fast-json-patch\";\nimport { UpdateSerializer } from \"./UpdateSerializer\";\nimport { watchIgnorable } from \"@vueuse/core\";\nimport type { PluginState, PluginAccess } from \"../usePlugin\";\n\nexport const patchPoolingDelay = 150;\n\n/** Internal per-plugin state with reconciliation support. */\ninterface InternalPluginState<\n Data = unknown,\n Outputs = unknown,\n Services = Record<string, unknown>,\n> extends PluginState<Data, Outputs, Services> {\n readonly ignoreUpdates: (fn: () => void) => void;\n}\n\nexport const createNextAuthorMarker = (marker: AuthorMarker | undefined): AuthorMarker => ({\n authorId: marker?.authorId ?? uniqueId(),\n localVersion: (marker?.localVersion ?? 0) + 1,\n});\n\nconst stringifyForDebug = (v: unknown) => {\n try {\n return JSON.stringify(v, null, 2);\n } catch (err) {\n return err instanceof Error ? err.message : String(err);\n }\n};\n\n/**\n * Creates an application instance with reactive state management, outputs, and methods for state updates and navigation.\n *\n * @template Args - The type of arguments used in the application.\n * @template Outputs - The type of block outputs extending `BlockOutputsBase`.\n * @template Data - The type of the block data.\n * @template Href - The type of navigation href, defaulting to a string starting with `/`.\n *\n * @param state - Initial state of the application, including args, outputs, UI state, and navigation state.\n * @param platforma - A platform interface for interacting with block states.\n * @param settings - Application settings, such as debug flags.\n *\n * @returns A reactive application object with methods, getters, and state.\n */\nexport function createAppV3<\n Data = unknown,\n Args = unknown,\n Outputs extends BlockOutputsBase = BlockOutputsBase,\n Href extends `/${string}` = `/${string}`,\n Plugins extends Record<string, unknown> = Record<string, unknown>,\n UiServices extends Partial<AllUiServices> = Partial<AllUiServices>,\n>(\n state: ValueWithUTag<BlockStateV3<Data, Outputs, Href>>,\n platforma: PlatformaExtended<PlatformaV3<Data, Args, Outputs, Href, Plugins, UiServices>>,\n settings: AppSettings,\n) {\n const debug = (msg: string, ...rest: unknown[]) => {\n if (settings.debug) {\n console.log(\n `%c>>> %c${msg}`,\n \"color: orange; font-weight: bold\",\n \"color: orange\",\n ...rest.map((r) => stringifyForDebug(r)),\n );\n }\n };\n\n const error = (msg: string, ...rest: unknown[]) => {\n console.error(\n `%c>>> %c${msg}`,\n \"color: red; font-weight: bold\",\n \"color: red\",\n ...rest.map((r) => stringifyForDebug(r)),\n );\n };\n\n const data = {\n isExternalSnapshot: false,\n author: {\n authorId: uniqueId(),\n localVersion: 0,\n },\n };\n\n const nextAuthorMarker = () => {\n data.author = createNextAuthorMarker(data.author);\n debug(\"nextAuthorMarker\", data.author);\n return data.author;\n };\n\n const closedRef = ref(false);\n\n const uTagRef = ref(state.uTag);\n\n const debounceSpan = settings.debounceSpan ?? 200;\n\n const setDataQueue = new UpdateSerializer({ debounceSpan });\n const pluginDataQueues = new Map<PluginHandle, UpdateSerializer>();\n const getPluginDataQueue = (handle: PluginHandle): UpdateSerializer => {\n let queue = pluginDataQueues.get(handle);\n if (!queue) {\n queue = new UpdateSerializer({ debounceSpan });\n pluginDataQueues.set(handle, queue);\n }\n return queue;\n };\n const setNavigationStateQueue = new UpdateSerializer({ debounceSpan });\n\n /** Lazily-created per-plugin reactive states. */\n const pluginStates = new Map<PluginHandle, InternalPluginState>();\n /**\n * Reactive snapshot of the application state, including args, outputs, UI state, and navigation state.\n */\n const snapshot = ref<{\n outputs: Partial<Outputs>;\n blockStorage: unknown;\n navigationState: NavigationState<Href>;\n }>(state.value) as Ref<{\n outputs: Partial<Outputs>;\n blockStorage: unknown;\n navigationState: NavigationState<Href>;\n }>;\n\n const updateData = async (value: Data) => {\n return platforma.mutateStorage({ operation: \"update-block-data\", value }, nextAuthorMarker());\n };\n\n const updatePluginData = async (handle: PluginHandle, value: unknown) => {\n return platforma.mutateStorage(\n { operation: \"update-plugin-data\", pluginId: handle, value },\n nextAuthorMarker(),\n );\n };\n\n const setNavigationState = async (state: NavigationState<Href>) => {\n return platforma.setNavigationState(state);\n };\n\n const outputs = computed<OutputValues<Outputs>>(() => {\n const entries = Object.entries(snapshot.value.outputs as Partial<Readonly<Outputs>>)\n .filter(([k]) => !isPluginOutputKey(k))\n .map(([k, outputWithStatus]) =>\n platforma.blockModelInfo.outputs[k]?.withStatus\n ? [k, ensureOutputHasStableFlag(outputWithStatus)]\n : [\n k,\n outputWithStatus.ok && outputWithStatus.value !== undefined\n ? outputWithStatus.value\n : undefined,\n ],\n );\n return Object.fromEntries(entries);\n });\n\n const outputErrors = computed<OutputErrors<Outputs>>(() => {\n const entries = Object.entries(snapshot.value.outputs as Partial<Readonly<Outputs>>)\n .filter(([k]) => !isPluginOutputKey(k))\n .map(([k, vOrErr]) => [\n k,\n vOrErr && vOrErr.ok === false ? new MultiError(vOrErr.errors) : undefined,\n ]);\n return Object.fromEntries(entries);\n });\n\n const appModel = reactive({\n apiVersion: 3,\n error: \"\",\n model: {\n data: deepClone(deriveDataFromStorage<Data>(snapshot.value.blockStorage)) as Data,\n outputs,\n outputErrors,\n },\n }) as {\n error: string;\n model: {\n data: Data;\n outputs: OutputValues<Outputs>;\n outputErrors: OutputErrors<Outputs>;\n };\n };\n\n const { ignoreUpdates } = watchIgnorable(\n () => appModel.model,\n (_newData) => {\n const newData = deepClone(_newData);\n debug(\"setDataQueue appModel.model, data\", newData.data);\n setDataQueue.run(() => updateData(newData.data).then(unwrapResult));\n },\n { deep: true },\n );\n\n const updateAppModel = (newData: { data: Data }) => {\n debug(\"updateAppModel\", newData);\n appModel.model.data = deepClone(newData.data) as Data;\n };\n\n (async () => {\n window.addEventListener(\"beforeunload\", () => {\n closedRef.value = true;\n Promise.allSettled([uiRegistry.dispose(), platforma.dispose().then(unwrapResult)]).catch(\n (err) => {\n error(\"error in dispose\", err);\n },\n );\n });\n\n while (!closedRef.value) {\n try {\n const patches = await platforma.getPatches(uTagRef.value).then(unwrapResult);\n\n debug(\"patches.length\", patches.value.length);\n debug(\"uTagRef.value\", uTagRef.value);\n debug(\"patches.uTag\", patches.uTag);\n debug(\"patches.author\", patches.author);\n debug(\"data.author\", data.author);\n\n uTagRef.value = patches.uTag;\n\n if (patches.value.length === 0) {\n await new Promise((resolve) => setTimeout(resolve, patchPoolingDelay));\n continue;\n }\n\n const isAuthorChanged = data.author?.authorId !== patches.author?.authorId;\n\n // Immutable behavior, apply external changes to the snapshot\n if (isAuthorChanged || data.isExternalSnapshot) {\n debug(\"got external changes, applying them to the snapshot\", patches.value);\n ignoreUpdates(() => {\n snapshot.value = applyPatch(snapshot.value, patches.value, false, false).newDocument;\n updateAppModel({ data: deriveDataFromStorage<Data>(snapshot.value.blockStorage) });\n // Reconcile plugin data from external source\n for (const [handle, pluginState] of pluginStates) {\n pluginState.ignoreUpdates(() => {\n pluginState.model.data = deepClone(\n getPluginData(snapshot.value.blockStorage, handle),\n );\n });\n }\n data.isExternalSnapshot = isAuthorChanged;\n });\n } else {\n // Mutable behavior\n debug(\"outputs changed\", patches.value);\n ignoreUpdates(() => {\n snapshot.value = applyPatch(snapshot.value, patches.value).newDocument;\n });\n }\n\n await new Promise((resolve) => setTimeout(resolve, patchPoolingDelay));\n } catch (err) {\n if (hasAbortError(err)) {\n debug(\"patches loop aborted\");\n closedRef.value = true;\n } else {\n error(\"error in patches loop\", err);\n await new Promise((resolve) => setTimeout(resolve, 1000));\n }\n }\n }\n })();\n\n const cloneData = () => deepClone(appModel.model.data) as Data;\n const cloneNavigationState = () =>\n deepClone(snapshot.value.navigationState) as Mutable<NavigationState<Href>>;\n\n const methods = {\n cloneData,\n cloneNavigationState,\n /**\n * Updates the UI state by applying a callback.\n *\n * @param cb - Callback to modify the current UI state.\n * @returns A promise resolving after the update is applied.\n * @todo Make it mutable since there is already an initial one\n */\n updateData(cb: (data: Data) => Data): Promise<boolean> {\n const newData = cb(cloneData());\n debug(\"updateData\", newData);\n appModel.model.data = newData;\n return setDataQueue.run(() => updateData(newData).then(unwrapResult));\n },\n /**\n * Navigates to a specific href by updating the navigation state.\n *\n * @param href - The target href to navigate to.\n * @returns A promise resolving after the navigation state is updated.\n */\n navigateTo(href: Href) {\n const newState = cloneNavigationState();\n newState.href = href;\n return setNavigationStateQueue.run(() => setNavigationState(newState).then(unwrapResult));\n },\n async allSettled() {\n await delay(0);\n const allQueues = [\n setDataQueue.allSettled(),\n ...Array.from(pluginDataQueues.values()).map((q) => q.allSettled()),\n ];\n await Promise.all(allQueues);\n },\n };\n\n const proxy = createNodeServiceProxy(platforma.serviceDispatch);\n const uiRegistry = createUiServiceRegistry({ proxy });\n const services = buildServices<UiServices>(platforma.serviceDispatch, uiRegistry);\n\n /** Creates a lazily-cached per-plugin reactive state. */\n const createPluginState = <F extends PluginFactoryLike>(\n handle: PluginHandle<F>,\n ): InternalPluginState<InferFactoryData<F>, InferFactoryOutputs<F>> => {\n const prefix = pluginOutputPrefix(handle);\n\n const pluginOutputs = computed(() => {\n const result: Record<string, unknown> = {};\n for (const [key, outputWithStatus] of Object.entries(\n snapshot.value.outputs as Partial<Readonly<Outputs>>,\n )) {\n if (!key.startsWith(prefix)) continue;\n const outputKey = key.slice(prefix.length);\n if (platforma.blockModelInfo.outputs[key]?.withStatus) {\n result[outputKey] = outputWithStatus\n ? ensureOutputHasStableFlag(outputWithStatus)\n : undefined;\n } else {\n result[outputKey] =\n outputWithStatus.ok && outputWithStatus.value !== undefined\n ? outputWithStatus.value\n : undefined;\n }\n }\n return result;\n });\n\n const pluginOutputErrors = computed(() => {\n const result: Record<string, Error | undefined> = {};\n for (const [key, vOrErr] of Object.entries(\n snapshot.value.outputs as Partial<Readonly<Outputs>>,\n )) {\n if (!key.startsWith(prefix)) continue;\n result[key.slice(prefix.length)] =\n vOrErr && vOrErr.ok === false ? new MultiError(vOrErr.errors) : undefined;\n }\n return result;\n });\n\n const pluginModel = reactive({\n data: deepClone(getPluginData(snapshot.value.blockStorage, handle)),\n outputs: pluginOutputs,\n outputErrors: pluginOutputErrors,\n }) as InternalPluginState<InferFactoryData<F>, InferFactoryOutputs<F>>[\"model\"];\n\n const { ignoreUpdates } = watchIgnorable(\n () => pluginModel.data,\n (newData) => {\n if (newData === undefined) return;\n debug(\"plugin setData\", handle, newData);\n getPluginDataQueue(handle).run(() =>\n updatePluginData(handle, deepClone(newData)).then(unwrapResult),\n );\n },\n { deep: true },\n );\n\n return {\n model: pluginModel,\n services: markRaw(services),\n ignoreUpdates,\n };\n };\n\n /** Plugin internals — provided via separate injection key, not exposed on useApp(). */\n const pluginAccess: PluginAccess = {\n getOrCreatePluginState<F extends PluginFactoryLike>(handle: PluginHandle<F>) {\n const existing = pluginStates.get(handle);\n if (existing) {\n return existing as unknown as PluginState<\n InferFactoryData<F>,\n InferFactoryOutputs<F>,\n InferFactoryUiServices<F>\n >;\n }\n const state = createPluginState(handle);\n pluginStates.set(handle, state);\n return state as unknown as PluginState<\n InferFactoryData<F>,\n InferFactoryOutputs<F>,\n InferFactoryUiServices<F>\n >;\n },\n };\n\n const plugins = Object.fromEntries(\n platforma.blockModelInfo.pluginIds.map((id) => [id, { handle: id }]),\n ) as InferPluginHandles<Plugins>;\n\n const getters = {\n closedRef,\n snapshot,\n plugins,\n services: markRaw(services),\n queryParams: computed(() => parseQuery<Href>(snapshot.value.navigationState.href as Href)),\n href: computed(() => snapshot.value.navigationState.href),\n hasErrors: computed(() =>\n Object.values(snapshot.value.outputs as Partial<Readonly<Outputs>>).some((v) => !v?.ok),\n ),\n };\n\n const app = Object.assign(reactive(Object.assign(appModel, getters)), methods);\n\n if (settings.debug) {\n // @ts-expect-error (to inspect in console in debug mode)\n globalThis.__block_app__ = app;\n }\n\n return { app, pluginAccess };\n}\n\nexport type BaseAppV3<\n Data = unknown,\n Args = unknown,\n Outputs extends BlockOutputsBase = BlockOutputsBase,\n Href extends `/${string}` = `/${string}`,\n Plugins extends Record<string, unknown> = Record<string, unknown>,\n UiServices extends Partial<AllUiServices> = Partial<AllUiServices>,\n> = ReturnType<typeof createAppV3<Data, Args, Outputs, Href, Plugins, UiServices>>[\"app\"];\n"],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { UiServiceRegistry } from '@milaboratories/pl-model-common';
2
+ import { NodeServiceProxy } from '@platforma-sdk/model';
3
+ export type UiServiceOptions = {
4
+ proxy: NodeServiceProxy;
5
+ };
6
+ export declare function createUiServiceRegistry(options: UiServiceOptions): UiServiceRegistry<{
7
+ PFrameSpec: import('@milaboratories/pl-model-common').Branded<"pframeSpec", import('@milaboratories/pl-model-common').ServiceTypesLike<import('@milaboratories/pl-model-common').PFrameSpecDriver, import('@milaboratories/pl-model-common').PFrameSpecDriver, "wasm">>;
8
+ PFrame: import('@milaboratories/pl-model-common').Branded<"pframe", import('@milaboratories/pl-model-common').ServiceTypesLike<import('@milaboratories/pl-model-common').PFrameModelDriver<import('@milaboratories/pl-model-common').PColumn<string | import('@milaboratories/pl-model-common').PColumnValues | import('@milaboratories/pl-model-common').DataInfo<string>>>, import('@milaboratories/pl-model-common').PFrameDriver, "node">>;
9
+ }>;
10
+ //# sourceMappingURL=service_factories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service_factories.d.ts","sourceRoot":"","sources":["../../src/internal/service_factories.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAY,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAE9E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,gBAAgB,CAAC;CACzB,CAAC;AAEF,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,gBAAgB;;;GAKhE"}
@@ -0,0 +1,19 @@
1
+ import { Services as e, UiServiceRegistry as t } from "@milaboratories/pl-model-common";
2
+ import { SpecDriver as n } from "@milaboratories/pf-spec-driver";
3
+ /**
4
+ * UI service factories — add a factory for each new service here.
5
+ *
6
+ * Each entry maps a Services key to a factory function that creates the
7
+ * UI-side driver instance:
8
+ * - WASM services: instantiated directly (e.g. SpecDriver)
9
+ * - Node services: proxied via IPC using NodeServiceProxy
10
+ */
11
+ function r(r) {
12
+ return new t(e, {
13
+ PFrameSpec: () => new n(),
14
+ PFrame: () => r.proxy(e.PFrame)
15
+ });
16
+ }
17
+ export { r as createUiServiceRegistry };
18
+
19
+ //# sourceMappingURL=service_factories.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service_factories.js","names":[],"sources":["../../src/internal/service_factories.ts"],"sourcesContent":["/**\n * UI service factories — add a factory for each new service here.\n *\n * Each entry maps a Services key to a factory function that creates the\n * UI-side driver instance:\n * - WASM services: instantiated directly (e.g. SpecDriver)\n * - Node services: proxied via IPC using NodeServiceProxy\n */\n\nimport { Services, UiServiceRegistry } from \"@milaboratories/pl-model-common\";\nimport { SpecDriver } from \"@milaboratories/pf-spec-driver\";\nimport type { NodeServiceProxy } from \"@platforma-sdk/model\";\n\nexport type UiServiceOptions = {\n proxy: NodeServiceProxy;\n};\n\nexport function createUiServiceRegistry(options: UiServiceOptions) {\n return new UiServiceRegistry(Services, {\n PFrameSpec: () => new SpecDriver(),\n PFrame: () => options.proxy(Services.PFrame),\n });\n}\n"],"mappings":";;;;;;;;;;AAiBA,SAAgB,EAAwB,GAA2B;AACjE,QAAO,IAAI,EAAkB,GAAU;EACrC,kBAAkB,IAAI,GAAY;EAClC,cAAc,EAAQ,MAAM,EAAS,OAAO;EAC7C,CAAC"}
@@ -1,7 +1,7 @@
1
1
  import { Reactive } from 'vue';
2
- import { PluginHandle, InferFactoryData, InferFactoryOutputs, PluginFactoryLike } from '@platforma-sdk/model';
2
+ import { PluginHandle, InferFactoryData, InferFactoryOutputs, InferFactoryUiServices, PluginFactoryLike } from '@platforma-sdk/model';
3
3
  /** Per-plugin reactive model exposed to consumers via usePlugin(). */
4
- export interface PluginState<Data = unknown, Outputs = unknown> {
4
+ export interface PluginState<Data = unknown, Outputs = unknown, Services = Record<string, unknown>> {
5
5
  readonly model: Reactive<{
6
6
  data: Data;
7
7
  outputs: Outputs extends Record<string, unknown> ? {
@@ -11,10 +11,11 @@ export interface PluginState<Data = unknown, Outputs = unknown> {
11
11
  [K in keyof Outputs]?: Error;
12
12
  } : Record<string, Error | undefined>;
13
13
  }>;
14
+ readonly services: Services;
14
15
  }
15
16
  /** Internal interface for plugin access — provided via Vue injection to usePlugin(). */
16
17
  export interface PluginAccess {
17
- getOrCreatePluginState<F extends PluginFactoryLike>(handle: PluginHandle<F>): PluginState<InferFactoryData<F>, InferFactoryOutputs<F>>;
18
+ getOrCreatePluginState<F extends PluginFactoryLike>(handle: PluginHandle<F>): PluginState<InferFactoryData<F>, InferFactoryOutputs<F>, InferFactoryUiServices<F>>;
18
19
  }
19
20
  /**
20
21
  * Composable for accessing a plugin's reactive model: data, outputs, and outputErrors.
@@ -40,5 +41,5 @@ export interface PluginAccess {
40
41
  * </script>
41
42
  * ```
42
43
  */
43
- export declare function usePlugin<F extends PluginFactoryLike>(handle: PluginHandle<F>): PluginState<InferFactoryData<F>, InferFactoryOutputs<F>>;
44
+ export declare function usePlugin<F extends PluginFactoryLike>(handle: PluginHandle<F>): PluginState<InferFactoryData<F>, InferFactoryOutputs<F>, InferFactoryUiServices<F>>;
44
45
  //# sourceMappingURL=usePlugin.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"usePlugin.d.ts","sourceRoot":"","sources":["../src/usePlugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,QAAQ,EAAE,MAAM,KAAK,CAAC;AAE5C,OAAO,KAAK,EACV,YAAY,EACZ,gBAAgB,EAChB,mBAAmB,EACnB,iBAAiB,EAClB,MAAM,sBAAsB,CAAC;AAE9B,sEAAsE;AACtE,MAAM,WAAW,WAAW,CAAC,IAAI,GAAG,OAAO,EAAE,OAAO,GAAG,OAAO;IAC5D,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;QACvB,IAAI,EAAE,IAAI,CAAC;QACX,OAAO,EAAE,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5C;aAAG,CAAC,IAAI,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,SAAS;SAAE,GAChD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC5B,YAAY,EAAE,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjD;aAAG,CAAC,IAAI,MAAM,OAAO,CAAC,CAAC,EAAE,KAAK;SAAE,GAChC,MAAM,CAAC,MAAM,EAAE,KAAK,GAAG,SAAS,CAAC,CAAC;KACvC,CAAC,CAAC;CACJ;AAED,wFAAwF;AACxF,MAAM,WAAW,YAAY;IAC3B,sBAAsB,CAAC,CAAC,SAAS,iBAAiB,EAChD,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,GACtB,WAAW,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC;CAC7D;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,iBAAiB,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,4DAW7E"}
1
+ {"version":3,"file":"usePlugin.d.ts","sourceRoot":"","sources":["../src/usePlugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,QAAQ,EAAE,MAAM,KAAK,CAAC;AAE5C,OAAO,KAAK,EACV,YAAY,EACZ,gBAAgB,EAChB,mBAAmB,EACnB,sBAAsB,EACtB,iBAAiB,EAClB,MAAM,sBAAsB,CAAC;AAE9B,sEAAsE;AACtE,MAAM,WAAW,WAAW,CAC1B,IAAI,GAAG,OAAO,EACd,OAAO,GAAG,OAAO,EACjB,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAElC,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;QACvB,IAAI,EAAE,IAAI,CAAC;QACX,OAAO,EAAE,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5C;aAAG,CAAC,IAAI,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,SAAS;SAAE,GAChD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC5B,YAAY,EAAE,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjD;aAAG,CAAC,IAAI,MAAM,OAAO,CAAC,CAAC,EAAE,KAAK;SAAE,GAChC,MAAM,CAAC,MAAM,EAAE,KAAK,GAAG,SAAS,CAAC,CAAC;KACvC,CAAC,CAAC;IACH,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;CAC7B;AAED,wFAAwF;AACxF,MAAM,WAAW,YAAY;IAC3B,sBAAsB,CAAC,CAAC,SAAS,iBAAiB,EAChD,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,GACtB,WAAW,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,mBAAmB,CAAC,CAAC,CAAC,EAAE,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC;CACxF;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,iBAAiB,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,uFAW7E"}
@@ -1 +1 @@
1
- {"version":3,"file":"usePlugin.js","names":[],"sources":["../src/usePlugin.ts"],"sourcesContent":["import { inject, type Reactive } from \"vue\";\nimport { pluginDataKey } from \"./defineApp\";\nimport type {\n PluginHandle,\n InferFactoryData,\n InferFactoryOutputs,\n PluginFactoryLike,\n} from \"@platforma-sdk/model\";\n\n/** Per-plugin reactive model exposed to consumers via usePlugin(). */\nexport interface PluginState<Data = unknown, Outputs = unknown> {\n readonly model: Reactive<{\n data: Data;\n outputs: Outputs extends Record<string, unknown>\n ? { [K in keyof Outputs]: Outputs[K] | undefined }\n : Record<string, unknown>;\n outputErrors: Outputs extends Record<string, unknown>\n ? { [K in keyof Outputs]?: Error }\n : Record<string, Error | undefined>;\n }>;\n}\n\n/** Internal interface for plugin access — provided via Vue injection to usePlugin(). */\nexport interface PluginAccess {\n getOrCreatePluginState<F extends PluginFactoryLike>(\n handle: PluginHandle<F>,\n ): PluginState<InferFactoryData<F>, InferFactoryOutputs<F>>;\n}\n\n/**\n * Composable for accessing a plugin's reactive model: data, outputs, and outputErrors.\n *\n * Mirrors the `app.model` access pattern — `plugin.model.data` is reactive and deep-watched,\n * mutations are automatically queued and sent to storage.\n *\n * @param handle - Opaque plugin handle obtained from `app.plugins`.\n * @typeParam F - The plugin factory type (inferred from the handle)\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { usePlugin, type InferPluginHandle } from '@platforma-sdk/ui-vue';\n * import type { CounterPlugin } from './plugins/counter';\n *\n * const props = defineProps<{ instance: InferPluginHandle<CounterPlugin> }>();\n * const plugin = usePlugin(props.instance);\n *\n * plugin.model.data.count += 1; // reactive, triggers storage update\n * plugin.model.outputs.displayText // computed, plugin's own outputs only\n * plugin.model.outputErrors.displayText // Error | undefined\n * </script>\n * ```\n */\nexport function usePlugin<F extends PluginFactoryLike>(handle: PluginHandle<F>) {\n const access = inject<PluginAccess>(pluginDataKey);\n\n if (!access) {\n throw new Error(\n \"usePlugin requires a V3 block (BlockModelV3). \" +\n \"Make sure the block uses apiVersion 3 and the plugin is installed.\",\n );\n }\n\n return access.getOrCreatePluginState<F>(handle);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAqDA,SAAgB,EAAuC,GAAyB;CAC9E,IAAM,IAAS,EAAqB,EAAc;AAElD,KAAI,CAAC,EACH,OAAU,MACR,mHAED;AAGH,QAAO,EAAO,uBAA0B,EAAO"}
1
+ {"version":3,"file":"usePlugin.js","names":[],"sources":["../src/usePlugin.ts"],"sourcesContent":["import { inject, type Reactive } from \"vue\";\nimport { pluginDataKey } from \"./defineApp\";\nimport type {\n PluginHandle,\n InferFactoryData,\n InferFactoryOutputs,\n InferFactoryUiServices,\n PluginFactoryLike,\n} from \"@platforma-sdk/model\";\n\n/** Per-plugin reactive model exposed to consumers via usePlugin(). */\nexport interface PluginState<\n Data = unknown,\n Outputs = unknown,\n Services = Record<string, unknown>,\n> {\n readonly model: Reactive<{\n data: Data;\n outputs: Outputs extends Record<string, unknown>\n ? { [K in keyof Outputs]: Outputs[K] | undefined }\n : Record<string, unknown>;\n outputErrors: Outputs extends Record<string, unknown>\n ? { [K in keyof Outputs]?: Error }\n : Record<string, Error | undefined>;\n }>;\n readonly services: Services;\n}\n\n/** Internal interface for plugin access — provided via Vue injection to usePlugin(). */\nexport interface PluginAccess {\n getOrCreatePluginState<F extends PluginFactoryLike>(\n handle: PluginHandle<F>,\n ): PluginState<InferFactoryData<F>, InferFactoryOutputs<F>, InferFactoryUiServices<F>>;\n}\n\n/**\n * Composable for accessing a plugin's reactive model: data, outputs, and outputErrors.\n *\n * Mirrors the `app.model` access pattern — `plugin.model.data` is reactive and deep-watched,\n * mutations are automatically queued and sent to storage.\n *\n * @param handle - Opaque plugin handle obtained from `app.plugins`.\n * @typeParam F - The plugin factory type (inferred from the handle)\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { usePlugin, type InferPluginHandle } from '@platforma-sdk/ui-vue';\n * import type { CounterPlugin } from './plugins/counter';\n *\n * const props = defineProps<{ instance: InferPluginHandle<CounterPlugin> }>();\n * const plugin = usePlugin(props.instance);\n *\n * plugin.model.data.count += 1; // reactive, triggers storage update\n * plugin.model.outputs.displayText // computed, plugin's own outputs only\n * plugin.model.outputErrors.displayText // Error | undefined\n * </script>\n * ```\n */\nexport function usePlugin<F extends PluginFactoryLike>(handle: PluginHandle<F>) {\n const access = inject<PluginAccess>(pluginDataKey);\n\n if (!access) {\n throw new Error(\n \"usePlugin requires a V3 block (BlockModelV3). \" +\n \"Make sure the block uses apiVersion 3 and the plugin is installed.\",\n );\n }\n\n return access.getOrCreatePluginState<F>(handle);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA2DA,SAAgB,EAAuC,GAAyB;CAC9E,IAAM,IAAS,EAAqB,EAAc;AAElD,KAAI,CAAC,EACH,OAAU,MACR,mHAED;AAGH,QAAO,EAAO,uBAA0B,EAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platforma-sdk/ui-vue",
3
- "version": "1.61.2",
3
+ "version": "1.62.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "exports": {
@@ -25,8 +25,10 @@
25
25
  "lru-cache": "^11.2.2",
26
26
  "vue": "^3.5.24",
27
27
  "zod": "~3.23.8",
28
- "@milaboratories/uikit": "2.11.3",
29
- "@platforma-sdk/model": "1.61.1"
28
+ "@milaboratories/pf-spec-driver": "1.2.0",
29
+ "@milaboratories/pl-model-common": "1.30.0",
30
+ "@platforma-sdk/model": "1.62.0",
31
+ "@milaboratories/uikit": "2.11.4"
30
32
  },
31
33
  "devDependencies": {
32
34
  "@faker-js/faker": "^9.2.0",
package/src/defineApp.ts CHANGED
@@ -5,6 +5,7 @@ import type {
5
5
  PlatformaV1,
6
6
  PlatformaV2,
7
7
  BlockCodeKnownFeatureFlags,
8
+ UiServices as AllUiServices,
8
9
  } from "@platforma-sdk/model";
9
10
  import {
10
11
  getPlatformaApiVersion,
@@ -166,15 +167,16 @@ export function defineAppV3<
166
167
  Outputs extends BlockOutputsBase = BlockOutputsBase,
167
168
  Href extends `/${string}` = `/${string}`,
168
169
  Plugins extends Record<string, unknown> = Record<string, unknown>,
170
+ UiServices extends Partial<AllUiServices> = Partial<AllUiServices>,
169
171
  Extend extends ExtendSettings<Href> = ExtendSettings<Href>,
170
172
  >(
171
- platforma: PlatformaV3<Data, Args, Outputs, Href, Plugins> & {
173
+ platforma: PlatformaV3<Data, Args, Outputs, Href, Plugins, UiServices> & {
172
174
  blockModelInfo: BlockModelInfo;
173
175
  },
174
- extendApp: (app: BaseAppV3<Data, Args, Outputs, Href, Plugins>) => Extend,
176
+ extendApp: (app: BaseAppV3<Data, Args, Outputs, Href, Plugins, UiServices>) => Extend,
175
177
  settings: AppSettings = {},
176
- ): SdkPluginV3<Data, Args, Outputs, Href, Plugins, Extend> {
177
- let app: AppV3<Data, Args, Outputs, Href, Plugins, Extend> | undefined = undefined;
178
+ ): SdkPluginV3<Data, Args, Outputs, Href, Plugins, Extend, UiServices> {
179
+ let app: AppV3<Data, Args, Outputs, Href, Plugins, Extend, UiServices> | undefined = undefined;
178
180
 
179
181
  // Captured during install() so V3 can provide plugin data access after async load
180
182
  let vueAppInstance: VueApp | undefined;
@@ -191,11 +193,14 @@ export function defineAppV3<
191
193
  await platforma.loadBlockState().then((stateOrError) => {
192
194
  const state = unwrapResult(stateOrError);
193
195
  plugin.loaded = true;
194
- const { app: baseApp, pluginAccess } = createAppV3<Data, Args, Outputs, Href, Plugins>(
195
- state,
196
- platforma,
197
- settings,
198
- );
196
+ const { app: baseApp, pluginAccess } = createAppV3<
197
+ Data,
198
+ Args,
199
+ Outputs,
200
+ Href,
201
+ Plugins,
202
+ UiServices
203
+ >(state, platforma, settings);
199
204
 
200
205
  if (!vueAppInstance) {
201
206
  throw new Error(
@@ -218,7 +223,7 @@ export function defineAppV3<
218
223
  getRoute(href: Href): Component | undefined {
219
224
  return routes[href];
220
225
  },
221
- } as AppV3<Data, Args, Outputs, Href, Plugins, Extend>);
226
+ } as AppV3<Data, Args, Outputs, Href, Plugins, Extend, UiServices>);
222
227
  });
223
228
  };
224
229
 
@@ -234,7 +239,8 @@ export function defineAppV3<
234
239
  Outputs,
235
240
  PageHref,
236
241
  Plugins,
237
- Extend
242
+ Extend,
243
+ UiServices
238
244
  >;
239
245
  },
240
246
  install(app: VueApp) {
@@ -247,7 +253,7 @@ export function defineAppV3<
247
253
  },
248
254
  });
249
255
 
250
- return plugin as SdkPluginV3<Data, Args, Outputs, Href, Plugins, Extend>;
256
+ return plugin as SdkPluginV3<Data, Args, Outputs, Href, Plugins, Extend, UiServices>;
251
257
  }
252
258
 
253
259
  export type AppV1<
@@ -275,7 +281,8 @@ export type AppV3<
275
281
  Href extends `/${string}` = `/${string}`,
276
282
  Plugins extends Record<string, unknown> = Record<string, unknown>,
277
283
  Local extends ExtendSettings<Href> = ExtendSettings<Href>,
278
- > = BaseAppV3<Data, Args, Outputs, Href, Plugins> &
284
+ UiServices extends Partial<AllUiServices> = Partial<AllUiServices>,
285
+ > = BaseAppV3<Data, Args, Outputs, Href, Plugins, UiServices> &
279
286
  Reactive<Omit<Local, "routes">> & { getRoute(href: Href): Component | undefined };
280
287
 
281
288
  // ---------------------------------------------------------------------------
@@ -319,12 +326,21 @@ export type SdkPluginV3<
319
326
  Href extends `/${string}` = `/${string}`,
320
327
  Plugins extends Record<string, unknown> = Record<string, unknown>,
321
328
  Local extends ExtendSettings<Href> = ExtendSettings<Href>,
329
+ UiServices extends Partial<AllUiServices> = Partial<AllUiServices>,
322
330
  > = {
323
331
  apiVersion: 3;
324
332
  featureFlags: BlockCodeKnownFeatureFlags;
325
333
  loaded: boolean;
326
334
  error: unknown;
327
- useApp<PageHref extends Href = Href>(): AppV3<Data, Args, Outputs, PageHref, Plugins, Local>;
335
+ useApp<PageHref extends Href = Href>(): AppV3<
336
+ Data,
337
+ Args,
338
+ Outputs,
339
+ PageHref,
340
+ Plugins,
341
+ Local,
342
+ UiServices
343
+ >;
328
344
  install(app: VueApp): void;
329
345
  };
330
346