@scenar/sdk 0.0.1
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/LICENSE +190 -0
- package/author/createScenario.d.ts +23 -0
- package/author/createScenario.d.ts.map +1 -0
- package/author/createScenario.js +47 -0
- package/author/createScenario.js.map +1 -0
- package/author/types.d.ts +70 -0
- package/author/types.d.ts.map +1 -0
- package/author/types.js +2 -0
- package/author/types.js.map +1 -0
- package/index.d.ts +8 -0
- package/index.d.ts.map +1 -0
- package/index.js +10 -0
- package/index.js.map +1 -0
- package/package.json +32 -0
- package/proto/action-mapper.d.ts +15 -0
- package/proto/action-mapper.d.ts.map +1 -0
- package/proto/action-mapper.js +69 -0
- package/proto/action-mapper.js.map +1 -0
- package/proto/errors.d.ts +11 -0
- package/proto/errors.d.ts.map +1 -0
- package/proto/errors.js +16 -0
- package/proto/errors.js.map +1 -0
- package/proto/load-scenario.d.ts +25 -0
- package/proto/load-scenario.d.ts.map +1 -0
- package/proto/load-scenario.js +51 -0
- package/proto/load-scenario.js.map +1 -0
- package/proto/proto-types.d.ts +97 -0
- package/proto/proto-types.d.ts.map +1 -0
- package/proto/proto-types.js +23 -0
- package/proto/proto-types.js.map +1 -0
- package/src/__tests__/action-mapper.test.ts +195 -0
- package/src/__tests__/createScenario.test.ts +108 -0
- package/src/__tests__/load-scenario.test.ts +187 -0
- package/src/author/createScenario.ts +64 -0
- package/src/author/types.ts +76 -0
- package/src/index.ts +30 -0
- package/src/proto/action-mapper.ts +82 -0
- package/src/proto/errors.ts +16 -0
- package/src/proto/load-scenario.ts +81 -0
- package/src/proto/proto-types.ts +93 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { ScenarioStep, StepAction } from "@scenar/core";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A view registry maps opaque view identifiers (strings) to
|
|
5
|
+
* "anything that accepts props." The SDK is framework-agnostic:
|
|
6
|
+
* values do NOT have to be React components — any callable with
|
|
7
|
+
* a single-parameter signature satisfies the constraint.
|
|
8
|
+
*
|
|
9
|
+
* The player's render prop is what actually calls the component.
|
|
10
|
+
* The SDK only uses the registry for type-level prop inference.
|
|
11
|
+
*/
|
|
12
|
+
export type ViewRegistry = Record<string, (props: never) => unknown>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Extract the props type from a view entry in the registry.
|
|
16
|
+
* Falls back to `Record<string, never>` for entries that accept
|
|
17
|
+
* no parameters (zero-prop components).
|
|
18
|
+
*/
|
|
19
|
+
export type PropsOf<V> = V extends (props: infer P) => unknown ? P : Record<string, never>;
|
|
20
|
+
|
|
21
|
+
/** Viewport dimensions for deterministic rendering. */
|
|
22
|
+
export interface ViewportConfig {
|
|
23
|
+
readonly width: number;
|
|
24
|
+
readonly height: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The data payload on each `ScenarioStep` produced by `createScenario`.
|
|
29
|
+
* This is a discriminated union keyed on `view`, where `props` is
|
|
30
|
+
* statically typed against the component registered under that view name.
|
|
31
|
+
*/
|
|
32
|
+
export type AuthoredStepData<Views extends ViewRegistry> = {
|
|
33
|
+
[K in keyof Views & string]: {
|
|
34
|
+
readonly view: K;
|
|
35
|
+
readonly props: PropsOf<Views[K]>;
|
|
36
|
+
};
|
|
37
|
+
}[keyof Views & string];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A single step as the author writes it in `createScenario({ steps: [...] })`.
|
|
41
|
+
*
|
|
42
|
+
* The generic parameter distributes over view keys so TypeScript
|
|
43
|
+
* narrows `props` based on the `view` discriminant.
|
|
44
|
+
*/
|
|
45
|
+
export type StepInput<Views extends ViewRegistry> = {
|
|
46
|
+
[K in keyof Views & string]: {
|
|
47
|
+
readonly view: K;
|
|
48
|
+
readonly delayMs: number;
|
|
49
|
+
readonly caption?: string;
|
|
50
|
+
readonly narrationText?: string;
|
|
51
|
+
readonly props: PropsOf<Views[K]>;
|
|
52
|
+
readonly interactions?: readonly StepAction[];
|
|
53
|
+
};
|
|
54
|
+
}[keyof Views & string];
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Input to `createScenario()`.
|
|
58
|
+
*/
|
|
59
|
+
export interface ScenarioInput<Views extends ViewRegistry> {
|
|
60
|
+
/** Canonical viewport dimensions. Optional — the engine has defaults. */
|
|
61
|
+
readonly viewport?: ViewportConfig;
|
|
62
|
+
/** Map of view identifiers to components / callables. */
|
|
63
|
+
readonly views: Views;
|
|
64
|
+
/** Ordered sequence of steps. Must contain at least one entry. */
|
|
65
|
+
readonly steps: readonly StepInput<Views>[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The output of `createScenario()` — ready to feed into
|
|
70
|
+
* `<ScenarioPlayer>` with a trivial render prop.
|
|
71
|
+
*/
|
|
72
|
+
export interface AuthoredScenario<Views extends ViewRegistry> {
|
|
73
|
+
readonly viewport: ViewportConfig | undefined;
|
|
74
|
+
readonly views: Views;
|
|
75
|
+
readonly steps: readonly ScenarioStep<AuthoredStepData<Views>>[];
|
|
76
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// @scenar/sdk — public API
|
|
2
|
+
// Typed authoring surface for Scenar scenarios.
|
|
3
|
+
|
|
4
|
+
// TS-first builder
|
|
5
|
+
export { createScenario } from "./author/createScenario.js";
|
|
6
|
+
export type {
|
|
7
|
+
AuthoredScenario,
|
|
8
|
+
AuthoredStepData,
|
|
9
|
+
ViewRegistry,
|
|
10
|
+
ViewportConfig,
|
|
11
|
+
StepInput,
|
|
12
|
+
ScenarioInput,
|
|
13
|
+
PropsOf,
|
|
14
|
+
} from "./author/types.js";
|
|
15
|
+
|
|
16
|
+
// Proto adapter
|
|
17
|
+
export { loadScenarioFromProto } from "./proto/load-scenario.js";
|
|
18
|
+
export type { LoadScenarioOptions } from "./proto/load-scenario.js";
|
|
19
|
+
export { InvalidScenarioError } from "./proto/errors.js";
|
|
20
|
+
|
|
21
|
+
// Proto structural types (for consumers building custom loaders)
|
|
22
|
+
export { PROTO_ACTION_TYPE } from "./proto/proto-types.js";
|
|
23
|
+
export type {
|
|
24
|
+
ProtoActionTypeValue,
|
|
25
|
+
ProtoScenario,
|
|
26
|
+
ProtoStep,
|
|
27
|
+
ProtoStepAction,
|
|
28
|
+
ProtoStepActionConfig,
|
|
29
|
+
ProtoViewportConfig,
|
|
30
|
+
} from "./proto/proto-types.js";
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { ActionType, StepAction } from "@scenar/core";
|
|
2
|
+
import { InvalidScenarioError } from "./errors.js";
|
|
3
|
+
import { PROTO_ACTION_TYPE, type ProtoStepAction } from "./proto-types.js";
|
|
4
|
+
|
|
5
|
+
const NUMERIC_TO_ACTION_TYPE: ReadonlyMap<number, ActionType> = new Map([
|
|
6
|
+
[PROTO_ACTION_TYPE.set_cursor, "set_cursor"],
|
|
7
|
+
[PROTO_ACTION_TYPE.clear_cursor, "clear_cursor"],
|
|
8
|
+
[PROTO_ACTION_TYPE.click, "click"],
|
|
9
|
+
[PROTO_ACTION_TYPE.type, "type"],
|
|
10
|
+
[PROTO_ACTION_TYPE.hover, "hover"],
|
|
11
|
+
[PROTO_ACTION_TYPE.drag, "drag"],
|
|
12
|
+
[PROTO_ACTION_TYPE.scroll_to, "scroll_to"],
|
|
13
|
+
[PROTO_ACTION_TYPE.viewport_transition, "viewport_transition"],
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Map a proto `StepAction` message to the engine's `StepAction` type.
|
|
18
|
+
*
|
|
19
|
+
* Expands the proto `config` oneof into flat optional fields on the
|
|
20
|
+
* engine action. ActionType numeric enum → string literal. Since we
|
|
21
|
+
* aligned the engine's ActionType strings with proto enum names in
|
|
22
|
+
* Phase 1, this is a direct name copy.
|
|
23
|
+
*
|
|
24
|
+
* @param proto - The proto StepAction message (structural shape).
|
|
25
|
+
* @param path - JSON-path prefix for error reporting.
|
|
26
|
+
*/
|
|
27
|
+
export function mapProtoAction(proto: ProtoStepAction, path: string): StepAction {
|
|
28
|
+
const actionType = NUMERIC_TO_ACTION_TYPE.get(proto.type);
|
|
29
|
+
|
|
30
|
+
if (actionType === undefined) {
|
|
31
|
+
throw new InvalidScenarioError(
|
|
32
|
+
`${path}.type`,
|
|
33
|
+
`unknown ActionType value ${proto.type}. Expected one of: ${[...NUMERIC_TO_ACTION_TYPE.keys()].join(", ")}.`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const base: StepAction = {
|
|
38
|
+
atPercent: proto.atPercent,
|
|
39
|
+
type: actionType,
|
|
40
|
+
target: proto.target || undefined,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const config = proto.config;
|
|
44
|
+
if (!config.case) return base;
|
|
45
|
+
|
|
46
|
+
switch (config.case) {
|
|
47
|
+
case "clickConfig":
|
|
48
|
+
case "scrollToConfig":
|
|
49
|
+
return base;
|
|
50
|
+
|
|
51
|
+
case "typeConfig":
|
|
52
|
+
return {
|
|
53
|
+
...base,
|
|
54
|
+
text: config.value.text,
|
|
55
|
+
typeDelay: config.value.typeDelayMs || undefined,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
case "hoverConfig":
|
|
59
|
+
return {
|
|
60
|
+
...base,
|
|
61
|
+
hoverDuration: config.value.hoverDurationMs || undefined,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
case "dragConfig":
|
|
65
|
+
return {
|
|
66
|
+
...base,
|
|
67
|
+
dragTarget: config.value.dragTarget,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
case "viewportTransitionConfig":
|
|
71
|
+
return {
|
|
72
|
+
...base,
|
|
73
|
+
viewportZoom: config.value.viewportZoom || undefined,
|
|
74
|
+
viewportReset: config.value.viewportReset || undefined,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
default: {
|
|
78
|
+
const _exhaustive: never = config;
|
|
79
|
+
return _exhaustive;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown when a proto `Scenario` message fails validation during
|
|
3
|
+
* `loadScenarioFromProto`. Includes the JSON-path to the offending
|
|
4
|
+
* field and a human-readable reason.
|
|
5
|
+
*/
|
|
6
|
+
export class InvalidScenarioError extends Error {
|
|
7
|
+
readonly path: string;
|
|
8
|
+
readonly reason: string;
|
|
9
|
+
|
|
10
|
+
constructor(path: string, reason: string) {
|
|
11
|
+
super(`[scenar] Invalid scenario at ${path}: ${reason}`);
|
|
12
|
+
this.name = "InvalidScenarioError";
|
|
13
|
+
this.path = path;
|
|
14
|
+
this.reason = reason;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { ScenarioStep } from "@scenar/core";
|
|
2
|
+
import type {
|
|
3
|
+
AuthoredScenario,
|
|
4
|
+
AuthoredStepData,
|
|
5
|
+
ViewRegistry,
|
|
6
|
+
} from "../author/types.js";
|
|
7
|
+
import { InvalidScenarioError } from "./errors.js";
|
|
8
|
+
import type { ProtoScenario } from "./proto-types.js";
|
|
9
|
+
import { mapProtoAction } from "./action-mapper.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Options for {@link loadScenarioFromProto}.
|
|
13
|
+
*/
|
|
14
|
+
export interface LoadScenarioOptions<Views extends ViewRegistry> {
|
|
15
|
+
/** Map of view identifiers to components / callables. */
|
|
16
|
+
readonly views: Views;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Convert a proto `Scenario` message into an `AuthoredScenario`
|
|
21
|
+
* ready for `<ScenarioPlayer>`.
|
|
22
|
+
*
|
|
23
|
+
* This is the YAML ingestion path: a scenario parsed from protobuf
|
|
24
|
+
* (or proto-JSON) enters here and comes out as the same shape that
|
|
25
|
+
* `createScenario()` produces.
|
|
26
|
+
*
|
|
27
|
+
* Ensures every step's `view` exists in the views registry and maps
|
|
28
|
+
* proto `StepAction` messages to engine `StepAction` values.
|
|
29
|
+
*
|
|
30
|
+
* @throws {InvalidScenarioError} with a path and reason on any
|
|
31
|
+
* structural or semantic validation failure.
|
|
32
|
+
*/
|
|
33
|
+
export function loadScenarioFromProto<Views extends ViewRegistry>(
|
|
34
|
+
scenario: ProtoScenario,
|
|
35
|
+
options: LoadScenarioOptions<Views>,
|
|
36
|
+
): AuthoredScenario<Views> {
|
|
37
|
+
if (scenario.steps.length === 0) {
|
|
38
|
+
throw new InvalidScenarioError("steps", "steps array must not be empty.");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const viewNames = new Set(Object.keys(options.views));
|
|
42
|
+
const steps: ScenarioStep<AuthoredStepData<Views>>[] = [];
|
|
43
|
+
|
|
44
|
+
for (let i = 0; i < scenario.steps.length; i++) {
|
|
45
|
+
const protoStep = scenario.steps[i]!;
|
|
46
|
+
const stepPath = `steps[${i}]`;
|
|
47
|
+
|
|
48
|
+
if (!protoStep.view) {
|
|
49
|
+
throw new InvalidScenarioError(`${stepPath}.view`, "view is required.");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!viewNames.has(protoStep.view)) {
|
|
53
|
+
throw new InvalidScenarioError(
|
|
54
|
+
`${stepPath}.view`,
|
|
55
|
+
`"${protoStep.view}" is not in the views registry. ` +
|
|
56
|
+
`Registered views: ${[...viewNames].join(", ")}.`,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const interactions = protoStep.interactions.map((protoAction, j) =>
|
|
61
|
+
mapProtoAction(protoAction, `${stepPath}.interactions[${j}]`),
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
steps.push({
|
|
65
|
+
delayMs: protoStep.delayMs,
|
|
66
|
+
data: {
|
|
67
|
+
view: protoStep.view,
|
|
68
|
+
props: (protoStep.props ?? {}) as AuthoredStepData<Views>["props"],
|
|
69
|
+
} as AuthoredStepData<Views>,
|
|
70
|
+
caption: protoStep.caption || undefined,
|
|
71
|
+
narration: protoStep.narrationText || undefined,
|
|
72
|
+
interactions: interactions.length > 0 ? interactions : undefined,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
viewport: scenario.viewport ? { width: scenario.viewport.width, height: scenario.viewport.height } : undefined,
|
|
78
|
+
views: options.views,
|
|
79
|
+
steps,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural types mirroring the proto-generated TypeScript stubs.
|
|
3
|
+
*
|
|
4
|
+
* The SDK accepts values matching these shapes via structural subtyping.
|
|
5
|
+
* This avoids a hard dependency on the generated stubs — consumers
|
|
6
|
+
* pass proto-generated objects directly and TypeScript validates
|
|
7
|
+
* compatibility structurally.
|
|
8
|
+
*
|
|
9
|
+
* Field names match protoc-gen-es v2 output (camelCase).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** Mirrors generated `ActionType` enum values. */
|
|
13
|
+
export const PROTO_ACTION_TYPE = {
|
|
14
|
+
unspecified: 0,
|
|
15
|
+
set_cursor: 1,
|
|
16
|
+
clear_cursor: 2,
|
|
17
|
+
click: 3,
|
|
18
|
+
type: 4,
|
|
19
|
+
hover: 5,
|
|
20
|
+
drag: 6,
|
|
21
|
+
scroll_to: 7,
|
|
22
|
+
viewport_transition: 8,
|
|
23
|
+
} as const;
|
|
24
|
+
|
|
25
|
+
export type ProtoActionTypeValue = (typeof PROTO_ACTION_TYPE)[keyof typeof PROTO_ACTION_TYPE];
|
|
26
|
+
|
|
27
|
+
/** Structural shape of a proto `ViewportConfig` message. */
|
|
28
|
+
export interface ProtoViewportConfig {
|
|
29
|
+
readonly width: number;
|
|
30
|
+
readonly height: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Structural shape of a proto `TypeConfig` message. */
|
|
34
|
+
export interface ProtoTypeConfig {
|
|
35
|
+
readonly text: string;
|
|
36
|
+
readonly typeDelayMs: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Structural shape of a proto `HoverConfig` message. */
|
|
40
|
+
export interface ProtoHoverConfig {
|
|
41
|
+
readonly hoverDurationMs: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Structural shape of a proto `DragConfig` message. */
|
|
45
|
+
export interface ProtoDragConfig {
|
|
46
|
+
readonly dragTarget: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Structural shape of a proto `ViewportTransitionConfig` message. */
|
|
50
|
+
export interface ProtoViewportTransitionConfig {
|
|
51
|
+
readonly viewportZoom: number;
|
|
52
|
+
readonly viewportReset: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Structural shape of the proto `StepAction.config` oneof.
|
|
57
|
+
* Matches protoc-gen-es v2's discriminated union output.
|
|
58
|
+
*/
|
|
59
|
+
export type ProtoStepActionConfig =
|
|
60
|
+
| { case: "clickConfig"; value: object }
|
|
61
|
+
| { case: "typeConfig"; value: ProtoTypeConfig }
|
|
62
|
+
| { case: "hoverConfig"; value: ProtoHoverConfig }
|
|
63
|
+
| { case: "dragConfig"; value: ProtoDragConfig }
|
|
64
|
+
| { case: "scrollToConfig"; value: object }
|
|
65
|
+
| { case: "viewportTransitionConfig"; value: ProtoViewportTransitionConfig }
|
|
66
|
+
| { case: undefined; value?: undefined };
|
|
67
|
+
|
|
68
|
+
/** Structural shape of a proto `StepAction` message. */
|
|
69
|
+
export interface ProtoStepAction {
|
|
70
|
+
readonly atPercent: number;
|
|
71
|
+
readonly type: number;
|
|
72
|
+
readonly target: string;
|
|
73
|
+
readonly config: ProtoStepActionConfig;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Structural shape of a proto `Step` message.
|
|
78
|
+
* `props` is `JsonObject` (protoc-gen-es v2 auto-converts `Struct`).
|
|
79
|
+
*/
|
|
80
|
+
export interface ProtoStep {
|
|
81
|
+
readonly view: string;
|
|
82
|
+
readonly delayMs: number;
|
|
83
|
+
readonly caption: string;
|
|
84
|
+
readonly narrationText: string;
|
|
85
|
+
readonly props?: Record<string, unknown>;
|
|
86
|
+
readonly interactions: readonly ProtoStepAction[];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Structural shape of the proto `Scenario` message. */
|
|
90
|
+
export interface ProtoScenario {
|
|
91
|
+
readonly viewport?: ProtoViewportConfig;
|
|
92
|
+
readonly steps: readonly ProtoStep[];
|
|
93
|
+
}
|