forgeos 0.1.0-alpha.35 → 0.1.0-alpha.37
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/AGENTS.md +1 -1
- package/CHANGELOG.md +22 -0
- package/docs/changelog.md +29 -0
- package/package.json +1 -1
- package/src/forge/_generated/releaseManifest.json +1 -1
- package/src/forge/_generated/releaseManifest.ts +3 -3
- package/src/forge/cli/authmd.ts +35 -12
- package/src/forge/cli/deps.ts +3 -1
- package/src/forge/compiler/client-sdk/render-client.ts +93 -17
- package/src/forge/compiler/package-graph/compiler.ts +3 -1
- package/src/forge/compiler/package-graph/constants.ts +1 -1
- package/src/forge/compiler/package-upgrades/planner.ts +155 -18
- package/src/forge/compiler/package-upgrades/types.ts +2 -0
- package/src/forge/compiler/types/package-graph.ts +6 -0
- package/src/forge/react/index.ts +141 -0
- package/src/forge/version.ts +1 -1
package/AGENTS.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @forge-generated generator=0.1.0-alpha.
|
|
1
|
+
// @forge-generated generator=0.1.0-alpha.37 input=055c7d65fca3d6d8b05af2899c233ff69d377edf0f46f719e2d164ee0574309a content=721818a6f9a664aa898092d4aa5cd68cbbd8ad6e150c7d3ad3ef785a2e3fcf53
|
|
2
2
|
# AGENTS.md
|
|
3
3
|
|
|
4
4
|
<!-- forge-generated:start -->
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# forgeos
|
|
2
2
|
|
|
3
|
+
## 0.1.0-alpha.37
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Fix `forge deps upgrade-plan` for npm alias installs used by ForgeOS apps.
|
|
8
|
+
|
|
9
|
+
- Record the real package name from package metadata in the generated package graph when a dependency is installed through an alias such as `forge: npm:forgeos@...`.
|
|
10
|
+
- Resolve upgrade plans by either the real package name (`forgeos`) or the dependency alias (`forge`) while preserving install specs like `forge@npm:forgeos@0.1.0-alpha.37`.
|
|
11
|
+
- Avoid attempting to resolve the unrelated public `forge@alpha` package when an app asks to upgrade its ForgeOS alias dependency.
|
|
12
|
+
- Add regression coverage for aliased package graph entries where the import name and real npm package name differ.
|
|
13
|
+
|
|
14
|
+
## 0.1.0-alpha.36
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- Smooth the alpha field-test loop after the Vendor Access app exercise.
|
|
19
|
+
|
|
20
|
+
- Teach `forge deps upgrade-plan` to resolve npm alias dependencies such as `forge: npm:forgeos@alpha`, while preserving alias install specs like `forge@npm:forgeos@0.1.0-alpha.36` in generated upgrade plans.
|
|
21
|
+
- Normalize `auth.md` runtime risk rendering so Commands and Risk/Approval metadata agree that commands default to `write`.
|
|
22
|
+
- Add non-throwing command proof APIs through generated `client.commandResult(...)` and React `useCommandResult(...)`, so expected policy denials can be rendered as structured results instead of noisy unhandled browser failures.
|
|
23
|
+
- Update the basic example schema/docs to use `organizations` as the root tenant table instead of a self-referential `tenants` root.
|
|
24
|
+
|
|
3
25
|
## 0.1.0-alpha.35
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
package/docs/changelog.md
CHANGED
|
@@ -6,6 +6,35 @@ The canonical source file in the repository is `CHANGELOG.md`.
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## 0.1.0-alpha.37
|
|
10
|
+
|
|
11
|
+
- Fixed `forge deps upgrade-plan` for npm alias installs used by ForgeOS apps.
|
|
12
|
+
- Recorded the real package name from package metadata in generated package
|
|
13
|
+
graph entries when a dependency is installed through an alias such as
|
|
14
|
+
`forge: npm:forgeos@...`.
|
|
15
|
+
- Resolved upgrade plans by either the real package name (`forgeos`) or the
|
|
16
|
+
dependency alias (`forge`) while preserving install specs like
|
|
17
|
+
`forge@npm:forgeos@0.1.0-alpha.37`.
|
|
18
|
+
- Avoided attempting to resolve the unrelated public `forge@alpha` package when
|
|
19
|
+
an app asks to upgrade its ForgeOS alias dependency.
|
|
20
|
+
- Added regression coverage for aliased package graph entries where the import
|
|
21
|
+
name and real npm package name differ.
|
|
22
|
+
|
|
23
|
+
## 0.1.0-alpha.36
|
|
24
|
+
|
|
25
|
+
- Smoothed the alpha field-test loop after the Vendor Access app exercise.
|
|
26
|
+
- Taught `forge deps upgrade-plan` to resolve npm alias dependencies such as
|
|
27
|
+
`forge: npm:forgeos@alpha`, while preserving alias install specs like
|
|
28
|
+
`forge@npm:forgeos@0.1.0-alpha.36` in generated upgrade plans.
|
|
29
|
+
- Normalized `auth.md` runtime risk rendering so Commands and Risk/Approval
|
|
30
|
+
metadata agree that commands default to `write`.
|
|
31
|
+
- Added non-throwing command proof APIs through generated
|
|
32
|
+
`client.commandResult(...)` and React `useCommandResult(...)`, so expected
|
|
33
|
+
policy denials can be rendered as structured results instead of noisy
|
|
34
|
+
unhandled browser failures.
|
|
35
|
+
- Updated the basic example schema/docs to use `organizations` as the root
|
|
36
|
+
tenant table instead of a self-referential `tenants` root.
|
|
37
|
+
|
|
9
38
|
## 0.1.0-alpha.35
|
|
10
39
|
|
|
11
40
|
- Hardened field-test regressions found while building WorkOS-style
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"defaultProvider":"local","diagnostics":[],"env":{"deployEnv":"FORGE_DEPLOY_ENV","deployId":"FORGE_DEPLOY_ID","publicReleaseId":"NEXT_PUBLIC_FORGE_RELEASE_ID","releaseId":"FORGE_RELEASE_ID"},"gitSha":"unknown","optionalProviders":["local","sentry-compatible","sentry","glitchtip","bugsink","otel","custom"],"packageName":"forgeos","packageVersion":"0.1.0-alpha.
|
|
1
|
+
{"defaultProvider":"local","diagnostics":[],"env":{"deployEnv":"FORGE_DEPLOY_ENV","deployId":"FORGE_DEPLOY_ID","publicReleaseId":"NEXT_PUBLIC_FORGE_RELEASE_ID","releaseId":"FORGE_RELEASE_ID"},"gitSha":"unknown","optionalProviders":["local","sentry-compatible","sentry","glitchtip","bugsink","otel","custom"],"packageName":"forgeos","packageVersion":"0.1.0-alpha.37","releaseId":"forgeos@0.1.0-alpha.37+unknown","schemaVersion":"0.1.0"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @forge-generated generator=0.1.0-alpha.
|
|
1
|
+
// @forge-generated generator=0.1.0-alpha.37 input=055c7d65fca3d6d8b05af2899c233ff69d377edf0f46f719e2d164ee0574309a content=1e26c5249853569091c1b295aab06bd2b9546462328fe8126eda099222430b61
|
|
2
2
|
export const releaseManifest = {
|
|
3
3
|
"defaultProvider": "local",
|
|
4
4
|
"diagnostics": [],
|
|
@@ -19,7 +19,7 @@ export const releaseManifest = {
|
|
|
19
19
|
"custom"
|
|
20
20
|
],
|
|
21
21
|
"packageName": "forgeos",
|
|
22
|
-
"packageVersion": "0.1.0-alpha.
|
|
23
|
-
"releaseId": "forgeos@0.1.0-alpha.
|
|
22
|
+
"packageVersion": "0.1.0-alpha.37",
|
|
23
|
+
"releaseId": "forgeos@0.1.0-alpha.37+unknown",
|
|
24
24
|
"schemaVersion": "0.1.0"
|
|
25
25
|
} as const;
|
package/src/forge/cli/authmd.ts
CHANGED
|
@@ -64,6 +64,8 @@ interface AgentToolsLike {
|
|
|
64
64
|
}>;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
type RuntimeEntryKind = "command" | "query" | "liveQuery" | "action";
|
|
68
|
+
|
|
67
69
|
function readGeneratedJson<T>(workspaceRoot: string, relativePath: string): T | null {
|
|
68
70
|
const absolute = join(workspaceRoot, relativePath);
|
|
69
71
|
if (!nodeFileSystem.exists(absolute)) {
|
|
@@ -76,11 +78,32 @@ function outputPath(options: AuthMdCommandOptions): string {
|
|
|
76
78
|
return options.output?.trim() || "public/auth.md";
|
|
77
79
|
}
|
|
78
80
|
|
|
79
|
-
function
|
|
81
|
+
function runtimeEntryRisk(kind: RuntimeEntryKind, entry: RuntimeEntry): string {
|
|
82
|
+
if (entry.risk) {
|
|
83
|
+
return entry.risk;
|
|
84
|
+
}
|
|
85
|
+
if (kind === "command") {
|
|
86
|
+
return "write";
|
|
87
|
+
}
|
|
88
|
+
if (kind === "action") {
|
|
89
|
+
return "external";
|
|
90
|
+
}
|
|
91
|
+
return "read";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function runtimeEntryNeedsApproval(kind: RuntimeEntryKind, entry: RuntimeEntry): boolean {
|
|
95
|
+
return entry.needsApproval ?? kind === "action";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function listRuntimeEntries(
|
|
99
|
+
title: string,
|
|
100
|
+
kind: RuntimeEntryKind,
|
|
101
|
+
entries: RuntimeEntry[] | undefined,
|
|
102
|
+
): string[] {
|
|
80
103
|
const rows = (entries ?? []).map((entry) =>
|
|
81
104
|
`| \`${entry.name}\` | ${entry.policy ? `\`${entry.policy}\`` : "none"} | ${
|
|
82
105
|
entry.requiresAuth === false ? "no" : "yes"
|
|
83
|
-
} | ${entry
|
|
106
|
+
} | ${runtimeEntryRisk(kind, entry)} | ${String(runtimeEntryNeedsApproval(kind, entry))} |`
|
|
84
107
|
);
|
|
85
108
|
return [
|
|
86
109
|
`## ${title}`,
|
|
@@ -115,16 +138,16 @@ function existingDocLinks(workspaceRoot: string): string[] {
|
|
|
115
138
|
|
|
116
139
|
function riskMetadata(contract: AgentContractLike | null, tools: AgentToolsLike | null) {
|
|
117
140
|
const runtime = [
|
|
118
|
-
...(contract?.commands ?? []).map((entry) => ({ kind: "command", ...entry
|
|
119
|
-
...(contract?.queries ?? []).map((entry) => ({ kind: "query", ...entry
|
|
120
|
-
...(contract?.liveQueries ?? []).map((entry) => ({ kind: "liveQuery", ...entry
|
|
121
|
-
...(contract?.actions ?? []).map((entry) => ({ kind: "action", ...entry
|
|
141
|
+
...(contract?.commands ?? []).map((entry) => ({ kind: "command" as const, ...entry })),
|
|
142
|
+
...(contract?.queries ?? []).map((entry) => ({ kind: "query" as const, ...entry })),
|
|
143
|
+
...(contract?.liveQueries ?? []).map((entry) => ({ kind: "liveQuery" as const, ...entry })),
|
|
144
|
+
...(contract?.actions ?? []).map((entry) => ({ kind: "action" as const, ...entry })),
|
|
122
145
|
].map((entry) => ({
|
|
123
146
|
kind: entry.kind,
|
|
124
147
|
name: entry.name,
|
|
125
148
|
policy: entry.policy ?? null,
|
|
126
|
-
risk: entry.
|
|
127
|
-
needs_approval:
|
|
149
|
+
risk: runtimeEntryRisk(entry.kind, entry),
|
|
150
|
+
needs_approval: runtimeEntryNeedsApproval(entry.kind, entry),
|
|
128
151
|
requires_auth: entry.requiresAuth !== false,
|
|
129
152
|
}));
|
|
130
153
|
const aiTools = (tools?.tools ?? []).map((tool) => ({
|
|
@@ -232,10 +255,10 @@ function renderAuthMd(workspaceRoot: string): AuthMdCommandResult & { content: s
|
|
|
232
255
|
`- Roles: \`${auth?.claims?.roles ?? "roles"}\``,
|
|
233
256
|
`- Permissions: \`${auth?.claims?.permissions ?? "permissions"}\``,
|
|
234
257
|
"",
|
|
235
|
-
...listRuntimeEntries("Commands", contract?.commands),
|
|
236
|
-
...listRuntimeEntries("Queries", contract?.queries),
|
|
237
|
-
...listRuntimeEntries("Live Queries", contract?.liveQueries),
|
|
238
|
-
...listRuntimeEntries("Actions", contract?.actions),
|
|
258
|
+
...listRuntimeEntries("Commands", "command", contract?.commands),
|
|
259
|
+
...listRuntimeEntries("Queries", "query", contract?.queries),
|
|
260
|
+
...listRuntimeEntries("Live Queries", "liveQuery", contract?.liveQueries),
|
|
261
|
+
...listRuntimeEntries("Actions", "action", contract?.actions),
|
|
239
262
|
"## Policies",
|
|
240
263
|
"",
|
|
241
264
|
"| Policy | Kind | Roles | Permissions |",
|
package/src/forge/cli/deps.ts
CHANGED
|
@@ -93,7 +93,9 @@ function findPackage(workspaceRoot: string, packageName: string): {
|
|
|
93
93
|
const lock = nodeFileSystem.exists(join(workspaceRoot, "forge.lock"))
|
|
94
94
|
? (JSON.parse((nodeFileSystem.readText(join(workspaceRoot, "forge.lock")) ?? "")) as ForgeLock)
|
|
95
95
|
: null;
|
|
96
|
-
const pkg = graph?.packages.find((candidate) =>
|
|
96
|
+
const pkg = graph?.packages.find((candidate) =>
|
|
97
|
+
candidate.name === packageName || candidate.packageName === packageName
|
|
98
|
+
);
|
|
97
99
|
return { graph, pkg, lock };
|
|
98
100
|
}
|
|
99
101
|
|
|
@@ -86,6 +86,22 @@ export type LiveSnapshot<T> = {
|
|
|
86
86
|
traceId?: string;
|
|
87
87
|
};
|
|
88
88
|
|
|
89
|
+
export type ForgeCommandResult<T = unknown> =
|
|
90
|
+
| {
|
|
91
|
+
ok: true;
|
|
92
|
+
result: T;
|
|
93
|
+
status: number;
|
|
94
|
+
traceId?: string;
|
|
95
|
+
diagnostics?: { code: string; message: string }[];
|
|
96
|
+
}
|
|
97
|
+
| {
|
|
98
|
+
ok: false;
|
|
99
|
+
error: { code: string; message: string; details?: unknown };
|
|
100
|
+
status: number;
|
|
101
|
+
traceId?: string;
|
|
102
|
+
diagnostics?: { code: string; message: string }[];
|
|
103
|
+
};
|
|
104
|
+
|
|
89
105
|
export type LiveQueryOptions = {
|
|
90
106
|
signal?: AbortSignal;
|
|
91
107
|
};
|
|
@@ -96,6 +112,7 @@ export type ForgeClient = {
|
|
|
96
112
|
readonly lastTraceId?: string;
|
|
97
113
|
query<Name extends QueryName>(name: Name, args: unknown): Promise<unknown>;
|
|
98
114
|
command<Name extends CommandName>(name: Name, args: unknown): Promise<unknown>;
|
|
115
|
+
commandResult<Name extends CommandName>(name: Name, args: unknown): Promise<ForgeCommandResult<unknown>>;
|
|
99
116
|
externalQuery<Name extends ExternalQueryRef>(name: Name, args: unknown): Promise<unknown>;
|
|
100
117
|
externalCommand<Name extends ExternalCommandRef>(name: Name, args: unknown): Promise<unknown>;
|
|
101
118
|
liveQuery<Name extends LiveQueryName>(
|
|
@@ -113,6 +130,7 @@ export function renderClientTs(): string {
|
|
|
113
130
|
return `import { api } from "./api.ts";
|
|
114
131
|
import type {
|
|
115
132
|
ForgeAuthProvider,
|
|
133
|
+
ForgeCommandResult,
|
|
116
134
|
ForgeClient,
|
|
117
135
|
ForgeClientConfig,
|
|
118
136
|
ExternalCommandRef,
|
|
@@ -128,6 +146,7 @@ export type {
|
|
|
128
146
|
ForgeAuthProvider,
|
|
129
147
|
ForgeClient,
|
|
130
148
|
ForgeClientConfig,
|
|
149
|
+
ForgeCommandResult,
|
|
131
150
|
ForgeStaticAuth,
|
|
132
151
|
QueryName,
|
|
133
152
|
CommandName,
|
|
@@ -258,6 +277,10 @@ class ForgeHttpClient implements ForgeClient {
|
|
|
258
277
|
return this.invoke("commands", name, args);
|
|
259
278
|
}
|
|
260
279
|
|
|
280
|
+
commandResult(name: string, args: unknown): Promise<ForgeCommandResult<unknown>> {
|
|
281
|
+
return this.invokeResult("commands", name, args);
|
|
282
|
+
}
|
|
283
|
+
|
|
261
284
|
externalQuery(name: ExternalQueryRef, args: unknown): Promise<unknown> {
|
|
262
285
|
return this.invokeExternal("queries", name, args);
|
|
263
286
|
}
|
|
@@ -297,27 +320,61 @@ class ForgeHttpClient implements ForgeClient {
|
|
|
297
320
|
name: string,
|
|
298
321
|
args: unknown,
|
|
299
322
|
): Promise<unknown> {
|
|
323
|
+
const result = await this.invokeResult(kind, name, args);
|
|
324
|
+
if (!result.ok) {
|
|
325
|
+
throw new ForgeError(result.error.message, {
|
|
326
|
+
code: result.error.code,
|
|
327
|
+
traceId: result.traceId,
|
|
328
|
+
status: result.status,
|
|
329
|
+
details: result.error.details ?? result.diagnostics,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
return result.result;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
private async invokeResult(
|
|
336
|
+
kind: "queries" | "commands",
|
|
337
|
+
name: string,
|
|
338
|
+
args: unknown,
|
|
339
|
+
): Promise<ForgeCommandResult<unknown>> {
|
|
300
340
|
const baseUrl = this.config.url.replace(/\\/$/, "");
|
|
301
341
|
const url = \`\${baseUrl}/\${kind}/\${encodeURIComponent(name)}\`;
|
|
302
342
|
const authHeaders = await resolveAuthHeaders(this.config.auth);
|
|
303
343
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
344
|
+
let response: Response;
|
|
345
|
+
try {
|
|
346
|
+
response = await fetch(url, {
|
|
347
|
+
method: "POST",
|
|
348
|
+
headers: {
|
|
349
|
+
"Content-Type": "application/json",
|
|
350
|
+
...authHeaders,
|
|
351
|
+
},
|
|
352
|
+
body: JSON.stringify({ args }),
|
|
353
|
+
});
|
|
354
|
+
} catch (error) {
|
|
355
|
+
return {
|
|
356
|
+
ok: false,
|
|
357
|
+
status: 0,
|
|
358
|
+
error: {
|
|
359
|
+
code: "FORGE_NETWORK_ERROR",
|
|
360
|
+
message: error instanceof Error ? error.message : "network request failed",
|
|
361
|
+
details: error,
|
|
362
|
+
},
|
|
363
|
+
};
|
|
364
|
+
}
|
|
312
365
|
|
|
313
366
|
let body: unknown;
|
|
314
367
|
try {
|
|
315
368
|
body = await response.json();
|
|
316
369
|
} catch {
|
|
317
|
-
|
|
318
|
-
|
|
370
|
+
return {
|
|
371
|
+
ok: false,
|
|
319
372
|
status: response.status,
|
|
320
|
-
|
|
373
|
+
error: {
|
|
374
|
+
code: "FORGE_HTTP_ERROR",
|
|
375
|
+
message: \`HTTP \${response.status}\`,
|
|
376
|
+
},
|
|
377
|
+
};
|
|
321
378
|
}
|
|
322
379
|
|
|
323
380
|
const payload = parseJsonPayload(body);
|
|
@@ -331,15 +388,26 @@ class ForgeHttpClient implements ForgeClient {
|
|
|
331
388
|
payload.error?.message ??
|
|
332
389
|
diagnostic?.message ??
|
|
333
390
|
\`Request failed with status \${response.status}\`;
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
traceId: payload.traceId,
|
|
391
|
+
return {
|
|
392
|
+
ok: false,
|
|
337
393
|
status: response.status,
|
|
338
|
-
|
|
339
|
-
|
|
394
|
+
traceId: payload.traceId,
|
|
395
|
+
diagnostics: payload.diagnostics,
|
|
396
|
+
error: {
|
|
397
|
+
code,
|
|
398
|
+
message,
|
|
399
|
+
details: payload.error?.details ?? payload.diagnostics,
|
|
400
|
+
},
|
|
401
|
+
};
|
|
340
402
|
}
|
|
341
403
|
|
|
342
|
-
return
|
|
404
|
+
return {
|
|
405
|
+
ok: true,
|
|
406
|
+
result: payload.result,
|
|
407
|
+
status: response.status,
|
|
408
|
+
traceId: payload.traceId,
|
|
409
|
+
diagnostics: payload.diagnostics,
|
|
410
|
+
};
|
|
343
411
|
}
|
|
344
412
|
|
|
345
413
|
private async invokeExternal(
|
|
@@ -524,8 +592,11 @@ export type {
|
|
|
524
592
|
ForgeReactAuthProvider,
|
|
525
593
|
ForgeReactClient,
|
|
526
594
|
ForgeReactError,
|
|
595
|
+
ForgeCommandCallResult,
|
|
527
596
|
UseCommandOptions,
|
|
528
597
|
UseCommandResult,
|
|
598
|
+
UseCommandResultHook,
|
|
599
|
+
UseCommandResultOptions,
|
|
529
600
|
UseLiveQueryOptions,
|
|
530
601
|
UseLiveQueryResult,
|
|
531
602
|
UseQueryOptions,
|
|
@@ -539,6 +610,7 @@ export const useForgeClient = forgeReact.useForgeClient;
|
|
|
539
610
|
export const useAuth = forgeReact.useAuth;
|
|
540
611
|
export const useQuery = forgeReact.useQuery;
|
|
541
612
|
export const useCommand = forgeReact.useCommand;
|
|
613
|
+
export const useCommandResult = forgeReact.useCommandResult;
|
|
542
614
|
export const useLiveQuery = forgeReact.useLiveQuery;
|
|
543
615
|
`;
|
|
544
616
|
}
|
|
@@ -551,8 +623,11 @@ export function renderReactDts(): string {
|
|
|
551
623
|
ForgeReactAuthProvider,
|
|
552
624
|
ForgeReactClient,
|
|
553
625
|
ForgeReactError,
|
|
626
|
+
ForgeCommandCallResult,
|
|
554
627
|
UseCommandOptions,
|
|
555
628
|
UseCommandResult,
|
|
629
|
+
UseCommandResultHook,
|
|
630
|
+
UseCommandResultOptions,
|
|
556
631
|
UseLiveQueryOptions,
|
|
557
632
|
UseLiveQueryResult,
|
|
558
633
|
UseQueryOptions,
|
|
@@ -564,6 +639,7 @@ export declare const useForgeClient: import("forge/react").ForgeReactBindings["u
|
|
|
564
639
|
export declare const useAuth: import("forge/react").ForgeReactBindings["useAuth"];
|
|
565
640
|
export declare const useQuery: import("forge/react").ForgeReactBindings["useQuery"];
|
|
566
641
|
export declare const useCommand: import("forge/react").ForgeReactBindings["useCommand"];
|
|
642
|
+
export declare const useCommandResult: import("forge/react").ForgeReactBindings["useCommandResult"];
|
|
567
643
|
export declare const useLiveQuery: import("forge/react").ForgeReactBindings["useLiveQuery"];
|
|
568
644
|
`;
|
|
569
645
|
}
|
|
@@ -271,6 +271,7 @@ export class PackageGraphCompiler {
|
|
|
271
271
|
|
|
272
272
|
const partialApi = {
|
|
273
273
|
name: dep.name,
|
|
274
|
+
...(packageJson.name && packageJson.name !== dep.name ? { packageName: packageJson.name } : {}),
|
|
274
275
|
version: dep.version,
|
|
275
276
|
packageManager: dep.packageManager,
|
|
276
277
|
resolutionMode: opts.resolutionMode,
|
|
@@ -454,10 +455,11 @@ function portableDtsPath(dtsPath: string, dep: Pick<Dependency, "installPath" |
|
|
|
454
455
|
}
|
|
455
456
|
|
|
456
457
|
function readPackageJson(installPath: string): {
|
|
458
|
+
name?: string;
|
|
457
459
|
exports?: unknown;
|
|
458
460
|
} {
|
|
459
461
|
const raw = readTextFile(join(installPath, "package.json"));
|
|
460
|
-
return JSON.parse(raw) as { exports?: unknown };
|
|
462
|
+
return JSON.parse(raw) as { name?: string; exports?: unknown };
|
|
461
463
|
}
|
|
462
464
|
|
|
463
465
|
function resolveSandboxBackend(
|
|
@@ -102,17 +102,138 @@ function readPackageJson(workspaceRoot: string): {
|
|
|
102
102
|
};
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
type DependencyType = "dependency" | "devDependency";
|
|
106
|
+
|
|
107
|
+
interface NpmAliasSpec {
|
|
108
|
+
packageName: string;
|
|
109
|
+
versionOrRange?: string;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
interface PackageResolution {
|
|
113
|
+
requestedName: string;
|
|
114
|
+
packageName: string;
|
|
115
|
+
dependencyName: string;
|
|
116
|
+
dependencySpec?: string;
|
|
117
|
+
dependencyType: DependencyType;
|
|
118
|
+
isNpmAlias: boolean;
|
|
119
|
+
current: PackageApi;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function matchesPackageName(candidate: PackageApi, packageName: string): boolean {
|
|
123
|
+
return candidate.name === packageName || candidate.packageName === packageName;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function parseNpmAliasSpec(spec: string): NpmAliasSpec | null {
|
|
127
|
+
if (!spec.startsWith("npm:")) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
const target = spec.slice("npm:".length);
|
|
131
|
+
if (!target) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
const separator =
|
|
135
|
+
target.startsWith("@")
|
|
136
|
+
? target.indexOf("@", target.indexOf("/") + 1)
|
|
137
|
+
: target.indexOf("@");
|
|
138
|
+
if (separator === -1) {
|
|
139
|
+
return { packageName: target };
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
packageName: target.slice(0, separator),
|
|
143
|
+
versionOrRange: target.slice(separator + 1),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function dependencyEntries(
|
|
148
|
+
pkg: ReturnType<typeof readPackageJson>,
|
|
149
|
+
): Array<{ type: DependencyType; name: string; spec: string }> {
|
|
150
|
+
return [
|
|
151
|
+
...Object.entries(pkg.dependencies ?? {}).map(([name, spec]) => ({
|
|
152
|
+
type: "dependency" as const,
|
|
153
|
+
name,
|
|
154
|
+
spec,
|
|
155
|
+
})),
|
|
156
|
+
...Object.entries(pkg.devDependencies ?? {}).map(([name, spec]) => ({
|
|
157
|
+
type: "devDependency" as const,
|
|
158
|
+
name,
|
|
159
|
+
spec,
|
|
160
|
+
})),
|
|
161
|
+
];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function dependencyType(workspaceRoot: string, packageName: string): DependencyType {
|
|
106
165
|
const pkg = readPackageJson(workspaceRoot);
|
|
107
166
|
if (packageName in (pkg.devDependencies ?? {})) {
|
|
108
167
|
return "devDependency";
|
|
109
168
|
}
|
|
169
|
+
for (const entry of dependencyEntries(pkg)) {
|
|
170
|
+
const alias = parseNpmAliasSpec(entry.spec);
|
|
171
|
+
if (alias?.packageName === packageName) {
|
|
172
|
+
return entry.type;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
110
175
|
return "dependency";
|
|
111
176
|
}
|
|
112
177
|
|
|
113
|
-
function
|
|
178
|
+
function resolvePackageForUpgrade(workspaceRoot: string, requestedName: string): PackageResolution | null {
|
|
114
179
|
const graph = readGeneratedJson<PackageGraph>(workspaceRoot, `${GENERATED_DIR}/packageGraph.json`);
|
|
115
|
-
|
|
180
|
+
if (!graph) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
const pkg = readPackageJson(workspaceRoot);
|
|
184
|
+
const entries = dependencyEntries(pkg);
|
|
185
|
+
|
|
186
|
+
const aliasEntry = entries.find((entry) => {
|
|
187
|
+
const alias = parseNpmAliasSpec(entry.spec);
|
|
188
|
+
return entry.name === requestedName || alias?.packageName === requestedName;
|
|
189
|
+
});
|
|
190
|
+
if (aliasEntry) {
|
|
191
|
+
const alias = parseNpmAliasSpec(aliasEntry.spec);
|
|
192
|
+
const packageName = alias?.packageName ?? aliasEntry.name;
|
|
193
|
+
const current = graph.packages.find((candidate) => matchesPackageName(candidate, packageName)) ??
|
|
194
|
+
graph.packages.find((candidate) => candidate.name === aliasEntry.name);
|
|
195
|
+
if (current) {
|
|
196
|
+
return {
|
|
197
|
+
requestedName,
|
|
198
|
+
packageName,
|
|
199
|
+
dependencyName: aliasEntry.name,
|
|
200
|
+
dependencySpec: aliasEntry.spec,
|
|
201
|
+
dependencyType: aliasEntry.type,
|
|
202
|
+
isNpmAlias: Boolean(alias),
|
|
203
|
+
current,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const current = graph.packages.find((candidate) => matchesPackageName(candidate, requestedName));
|
|
209
|
+
if (!current) {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
const directEntry = entries.find((entry) => {
|
|
213
|
+
const alias = parseNpmAliasSpec(entry.spec);
|
|
214
|
+
return entry.name === requestedName || alias?.packageName === requestedName;
|
|
215
|
+
});
|
|
216
|
+
const alias = directEntry ? parseNpmAliasSpec(directEntry.spec) : null;
|
|
217
|
+
const packageName = alias?.packageName ?? current.packageName ?? requestedName;
|
|
218
|
+
return {
|
|
219
|
+
requestedName,
|
|
220
|
+
packageName,
|
|
221
|
+
dependencyName: directEntry?.name ?? requestedName,
|
|
222
|
+
dependencySpec: directEntry?.spec,
|
|
223
|
+
dependencyType: directEntry?.type ?? dependencyType(workspaceRoot, requestedName),
|
|
224
|
+
isNpmAlias: Boolean(alias),
|
|
225
|
+
current,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function packageSpecForDependency(
|
|
230
|
+
resolution: Pick<PackageResolution, "packageName" | "dependencyName" | "isNpmAlias">,
|
|
231
|
+
version: string,
|
|
232
|
+
): string {
|
|
233
|
+
if (resolution.isNpmAlias) {
|
|
234
|
+
return `${resolution.dependencyName}@npm:${resolution.packageName}@${version}`;
|
|
235
|
+
}
|
|
236
|
+
return `${resolution.packageName}@${version}`;
|
|
116
237
|
}
|
|
117
238
|
|
|
118
239
|
function fixtureMetadata(registryDir: string | undefined, packageName: string): {
|
|
@@ -348,9 +469,9 @@ export async function createUpgradePlan(
|
|
|
348
469
|
): Promise<UpgradePlannerResult> {
|
|
349
470
|
const diagnostics: Diagnostic[] = [];
|
|
350
471
|
const packageManager = detectPackageManager(options.workspaceRoot);
|
|
351
|
-
const
|
|
472
|
+
const resolution = resolvePackageForUpgrade(options.workspaceRoot, options.packageName);
|
|
352
473
|
|
|
353
|
-
if (!
|
|
474
|
+
if (!resolution) {
|
|
354
475
|
return {
|
|
355
476
|
ok: false,
|
|
356
477
|
diagnostics: [
|
|
@@ -363,10 +484,11 @@ export async function createUpgradePlan(
|
|
|
363
484
|
exitCode: 1,
|
|
364
485
|
};
|
|
365
486
|
}
|
|
487
|
+
const current = resolution.current;
|
|
366
488
|
|
|
367
489
|
const targetInfo = await resolveTargetVersion({
|
|
368
490
|
workspaceRoot: options.workspaceRoot,
|
|
369
|
-
packageName:
|
|
491
|
+
packageName: resolution.packageName,
|
|
370
492
|
currentVersion: current.version,
|
|
371
493
|
target: options.target,
|
|
372
494
|
registryDir: options.registryDir ?? process.env.FORGE_DEPS_REGISTRY_DIR,
|
|
@@ -388,7 +510,7 @@ export async function createUpgradePlan(
|
|
|
388
510
|
|
|
389
511
|
const analyzed = await analyzeTargetPackage({
|
|
390
512
|
workspaceRoot: options.workspaceRoot,
|
|
391
|
-
packageName:
|
|
513
|
+
packageName: resolution.packageName,
|
|
392
514
|
versionInfo: targetInfo,
|
|
393
515
|
packageManager,
|
|
394
516
|
registryDir: options.registryDir ?? process.env.FORGE_DEPS_REGISTRY_DIR,
|
|
@@ -398,7 +520,7 @@ export async function createUpgradePlan(
|
|
|
398
520
|
return { ok: false, diagnostics, exitCode: 1 };
|
|
399
521
|
}
|
|
400
522
|
|
|
401
|
-
const recipe = resolveByPackageName(
|
|
523
|
+
const recipe = resolveByPackageName(resolution.packageName) ?? undefined;
|
|
402
524
|
const currentClassified = { api: current, classification: classify(current, recipe), recipe };
|
|
403
525
|
const targetClassified = {
|
|
404
526
|
api: analyzed.api,
|
|
@@ -425,7 +547,7 @@ export async function createUpgradePlan(
|
|
|
425
547
|
}
|
|
426
548
|
|
|
427
549
|
const affected = analyzeUpgradeImpact({
|
|
428
|
-
packageName:
|
|
550
|
+
packageName: resolution.packageName,
|
|
429
551
|
appGraph,
|
|
430
552
|
runtimeGraph,
|
|
431
553
|
queryRegistry,
|
|
@@ -436,21 +558,35 @@ export async function createUpgradePlan(
|
|
|
436
558
|
const bump = semverBump(current.version, analyzed.api.version);
|
|
437
559
|
const risk = buildRiskReport({ bump, apiDiff, runtimeDiff, affected });
|
|
438
560
|
const commands = recommendedCommands(affected);
|
|
561
|
+
const toInfo = {
|
|
562
|
+
...targetInfo,
|
|
563
|
+
version: analyzed.api.version,
|
|
564
|
+
spec: packageSpecForDependency(resolution, analyzed.api.version),
|
|
565
|
+
};
|
|
439
566
|
const planDir = packagePlanDir(
|
|
440
567
|
options.workspaceRoot,
|
|
441
|
-
|
|
568
|
+
resolution.dependencyName,
|
|
442
569
|
current.version,
|
|
443
570
|
analyzed.api.version,
|
|
444
571
|
);
|
|
445
|
-
const id = `${packageKey(
|
|
572
|
+
const id = `${packageKey(resolution.dependencyName)}-${current.version}-to-${analyzed.api.version}`;
|
|
446
573
|
const plan: PackageUpgradePlan = {
|
|
447
574
|
schemaVersion: "0.1.0",
|
|
448
575
|
plannerVersion: GENERATOR_VERSION,
|
|
449
576
|
id,
|
|
450
|
-
packageName:
|
|
577
|
+
packageName: resolution.packageName,
|
|
578
|
+
...(resolution.requestedName !== resolution.packageName
|
|
579
|
+
? { requestedPackageName: resolution.requestedName }
|
|
580
|
+
: {}),
|
|
581
|
+
...(resolution.isNpmAlias ? { dependencyAlias: resolution.dependencyName } : {}),
|
|
451
582
|
...(recipe?.alias ? { integrationAlias: recipe.alias } : {}),
|
|
452
|
-
from: {
|
|
453
|
-
|
|
583
|
+
from: {
|
|
584
|
+
version: current.version,
|
|
585
|
+
spec: resolution.isNpmAlias
|
|
586
|
+
? packageSpecForDependency(resolution, current.version)
|
|
587
|
+
: `${resolution.packageName}@${current.version}`,
|
|
588
|
+
},
|
|
589
|
+
to: toInfo,
|
|
454
590
|
packageManager,
|
|
455
591
|
semver: {
|
|
456
592
|
bump,
|
|
@@ -512,18 +648,19 @@ export function listOutdatedFromFixture(options: {
|
|
|
512
648
|
|
|
513
649
|
return graph.packages
|
|
514
650
|
.flatMap((pkg) => {
|
|
515
|
-
const
|
|
651
|
+
const packageName = pkg.packageName ?? pkg.name;
|
|
652
|
+
const metadata = fixtureMetadata(registryDir, packageName);
|
|
516
653
|
const latest = metadata?.["dist-tags"]?.latest;
|
|
517
654
|
if (!latest || latest === pkg.version) {
|
|
518
655
|
return [];
|
|
519
656
|
}
|
|
520
|
-
const recipe = resolveByPackageName(
|
|
657
|
+
const recipe = resolveByPackageName(packageName);
|
|
521
658
|
return [{
|
|
522
|
-
name:
|
|
659
|
+
name: packageName,
|
|
523
660
|
current: pkg.version,
|
|
524
661
|
wanted: latest,
|
|
525
662
|
latest,
|
|
526
|
-
type: dependencyType(options.workspaceRoot,
|
|
663
|
+
type: dependencyType(options.workspaceRoot, packageName),
|
|
527
664
|
managedByRecipe: Boolean(recipe),
|
|
528
665
|
...(recipe?.alias ? { recipe: recipe.alias } : {}),
|
|
529
666
|
}];
|
|
@@ -146,6 +146,8 @@ export interface PackageUpgradePlan {
|
|
|
146
146
|
plannerVersion: string;
|
|
147
147
|
id: string;
|
|
148
148
|
packageName: string;
|
|
149
|
+
requestedPackageName?: string;
|
|
150
|
+
dependencyAlias?: string;
|
|
149
151
|
integrationAlias?: string;
|
|
150
152
|
from: PackageVersionInfo;
|
|
151
153
|
to: PackageVersionInfo;
|
|
@@ -92,7 +92,13 @@ export interface Entrypoint {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
export interface PackageApi {
|
|
95
|
+
/**
|
|
96
|
+
* Import/dependency name used by the app, which may be an npm alias such as
|
|
97
|
+
* "forge" for "npm:forgeos@...".
|
|
98
|
+
*/
|
|
95
99
|
name: string;
|
|
100
|
+
/** Real package.json name when it differs from the dependency/import name. */
|
|
101
|
+
packageName?: string;
|
|
96
102
|
version: string;
|
|
97
103
|
packageManager: PackageManager;
|
|
98
104
|
resolutionMode: ResolutionMode;
|
package/src/forge/react/index.ts
CHANGED
|
@@ -45,6 +45,22 @@ export type ForgeReactError = Error & {
|
|
|
45
45
|
details?: unknown;
|
|
46
46
|
};
|
|
47
47
|
|
|
48
|
+
export type ForgeCommandCallResult<T = unknown> =
|
|
49
|
+
| {
|
|
50
|
+
ok: true;
|
|
51
|
+
result: T;
|
|
52
|
+
status: number;
|
|
53
|
+
traceId?: string;
|
|
54
|
+
diagnostics?: { code: string; message: string }[];
|
|
55
|
+
}
|
|
56
|
+
| {
|
|
57
|
+
ok: false;
|
|
58
|
+
error: { code: string; message: string; details?: unknown };
|
|
59
|
+
status: number;
|
|
60
|
+
traceId?: string;
|
|
61
|
+
diagnostics?: { code: string; message: string }[];
|
|
62
|
+
};
|
|
63
|
+
|
|
48
64
|
export type LiveSnapshot<T> = {
|
|
49
65
|
subscriptionId: string;
|
|
50
66
|
revision: number;
|
|
@@ -56,6 +72,7 @@ export type ForgeReactClient = {
|
|
|
56
72
|
lastTraceId?: string;
|
|
57
73
|
query(name: string, args: unknown): Promise<unknown>;
|
|
58
74
|
command(name: string, args: unknown): Promise<unknown>;
|
|
75
|
+
commandResult?(name: string, args: unknown): Promise<ForgeCommandCallResult<unknown>>;
|
|
59
76
|
liveQuery(
|
|
60
77
|
name: string,
|
|
61
78
|
args: unknown,
|
|
@@ -96,6 +113,22 @@ export type UseCommandResult<TArgs, TResult> = {
|
|
|
96
113
|
reset: () => void;
|
|
97
114
|
};
|
|
98
115
|
|
|
116
|
+
export type UseCommandResultOptions<TResult> = {
|
|
117
|
+
onSuccess?: (result: TResult) => void;
|
|
118
|
+
onError?: (error: ForgeReactError) => void;
|
|
119
|
+
onSettled?: (result: ForgeCommandCallResult<TResult>) => void;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export type UseCommandResultHook<TArgs, TResult> = {
|
|
123
|
+
run: (args: TArgs) => Promise<ForgeCommandCallResult<TResult>>;
|
|
124
|
+
loading: boolean;
|
|
125
|
+
error: ForgeReactError | null;
|
|
126
|
+
result: ForgeCommandCallResult<TResult> | undefined;
|
|
127
|
+
data: TResult | undefined;
|
|
128
|
+
traceId?: string;
|
|
129
|
+
reset: () => void;
|
|
130
|
+
};
|
|
131
|
+
|
|
99
132
|
export type UseLiveQueryOptions = {
|
|
100
133
|
enabled?: boolean;
|
|
101
134
|
};
|
|
@@ -123,6 +156,10 @@ export type ForgeReactBindings<TClient extends ForgeReactClient = ForgeReactClie
|
|
|
123
156
|
name: string,
|
|
124
157
|
options?: UseCommandOptions<TResult>,
|
|
125
158
|
) => UseCommandResult<TArgs, TResult>;
|
|
159
|
+
useCommandResult: <TArgs = unknown, TResult = unknown>(
|
|
160
|
+
name: string,
|
|
161
|
+
options?: UseCommandResultOptions<TResult>,
|
|
162
|
+
) => UseCommandResultHook<TArgs, TResult>;
|
|
126
163
|
useLiveQuery: <TResult = unknown>(
|
|
127
164
|
name: string,
|
|
128
165
|
args: unknown,
|
|
@@ -165,6 +202,29 @@ function toForgeError(error: unknown): ForgeReactError {
|
|
|
165
202
|
return wrapped;
|
|
166
203
|
}
|
|
167
204
|
|
|
205
|
+
function commandErrorToForgeError(result: Extract<ForgeCommandCallResult, { ok: false }>): ForgeReactError {
|
|
206
|
+
const error = new Error(result.error.message) as ForgeReactError;
|
|
207
|
+
error.code = result.error.code;
|
|
208
|
+
error.status = result.status;
|
|
209
|
+
error.traceId = result.traceId;
|
|
210
|
+
error.details = result.error.details ?? result.diagnostics;
|
|
211
|
+
return error;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function caughtErrorToCommandResult(error: unknown): ForgeCommandCallResult<never> {
|
|
215
|
+
const forgeError = toForgeError(error);
|
|
216
|
+
return {
|
|
217
|
+
ok: false,
|
|
218
|
+
status: forgeError.status ?? 0,
|
|
219
|
+
traceId: forgeError.traceId,
|
|
220
|
+
error: {
|
|
221
|
+
code: forgeError.code ?? "FORGE_REACT_COMMAND_FAILED",
|
|
222
|
+
message: forgeError.message,
|
|
223
|
+
details: forgeError.details,
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
168
228
|
function resolveDevAuth(devAuth: ForgeDevAuthConfig | undefined): ForgeReactAuthProvider | undefined {
|
|
169
229
|
if (!devAuth) {
|
|
170
230
|
return undefined;
|
|
@@ -344,6 +404,86 @@ export function createForgeReactBindings<TClient extends ForgeReactClient>(
|
|
|
344
404
|
};
|
|
345
405
|
}
|
|
346
406
|
|
|
407
|
+
function useCommandResult<TArgs = unknown, TResult = unknown>(
|
|
408
|
+
name: string,
|
|
409
|
+
options?: UseCommandResultOptions<TResult>,
|
|
410
|
+
): UseCommandResultHook<TArgs, TResult> {
|
|
411
|
+
const client = useForgeClient();
|
|
412
|
+
const [state, setState] = React.useState<{
|
|
413
|
+
loading: boolean;
|
|
414
|
+
error: ForgeReactError | null;
|
|
415
|
+
result: ForgeCommandCallResult<TResult> | undefined;
|
|
416
|
+
data: TResult | undefined;
|
|
417
|
+
traceId?: string;
|
|
418
|
+
}>({
|
|
419
|
+
loading: false,
|
|
420
|
+
error: null,
|
|
421
|
+
result: undefined,
|
|
422
|
+
data: undefined,
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
const run = React.useCallback(
|
|
426
|
+
async (args: TArgs): Promise<ForgeCommandCallResult<TResult>> => {
|
|
427
|
+
setState((prev) => ({ ...prev, loading: true, error: null }));
|
|
428
|
+
let result: ForgeCommandCallResult<TResult>;
|
|
429
|
+
if (typeof client.commandResult === "function") {
|
|
430
|
+
result = (await client.commandResult(name, args)) as ForgeCommandCallResult<TResult>;
|
|
431
|
+
} else {
|
|
432
|
+
try {
|
|
433
|
+
const data = (await client.command(name, args)) as TResult;
|
|
434
|
+
result = {
|
|
435
|
+
ok: true,
|
|
436
|
+
result: data,
|
|
437
|
+
status: 200,
|
|
438
|
+
traceId: client.lastTraceId,
|
|
439
|
+
};
|
|
440
|
+
} catch (error) {
|
|
441
|
+
result = caughtErrorToCommandResult(error);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (result.ok) {
|
|
446
|
+
setState({
|
|
447
|
+
loading: false,
|
|
448
|
+
error: null,
|
|
449
|
+
result,
|
|
450
|
+
data: result.result,
|
|
451
|
+
traceId: result.traceId ?? client.lastTraceId,
|
|
452
|
+
});
|
|
453
|
+
options?.onSuccess?.(result.result);
|
|
454
|
+
} else {
|
|
455
|
+
const forgeError = commandErrorToForgeError(result);
|
|
456
|
+
setState({
|
|
457
|
+
loading: false,
|
|
458
|
+
error: forgeError,
|
|
459
|
+
result,
|
|
460
|
+
data: undefined,
|
|
461
|
+
traceId: result.traceId,
|
|
462
|
+
});
|
|
463
|
+
options?.onError?.(forgeError);
|
|
464
|
+
}
|
|
465
|
+
options?.onSettled?.(result);
|
|
466
|
+
return result;
|
|
467
|
+
},
|
|
468
|
+
[client, name, options?.onError, options?.onSettled, options?.onSuccess],
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
const reset = React.useCallback(() => {
|
|
472
|
+
setState({
|
|
473
|
+
loading: false,
|
|
474
|
+
error: null,
|
|
475
|
+
result: undefined,
|
|
476
|
+
data: undefined,
|
|
477
|
+
});
|
|
478
|
+
}, []);
|
|
479
|
+
|
|
480
|
+
return {
|
|
481
|
+
...state,
|
|
482
|
+
run,
|
|
483
|
+
reset,
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
347
487
|
function useLiveQuery<TResult = unknown>(
|
|
348
488
|
name: string,
|
|
349
489
|
args: unknown,
|
|
@@ -430,6 +570,7 @@ export function createForgeReactBindings<TClient extends ForgeReactClient>(
|
|
|
430
570
|
useAuth,
|
|
431
571
|
useQuery,
|
|
432
572
|
useCommand,
|
|
573
|
+
useCommandResult,
|
|
433
574
|
useLiveQuery,
|
|
434
575
|
};
|
|
435
576
|
}
|
package/src/forge/version.ts
CHANGED