@knpkv/alchemy-console 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +185 -2
  3. package/dist/api.d.ts +126 -0
  4. package/dist/api.d.ts.map +1 -0
  5. package/dist/api.js +62 -0
  6. package/dist/api.js.map +1 -0
  7. package/dist/client/bootstrap.js +2834 -0
  8. package/dist/discovery.d.ts +12 -0
  9. package/dist/discovery.d.ts.map +1 -0
  10. package/dist/discovery.js +39 -0
  11. package/dist/discovery.js.map +1 -0
  12. package/dist/index.d.ts +14 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +11 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/internal/files.d.ts +9 -0
  17. package/dist/internal/files.d.ts.map +1 -0
  18. package/dist/internal/files.js +34 -0
  19. package/dist/internal/files.js.map +1 -0
  20. package/dist/local-state.d.ts +8 -0
  21. package/dist/local-state.d.ts.map +1 -0
  22. package/dist/local-state.js +53 -0
  23. package/dist/local-state.js.map +1 -0
  24. package/dist/main.d.ts +3 -0
  25. package/dist/main.d.ts.map +1 -0
  26. package/dist/main.js +39 -0
  27. package/dist/main.js.map +1 -0
  28. package/dist/profiles.d.ts +24 -0
  29. package/dist/profiles.d.ts.map +1 -0
  30. package/dist/profiles.js +82 -0
  31. package/dist/profiles.js.map +1 -0
  32. package/dist/s3-live.d.ts +13 -0
  33. package/dist/s3-live.d.ts.map +1 -0
  34. package/dist/s3-live.js +112 -0
  35. package/dist/s3-live.js.map +1 -0
  36. package/dist/s3-reader.d.ts +46 -0
  37. package/dist/s3-reader.d.ts.map +1 -0
  38. package/dist/s3-reader.js +10 -0
  39. package/dist/s3-reader.js.map +1 -0
  40. package/dist/s3-state.d.ts +21 -0
  41. package/dist/s3-state.d.ts.map +1 -0
  42. package/dist/s3-state.js +129 -0
  43. package/dist/s3-state.js.map +1 -0
  44. package/dist/schema.d.ts +43 -0
  45. package/dist/schema.d.ts.map +1 -0
  46. package/dist/schema.js +27 -0
  47. package/dist/schema.js.map +1 -0
  48. package/dist/server/Bootstrap.d.ts +4 -0
  49. package/dist/server/Bootstrap.d.ts.map +1 -0
  50. package/dist/server/Bootstrap.js +12 -0
  51. package/dist/server/Bootstrap.js.map +1 -0
  52. package/dist/server/Catalog.d.ts +27 -0
  53. package/dist/server/Catalog.d.ts.map +1 -0
  54. package/dist/server/Catalog.js +83 -0
  55. package/dist/server/Catalog.js.map +1 -0
  56. package/dist/server/Config.d.ts +17 -0
  57. package/dist/server/Config.d.ts.map +1 -0
  58. package/dist/server/Config.js +23 -0
  59. package/dist/server/Config.js.map +1 -0
  60. package/dist/server/ControlSocket.d.ts +19 -0
  61. package/dist/server/ControlSocket.d.ts.map +1 -0
  62. package/dist/server/ControlSocket.js +147 -0
  63. package/dist/server/ControlSocket.js.map +1 -0
  64. package/dist/server/HttpApplication.d.ts +7 -0
  65. package/dist/server/HttpApplication.d.ts.map +1 -0
  66. package/dist/server/HttpApplication.js +46 -0
  67. package/dist/server/HttpApplication.js.map +1 -0
  68. package/dist/server/Launch.d.ts +7 -0
  69. package/dist/server/Launch.d.ts.map +1 -0
  70. package/dist/server/Launch.js +17 -0
  71. package/dist/server/Launch.js.map +1 -0
  72. package/dist/server/OwnerSession.d.ts +9 -0
  73. package/dist/server/OwnerSession.d.ts.map +1 -0
  74. package/dist/server/OwnerSession.js +27 -0
  75. package/dist/server/OwnerSession.js.map +1 -0
  76. package/dist/server/Server.d.ts +6 -0
  77. package/dist/server/Server.d.ts.map +1 -0
  78. package/dist/server/Server.js +22 -0
  79. package/dist/server/Server.js.map +1 -0
  80. package/dist/server/Sources.d.ts +12 -0
  81. package/dist/server/Sources.d.ts.map +1 -0
  82. package/dist/server/Sources.js +118 -0
  83. package/dist/server/Sources.js.map +1 -0
  84. package/dist/sso-credentials.d.ts +17 -0
  85. package/dist/sso-credentials.d.ts.map +1 -0
  86. package/dist/sso-credentials.js +43 -0
  87. package/dist/sso-credentials.js.map +1 -0
  88. package/dist/state.d.ts +15 -0
  89. package/dist/state.d.ts.map +1 -0
  90. package/dist/state.js +45 -0
  91. package/dist/state.js.map +1 -0
  92. package/dist/version.d.ts +5 -0
  93. package/dist/version.d.ts.map +1 -0
  94. package/dist/version.js +61 -0
  95. package/dist/version.js.map +1 -0
  96. package/package.json +60 -3
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 knpkv
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,186 @@
1
- # Temporary Holding Version
1
+ # @knpkv/alchemy-console
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Run `alchemy-console` to serve the read-only API on `127.0.0.1:3114` and open a
4
+ one-time login link. `alchemy-console login` behaves the same way: it asks an
5
+ existing server for a fresh link, or starts one in the foreground. `--no-open`
6
+ prints the link without opening a browser. CI, non-interactive stdout and
7
+ `BROWSER=none` also skip opening. `--port` selects a port from 1024 through 65535.
8
+ `alchemy-console serve` always prints only and runs until SIGINT or SIGTERM.
9
+ Closing a browser tab leaves the foreground server running.
10
+
11
+ The current browser page performs login, then navigates to JSON at `/api/stacks`.
12
+ Stack screens will follow the approved design references in a separate change.
13
+
14
+ Configuration is read at startup:
15
+
16
+ | Variable | Default | Boundary |
17
+ | ---------------------- | ---------------------------------------------- | ------------------------------------- |
18
+ | `ALCHEMY_CONSOLE_ROOT` | `Work/dev` under the user's home | Server-private project discovery path |
19
+ | `AWS_CONFIG_FILE` | `.aws/config` under the user's home | Server-private SSO configuration path |
20
+ | `ALCHEMY_CONSOLE_HOME` | `.cache/alchemy-console` under the user's home | Server-private control directory |
21
+
22
+ Login reuse is per port. A reused server keeps its startup configuration. The
23
+ control directory must be owned by the current user with mode `0700`; each
24
+ `serve-<port>.sock` is `0600`. Symlinks, foreign owners and pre-existing stale
25
+ sockets fail explicitly. For an abandoned socket, confirm its server has stopped
26
+ before removing that entry and restarting. The control channel currently requires
27
+ POSIX ownership and Unix sockets.
28
+
29
+ ## Authenticated API
30
+
31
+ The client-safe contracts are exported from `@knpkv/alchemy-console/api` and may be
32
+ used with Effect's `HttpApiClient`. Both endpoints require the process's owner
33
+ cookie, and all responses are private and uncacheable:
34
+
35
+ The cookie name includes the bound listener port, so sessions on different ports
36
+ can coexist in one browser. Both API boundaries select that listener's cookie.
37
+ Cookies still share the loopback host and travel across ports; the name separates
38
+ sessions and does not provide port confidentiality. The API middleware resolves
39
+ the name at runtime rather than declaring a fixed OpenAPI cookie scheme.
40
+
41
+ | Route | Result |
42
+ | --------------------- | ------------------------------------------------------ |
43
+ | `GET /api/stacks` | Stack summaries and explicit local/S3 discovery issues |
44
+ | `GET /api/stacks/:id` | Current normalized resource state for a listed stack |
45
+
46
+ Listing runs discovery on demand, without polling or schedules. IDs are random,
47
+ process-scoped identifiers with no encoded locator. They remain stable across
48
+ scans while a stack remains present, then expire when it disappears or the server
49
+ restarts. They are normalized, authenticated client-visible identifiers; the
50
+ server never persists them. Do not emit them or stack metadata through public
51
+ routes or telemetry. Browsers must not persist private provider coordinates.
52
+
53
+ Discovery reports sanitized reason codes for failed projects, SSO profiles and
54
+ buckets while continuing other sources. A discovered entrypoint with no state
55
+ cannot supply an app or stage and contributes no stack row. Remote resource
56
+ counts and Alchemy versions remain unknown until the corresponding state or
57
+ lockfile establishes them. Discovery is capped at 1000 projects/stacks, 100 SSO
58
+ profiles, 1000 candidate buckets per profile and two minutes per list request.
59
+
60
+ The one public authentication route, `POST /auth/bootstrap`, exchanges a code
61
+ from the URL fragment for an HttpOnly, SameSite=Strict owner cookie. Codes are
62
+ single-use and expire after 60 seconds; a fresh mint replaces an unspent code.
63
+ The page removes the fragment before exchanging it. Codes are minted and
64
+ announced only after the authenticated HTTP listener is ready. Credential URLs
65
+ go only to stdout and the browser opener, never logs or telemetry. Session tokens
66
+ remain in the server and HttpOnly cookie. Every unsafe API method is refused,
67
+ including apply and destroy. Reads reject foreign origins and cross-site browser
68
+ requests. Listener and request Host are restricted to the bound loopback address.
69
+
70
+ ## Data readers
71
+
72
+ The root export provides project discovery, lockfile version lookup, local
73
+ state metadata and SSO/S3 readers for server-side consumers.
74
+
75
+ `discoverProjects(directory)` scans an explicit root for `alchemy.run.ts` without
76
+ importing or executing it. It skips `.git`, `node_modules`, `.alchemy`, `.direnv`,
77
+ `.cache`, and `dist`, deduplicates directory aliases, and rejects symlinks outside
78
+ the root. Directory errors fail the scan rather than hiding inaccessible projects.
79
+
80
+ `readAlchemyVersion(projectDirectory, searchRoot)` reads the nearest pnpm or Bun
81
+ text lockfile within the root. pnpm resolves the project's exact workspace
82
+ importer; Bun reads the resolved `alchemy` package tuple. Missing lockfiles or an
83
+ unresolved Alchemy version return `null`. Invalid lockfiles fail explicitly.
84
+
85
+ `readLocalState(directory, alchemyVersion)` reads
86
+ `.alchemy/state/<app>/<stage>/<encoded-fqn>.json` for beta.74 and beta.77. Other
87
+ versions, including unknown versions, fail with `unsupported-version`. Resource
88
+ metadata includes FQN, logical ID, type, status, and namespace parent. Action rows
89
+ are validated and excluded. Stack output and temporary files are ignored. Missing
90
+ state returns `available: false`; an existing empty stage returns an empty resource
91
+ list with `available: true`. JSON names must match the stored FQN encoding.
92
+
93
+ ## SSO and S3
94
+
95
+ `discoverSsoProfiles(configFile)` reads the explicitly supplied AWS config file.
96
+ It resolves modern `sso_session` references and legacy inline SSO settings,
97
+ including the default profile. Static keys, credential processes, assumed-role
98
+ chains, and service sections are excluded. Missing config returns an empty list;
99
+ incomplete or conflicting SSO configuration fails with a safe tagged reason.
100
+ The executable will supply the local config path; this layer does not choose an
101
+ ambient profile or execute login commands.
102
+
103
+ `s3ReaderLayer(profile)` uses `ssoCredentialsLayer()` and an Effect `HttpClient`.
104
+ Credentials come exclusively from the selected SSO identity and cached local
105
+ session. There is no environment, static-key, instance-metadata, or process
106
+ credential fallback. The SDK may refresh an existing SSO session in its local
107
+ token cache. Credentials remain redacted in server memory and are used only for
108
+ request signing. The profile supplies the SSO portal region; listed bucket regions
109
+ route S3 requests. Object requests include the selected account as
110
+ `ExpectedBucketOwner`. S3 transport tracing is disabled so private request URLs
111
+ and signing headers cannot enter telemetry.
112
+
113
+ `discoverS3Buckets()` paginates the selected account's owned buckets and selects
114
+ names containing `alchemy-state`. A caller may supply another naming predicate
115
+ or explicit server-private bucket locators. Listing owned buckets cannot discover
116
+ every cross-account bucket that a role might be allowed to read. Denied access or
117
+ expired sessions fail explicitly; they do not produce an empty account catalog.
118
+ An API can report these failures per profile while continuing other profiles.
119
+
120
+ `discoverS3Stacks(bucket)` recognizes `<optional-prefix><app>/<stage>/<encoded-fqn>.json`
121
+ keys. `listS3Versions(bucket, prefix)` returns persisted object version coordinates
122
+ and deletion markers. Both S3 continuation markers are retained; repeated markers
123
+ fail. `readS3State(stack, alchemyVersion?)` reads each latest non-deleted resource
124
+ using its listed version ID. Stack outputs and temporary files are excluded.
125
+ `readS3Resource(stack, key, versionId)` reads one historical version after checking
126
+ that the key belongs to that stack. JSON names must match the resource FQN.
127
+ Latest deletion markers never resurrect an older resource. Null versions from
128
+ unversioned or suspended buckets are supported. Multiple resource reads are not
129
+ an atomic snapshot of a deployment.
130
+
131
+ Remote state alone does not prove the installed Alchemy version or last successful
132
+ deploy. Both remain `null` unless a matching lockfile supplies the version.
133
+ Known beta.74/beta.77 resource shapes are validated even when remote version is
134
+ unknown; an explicitly supplied unsupported version fails. The adapter imports
135
+ only `ListBuckets`, `ListObjectVersions`, and `GetObject`. It never builds Alchemy's
136
+ state-store layer, which can create or reconfigure buckets.
137
+
138
+ Read-only IAM access needs `s3:ListAllMyBuckets`, `s3:ListBucketVersions`, and
139
+ `s3:GetObjectVersion` for version-pinned reads. No write policy is required. See the AWS
140
+ [version listing](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectVersions.html)
141
+ and [object reading](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html)
142
+ references. No live AWS or Alchemy stack is used by the tests.
143
+
144
+ The flat resource list retains namespace parents, including parents without their
145
+ own resource row. A consumer can group these into a tree. Provider IDs, account,
146
+ region, console links, and last deploy are currently `null`. Local file timestamps
147
+ do not prove a successful deployment. Readers do not invent missing metadata.
148
+
149
+ ## Private data and redaction
150
+
151
+ Discovered directories and entrypoints are server-private filesystem locators.
152
+ Keep them in server memory; do not persist them to browser storage or emit them
153
+ through HTTP, logs, telemetry, or public error messages. The normalized stack and
154
+ resource summaries may cross an authenticated owner API boundary. They must never
155
+ appear on unauthenticated routes or public telemetry.
156
+
157
+ Persisted Alchemy state can contain plaintext secret payloads inside
158
+ `__redacted__` marker objects. This package never invokes Alchemy's reviver or
159
+ decrypts anything. It discards props, attrs, old state, action inputs, and outputs.
160
+ Marker objects in metadata become `<redacted>`, including sibling fields. Parsing
161
+ errors include only a tagged reason, never raw contents or parser diagnostics.
162
+ Reads are capped at 8 MiB per file and directory listings at 50,000 entries.
163
+
164
+ SSO profiles retain server-private names, config paths, account/role selectors,
165
+ session names, start URLs, and routing regions. S3 bucket names, state prefixes,
166
+ object keys, version IDs, and pagination markers are also server-private. Their
167
+ persisted representation is AWS config or S3 object coordinates; console code
168
+ holds them in server memory only. They must not cross HTTP, browser storage,
169
+ logs, telemetry, or public errors. Only normalized `StackState` and
170
+ `ResourceSummary` fields may cross the authenticated owner API. Credential-bearing
171
+ `SsoCredentials` results remain solely inside the signing boundary. S3 reads have
172
+ the same 8 MiB payload cap and a 50,000-entry / 100-page listing cap.
173
+
174
+ Cloudflare profiles will also remain server-side. The UI will never apply or
175
+ destroy. Plan diffs remain on demand, with no scheduled execution.
176
+
177
+ ## Format references
178
+
179
+ Synthetic test states follow the pinned upstream
180
+ [resource format](https://github.com/alchemy-run/alchemy/blob/v2.0.0-beta.77/packages/alchemy/src/State/ResourceState.ts),
181
+ [local layout](https://github.com/alchemy-run/alchemy/blob/v2.0.0-beta.77/packages/alchemy/src/State/LocalState.ts),
182
+ and [state encoding](https://github.com/alchemy-run/alchemy/blob/v2.0.0-beta.77/packages/alchemy/src/State/StateEncoding.ts).
183
+ No real stack, account, bucket, credential, or project path is included in fixtures.
184
+
185
+ Run `pnpm --filter @knpkv/alchemy-console test` for the focused fixture tests.
186
+ Build workspace dependencies before the package's `check` script.
package/dist/api.d.ts ADDED
@@ -0,0 +1,126 @@
1
+ /** Client-safe contracts. IDs and metadata cross only the authenticated owner API, never public telemetry. */
2
+ import { Schema } from "effect";
3
+ import { HttpApi, HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware } from "effect/http-api";
4
+ /** Random, process-scoped identity; contains no encoded provider locator. Not persisted by the server. */
5
+ export declare const StackId: Schema.String;
6
+ export type StackId = typeof StackId.Type;
7
+ export declare const SourceFailureReason: Schema.Literals<readonly ["io", "invalid-state", "invalid-lockfile", "unsupported-version", "limit", "outside-root", "invalid-config", "authentication", "denied", "not-found", "provider", "invalid-response", "invalid-source"]>;
8
+ export declare const DiscoveryIssue: Schema.Struct<{
9
+ readonly source: Schema.Literals<readonly ["local", "s3"]>;
10
+ readonly reason: Schema.Literals<readonly ["io", "invalid-state", "invalid-lockfile", "unsupported-version", "limit", "outside-root", "invalid-config", "authentication", "denied", "not-found", "provider", "invalid-response", "invalid-source"]>;
11
+ }>;
12
+ export type DiscoveryIssue = typeof DiscoveryIssue.Type;
13
+ export declare const StackSummary: Schema.Struct<{
14
+ readonly id: Schema.String;
15
+ readonly app: Schema.NonEmptyString;
16
+ readonly stage: Schema.NonEmptyString;
17
+ readonly backend: Schema.Literals<readonly ["local", "s3"]>;
18
+ readonly alchemyVersion: Schema.NullOr<Schema.String>;
19
+ readonly lastDeploy: Schema.NullOr<Schema.String>;
20
+ readonly resourceCount: Schema.NullOr<Schema.Int>;
21
+ }>;
22
+ export type StackSummary = typeof StackSummary.Type;
23
+ export declare const StackList: Schema.Struct<{
24
+ readonly stacks: Schema.$Array<Schema.Struct<{
25
+ readonly id: Schema.String;
26
+ readonly app: Schema.NonEmptyString;
27
+ readonly stage: Schema.NonEmptyString;
28
+ readonly backend: Schema.Literals<readonly ["local", "s3"]>;
29
+ readonly alchemyVersion: Schema.NullOr<Schema.String>;
30
+ readonly lastDeploy: Schema.NullOr<Schema.String>;
31
+ readonly resourceCount: Schema.NullOr<Schema.Int>;
32
+ }>>;
33
+ readonly issues: Schema.$Array<Schema.Struct<{
34
+ readonly source: Schema.Literals<readonly ["local", "s3"]>;
35
+ readonly reason: Schema.Literals<readonly ["io", "invalid-state", "invalid-lockfile", "unsupported-version", "limit", "outside-root", "invalid-config", "authentication", "denied", "not-found", "provider", "invalid-response", "invalid-source"]>;
36
+ }>>;
37
+ }>;
38
+ export type StackList = typeof StackList.Type;
39
+ export declare const StackDetail: Schema.Struct<{
40
+ readonly id: Schema.String;
41
+ readonly state: Schema.Struct<{
42
+ readonly app: Schema.NonEmptyString;
43
+ readonly stage: Schema.NonEmptyString;
44
+ readonly backend: Schema.Literals<readonly ["local", "s3"]>;
45
+ readonly alchemyVersion: Schema.NullOr<Schema.String>;
46
+ readonly lastDeploy: Schema.NullOr<Schema.String>;
47
+ readonly resources: Schema.$Array<Schema.Struct<{
48
+ readonly fqn: Schema.NonEmptyString;
49
+ readonly logicalId: Schema.NonEmptyString;
50
+ readonly resourceType: Schema.NonEmptyString;
51
+ readonly status: Schema.Literals<readonly ["creating", "created", "updating", "updated", "deleting", "replacing", "replaced"]>;
52
+ readonly parent: Schema.NullOr<Schema.String>;
53
+ readonly providerId: Schema.NullOr<Schema.String>;
54
+ readonly account: Schema.NullOr<Schema.String>;
55
+ readonly region: Schema.NullOr<Schema.String>;
56
+ readonly consoleUrl: Schema.NullOr<Schema.String>;
57
+ }>>;
58
+ }>;
59
+ }>;
60
+ export type StackDetail = typeof StackDetail.Type;
61
+ declare const CatalogReadError_base: Schema.Class<CatalogReadError, Schema.TaggedStruct<"CatalogReadError", {
62
+ readonly reason: Schema.Literals<readonly ["io", "invalid-state", "invalid-lockfile", "unsupported-version", "limit", "outside-root", "invalid-config", "authentication", "denied", "not-found", "provider", "invalid-response", "invalid-source"]>;
63
+ }>, import("effect/Cause").YieldableError>;
64
+ export declare class CatalogReadError extends CatalogReadError_base {
65
+ }
66
+ declare const StackNotFound_base: Schema.Class<StackNotFound, Schema.TaggedStruct<"StackNotFound", {}>, import("effect/Cause").YieldableError>;
67
+ export declare class StackNotFound extends StackNotFound_base {
68
+ }
69
+ declare const Unauthorized_base: Schema.Class<Unauthorized, Schema.TaggedStruct<"Unauthorized", {}>, import("effect/Cause").YieldableError>;
70
+ export declare class Unauthorized extends Unauthorized_base {
71
+ }
72
+ declare const Forbidden_base: Schema.Class<Forbidden, Schema.TaggedStruct<"Forbidden", {}>, import("effect/Cause").YieldableError>;
73
+ export declare class Forbidden extends Forbidden_base {
74
+ }
75
+ declare const OwnerAuth_base: HttpApiMiddleware.ServiceClass<OwnerAuth, "@knpkv/alchemy-console/OwnerAuth", {
76
+ requires: never;
77
+ provides: never;
78
+ error: readonly [typeof Unauthorized, typeof Forbidden];
79
+ clientError: never;
80
+ requiredForClient: false;
81
+ security: never;
82
+ }, HttpApiMiddleware.HttpApiMiddleware<never, readonly [typeof Unauthorized, typeof Forbidden], never>>;
83
+ /** The server resolves its listener-specific cookie name at request time. */
84
+ export declare class OwnerAuth extends OwnerAuth_base {
85
+ }
86
+ declare const AlchemyConsoleApi_base: HttpApi.HttpApi<"AlchemyConsoleApi", HttpApiGroup.HttpApiGroup<"stacks", HttpApiEndpoint.HttpApiEndpoint<"list", "GET", "/api/stacks", never, never, never, never, Schema.toCodecJson<Schema.Struct<{
87
+ readonly stacks: Schema.$Array<Schema.Struct<{
88
+ readonly id: Schema.String;
89
+ readonly app: Schema.NonEmptyString;
90
+ readonly stage: Schema.NonEmptyString;
91
+ readonly backend: Schema.Literals<readonly ["local", "s3"]>;
92
+ readonly alchemyVersion: Schema.NullOr<Schema.String>;
93
+ readonly lastDeploy: Schema.NullOr<Schema.String>;
94
+ readonly resourceCount: Schema.NullOr<Schema.Int>;
95
+ }>>;
96
+ readonly issues: Schema.$Array<Schema.Struct<{
97
+ readonly source: Schema.Literals<readonly ["local", "s3"]>;
98
+ readonly reason: Schema.Literals<readonly ["io", "invalid-state", "invalid-lockfile", "unsupported-version", "limit", "outside-root", "invalid-config", "authentication", "denied", "not-found", "provider", "invalid-response", "invalid-source"]>;
99
+ }>>;
100
+ }>>, Schema.toCodecJson<typeof CatalogReadError>, OwnerAuth, never> | HttpApiEndpoint.HttpApiEndpoint<"get", "GET", "/api/stacks/:id", Schema.toCodecStringTree<Schema.Struct<{
101
+ readonly id: Schema.String;
102
+ }>>, never, never, never, Schema.toCodecJson<Schema.Struct<{
103
+ readonly id: Schema.String;
104
+ readonly state: Schema.Struct<{
105
+ readonly app: Schema.NonEmptyString;
106
+ readonly stage: Schema.NonEmptyString;
107
+ readonly backend: Schema.Literals<readonly ["local", "s3"]>;
108
+ readonly alchemyVersion: Schema.NullOr<Schema.String>;
109
+ readonly lastDeploy: Schema.NullOr<Schema.String>;
110
+ readonly resources: Schema.$Array<Schema.Struct<{
111
+ readonly fqn: Schema.NonEmptyString;
112
+ readonly logicalId: Schema.NonEmptyString;
113
+ readonly resourceType: Schema.NonEmptyString;
114
+ readonly status: Schema.Literals<readonly ["creating", "created", "updating", "updated", "deleting", "replacing", "replaced"]>;
115
+ readonly parent: Schema.NullOr<Schema.String>;
116
+ readonly providerId: Schema.NullOr<Schema.String>;
117
+ readonly account: Schema.NullOr<Schema.String>;
118
+ readonly region: Schema.NullOr<Schema.String>;
119
+ readonly consoleUrl: Schema.NullOr<Schema.String>;
120
+ }>>;
121
+ }>;
122
+ }>>, Schema.toCodecJson<typeof CatalogReadError | typeof StackNotFound>, OwnerAuth, never>, false>>;
123
+ export declare class AlchemyConsoleApi extends AlchemyConsoleApi_base {
124
+ }
125
+ export {};
126
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,8GAA8G;AAC9G,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC/B,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAG3F,0GAA0G;AAC1G,eAAO,MAAM,OAAO,eAAuC,CAAA;AAC3D,MAAM,MAAM,OAAO,GAAG,OAAO,OAAO,CAAC,IAAI,CAAA;AAEzC,eAAO,MAAM,mBAAmB,oOAc9B,CAAA;AAEF,eAAO,MAAM,cAAc;;;EAGzB,CAAA;AACF,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,IAAI,CAAA;AAEvD,eAAO,MAAM,YAAY;;;;;;;;EAQvB,CAAA;AACF,MAAM,MAAM,YAAY,GAAG,OAAO,YAAY,CAAC,IAAI,CAAA;AAEnD,eAAO,MAAM,SAAS;;;;;;;;;;;;;;EAA8F,CAAA;AACpH,MAAM,MAAM,SAAS,GAAG,OAAO,SAAS,CAAC,IAAI,CAAA;AAC7C,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;EAAoD,CAAA;AAC5E,MAAM,MAAM,WAAW,GAAG,OAAO,WAAW,CAAC,IAAI,CAAA;;;;AAEjD,qBAAa,gBAAiB,SAAQ,qBAEZ;CAAG;;AAE7B,qBAAa,aAAc,SAAQ,kBAAgF;CAAG;;AAEtH,qBAAa,YAAa,SAAQ,iBAA8E;CAAG;;AAEnH,qBAAa,SAAU,SAAQ,cAAwE;CAAG;;;;;;;;;AAE1G,6EAA6E;AAC7E,qBAAa,SAAU,SAAQ,cAE7B;CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAWL,qBAAa,iBAAkB,SAAQ,sBAAmE;CAAG"}
package/dist/api.js ADDED
@@ -0,0 +1,62 @@
1
+ /** Client-safe contracts. IDs and metadata cross only the authenticated owner API, never public telemetry. */
2
+ import { Schema } from "effect";
3
+ import { HttpApi, HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware } from "effect/http-api";
4
+ import { StackState } from "./schema.js";
5
+ /** Random, process-scoped identity; contains no encoded provider locator. Not persisted by the server. */
6
+ export const StackId = Schema.String.check(Schema.isUUID());
7
+ export const SourceFailureReason = Schema.Literals([
8
+ "io",
9
+ "invalid-state",
10
+ "invalid-lockfile",
11
+ "unsupported-version",
12
+ "limit",
13
+ "outside-root",
14
+ "invalid-config",
15
+ "authentication",
16
+ "denied",
17
+ "not-found",
18
+ "provider",
19
+ "invalid-response",
20
+ "invalid-source"
21
+ ]);
22
+ export const DiscoveryIssue = Schema.Struct({
23
+ source: Schema.Literals(["local", "s3"]),
24
+ reason: SourceFailureReason
25
+ });
26
+ export const StackSummary = Schema.Struct({
27
+ id: StackId,
28
+ app: Schema.NonEmptyString,
29
+ stage: Schema.NonEmptyString,
30
+ backend: Schema.Literals(["local", "s3"]),
31
+ alchemyVersion: Schema.NullOr(Schema.String),
32
+ lastDeploy: Schema.NullOr(Schema.String),
33
+ resourceCount: Schema.NullOr(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)))
34
+ });
35
+ export const StackList = Schema.Struct({ stacks: Schema.Array(StackSummary), issues: Schema.Array(DiscoveryIssue) });
36
+ export const StackDetail = Schema.Struct({ id: StackId, state: StackState });
37
+ export class CatalogReadError extends Schema.TaggedError()("CatalogReadError", {
38
+ reason: SourceFailureReason
39
+ }, { httpApiStatus: 503 }) {
40
+ }
41
+ export class StackNotFound extends Schema.TaggedError()("StackNotFound", {}, { httpApiStatus: 404 }) {
42
+ }
43
+ export class Unauthorized extends Schema.TaggedError()("Unauthorized", {}, { httpApiStatus: 401 }) {
44
+ }
45
+ export class Forbidden extends Schema.TaggedError()("Forbidden", {}, { httpApiStatus: 403 }) {
46
+ }
47
+ /** The server resolves its listener-specific cookie name at request time. */
48
+ export class OwnerAuth extends HttpApiMiddleware.Service()("@knpkv/alchemy-console/OwnerAuth", {
49
+ error: [Unauthorized, Forbidden]
50
+ }) {
51
+ }
52
+ const stacks = HttpApiGroup.make("stacks")
53
+ .add(HttpApiEndpoint.get("list", "/stacks", { success: StackList, error: CatalogReadError }))
54
+ .add(HttpApiEndpoint.get("get", "/stacks/:id", {
55
+ params: Schema.Struct({ id: StackId }),
56
+ success: StackDetail,
57
+ error: [StackNotFound, CatalogReadError]
58
+ }))
59
+ .prefix("/api");
60
+ export class AlchemyConsoleApi extends HttpApi.make("AlchemyConsoleApi").add(stacks).middleware(OwnerAuth) {
61
+ }
62
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}