@dolphy-app/extension-sdk 0.4.0 → 0.6.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/dist/index.d.ts CHANGED
@@ -1,116 +1,12 @@
1
- import { a as defineAnswerView, i as MountAnswerView, n as AnswerViewApi, r as AnswerViewInstance, t as AnswerView } from "./answer-view-D6wnyThb.js";
2
- import { CommandHandler, ExerciseTypeHandler, ExporterHandler, ExtensionContext as ExtensionContext$1, ExtensionIdSet, ExtensionModule, GradePolicyHandler, ImporterHandler, JsonValue, LearningEventHandler, MarkdownRenderContext, MarkdownRendererModule, NotifyEffect, OpenPanelEffect, PanelContext as PanelContext$1, PanelModule, ScheduleHandler, WidgetContext as WidgetContext$1, WidgetModule } from "@dolphy-app/extension-api";
1
+ import { t as ExtensionEngine } from "./index-I_CXC6Lj.js";
2
+ import { a as MountContext, c as ServerContext, d as defineExerciseType, f as defineMountable, i as InjectionRegistration, l as ServerEntry, n as ClientContext, o as Mountable, p as defineServer, r as ClientEntry, s as PanelRegistration, t as AppApi, u as defineClient } from "./define-entry-lsuxzKdD.js";
3
+ import { RpcTarget, callRpc, defineRpc } from "./rpc.js";
4
+ import { JsonValue, NotifyEffect, OpenPanelEffect } from "@dolphy-app/extension-api";
3
5
  export * from "@dolphy-app/extension-api";
4
- //#region packages/extension-sdk/src/ids.d.ts
5
- /**
6
- * The ids declared in `extension.json`. Empty here: `dolphy-ext types` (and
7
- * every `dolphy-ext build`) writes `.dolphy/ids.d.ts`, which augments this
8
- * interface with one key per kind of id:
9
- *
10
- * ```ts
11
- * declare module '@dolphy-app/extension-sdk' {
12
- * interface ExtensionIds {
13
- * exerciseTypes: 'acme.echo';
14
- * commands: 'acme.a' | 'acme.b';
15
- * settings: { 'acme.goal': number };
16
- * // …
17
- * }
18
- * }
19
- * ```
20
- *
21
- * While the interface is empty (no generated file) every id is a plain
22
- * `string` and `defineExtension` does not require any record.
23
- */
24
- interface ExtensionIds {}
25
- type Declared<K extends keyof ExtensionIdSet> = K extends keyof ExtensionIds ? ExtensionIds[K] extends ExtensionIdSet[K] ? ExtensionIds[K] : ExtensionIdSet[K] : ExtensionIdSet[K];
26
- /** `ExtensionIds` with the plain-string fallback for the kinds it does not declare. */
27
- type ResolvedIds = { [K in keyof ExtensionIdSet]: Declared<K>; };
28
- /** Whether the generated declarations are part of the program. */
29
- type HasGeneratedIds = [keyof ExtensionIds] extends [never] ? false : true;
30
- /**
31
- * `ExtensionContext` of this extension: `settings`, `events`, `commands` and
32
- * the `register…` methods accept the declared ids only.
33
- */
34
- type ExtensionContext = ExtensionContext$1<ResolvedIds>;
35
- /** Context of a panel module; `call` accepts the declared command ids only. */
36
- type PanelContext = PanelContext$1<ResolvedIds['commands']>;
37
- /** Context of a widget module; `call` accepts the declared command ids only. */
38
- type WidgetContext = WidgetContext$1<ResolvedIds['commands']>;
39
- /**
40
- * A record that holds exactly the declared ids: a missing and an extra key are
41
- * both compile errors. With no generated declarations any keys are accepted;
42
- * with none declared of this kind no key is accepted.
43
- */
44
- type Exact<Id extends string, Value> = [HasGeneratedIds] extends [false] ? {
45
- readonly [key: string]: Value;
46
- } : [Id] extends [never] ? {
47
- readonly [key: string]: never;
48
- } : { readonly [K in Id]: Value; };
49
- /** `export const views = { … } satisfies ExtensionViews`: one `defineAnswerView` per declared exercise type. */
50
- type ExtensionViews = Exact<ResolvedIds['exerciseTypes'], AnswerView>;
51
- /** `export const panels = { … } satisfies ExtensionPanels`: one `defineExtensionPanel` per declared panel. */
52
- type ExtensionPanels = Exact<ResolvedIds['panels'], PanelModule<HTMLElement, ResolvedIds['commands']>>;
53
- /** `export const widgets = { … } satisfies ExtensionWidgets`: one `defineExtensionWidget` per declared widget. */
54
- type ExtensionWidgets = Exact<ResolvedIds['widgets'], WidgetModule<HTMLElement, ResolvedIds['commands']>>;
55
- /** `export const markdown = { … } satisfies ExtensionMarkdown`: one `defineMarkdownRenderer` per declared language. */
56
- type ExtensionMarkdown = Exact<ResolvedIds['markdownLanguages'], MarkdownRendererModule<HTMLElement>>;
57
- //#endregion
58
6
  //#region packages/extension-sdk/src/commands.d.ts
