@reset-framework/sdk 0.2.0 → 0.2.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/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  Responsibilities:
6
6
 
7
- - expose a stable `invoke()` API for app frontends
7
+ - expose a stable typed client for app frontends
8
8
  - hide the raw `window.reset` bridge envelope
9
9
  - provide runtime availability checks for local browser development
10
10
  - centralize the shared browser helpers used by templates and demo apps
@@ -12,5 +12,30 @@ Responsibilities:
12
12
  Package layout:
13
13
 
14
14
  - `src/index.js`: public entrypoint
15
- - `src/client.js`: runtime bridge helpers
15
+ - `src/client.js`: high-level client and namespaced APIs
16
+ - `src/transport.js`: low-level transport and response validation
16
17
  - `src/errors.js`: SDK-specific error types
18
+
19
+ Recommended usage:
20
+
21
+ ```ts
22
+ import { reset } from "@reset-framework/sdk"
23
+
24
+ const appInfo = await reset.app.getInfo()
25
+ const runtimeInfo = await reset.runtime.getInfo()
26
+ ```
27
+
28
+ Available namespaces:
29
+
30
+ - `reset.app.getId()`
31
+ - `reset.app.getName()`
32
+ - `reset.app.getSlug()`
33
+ - `reset.app.getVersion()`
34
+ - `reset.app.getInfo()`
35
+ - `reset.runtime.getPlatform()`
36
+ - `reset.runtime.getArchitecture()`
37
+ - `reset.runtime.getFrameworkVersion()`
38
+ - `reset.runtime.getBridgeVersion()`
39
+ - `reset.runtime.getInfo()`
40
+ - `reset.commands.invoke()`
41
+ - `reset.commands.invokeRaw()`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reset-framework/sdk",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "description": "Browser-side SDK for Reset Framework applications.",
6
6
  "license": "MIT",
package/src/client.js CHANGED
@@ -1,53 +1,170 @@
1
- import { ResetRuntimeUnavailableError } from "./errors.js"
1
+ import {
2
+ createResetTransport,
3
+ getResetRuntime,
4
+ invoke,
5
+ invokeRaw,
6
+ isResetRuntimeAvailable
7
+ } from "./transport.js"
8
+ import { ResetProtocolError } from "./errors.js"
2
9
 
