@stigmer/sdk 3.12.4 → 3.12.5
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/__tests__/mcpserver-connect.test.d.ts +2 -0
- package/__tests__/mcpserver-connect.test.d.ts.map +1 -0
- package/__tests__/mcpserver-connect.test.js +146 -0
- package/__tests__/mcpserver-connect.test.js.map +1 -0
- package/__tests__/update-input.test.d.ts +2 -0
- package/__tests__/update-input.test.d.ts.map +1 -0
- package/__tests__/update-input.test.js +169 -0
- package/__tests__/update-input.test.js.map +1 -0
- package/gen/agentexecution.d.ts +6 -0
- package/gen/agentexecution.d.ts.map +1 -1
- package/gen/agentexecution.js +9 -1
- package/gen/agentexecution.js.map +1 -1
- package/gen/client.d.ts +3 -3
- package/gen/client.d.ts.map +1 -1
- package/gen/identityaccount.d.ts +8 -0
- package/gen/identityaccount.d.ts.map +1 -1
- package/gen/identityaccount.js +11 -1
- package/gen/identityaccount.js.map +1 -1
- package/gen/mcpserver.d.ts +1 -0
- package/gen/mcpserver.d.ts.map +1 -1
- package/gen/mcpserver.js +8 -0
- package/gen/mcpserver.js.map +1 -1
- package/gen/oauthapp.d.ts +2 -1
- package/gen/oauthapp.d.ts.map +1 -1
- package/gen/oauthapp.js +1 -0
- package/gen/oauthapp.js.map +1 -1
- package/gen/organization.d.ts +5 -0
- package/gen/organization.d.ts.map +1 -1
- package/gen/organization.js +8 -1
- package/gen/organization.js.map +1 -1
- package/gen/platformclient.d.ts +1 -0
- package/gen/platformclient.d.ts.map +1 -1
- package/gen/platformclient.js +2 -0
- package/gen/platformclient.js.map +1 -1
- package/index.d.ts +2 -0
- package/index.d.ts.map +1 -1
- package/index.js +3 -0
- package/index.js.map +1 -1
- package/mcpserver-connect.d.ts +79 -0
- package/mcpserver-connect.d.ts.map +1 -0
- package/mcpserver-connect.js +114 -0
- package/mcpserver-connect.js.map +1 -0
- package/package.json +2 -2
- package/src/__tests__/mcpserver-connect.test.ts +181 -0
- package/src/__tests__/update-input.test.ts +219 -0
- package/src/gen/agentexecution.ts +17 -1
- package/src/gen/client.ts +3 -3
- package/src/gen/identityaccount.ts +21 -1
- package/src/gen/mcpserver.ts +6 -0
- package/src/gen/oauthapp.ts +3 -1
- package/src/gen/organization.ts +15 -1
- package/src/gen/platformclient.ts +3 -0
- package/src/index.ts +14 -0
- package/src/mcpserver-connect.ts +159 -0
- package/src/update-input.ts +168 -0
- package/update-input.d.ts +24 -0
- package/update-input.d.ts.map +1 -0
- package/update-input.js +126 -0
- package/update-input.js.map +1 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { Code, ConnectError } from "@connectrpc/connect";
|
|
2
|
+
import type { McpServer } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
|
|
3
|
+
import type { ConnectInput } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/io_pb";
|
|
4
|
+
import { ConnectPhase } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/status_pb";
|
|
5
|
+
import { isUnimplemented, wrapError } from "./gen/errors.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The slice of {@link McpServerClient} the connect protocol needs — the
|
|
9
|
+
* async-lane pair plus the blocking fallback. Structural on purpose: the
|
|
10
|
+
* full client satisfies it, and tests can hand in three functions.
|
|
11
|
+
*/
|
|
12
|
+
export interface McpServerConnectLane {
|
|
13
|
+
startConnect(input: ConnectInput): Promise<McpServer>;
|
|
14
|
+
connect(input: ConnectInput): Promise<McpServer>;
|
|
15
|
+
get(id: string): Promise<McpServer>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* How often {@link connectAndWait} re-reads the resource while a connect
|
|
20
|
+
* operation runs. Discovery legitimately takes tens of seconds to minutes;
|
|
21
|
+
* a few seconds of staleness is invisible next to that, and each poll is
|
|
22
|
+
* one cheap get.
|
|
23
|
+
*/
|
|
24
|
+
export const CONNECT_POLL_INTERVAL_MS = 2_500;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Absolute bound on waiting for a connect to settle: the backend's async
|
|
28
|
+
* connect ceiling (its asyncConnectTimeout, 60 min) plus margin. Every
|
|
29
|
+
* operation settles within the ceiling by construction; only a
|
|
30
|
+
* connect_status orphaned by a backend restart can still read CONNECTING
|
|
31
|
+
* past it, and this bound turns that into a typed error instead of an
|
|
32
|
+
* infinite wait.
|
|
33
|
+
*/
|
|
34
|
+
export const CONNECT_SETTLE_BOUND_MS = 65 * 60_000;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Thrown by {@link connectAndWait} when it stopped waiting while the
|
|
38
|
+
* server-side operation was still running. NOT a failure: the backend
|
|
39
|
+
* finishes the connect on its own and persists the result — re-read the
|
|
40
|
+
* resource (or re-run the connect) to observe the outcome.
|
|
41
|
+
*/
|
|
42
|
+
export class ConnectStillRunningError extends Error {
|
|
43
|
+
readonly mcpServerId: string;
|
|
44
|
+
|
|
45
|
+
constructor(mcpServerId: string, waitedMs: number) {
|
|
46
|
+
super(
|
|
47
|
+
`the connect of MCP server '${mcpServerId}' is still running server-side after ${Math.round(waitedMs / 1000)}s — ` +
|
|
48
|
+
"it will persist its result on its own; re-read the server to observe the outcome",
|
|
49
|
+
);
|
|
50
|
+
this.name = "ConnectStillRunningError";
|
|
51
|
+
this.mcpServerId = mcpServerId;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Options for {@link connectAndWait}. */
|
|
56
|
+
export interface ConnectAndWaitOptions {
|
|
57
|
+
/** Poll cadence. Defaults to {@link CONNECT_POLL_INTERVAL_MS}. */
|
|
58
|
+
readonly pollIntervalMs?: number;
|
|
59
|
+
/**
|
|
60
|
+
* Stop waiting after this long (soft bound — the server-side operation
|
|
61
|
+
* keeps running). Defaults to {@link CONNECT_SETTLE_BOUND_MS}, and is
|
|
62
|
+
* capped by it: waiting longer than the backend's own ceiling can only
|
|
63
|
+
* ever observe an orphaned record.
|
|
64
|
+
*/
|
|
65
|
+
readonly deadlineMs?: number;
|
|
66
|
+
/**
|
|
67
|
+
* Observe the CONNECTING snapshot the moment the operation is accepted —
|
|
68
|
+
* the place to read `status.connect_status.warning` (the dead-runner
|
|
69
|
+
* advisory) before the wait begins. Not called on the blocking fallback,
|
|
70
|
+
* which has no start-time snapshot.
|
|
71
|
+
*/
|
|
72
|
+
readonly onStarted?: (started: McpServer) => void;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Connect an MCP server and wait for the operation to settle, without any
|
|
77
|
+
* long-lived RPC (stigmer/stigmer#425).
|
|
78
|
+
*
|
|
79
|
+
* Uses the async lane — `startConnect` returns immediately with
|
|
80
|
+
* `status.connect_status = CONNECTING`, then the resource is polled until
|
|
81
|
+
* the operation settles — so the wait survives transport limits that kill
|
|
82
|
+
* a long-blocking unary call (browsers drop no-bytes responses around
|
|
83
|
+
* ~300s, below the server's discovery ceiling). Callers never see the
|
|
84
|
+
* mechanics: the promise resolves with the updated server exactly as the
|
|
85
|
+
* blocking `connect` RPC resolved, and a failed operation rejects with the
|
|
86
|
+
* same {@link StigmerError} classification the blocking RPC would have
|
|
87
|
+
* thrown (`connect_status.failure_code` is the Go gRPC code name, which
|
|
88
|
+
* matches connect-es `Code` member names — rehydration is lossless).
|
|
89
|
+
*
|
|
90
|
+
* Backends that predate the async lane answer UNIMPLEMENTED for
|
|
91
|
+
* `startConnect`; this falls back to the blocking `connect` RPC — the same
|
|
92
|
+
* edition capability-probe idiom as the skill artifact-transfer lane
|
|
93
|
+
* (skill.ts) and the org-OAuth-app surface.
|
|
94
|
+
*/
|
|
95
|
+
export async function connectAndWait(
|
|
96
|
+
client: McpServerConnectLane,
|
|
97
|
+
input: ConnectInput,
|
|
98
|
+
options?: ConnectAndWaitOptions,
|
|
99
|
+
): Promise<McpServer> {
|
|
100
|
+
const pollIntervalMs = options?.pollIntervalMs ?? CONNECT_POLL_INTERVAL_MS;
|
|
101
|
+
const waitMs = Math.min(options?.deadlineMs ?? CONNECT_SETTLE_BOUND_MS, CONNECT_SETTLE_BOUND_MS);
|
|
102
|
+
|
|
103
|
+
let started: McpServer;
|
|
104
|
+
try {
|
|
105
|
+
started = await client.startConnect(input);
|
|
106
|
+
} catch (err) {
|
|
107
|
+
if (!isUnimplemented(err)) throw err;
|
|
108
|
+
return blockingConnect(client, input, waitMs);
|
|
109
|
+
}
|
|
110
|
+
options?.onStarted?.(started);
|
|
111
|
+
|
|
112
|
+
const deadline = Date.now() + waitMs;
|
|
113
|
+
for (;;) {
|
|
114
|
+
if (Date.now() >= deadline) {
|
|
115
|
+
throw new ConnectStillRunningError(input.mcpServerId, waitMs);
|
|
116
|
+
}
|
|
117
|
+
await sleep(Math.min(pollIntervalMs, Math.max(1, deadline - Date.now())));
|
|
118
|
+
|
|
119
|
+
const current = await client.get(input.mcpServerId);
|
|
120
|
+
const cs = current.status?.connectStatus;
|
|
121
|
+
if (cs?.phase === ConnectPhase.succeeded) return current;
|
|
122
|
+
if (cs?.phase === ConnectPhase.failed) {
|
|
123
|
+
throw rehydrateConnectFailure(cs.failureCode, cs.failureMessage);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// The pre-async-lane path, kept behaviorally identical for old backends: one
|
|
129
|
+
// blocking RPC, raced (not cancelled — the server finishes and persists on
|
|
130
|
+
// its own) against the caller's soft bound.
|
|
131
|
+
async function blockingConnect(client: McpServerConnectLane, input: ConnectInput, waitMs: number): Promise<McpServer> {
|
|
132
|
+
const push = client.connect(input);
|
|
133
|
+
// The losing push may settle after the caller has moved on; swallow its
|
|
134
|
+
// late rejection so it cannot surface as an unhandled-rejection crash.
|
|
135
|
+
void push.catch(() => {});
|
|
136
|
+
|
|
137
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
138
|
+
const timeout = new Promise<never>((_, reject) => {
|
|
139
|
+
timer = setTimeout(() => reject(new ConnectStillRunningError(input.mcpServerId, waitMs)), waitMs);
|
|
140
|
+
});
|
|
141
|
+
try {
|
|
142
|
+
return await Promise.race([push, timeout]);
|
|
143
|
+
} finally {
|
|
144
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Rebuild the failure the blocking connect RPC would have thrown, from the
|
|
149
|
+
// classification the backend persisted on connect_status. Routing the
|
|
150
|
+
// rehydrated ConnectError through wrapError makes the result byte-identical
|
|
151
|
+
// to a live RPC failure (same StigmerError code mapping, same raw message).
|
|
152
|
+
function rehydrateConnectFailure(failureCode: string, failureMessage: string) {
|
|
153
|
+
const code = (Code as Record<string, unknown>)[failureCode];
|
|
154
|
+
return wrapError(new ConnectError(failureMessage, typeof code === "number" ? (code as Code) : Code.Unknown));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function sleep(ms: number): Promise<void> {
|
|
158
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
159
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// Complete update-input mappers.
|
|
2
|
+
//
|
|
3
|
+
// The platform's update RPCs are FULL-SPEC REPLACEMENTS: the request spec
|
|
4
|
+
// wholesale overwrites the stored spec (both editions; only a small
|
|
5
|
+
// server-side preserve list of immutable identifiers survives). A client
|
|
6
|
+
// that sends only the fields it edits therefore silently WIPES every other
|
|
7
|
+
// mutable spec field — a data-loss bug class, not a single bug
|
|
8
|
+
// (stigmer/stigmer#293 Phase 1 surfaced it when `spec.preferences` set via
|
|
9
|
+
// CLI was dropped by a console profile save).
|
|
10
|
+
//
|
|
11
|
+
// These mappers are the structural fix: they convert a LOADED resource into
|
|
12
|
+
// a complete `*Input` for the generated update builders. Editors spread the
|
|
13
|
+
// mapper output and override only the fields they edit:
|
|
14
|
+
//
|
|
15
|
+
// await update({ ...toOrganizationUpdateInput(org), description: next });
|
|
16
|
+
//
|
|
17
|
+
// The `CompleteInput` mapped type removes optionality from every Input key,
|
|
18
|
+
// so when codegen adds a new Input field, compilation fails HERE — the one
|
|
19
|
+
// place a human must decide how the new field round-trips — instead of the
|
|
20
|
+
// field silently wiping in every subset-editing form.
|
|
21
|
+
//
|
|
22
|
+
// NESTED MESSAGE RULE: `CompleteInput` only sees TOP-LEVEL keys — a nested
|
|
23
|
+
// message (e.g. `preferences`) is one key whose object literal would keep
|
|
24
|
+
// compiling when codegen adds fields INSIDE it, recreating the wipe bug one
|
|
25
|
+
// level down. Every nested-message literal in this file must therefore be
|
|
26
|
+
// built by a dedicated `toXxxInput` helper whose return literal is itself
|
|
27
|
+
// typed `CompleteInput<XxxInput>`. Editors that override a nested message
|
|
28
|
+
// must spread the mapper's complete value and override only their fields:
|
|
29
|
+
//
|
|
30
|
+
// preferences: { ...mapped.preferences, standingContext: next }
|
|
31
|
+
|
|
32
|
+
import type { ApiResourceReference } from "@stigmer/protos/ai/stigmer/commons/apiresource/io_pb";
|
|
33
|
+
import type { IdentityAccount } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/api_pb";
|
|
34
|
+
import type { IdentityAccountPreferences } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/spec_pb";
|
|
35
|
+
import type { Organization } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/api_pb";
|
|
36
|
+
import type { OrganizationPreferences } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/spec_pb";
|
|
37
|
+
import type {
|
|
38
|
+
IdentityAccountInput,
|
|
39
|
+
IdentityAccountPreferencesInput,
|
|
40
|
+
} from "./gen/identityaccount.js";
|
|
41
|
+
import type {
|
|
42
|
+
OrganizationInput,
|
|
43
|
+
OrganizationPreferencesInput,
|
|
44
|
+
} from "./gen/organization.js";
|
|
45
|
+
import type { ResourceRef } from "./gen/types.js";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Every key of `T`, required to be present. Optional keys keep their
|
|
49
|
+
* `| undefined` value type (explicit `undefined` is stripped by the proto
|
|
50
|
+
* builders), so a mapper cannot compile while omitting an Input field.
|
|
51
|
+
*
|
|
52
|
+
* Mapping over `keyof Required<T>` (instead of a homomorphic `-?` map)
|
|
53
|
+
* is deliberate: `-?` would also strip `undefined` from optional value
|
|
54
|
+
* types, forcing fake values for genuinely absent fields.
|
|
55
|
+
*/
|
|
56
|
+
type CompleteInput<T> = { [K in keyof Required<T>]: T[K] };
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Maps a loaded {@link Organization} to a complete {@link OrganizationInput}
|
|
60
|
+
* for `organization.update()`.
|
|
61
|
+
*
|
|
62
|
+
* Proto3 scalar defaults (empty string, `false`, enum `0`) are normalized to
|
|
63
|
+
* `undefined` — omitting them is wire-identical to sending the default, and
|
|
64
|
+
* it keeps the built proto in the builders' canonical shape.
|
|
65
|
+
*/
|
|
66
|
+
export function toOrganizationUpdateInput(
|
|
67
|
+
org: Organization,
|
|
68
|
+
): OrganizationInput {
|
|
69
|
+
const input: CompleteInput<OrganizationInput> = {
|
|
70
|
+
name: org.metadata?.name ?? "",
|
|
71
|
+
slug: org.metadata?.slug || undefined,
|
|
72
|
+
org: org.metadata?.org || org.metadata?.slug || "",
|
|
73
|
+
labels: nonEmptyLabels(org.metadata?.labels),
|
|
74
|
+
visibility: org.metadata?.visibility || undefined,
|
|
75
|
+
description: org.spec?.description || undefined,
|
|
76
|
+
logoUrl: org.spec?.logoUrl || undefined,
|
|
77
|
+
managementMode: org.spec?.managementMode || undefined,
|
|
78
|
+
identityProviderRef: toResourceRef(org.spec?.identityProviderRef),
|
|
79
|
+
externalOrgId: org.spec?.externalOrgId || undefined,
|
|
80
|
+
isPersonal: org.spec?.isPersonal || undefined,
|
|
81
|
+
preferences: org.spec?.preferences
|
|
82
|
+
? toOrganizationPreferencesInput(org.spec.preferences)
|
|
83
|
+
: undefined,
|
|
84
|
+
};
|
|
85
|
+
return input;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Maps loaded {@link OrganizationPreferences} to a complete
|
|
90
|
+
* {@link OrganizationPreferencesInput} (nested-message rule above).
|
|
91
|
+
*/
|
|
92
|
+
function toOrganizationPreferencesInput(
|
|
93
|
+
preferences: OrganizationPreferences,
|
|
94
|
+
): OrganizationPreferencesInput {
|
|
95
|
+
const input: CompleteInput<OrganizationPreferencesInput> = {
|
|
96
|
+
standingContext: preferences.standingContext || undefined,
|
|
97
|
+
};
|
|
98
|
+
return input;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Maps a loaded {@link IdentityAccount} to a complete
|
|
103
|
+
* {@link IdentityAccountInput} for `identityAccount.update()`.
|
|
104
|
+
*
|
|
105
|
+
* The generated builder never sets `metadata.id`; the update pipeline
|
|
106
|
+
* locates the resource by org + slug instead (and authorizes against the
|
|
107
|
+
* loaded resource's id), so carrying `org` and `slug` here is what makes
|
|
108
|
+
* the update addressable.
|
|
109
|
+
*/
|
|
110
|
+
export function toIdentityAccountUpdateInput(
|
|
111
|
+
account: IdentityAccount,
|
|
112
|
+
): IdentityAccountInput {
|
|
113
|
+
const input: CompleteInput<IdentityAccountInput> = {
|
|
114
|
+
name: account.metadata?.name ?? "",
|
|
115
|
+
slug: account.metadata?.slug || undefined,
|
|
116
|
+
org: account.metadata?.org ?? "",
|
|
117
|
+
labels: nonEmptyLabels(account.metadata?.labels),
|
|
118
|
+
visibility: account.metadata?.visibility || undefined,
|
|
119
|
+
idpId: account.spec?.idpId ?? "",
|
|
120
|
+
email: account.spec?.email || undefined,
|
|
121
|
+
firstName: account.spec?.firstName || undefined,
|
|
122
|
+
lastName: account.spec?.lastName || undefined,
|
|
123
|
+
pictureUrl: account.spec?.pictureUrl || undefined,
|
|
124
|
+
isMachineAccount: account.spec?.isMachineAccount || undefined,
|
|
125
|
+
provisioningMode: account.spec?.provisioningMode || undefined,
|
|
126
|
+
identityProviderRef: toResourceRef(account.spec?.identityProviderRef),
|
|
127
|
+
preferences: account.spec?.preferences
|
|
128
|
+
? toIdentityAccountPreferencesInput(account.spec.preferences)
|
|
129
|
+
: undefined,
|
|
130
|
+
};
|
|
131
|
+
return input;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Maps loaded {@link IdentityAccountPreferences} to a complete
|
|
136
|
+
* {@link IdentityAccountPreferencesInput} (nested-message rule above).
|
|
137
|
+
*/
|
|
138
|
+
function toIdentityAccountPreferencesInput(
|
|
139
|
+
preferences: IdentityAccountPreferences,
|
|
140
|
+
): IdentityAccountPreferencesInput {
|
|
141
|
+
const input: CompleteInput<IdentityAccountPreferencesInput> = {
|
|
142
|
+
standingContext: preferences.standingContext || undefined,
|
|
143
|
+
defaultHarness: preferences.defaultHarness || undefined,
|
|
144
|
+
defaultNativeModel: preferences.defaultNativeModel || undefined,
|
|
145
|
+
defaultCursorModel: preferences.defaultCursorModel || undefined,
|
|
146
|
+
};
|
|
147
|
+
return input;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function nonEmptyLabels(
|
|
151
|
+
labels: Record<string, string> | undefined,
|
|
152
|
+
): Record<string, string> | undefined {
|
|
153
|
+
return labels && Object.keys(labels).length > 0 ? labels : undefined;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function toResourceRef(
|
|
157
|
+
ref: ApiResourceReference | undefined,
|
|
158
|
+
): ResourceRef | undefined {
|
|
159
|
+
// The builders treat a ref without org and slug as absent; mirror that so
|
|
160
|
+
// an empty message round-trips to "no reference".
|
|
161
|
+
if (!ref || (!ref.org && !ref.slug)) return undefined;
|
|
162
|
+
return {
|
|
163
|
+
org: ref.org,
|
|
164
|
+
slug: ref.slug,
|
|
165
|
+
...(ref.version && { version: ref.version }),
|
|
166
|
+
...(ref.kind && { kind: ref.kind }),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { IdentityAccount } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/api_pb";
|
|
2
|
+
import type { Organization } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/api_pb";
|
|
3
|
+
import type { IdentityAccountInput } from "./gen/identityaccount.js";
|
|
4
|
+
import type { OrganizationInput } from "./gen/organization.js";
|
|
5
|
+
/**
|
|
6
|
+
* Maps a loaded {@link Organization} to a complete {@link OrganizationInput}
|
|
7
|
+
* for `organization.update()`.
|
|
8
|
+
*
|
|
9
|
+
* Proto3 scalar defaults (empty string, `false`, enum `0`) are normalized to
|
|
10
|
+
* `undefined` — omitting them is wire-identical to sending the default, and
|
|
11
|
+
* it keeps the built proto in the builders' canonical shape.
|
|
12
|
+
*/
|
|
13
|
+
export declare function toOrganizationUpdateInput(org: Organization): OrganizationInput;
|
|
14
|
+
/**
|
|
15
|
+
* Maps a loaded {@link IdentityAccount} to a complete
|
|
16
|
+
* {@link IdentityAccountInput} for `identityAccount.update()`.
|
|
17
|
+
*
|
|
18
|
+
* The generated builder never sets `metadata.id`; the update pipeline
|
|
19
|
+
* locates the resource by org + slug instead (and authorizes against the
|
|
20
|
+
* loaded resource's id), so carrying `org` and `slug` here is what makes
|
|
21
|
+
* the update addressable.
|
|
22
|
+
*/
|
|
23
|
+
export declare function toIdentityAccountUpdateInput(account: IdentityAccount): IdentityAccountInput;
|
|
24
|
+
//# sourceMappingURL=update-input.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-input.d.ts","sourceRoot":"","sources":["../src/update-input.ts"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0DAA0D,CAAC;AAEhG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2DAA2D,CAAC;AAE9F,OAAO,KAAK,EACV,oBAAoB,EAErB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EACV,iBAAiB,EAElB,MAAM,uBAAuB,CAAC;AAc/B;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,YAAY,GAChB,iBAAiB,CAkBnB;AAeD;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,eAAe,GACvB,oBAAoB,CAoBtB"}
|
package/update-input.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// Complete update-input mappers.
|
|
2
|
+
//
|
|
3
|
+
// The platform's update RPCs are FULL-SPEC REPLACEMENTS: the request spec
|
|
4
|
+
// wholesale overwrites the stored spec (both editions; only a small
|
|
5
|
+
// server-side preserve list of immutable identifiers survives). A client
|
|
6
|
+
// that sends only the fields it edits therefore silently WIPES every other
|
|
7
|
+
// mutable spec field — a data-loss bug class, not a single bug
|
|
8
|
+
// (stigmer/stigmer#293 Phase 1 surfaced it when `spec.preferences` set via
|
|
9
|
+
// CLI was dropped by a console profile save).
|
|
10
|
+
//
|
|
11
|
+
// These mappers are the structural fix: they convert a LOADED resource into
|
|
12
|
+
// a complete `*Input` for the generated update builders. Editors spread the
|
|
13
|
+
// mapper output and override only the fields they edit:
|
|
14
|
+
//
|
|
15
|
+
// await update({ ...toOrganizationUpdateInput(org), description: next });
|
|
16
|
+
//
|
|
17
|
+
// The `CompleteInput` mapped type removes optionality from every Input key,
|
|
18
|
+
// so when codegen adds a new Input field, compilation fails HERE — the one
|
|
19
|
+
// place a human must decide how the new field round-trips — instead of the
|
|
20
|
+
// field silently wiping in every subset-editing form.
|
|
21
|
+
//
|
|
22
|
+
// NESTED MESSAGE RULE: `CompleteInput` only sees TOP-LEVEL keys — a nested
|
|
23
|
+
// message (e.g. `preferences`) is one key whose object literal would keep
|
|
24
|
+
// compiling when codegen adds fields INSIDE it, recreating the wipe bug one
|
|
25
|
+
// level down. Every nested-message literal in this file must therefore be
|
|
26
|
+
// built by a dedicated `toXxxInput` helper whose return literal is itself
|
|
27
|
+
// typed `CompleteInput<XxxInput>`. Editors that override a nested message
|
|
28
|
+
// must spread the mapper's complete value and override only their fields:
|
|
29
|
+
//
|
|
30
|
+
// preferences: { ...mapped.preferences, standingContext: next }
|
|
31
|
+
/**
|
|
32
|
+
* Maps a loaded {@link Organization} to a complete {@link OrganizationInput}
|
|
33
|
+
* for `organization.update()`.
|
|
34
|
+
*
|
|
35
|
+
* Proto3 scalar defaults (empty string, `false`, enum `0`) are normalized to
|
|
36
|
+
* `undefined` — omitting them is wire-identical to sending the default, and
|
|
37
|
+
* it keeps the built proto in the builders' canonical shape.
|
|
38
|
+
*/
|
|
39
|
+
export function toOrganizationUpdateInput(org) {
|
|
40
|
+
const input = {
|
|
41
|
+
name: org.metadata?.name ?? "",
|
|
42
|
+
slug: org.metadata?.slug || undefined,
|
|
43
|
+
org: org.metadata?.org || org.metadata?.slug || "",
|
|
44
|
+
labels: nonEmptyLabels(org.metadata?.labels),
|
|
45
|
+
visibility: org.metadata?.visibility || undefined,
|
|
46
|
+
description: org.spec?.description || undefined,
|
|
47
|
+
logoUrl: org.spec?.logoUrl || undefined,
|
|
48
|
+
managementMode: org.spec?.managementMode || undefined,
|
|
49
|
+
identityProviderRef: toResourceRef(org.spec?.identityProviderRef),
|
|
50
|
+
externalOrgId: org.spec?.externalOrgId || undefined,
|
|
51
|
+
isPersonal: org.spec?.isPersonal || undefined,
|
|
52
|
+
preferences: org.spec?.preferences
|
|
53
|
+
? toOrganizationPreferencesInput(org.spec.preferences)
|
|
54
|
+
: undefined,
|
|
55
|
+
};
|
|
56
|
+
return input;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Maps loaded {@link OrganizationPreferences} to a complete
|
|
60
|
+
* {@link OrganizationPreferencesInput} (nested-message rule above).
|
|
61
|
+
*/
|
|
62
|
+
function toOrganizationPreferencesInput(preferences) {
|
|
63
|
+
const input = {
|
|
64
|
+
standingContext: preferences.standingContext || undefined,
|
|
65
|
+
};
|
|
66
|
+
return input;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Maps a loaded {@link IdentityAccount} to a complete
|
|
70
|
+
* {@link IdentityAccountInput} for `identityAccount.update()`.
|
|
71
|
+
*
|
|
72
|
+
* The generated builder never sets `metadata.id`; the update pipeline
|
|
73
|
+
* locates the resource by org + slug instead (and authorizes against the
|
|
74
|
+
* loaded resource's id), so carrying `org` and `slug` here is what makes
|
|
75
|
+
* the update addressable.
|
|
76
|
+
*/
|
|
77
|
+
export function toIdentityAccountUpdateInput(account) {
|
|
78
|
+
const input = {
|
|
79
|
+
name: account.metadata?.name ?? "",
|
|
80
|
+
slug: account.metadata?.slug || undefined,
|
|
81
|
+
org: account.metadata?.org ?? "",
|
|
82
|
+
labels: nonEmptyLabels(account.metadata?.labels),
|
|
83
|
+
visibility: account.metadata?.visibility || undefined,
|
|
84
|
+
idpId: account.spec?.idpId ?? "",
|
|
85
|
+
email: account.spec?.email || undefined,
|
|
86
|
+
firstName: account.spec?.firstName || undefined,
|
|
87
|
+
lastName: account.spec?.lastName || undefined,
|
|
88
|
+
pictureUrl: account.spec?.pictureUrl || undefined,
|
|
89
|
+
isMachineAccount: account.spec?.isMachineAccount || undefined,
|
|
90
|
+
provisioningMode: account.spec?.provisioningMode || undefined,
|
|
91
|
+
identityProviderRef: toResourceRef(account.spec?.identityProviderRef),
|
|
92
|
+
preferences: account.spec?.preferences
|
|
93
|
+
? toIdentityAccountPreferencesInput(account.spec.preferences)
|
|
94
|
+
: undefined,
|
|
95
|
+
};
|
|
96
|
+
return input;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Maps loaded {@link IdentityAccountPreferences} to a complete
|
|
100
|
+
* {@link IdentityAccountPreferencesInput} (nested-message rule above).
|
|
101
|
+
*/
|
|
102
|
+
function toIdentityAccountPreferencesInput(preferences) {
|
|
103
|
+
const input = {
|
|
104
|
+
standingContext: preferences.standingContext || undefined,
|
|
105
|
+
defaultHarness: preferences.defaultHarness || undefined,
|
|
106
|
+
defaultNativeModel: preferences.defaultNativeModel || undefined,
|
|
107
|
+
defaultCursorModel: preferences.defaultCursorModel || undefined,
|
|
108
|
+
};
|
|
109
|
+
return input;
|
|
110
|
+
}
|
|
111
|
+
function nonEmptyLabels(labels) {
|
|
112
|
+
return labels && Object.keys(labels).length > 0 ? labels : undefined;
|
|
113
|
+
}
|
|
114
|
+
function toResourceRef(ref) {
|
|
115
|
+
// The builders treat a ref without org and slug as absent; mirror that so
|
|
116
|
+
// an empty message round-trips to "no reference".
|
|
117
|
+
if (!ref || (!ref.org && !ref.slug))
|
|
118
|
+
return undefined;
|
|
119
|
+
return {
|
|
120
|
+
org: ref.org,
|
|
121
|
+
slug: ref.slug,
|
|
122
|
+
...(ref.version && { version: ref.version }),
|
|
123
|
+
...(ref.kind && { kind: ref.kind }),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=update-input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-input.js","sourceRoot":"","sources":["../src/update-input.ts"],"names":[],"mappings":"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"}
|