59
7
  /** A command result: the app shows a notification (1–500 characters, as is, no markup). */
60
8
  export declare const notify: (text: string) => NotifyEffect;
61
- /** A command result: the app opens a panel of this extension (a declared panel id); `props` reach the panel as `ctx.props`. */
62
- export declare const openPanel: (panelId: ResolvedIds["panels"], props?: JsonValue) => OpenPanelEffect;
63
- //#endregion
64
- //#region packages/extension-sdk/src/define-extension.d.ts
65
- declare const inActivateBrand: unique symbol;
66
- /** Type of `inActivate`. */
67
- interface InActivate {
68
- readonly [inActivateBrand]: true;
69
- }
70
- /**
71
- * A record value in `defineExtension` that says "this id is registered in
72
- * `activate`" (`ctx.commands.register`, `ctx.events.on`,
73
- * `ctx.importers.register`, `ctx.exporters.register`, `ctx.schedule.on`,
74
- * `ctx.registerExerciseType`, `ctx.registerGradePolicy`) rather than by a
75
- * handler in the record. Needed because the records must name every declared
76
- * id: a handler that needs `ctx` is written in `activate`, and its id gets this
77
- * marker in the record.
78
- */
79
- export declare const inActivate: InActivate;
80
- /** Without generated declarations a record may name any subset: an index signature already does, a record keyed by the event names needs `Partial`. */
81
- type Lenient<Entries> = string extends keyof Entries ? Entries : Partial<Entries>;
82
- /**
83
- * The definition record of one kind of id. With generated declarations the
84
- * record is required (when the manifest declares any id of the kind) and holds
85
- * exactly the declared ids: a missing and an extra key are compile errors.
86
- * Without them every key is accepted and the record is optional.
87
- */
88
- type Section<Name extends string, Id extends string, Entries> = [HasGeneratedIds] extends [false] ? { readonly [N in Name]?: Lenient<Entries>; } : [Id] extends [never] ? { readonly [N in Name]?: never; } : { readonly [N in Name]: Entries; };
89
- type Ids = ResolvedIds;
90
- /** Learning-event handlers by event name; the events must be declared in `contributes.events`, the `learning.events` permission is needed. */
91
- type EventHandlers = { readonly [N in Ids['events']]: LearningEventHandler<N> | InActivate; };
92
- /**
93
- * What `defineExtension` takes. `exerciseTypes`, `gradePolicies`, `events`,
94
- * `commands`, `schedules`, `importers` and `exporters` name every id
95
- * `extension.json` declares for them, exactly: a handler, or `inActivate` for
96
- * an id that `activate` registers.
97
- */
98
- type ExtensionDefinition = Section<'exerciseTypes', Ids['exerciseTypes'], { readonly [K in Ids['exerciseTypes']]: ExerciseTypeHandler | InActivate; }> & Section<'gradePolicies', Ids['gradePolicies'], { readonly [K in Ids['gradePolicies']]: GradePolicyHandler | InActivate; }> & Section<'events', Ids['events'], EventHandlers> & Section<'commands', Ids['commands'], { readonly [K in Ids['commands']]: CommandHandler | InActivate; }> & Section<'schedules', Ids['schedules'], { readonly [K in Ids['schedules']]: ScheduleHandler | InActivate; }> & Section<'importers', Ids['importers'], { readonly [K in Ids['importers']]: ImporterHandler | InActivate; }> & Section<'exporters', Ids['exporters'], { readonly [K in Ids['exporters']]: ExporterHandler | InActivate; }> & {
99
- /** Runs after the records are registered. */
100
- activate?(context: ExtensionContext): void | Promise<void>;
101
- deactivate?(): void | Promise<void>;
102
- };
103
- export declare const defineExerciseType: <Spec, Answer, View>(handler: ExerciseTypeHandler<Spec, Answer, View>) => ExerciseTypeHandler<Spec, Answer, View>;
104
- export declare const defineExtension: (declared: ExtensionDefinition) => ExtensionModule;
105
- //#endregion
106
- //#region packages/extension-sdk/src/markdown-renderer.d.ts
107
- /** Entry `markdown[<language>]` in `src/index.ts` (`contributes.markdownRenderers`). */
108
- export declare const defineMarkdownRenderer: (render: (source: string, container: HTMLElement, context: MarkdownRenderContext) => void | Promise<void>) => MarkdownRendererModule<HTMLElement>;
109
- //#endregion
110
- //#region packages/extension-sdk/src/panel.d.ts
111
- /** An entry of `panels[<panel id>]` in `src/index.ts` (`contributes.panels`); `ctx.call` accepts the declared command ids. */
112
- export declare const defineExtensionPanel: (module: PanelModule<HTMLElement, ResolvedIds["commands"]>) => PanelModule<HTMLElement, ResolvedIds["commands"]>;
113
- /** An entry of `widgets[<widget id>]` in `src/index.ts` (`contributes.widgets`); `ctx.call` accepts the declared command ids. */
114
- export declare const defineExtensionWidget: (module: WidgetModule<HTMLElement, ResolvedIds["commands"]>) => WidgetModule<HTMLElement, ResolvedIds["commands"]>;
9
+ /** A command result: the app opens a panel of this extension (a panel id of `client.addPanel`); `props` reach the panel as `usePanel().props`. */
10
+ export declare const openPanel: (panelId: string, props?: JsonValue) => OpenPanelEffect;
115
11
  //#endregion