3
- function getDefaultWindowTarget() {
4
- if (typeof window === "undefined") {
5
- return undefined
10
+ function isObjectLike(value) {
11
+ return typeof value === "object" && value !== null && !Array.isArray(value)
12
+ }
13
+
14
+ function requireObject(value, command) {
15
+ if (!isObjectLike(value)) {
16
+ throw new ResetProtocolError(
17
+ `Reset runtime returned an invalid payload for '${command}'.`,
18
+ { command }
19
+ )
6
20
  }
7
21
 
8
- return window
22
+ return value
9
23
  }
10
24
 
11
- export function isResetRuntimeAvailable(target = getDefaultWindowTarget()) {
12
- return Boolean(
13
- target &&
14
- target.reset &&
15
- typeof target.reset.invoke === "function"
16
- )
17
- }
25
+ function requireStringField(record, field, command) {
26
+ const value = record[field]
18
27
 
19
- export function getResetRuntime(target = getDefaultWindowTarget()) {
20
- if (!isResetRuntimeAvailable(target)) {
21
- throw new ResetRuntimeUnavailableError()
28
+ if (typeof value !== "string" || value.trim() === "") {
29
+ throw new ResetProtocolError(
30
+ `Reset runtime returned an invalid '${field}' field for '${command}'.`,
31
+ { command }
32
+ )
22
33
  }
23
34
 
24
- return target.reset
35
+ return value
25
36
  }
26
37
 
27
- export async function invokeRaw(command, payload = {}, target = getDefaultWindowTarget()) {
28
- return getResetRuntime(target).invoke(command, payload)
38
+ function requireBooleanField(record, field, command) {
39
+ const value = record[field]
40
+
41
+ if (typeof value !== "boolean") {
42
+ throw new ResetProtocolError(
43
+ `Reset runtime returned an invalid '${field}' field for '${command}'.`,
44
+ { command }
45
+ )
46
+ }
47
+
48
+ return value
29
49
  }
30
50
 
31
- export async function invoke(command, payload = {}, target = getDefaultWindowTarget()) {
32
- const response = await invokeRaw(command, payload, target)
51
+ function normalizeAppInfo(payload, command) {
52
+ const record = requireObject(payload, command)
33
53
 
34
- if (!response.ok) {
35
- throw new Error(response.error)
36
- }
54
+ return Object.freeze({
55
+ id: requireStringField(record, "id", command),
56
+ name: requireStringField(record, "name", command),
57
+ slug: requireStringField(record, "slug", command),
58
+ version: requireStringField(record, "version", command),
59
+ windowTitle: requireStringField(record, "windowTitle", command)
60
+ })
61
+ }
62
+
63
+ function normalizeRuntimeInfo(payload, command) {
64
+ const record = requireObject(payload, command)
37
65
 
38
- return response.result
66
+ return Object.freeze({
67
+ platform: requireStringField(record, "platform", command),
68
+ arch: requireStringField(record, "arch", command),
69
+ frameworkVersion: requireStringField(record, "frameworkVersion", command),
70
+ bridgeVersion: requireStringField(record, "bridgeVersion", command),
71
+ debug: requireBooleanField(record, "debug", command)
72
+ })
39
73
  }
40
74
 
41
- export function createResetClient(target = getDefaultWindowTarget()) {
42
- return {
43
- isAvailable() {
44
- return isResetRuntimeAvailable(target)
45
- },
75
+ function createCommandApi(transport) {
76
+ return Object.freeze({
46
77
  invoke(command, payload = {}) {
47
- return invoke(command, payload, target)
78
+ return transport.invoke(command, payload)
48
79
  },
49
80
  invokeRaw(command, payload = {}) {
50
- return invokeRaw(command, payload, target)
81
+ return transport.invokeRaw(command, payload)
51
82
  }
52
- }
83
+ })
84
+ }
85
+
86
+ function createAppApi(transport) {
87
+ return Object.freeze({
88
+ async getId() {
89
+ const payload = requireObject(await transport.invoke("app.getId"), "app.getId")
90
+ return requireStringField(payload, "id", "app.getId")
91
+ },
92
+ async getName() {
93
+ const payload = requireObject(await transport.invoke("app.getName"), "app.getName")
94
+ return requireStringField(payload, "name", "app.getName")
95
+ },
96
+ async getSlug() {
97
+ const payload = requireObject(await transport.invoke("app.getSlug"), "app.getSlug")
98
+ return requireStringField(payload, "slug", "app.getSlug")
99
+ },
100
+ async getVersion() {
101
+ const payload = requireObject(await transport.invoke("app.getVersion"), "app.getVersion")
102
+ return requireStringField(payload, "version", "app.getVersion")
103
+ },
104
+ async getInfo() {
105
+ return normalizeAppInfo(await transport.invoke("app.getInfo"), "app.getInfo")
106
+ }
107
+ })
108
+ }
109
+
110
+ function createRuntimeApi(transport) {
111
+ return Object.freeze({
112
+ async getPlatform() {
113
+ const payload = requireObject(await transport.invoke("runtime.getPlatform"), "runtime.getPlatform")
114
+ return requireStringField(payload, "platform", "runtime.getPlatform")
115
+ },
116
+ async getArchitecture() {
117
+ const payload = requireObject(await transport.invoke("runtime.getArchitecture"), "runtime.getArchitecture")
118
+ return requireStringField(payload, "arch", "runtime.getArchitecture")
119
+ },
120
+ async getFrameworkVersion() {
121
+ const payload = requireObject(
122
+ await transport.invoke("runtime.getFrameworkVersion"),
123
+ "runtime.getFrameworkVersion"
124
+ )
125
+ return requireStringField(payload, "version", "runtime.getFrameworkVersion")
126
+ },
127
+ async getBridgeVersion() {
128
+ const payload = requireObject(
129
+ await transport.invoke("runtime.getBridgeVersion"),
130
+ "runtime.getBridgeVersion"
131
+ )
132
+ return requireStringField(payload, "version", "runtime.getBridgeVersion")
133
+ },
134
+ async getInfo() {
135
+ return normalizeRuntimeInfo(await transport.invoke("runtime.getInfo"), "runtime.getInfo")
136
+ }
137
+ })
53
138
  }
139
+
140
+ export function createResetClient(source) {
141
+ const transport = createResetTransport(source)
142
+ const commands = createCommandApi(transport)
143
+ const app = createAppApi(transport)
144
+ const runtime = createRuntimeApi(transport)
145
+
146
+ return Object.freeze({
147
+ isAvailable() {
148
+ return transport.isAvailable()
149
+ },
150
+ getRuntime() {
151
+ return transport.getRuntime()
152
+ },
153
+ commands,
154
+ app,
155
+ runtime
156
+ })
157
+ }
158
+
159
+ export {
160
+ createResetTransport,
161
+ getResetRuntime,
162
+ invoke,
163
+ invokeRaw,
164
+ isResetRuntimeAvailable
165
+ } from "./transport.js"
166
+
167
+ export const reset = createResetClient()
168
+ export const commands = reset.commands
169
+ export const app = reset.app
170
+ export const runtime = reset.runtime
package/src/errors.js CHANGED
@@ -1,6 +1,31 @@
1
- export class ResetRuntimeUnavailableError extends Error {
2
- constructor(message = "reset runtime is not available") {
1
+ export class ResetRuntimeError extends Error {
2
+ constructor(message, options = {}) {
3
3
  super(message)
4
- this.name = "ResetRuntimeUnavailableError"
4
+ this.name = new.target.name
5
+
6
+ if ("cause" in options) {
7
+ this.cause = options.cause
8
+ }
9
+ }
10
+ }
11
+
12
+ export class ResetRuntimeUnavailableError extends ResetRuntimeError {
13
+ constructor(message = "Reset runtime is not available in the current environment.") {
14
+ super(message)
15
+ }
16
+ }
17
+
18
+ export class ResetProtocolError extends ResetRuntimeError {
19
+ constructor(message, options = {}) {
20
+ super(message, options)
21
+ this.command = options.command
22
+ }
23
+ }
24
+
25
+ export class ResetInvocationError extends ResetRuntimeError {
26
+ constructor(command, message, response, options = {}) {
27
+ super(message, options)
28
+ this.command = command
29
+ this.response = response
5
30
  }
6
31
  }
package/src/index.d.ts CHANGED
@@ -12,6 +12,10 @@ export type ResetInvokeResponse<T = unknown> =
12
12
  | ResetInvokeSuccess<T>
13
13
  | ResetInvokeError;
14
14
 
15
+ export type ResetPlatform = "macos" | "windows" | "linux" | "unknown";
16
+
17
+ export type ResetArchitecture = "arm64" | "x64" | "x86" | "unknown";
18
+
15
19
  export interface ResetRuntime {
16
20
  invoke<T = unknown>(
17
21
  command: string,
@@ -23,39 +27,133 @@ export interface ResetRuntimeWindow {
23
27
  reset?: ResetRuntime;
24
28
  }
25
29
 
30
+ export interface ResetRuntimeSourceOptions {
31
+ target?: ResetRuntimeWindow;
32
+ runtime?: ResetRuntime;
33
+ resolveRuntime?: () => ResetRuntime | undefined;
34
+ }
35
+
36
+ export type ResetRuntimeSource =
37
+ | ResetRuntime
38
+ | ResetRuntimeWindow
39
+ | ResetRuntimeSourceOptions
40
+ | (() => ResetRuntime | undefined)
41
+ | undefined;
42
+
43
+ export interface ResetTransport {
44
+ isAvailable(): boolean;
45
+ getRuntime(): ResetRuntime;
46
+ invokeRaw<T = unknown>(
47
+ command: string,
48
+ payload?: unknown
49
+ ): Promise<ResetInvokeResponse<T>>;
50
+ invoke<T = unknown>(command: string, payload?: unknown): Promise<T>;
51
+ }
52
+
53
+ export interface ResetCommandApi {
54
+ invoke<T = unknown>(command: string, payload?: unknown): Promise<T>;
55
+ invokeRaw<T = unknown>(
56
+ command: string,
57
+ payload?: unknown
58
+ ): Promise<ResetInvokeResponse<T>>;
59
+ }
60
+
61
+ export interface ResetAppInfo {
62
+ id: string;
63
+ name: string;
64
+ slug: string;
65
+ version: string;
66
+ windowTitle: string;
67
+ }
68
+
69
+ export interface ResetRuntimeInfo {
70
+ platform: ResetPlatform;
71
+ arch: ResetArchitecture;
72
+ frameworkVersion: string;
73
+ bridgeVersion: string;
74
+ debug: boolean;
75
+ }
76
+
77
+ export interface ResetAppApi {
78
+ getId(): Promise<string>;
79
+ getName(): Promise<string>;
80
+ getSlug(): Promise<string>;
81
+ getVersion(): Promise<string>;
82
+ getInfo(): Promise<ResetAppInfo>;
83
+ }
84
+
85
+ export interface ResetRuntimeApi {
86
+ getPlatform(): Promise<ResetPlatform>;
87
+ getArchitecture(): Promise<ResetArchitecture>;
88
+ getFrameworkVersion(): Promise<string>;
89
+ getBridgeVersion(): Promise<string>;
90
+ getInfo(): Promise<ResetRuntimeInfo>;
91
+ }
92
+
93
+ export interface ResetClient {
94
+ isAvailable(): boolean;
95
+ getRuntime(): ResetRuntime;
96
+ commands: ResetCommandApi;
97
+ app: ResetAppApi;
98
+ runtime: ResetRuntimeApi;
99
+ }
100
+
26
101
  declare global {
27
102
  interface Window extends ResetRuntimeWindow {}
28
103
  }
29
104
 
30
- export class ResetRuntimeUnavailableError extends Error {
105
+ export class ResetRuntimeError extends Error {
106
+ cause?: unknown;
107
+ constructor(message?: string, options?: { cause?: unknown });
108
+ }
109
+
110
+ export class ResetRuntimeUnavailableError extends ResetRuntimeError {
31
111
  constructor(message?: string);
32
112
  }
33
113
 
114
+ export class ResetProtocolError extends ResetRuntimeError {
115
+ command?: string;
116
+ constructor(message?: string, options?: { command?: string; cause?: unknown });
117
+ }
118
+
119
+ export class ResetInvocationError extends ResetRuntimeError {
120
+ command: string;
121
+ response: ResetInvokeError;
122
+ constructor(
123
+ command: string,
124
+ message: string,
125
+ response: ResetInvokeError,
126
+ options?: { cause?: unknown }
127
+ );
128
+ }
129
+
34
130
  export function isResetRuntimeAvailable(
35
- target?: ResetRuntimeWindow
131
+ source?: ResetRuntimeSource
36
132
  ): boolean;
37
133
 
38
134
  export function getResetRuntime(
39
- target?: ResetRuntimeWindow
135
+ source?: ResetRuntimeSource
40
136
  ): ResetRuntime;
41
137
 
138
+ export function createResetTransport(
139
+ source?: ResetRuntimeSource
140
+ ): ResetTransport;
141
+
42
142
  export function invokeRaw<T = unknown>(
43
143
  command: string,
44
144
  payload?: unknown,
45
- target?: ResetRuntimeWindow
145
+ source?: ResetRuntimeSource
46
146
  ): Promise<ResetInvokeResponse<T>>;
47
147
 
48
148
  export function invoke<T = unknown>(
49
149
  command: string,
50
150
  payload?: unknown,
51
- target?: ResetRuntimeWindow
151
+ source?: ResetRuntimeSource
52
152
  ): Promise<T>;
53
153
 
54
- export function createResetClient(target?: ResetRuntimeWindow): {
55
- isAvailable(): boolean;
56
- invoke<T = unknown>(command: string, payload?: unknown): Promise<T>;
57
- invokeRaw<T = unknown>(
58
- command: string,
59
- payload?: unknown
60
- ): Promise<ResetInvokeResponse<T>>;
61
- };
154
+ export function createResetClient(source?: ResetRuntimeSource): ResetClient;
155
+
156
+ export const reset: ResetClient;
157
+ export const commands: ResetCommandApi;
158
+ export const app: ResetAppApi;
159
+ export const runtime: ResetRuntimeApi;
package/src/index.js CHANGED
@@ -1,9 +1,19 @@
1
1
  export {
2
+ app,
3
+ commands,
2
4
  createResetClient,
5
+ createResetTransport,
3
6
  getResetRuntime,
4
7
  invoke,
5
8
  invokeRaw,
6
- isResetRuntimeAvailable
9
+ isResetRuntimeAvailable,
10
+ reset,
11
+ runtime
7
12
  } from "./client.js"
8
13
 
9
- export { ResetRuntimeUnavailableError } from "./errors.js"
14
+ export {
15
+ ResetInvocationError,
16
+ ResetProtocolError,
17
+ ResetRuntimeError,
18
+ ResetRuntimeUnavailableError
19
+ } from "./errors.js"
@@ -0,0 +1,162 @@
1
+ import {
2
+ ResetInvocationError,
3
+ ResetProtocolError,
4
+ ResetRuntimeUnavailableError
5
+ } from "./errors.js"
6
+
7
+ function getDefaultWindowTarget() {
8
+ if (typeof window === "undefined") {
9
+ return undefined
10
+ }
11
+
12
+ return window
13
+ }
14
+
15
+ function hasOwn(object, key) {
16
+ return Object.prototype.hasOwnProperty.call(object, key)
17
+ }
18
+
19
+ function isObjectLike(value) {
20
+ return typeof value === "object" && value !== null
21
+ }
22
+
23
+ function isRuntimeLike(value) {
24
+ return isObjectLike(value) && typeof value.invoke === "function"
25
+ }
26
+
27
+ function createTargetRuntimeResolver(target) {
28
+ return () => {
29
+ if (!isObjectLike(target) || !isRuntimeLike(target.reset)) {
30
+ return undefined
31
+ }
32
+
33
+ return target.reset
34
+ }
35
+ }
36
+
37
+ function normalizeRuntimeResolver(source) {
38
+ if (source === undefined) {
39
+ return () => {
40
+ const target = getDefaultWindowTarget()
41
+ return isObjectLike(target) && isRuntimeLike(target.reset) ? target.reset : undefined
42
+ }
43
+ }
44
+
45
+ if (typeof source === "function") {
46
+ return () => {
47
+ const runtime = source()
48
+ return isRuntimeLike(runtime) ? runtime : undefined
49
+ }
50
+ }
51
+
52
+ if (isRuntimeLike(source)) {
53
+ return () => source
54
+ }
55
+
56
+ if (isObjectLike(source) && (hasOwn(source, "runtime") || hasOwn(source, "resolveRuntime") || hasOwn(source, "target"))) {
57
+ if (typeof source.resolveRuntime === "function") {
58
+ return () => {
59
+ const runtime = source.resolveRuntime()
60
+ return isRuntimeLike(runtime) ? runtime : undefined
61
+ }
62
+ }
63
+
64
+ if (isRuntimeLike(source.runtime)) {
65
+ return () => source.runtime
66
+ }
67
+
68
+ return createTargetRuntimeResolver(source.target)
69
+ }
70
+
71
+ return createTargetRuntimeResolver(source)
72
+ }
73
+
74
+ function assertCommand(command) {
75
+ if (typeof command !== "string" || command.trim() === "") {
76
+ throw new TypeError("Reset command must be a non-empty string.")
77
+ }
78
+ }
79
+
80
+ function normalizeResponse(command, response) {
81
+ if (!isObjectLike(response) || typeof response.ok !== "boolean") {
82
+ throw new ResetProtocolError(
83
+ `Reset runtime returned an invalid response envelope for '${command}'.`,
84
+ { command }
85
+ )
86
+ }
87
+
88
+ if (response.ok === false) {
89
+ if (typeof response.error !== "string" || response.error.trim() === "") {
90
+ throw new ResetProtocolError(
91
+ `Reset runtime returned an invalid error response for '${command}'.`,
92
+ { command }
93
+ )
94
+ }
95
+
96
+ return Object.freeze({
97
+ ok: false,
98
+ error: response.error
99
+ })
100
+ }
101
+
102
+ return Object.freeze({
103
+ ok: true,
104
+ result: response.result
105
+ })
106
+ }
107
+
108
+ function resolveRuntimeOrThrow(resolveRuntime) {
109
+ const runtime = resolveRuntime()
110
+
111
+ if (!isRuntimeLike(runtime)) {
112
+ throw new ResetRuntimeUnavailableError()
113
+ }
114
+
115
+ return runtime
116
+ }
117
+
118
+ export function isResetRuntimeAvailable(source) {
119
+ return isRuntimeLike(normalizeRuntimeResolver(source)())
120
+ }
121
+
122
+ export function getResetRuntime(source) {
123
+ return resolveRuntimeOrThrow(normalizeRuntimeResolver(source))
124
+ }
125
+
126
+ export async function invokeRaw(command, payload = {}, source) {
127
+ assertCommand(command)
128
+
129
+ const runtime = resolveRuntimeOrThrow(normalizeRuntimeResolver(source))
130
+ const response = await runtime.invoke(command, payload)
131
+
132
+ return normalizeResponse(command, response)
133
+ }
134
+
135
+ export async function invoke(command, payload = {}, source) {
136
+ const response = await invokeRaw(command, payload, source)
137
+
138
+ if (!response.ok) {
139
+ throw new ResetInvocationError(command, response.error, response)
140
+ }
141
+
142
+ return response.result
143
+ }
144
+
145
+ export function createResetTransport(source) {
146
+ const resolveRuntime = normalizeRuntimeResolver(source)
147
+
148
+ return Object.freeze({
149
+ isAvailable() {
150
+ return isRuntimeLike(resolveRuntime())
151
+ },
152
+ getRuntime() {
153
+ return resolveRuntimeOrThrow(resolveRuntime)
154
+ },
155
+ invokeRaw(command, payload = {}) {
156
+ return invokeRaw(command, payload, resolveRuntime)
157
+ },
158
+ invoke(command, payload = {}) {
159
+ return invoke(command, payload, resolveRuntime)
160
+ }
161
+ })
162
+ }