116
- export { type AnswerView, type AnswerViewApi, type AnswerViewInstance, type EventHandlers, type ExtensionContext, type ExtensionDefinition, type ExtensionIds, type ExtensionMarkdown, type ExtensionPanels, type ExtensionViews, type ExtensionWidgets, type InActivate, type MountAnswerView, type PanelContext, type WidgetContext, defineAnswerView };
12
+ export { type AppApi, type ClientContext, type ClientEntry, type ExtensionEngine, type InjectionRegistration, type MountContext, type Mountable, type PanelRegistration, type RpcTarget, type ServerContext, type ServerEntry, callRpc, defineClient, defineExerciseType, defineMountable, defineRpc, defineServer };
package/dist/index.js CHANGED
@@ -1,97 +1,16 @@
1
+ import { i as defineServer, n as defineExerciseType, r as defineMountable, t as defineClient } from "./define-entry-BTz2F3qv.js";
2
+ import { callRpc, defineRpc } from "./rpc.js";
3
+
1
4
  export * from "@dolphy-app/extension-api"
2
5
 
3
6
  //#region packages/extension-sdk/src/commands.ts
4
7
  /** A command result: the app shows a notification (1–500 characters, as is, no markup). */
5
8
  const notify = (text) => ({ notify: text });
6
- /** A command result: the app opens a panel of this extension (a declared panel id); `props` reach the panel as `ctx.props`. */
9
+ /** A command result: the app opens a panel of this extension (a panel id of `client.addPanel`); `props` reach the panel as `usePanel().props`. */
7
10
  const openPanel = (panelId, props) => props === void 0 ? { openPanel: panelId } : {
8
11
  openPanel: panelId,
9
12
  props
10
13
  };
11
14
 
12
15
  //#endregion
13
- //#region packages/extension-sdk/src/define-extension.ts
14
- /**
15
- * A record value in `defineExtension` that says "this id is registered in
16
- * `activate`" (`ctx.commands.register`, `ctx.events.on`,
17
- * `ctx.importers.register`, `ctx.exporters.register`, `ctx.schedule.on`,
18
- * `ctx.registerExerciseType`, `ctx.registerGradePolicy`) rather than by a
19
- * handler in the record. Needed because the records must name every declared
20
- * id: a handler that needs `ctx` is written in `activate`, and its id gets this
21
- * marker in the record.
22
- */
23
- const inActivate = /*#__PURE__*/ Object.freeze({});
24
- const defineExerciseType = /* @__NO_SIDE_EFFECTS__ */ (handler) => handler;
25
- const disposeInReverse = async (registrations) => {
26
- const errors = [];
27
- for (const registration of registrations.splice(0).reverse()) try {
28
- await registration.dispose();
29
- } catch (error) {
30
- errors.push(error);
31
- }
32
- return errors;
33
- };
34
- /** The entries of a record that carry a handler (not `inActivate`). */
35
- const handlersOf = (record) => Object.entries(record ?? {}).filter((entry) => entry[1] !== inActivate);
36
- const defineExtension = /* @__NO_SIDE_EFFECTS__ */ (declared) => {
37
- const definition = declared;
38
- const registrations = [];
39
- const register = (context) => {
40
- for (const [type, handler] of handlersOf(definition.exerciseTypes)) registrations.push(context.registerExerciseType(type, handler));
41
- for (const [id, handler] of handlersOf(definition.gradePolicies)) registrations.push(context.registerGradePolicy(id, handler));
42
- for (const [name, handler] of handlersOf(definition.events)) registrations.push(context.events.on(name, handler));
43
- for (const [id, handler] of handlersOf(definition.commands)) registrations.push(context.commands.register(id, handler));
44
- for (const [id, handler] of handlersOf(definition.schedules)) registrations.push(context.schedule.on(id, handler));
45
- for (const [id, handler] of handlersOf(definition.importers)) registrations.push(context.importers.register(id, handler));
46
- for (const [id, handler] of handlersOf(definition.exporters)) registrations.push(context.exporters.register(id, handler));
47
- };
48
- const activate = async (context) => {
49
- try {
50
- register(context);
51
- await definition.activate?.(context);
52
- } catch (error) {
53
- const disposalErrors = await disposeInReverse(registrations);
54
- if (disposalErrors.length === 0) throw error;
55
- throw new AggregateError([error, ...disposalErrors], "activation failed and rollback was incomplete");
56
- }
57
- };
58
- const deactivate = async () => {
59
- const failures = [];
60
- try {
61
- await definition.deactivate?.();
62
- } catch (error) {
63
- failures.push(error);
64
- }
65
- const disposalErrors = await disposeInReverse(registrations);
66
- if (disposalErrors.length > 0) failures.push(new AggregateError(disposalErrors, "failed to dispose contributions"));
67
- if (failures.length === 1) throw failures[0];
68
- if (failures.length > 1) throw new AggregateError(failures, "failed to deactivate extension");
69
- };
70
- return {
71
- activate,
72
- deactivate
73
- };
74
- };
75
-
76
- //#endregion
77
- //#region packages/extension-sdk/src/answer-view.ts
78
- /**
79
- * Entry `views[<exercise kind id>]` in `src/index.ts`. Registers nothing:
80
- * the custom element with the manifest tag is defined by the build's browser file.
81
- */
82
- const defineAnswerView = /* @__NO_SIDE_EFFECTS__ */ (mount) => ({ mount });
83
-
84
- //#endregion
85
- //#region packages/extension-sdk/src/markdown-renderer.ts
86
- /** Entry `markdown[<language>]` in `src/index.ts` (`contributes.markdownRenderers`). */
87
- const defineMarkdownRenderer = /* @__NO_SIDE_EFFECTS__ */ (render) => ({ render });
88
-
89
- //#endregion
90
- //#region packages/extension-sdk/src/panel.ts
91
- /** An entry of `panels[<panel id>]` in `src/index.ts` (`contributes.panels`); `ctx.call` accepts the declared command ids. */
92
- const defineExtensionPanel = /* @__NO_SIDE_EFFECTS__ */ (module) => module;
93
- /** An entry of `widgets[<widget id>]` in `src/index.ts` (`contributes.widgets`); `ctx.call` accepts the declared command ids. */
94
- const defineExtensionWidget = /* @__NO_SIDE_EFFECTS__ */ (module) => module;
95
-
96
- //#endregion
97
- export { defineAnswerView, defineExerciseType, defineExtension, defineExtensionPanel, defineExtensionWidget, defineMarkdownRenderer, inActivate, notify, openPanel };
16
+ export { callRpc, defineClient, defineExerciseType, defineMountable, defineRpc, defineServer, notify, openPanel };
@@ -0,0 +1,51 @@
1
+ import { t as ExtensionEngine } from "./index-I_CXC6Lj.js";
2
+ import { a as MountContext$1, o as Mountable, t as AppApi$1 } from "./define-entry-lsuxzKdD.js";
3
+ import { AppLocale, AppTheme, InjectionHandle, PanelHandle, RpcContract } from "@dolphy-app/extension-api";
4
+ import { ComponentType } from "react";
5
+ //#region packages/extension-sdk/src/react.d.ts
6
+ /**
7
+ * The `MountContext` of the component drawn by `reactComponent`. `Props` and
8
+ * `Handle` are the ones of the surface: pick them by the way the component is
9
+ * registered. Only inside a component that `reactComponent` draws.
10
+ */
11
+ export declare const useMountContext: <Props = unknown, Handle = undefined>() => MountContext$1<Props, Handle>;
12
+ /** The window API, the same object as `useApp()` of a Vue component. Only inside a component that `reactComponent` draws. */
13
+ export declare const useApp: () => AppApi$1;
14
+ /** The engine client of the window, the same object as `useEngine()` of a Vue component. Only inside a component that `reactComponent` draws. */
15
+ export declare const useEngine: () => ExtensionEngine;
16
+ /**
17
+ * Binds the contract to the server part of this extension, as `useRpc` of a
18
+ * Vue component does: the returned function validates the input and the
19
+ * answer with the contract and rejects with an `Error` on a failure. The
20
+ * function is stable while the contract is. Only inside a component that
21
+ * `reactComponent` draws.
22
+ */
23
+ export declare const useRpc: <Input, Output>(contract: RpcContract<Input, Output>) => ((input: Input) => Promise<Output>);
24
+ /**
25
+ * The handle of the panel the component is drawn for: `panelId`, `props`,
26
+ * `context` (the current values; the component renders again when they change)
27
+ * and `call` for the commands of this extension. Only inside a panel.
28
+ */
29
+ export declare const usePanel: <Commands extends string = string>() => PanelHandle<Commands>;
30
+ /** The handle of the injected component: the `target` element and the `position`. Only inside a component that `client.addInjection` registered. */
31
+ export declare const useInjection: () => InjectionHandle;
32
+ /** The theme the window shows; the component renders again when it changes. Only inside a component that `reactComponent` draws. */
33
+ export declare const useTheme: () => AppTheme;
34
+ /** The language of the window; the component renders again when it changes. Only inside a component that `reactComponent` draws. */
35
+ export declare const useLocale: () => AppLocale;
36
+ export interface ReactComponentOptions {
37
+ /** Draws the component inside `React.StrictMode`; default `false`. */
38
+ strictMode?: boolean;
39
+ }
40
+ /**
41
+ * Wraps a React component into a `Mountable`: the app gives it an element, the
42
+ * adapter draws `<Component {...ctx.props} />` into it with `createRoot`, draws
43
+ * it again when the props, the theme or the language change and unmounts the
44
+ * root on cleanup. An error of the render goes to `ctx.reportError`. Inside
45
+ * the component `useApp`, `useEngine`, `useRpc`, `usePanel`, `useInjection`,
46
+ * `useTheme`, `useLocale` and `useMountContext` work. `Props` and `Handle` are
47
+ * those of the surface the component is registered for, such as
48
+ * `PanelProps` and `PanelHandle`.
49
+ */
50
+ export declare const reactComponent: <Props extends object, Handle = undefined>(component: ComponentType<Props>, options?: ReactComponentOptions) => Mountable<Props, Handle>;
51
+ //#endregion
package/dist/react.js ADDED
@@ -0,0 +1,112 @@
1
+ import { r as defineMountable } from "./define-entry-BTz2F3qv.js";
2
+ import { Component, StrictMode, createContext, createElement, useContext, useMemo } from "react";
3
+ import { flushSync } from "react-dom";
4
+ import { createRoot } from "react-dom/client";
5
+
6
+ //#region packages/extension-sdk/src/react.ts
7
+ const DolphyContext = createContext(null);
8
+ const useDolphyState = (hook) => {
9
+ const state = useContext(DolphyContext);
10
+ if (state === null) throw new Error(`${hook}() works inside a component that reactComponent() draws, there is no Dolphy context here`);
11
+ return state;
12
+ };
13
+ /**
14
+ * The `MountContext` of the component drawn by `reactComponent`. `Props` and
15
+ * `Handle` are the ones of the surface: pick them by the way the component is
16
+ * registered. Only inside a component that `reactComponent` draws.
17
+ */
18
+ const useMountContext = () => {
19
+ const { ctx } = useDolphyState("useMountContext");
20
+ return ctx;
21
+ };
22
+ /** The window API, the same object as `useApp()` of a Vue component. Only inside a component that `reactComponent` draws. */
23
+ const useApp = () => useDolphyState("useApp").ctx.app;
24
+ /** The engine client of the window, the same object as `useEngine()` of a Vue component. Only inside a component that `reactComponent` draws. */
25
+ const useEngine = () => useDolphyState("useEngine").ctx.engine;
26
+ /**
27
+ * Binds the contract to the server part of this extension, as `useRpc` of a
28
+ * Vue component does: the returned function validates the input and the
29
+ * answer with the contract and rejects with an `Error` on a failure. The
30
+ * function is stable while the contract is. Only inside a component that
31
+ * `reactComponent` draws.
32
+ */
33
+ const useRpc = (contract) => {
34
+ const { ctx } = useDolphyState("useRpc");
35
+ return useMemo(() => (input) => ctx.callRpc(contract, input), [ctx, contract]);
36
+ };
37
+ /**
38
+ * The handle of the panel the component is drawn for: `panelId`, `props`,
39
+ * `context` (the current values; the component renders again when they change)
40
+ * and `call` for the commands of this extension. Only inside a panel.
41
+ */
42
+ const usePanel = () => {
43
+ const { handle } = useDolphyState("usePanel").ctx;
44
+ if (typeof handle !== "object" || handle === null || !("panelId" in handle)) throw new Error("usePanel() works inside a panel component that the app draws, there is no panel here");
45
+ return handle;
46
+ };
47
+ /** The handle of the injected component: the `target` element and the `position`. Only inside a component that `client.addInjection` registered. */
48
+ const useInjection = () => {
49
+ const { handle } = useDolphyState("useInjection").ctx;
50
+ if (typeof handle !== "object" || handle === null || !("target" in handle) || !("position" in handle)) throw new Error("useInjection() works inside an injected component that the app draws, there is no injection here");
51
+ return handle;
52
+ };
53
+ /** The theme the window shows; the component renders again when it changes. Only inside a component that `reactComponent` draws. */
54
+ const useTheme = () => useDolphyState("useTheme").theme;
55
+ /** The language of the window; the component renders again when it changes. Only inside a component that `reactComponent` draws. */
56
+ const useLocale = () => useDolphyState("useLocale").locale;
57
+ /** Catches the errors of the render, reports them to the app and draws nothing: the app shows its own card in place of the element. */
58
+ var ErrorBoundary = class extends Component {
59
+ state = { failed: false };
60
+ static getDerivedStateFromError() {
61
+ return { failed: true };
62
+ }
63
+ componentDidCatch(error) {
64
+ this.props.onError(error);
65
+ }
66
+ render() {
67
+ return this.state.failed ? null : this.props.children;
68
+ }
69
+ };
70
+ /**
71
+ * Wraps a React component into a `Mountable`: the app gives it an element, the
72
+ * adapter draws `<Component {...ctx.props} />` into it with `createRoot`, draws
73
+ * it again when the props, the theme or the language change and unmounts the
74
+ * root on cleanup. An error of the render goes to `ctx.reportError`. Inside
75
+ * the component `useApp`, `useEngine`, `useRpc`, `usePanel`, `useInjection`,
76
+ * `useTheme`, `useLocale` and `useMountContext` work. `Props` and `Handle` are
77
+ * those of the surface the component is registered for, such as
78
+ * `PanelProps` and `PanelHandle`.
79
+ */
80
+ const reactComponent = /* @__NO_SIDE_EFFECTS__ */ (component, options = {}) => /* @__PURE__ */ defineMountable((el, ctx) => {
81
+ let alive = true;
82
+ const root = createRoot(el, {
83
+ onCaughtError: () => void 0,
84
+ onUncaughtError: (error) => ctx.reportError(error)
85
+ });
86
+ const render = () => {
87
+ if (!alive) return;
88
+ const state = {
89
+ ctx,
90
+ theme: ctx.theme,
91
+ locale: ctx.locale
92
+ };
93
+ const tree = createElement(DolphyContext.Provider, { value: state }, createElement(ErrorBoundary, { onError: ctx.reportError }, createElement(component, ctx.props)));
94
+ flushSync(() => {
95
+ root.render(options.strictMode === true ? createElement(StrictMode, null, tree) : tree);
96
+ });
97
+ };
98
+ render();
99
+ const stops = [
100
+ ctx.onProps(render),
101
+ ctx.onTheme(render),
102
+ ctx.onLocale(render)
103
+ ];
104
+ return () => {
105
+ alive = false;
106
+ for (const stop of stops) stop();
107
+ root.unmount();
108
+ };
109
+ });
110
+
111
+ //#endregion
112
+ export { reactComponent, useApp, useEngine, useInjection, useLocale, useMountContext, usePanel, useRpc, useTheme };
package/dist/rpc.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ import { t as ExtensionEngine } from "./index-I_CXC6Lj.js";
2
+ import { RpcContract } from "@dolphy-app/extension-api";
3
+ //#region packages/extension-sdk/src/rpc.d.ts
4
+ /**
5
+ * `export const sayHello = defineRpc({ name, input, output })`: a typed call
6
+ * between the client and the server part of an extension. Put the contract in a
7
+ * module both parts import; the server answers it with `server.handle`, a
8
+ * component calls it with `useRpc`. Returns `contract` as is; it checks the
9
+ * types and that `name` matches `RPC_NAME_PATTERN` and is at most
10
+ * `EXTENSION_RPC_LIMITS.nameLength` characters. Needs neither Vue nor the
11
+ * engine, so server code imports it.
12
+ */
13
+ export declare const defineRpc: <Input, Output>(contract: RpcContract<Input, Output>) => RpcContract<Input, Output>;
14
+ /** What `callRpc` needs from a context: `ClientContext` and `MountContext` have it. */
15
+ export interface RpcTarget {
16
+ readonly extensionId: string;
17
+ readonly engine: ExtensionEngine;
18
+ }
19
+ /**
20
+ * Calls the server part of the extension without Vue: validates `input` with
21
+ * `contract.input`, calls the handler of `server.handle` through
22
+ * `engine.extensions.invokeRpc` and validates the answer with
23
+ * `contract.output`. A schema violation, an error of the handler and an
24
+ * unavailable server reject the promise with an `Error` that carries the
25
+ * message. `useRpc` and `MountContext.callRpc` do the same.
26
+ */
27
+ export declare const callRpc: <Input, Output>({ engine, extensionId }: RpcTarget, contract: RpcContract<Input, Output>, input: Input) => Promise<Output>;
28
+ //#endregion
package/dist/rpc.js ADDED
@@ -0,0 +1,37 @@
1
+ import { EXTENSION_RPC_LIMITS, RPC_NAME_PATTERN } from "@dolphy-app/extension-api";
2
+
3
+ //#region packages/extension-sdk/src/rpc.ts
4
+ /**
5
+ * `export const sayHello = defineRpc({ name, input, output })`: a typed call
6
+ * between the client and the server part of an extension. Put the contract in a
7
+ * module both parts import; the server answers it with `server.handle`, a
8
+ * component calls it with `useRpc`. Returns `contract` as is; it checks the
9
+ * types and that `name` matches `RPC_NAME_PATTERN` and is at most
10
+ * `EXTENSION_RPC_LIMITS.nameLength` characters. Needs neither Vue nor the
11
+ * engine, so server code imports it.
12
+ */
13
+ const defineRpc = (contract) => {
14
+ const { name } = contract;
15
+ if (typeof name !== "string" || name.length > EXTENSION_RPC_LIMITS.nameLength || !RPC_NAME_PATTERN.test(name)) throw new Error(`rpc name '${String(name)}' must match ${RPC_NAME_PATTERN.source} and be at most ${EXTENSION_RPC_LIMITS.nameLength} characters`);
16
+ return contract;
17
+ };
18
+ /**
19
+ * Calls the server part of the extension without Vue: validates `input` with
20
+ * `contract.input`, calls the handler of `server.handle` through
21
+ * `engine.extensions.invokeRpc` and validates the answer with
22
+ * `contract.output`. A schema violation, an error of the handler and an
23
+ * unavailable server reject the promise with an `Error` that carries the
24
+ * message. `useRpc` and `MountContext.callRpc` do the same.
25
+ */
26
+ const callRpc = async ({ engine, extensionId }, contract, input) => {
27
+ contract.input.parse(input);
28
+ const result = await engine.extensions.invokeRpc({
29
+ extensionId,
30
+ name: contract.name,
31
+ input
32
+ });
33
+ return contract.output.parse(result);
34
+ };
35
+
36
+ //#endregion
37
+ export { callRpc, defineRpc };