@homeflare/alchemy 0.29.0 → 0.30.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.
- package/README.md +20 -0
- package/dist/caddy/config-reconcile.d.ts.map +1 -1
- package/dist/caddy/config.d.ts.map +1 -1
- package/dist/caddy/format-warnings.d.ts +29 -0
- package/dist/caddy/format-warnings.d.ts.map +1 -0
- package/dist/caddy/format.d.ts +50 -0
- package/dist/caddy/format.d.ts.map +1 -0
- package/dist/caddy/index.d.ts +1 -0
- package/dist/caddy/index.d.ts.map +1 -1
- package/dist/caddy/index.js +78 -10
- package/dist/caddy/index.js.map +7 -5
- package/dist/github/declare-repo-baseline.d.ts.map +1 -1
- package/dist/github/index.js +21 -2
- package/dist/github/index.js.map +6 -5
- package/dist/github/repo-apply-order-fake-providers.d.ts +48 -0
- package/dist/github/repo-apply-order-fake-providers.d.ts.map +1 -0
- package/dist/github/repo-apply-order-fake-stack.d.ts +13 -0
- package/dist/github/repo-apply-order-fake-stack.d.ts.map +1 -0
- package/dist/github/repo-auto-merge-gate.d.ts +54 -0
- package/dist/github/repo-auto-merge-gate.d.ts.map +1 -0
- package/dist/github/repo-policy.d.ts.map +1 -1
- package/dist/google-workspace/credentials.d.ts +54 -0
- package/dist/google-workspace/credentials.d.ts.map +1 -0
- package/dist/google-workspace/domain-alias.d.ts +74 -0
- package/dist/google-workspace/domain-alias.d.ts.map +1 -0
- package/dist/google-workspace/fake-google-workspace.d.ts +41 -0
- package/dist/google-workspace/fake-google-workspace.d.ts.map +1 -0
- package/dist/google-workspace/group-member.d.ts +73 -0
- package/dist/google-workspace/group-member.d.ts.map +1 -0
- package/dist/google-workspace/group.d.ts +87 -0
- package/dist/google-workspace/group.d.ts.map +1 -0
- package/dist/google-workspace/index.d.ts +19 -0
- package/dist/google-workspace/index.d.ts.map +1 -0
- package/dist/google-workspace/index.js +283 -0
- package/dist/google-workspace/index.js.map +17 -0
- package/dist/google-workspace/org-unit.d.ts +75 -0
- package/dist/google-workspace/org-unit.d.ts.map +1 -0
- package/dist/google-workspace/providers.d.ts +16 -0
- package/dist/google-workspace/providers.d.ts.map +1 -0
- package/dist/google-workspace/resource.d.ts +80 -0
- package/dist/google-workspace/resource.d.ts.map +1 -0
- package/dist/google-workspace/values.d.ts +28 -0
- package/dist/google-workspace/values.d.ts.map +1 -0
- package/dist/grafana/credentials.d.ts +38 -0
- package/dist/grafana/credentials.d.ts.map +1 -0
- package/dist/grafana/datasource.d.ts +90 -0
- package/dist/grafana/datasource.d.ts.map +1 -0
- package/dist/grafana/fake-grafana.d.ts +48 -0
- package/dist/grafana/fake-grafana.d.ts.map +1 -0
- package/dist/grafana/index.d.ts +17 -0
- package/dist/grafana/index.d.ts.map +1 -0
- package/dist/grafana/index.js +149 -0
- package/dist/grafana/index.js.map +13 -0
- package/dist/grafana/providers.d.ts +17 -0
- package/dist/grafana/providers.d.ts.map +1 -0
- package/dist/grafana/resource.d.ts +68 -0
- package/dist/grafana/resource.d.ts.map +1 -0
- package/docs/caddy-fmt.md +59 -0
- package/docs/caddy.md +7 -0
- package/docs/credentials.md +11 -0
- package/docs/google-workspace.md +105 -0
- package/docs/grafana.md +102 -0
- package/docs/peers.md +9 -0
- package/docs/upstream-conformance.md +37 -0
- package/package.json +9 -1
- package/src/caddy/config-format-warning.test.ts +91 -0
- package/src/caddy/config-reconcile.ts +6 -0
- package/src/caddy/config.ts +8 -1
- package/src/caddy/format-warnings.test.ts +50 -0
- package/src/caddy/format-warnings.ts +39 -0
- package/src/caddy/format.test.ts +105 -0
- package/src/caddy/format.ts +121 -0
- package/src/caddy/index.ts +7 -0
- package/src/caddy/providers.test.ts +8 -0
- package/src/github/declare-repo-baseline-apply-order.test.ts +86 -0
- package/src/github/declare-repo-baseline.ts +34 -1
- package/src/github/repo-apply-order-fake-providers.ts +119 -0
- package/src/github/repo-apply-order-fake-stack.ts +74 -0
- package/src/github/repo-auto-merge-gate.ts +57 -0
- package/src/github/repo-policy-apply-order.test.ts +72 -0
- package/src/github/repo-policy.ts +28 -1
- package/src/google-workspace/credentials.test.ts +40 -0
- package/src/google-workspace/credentials.ts +75 -0
- package/src/google-workspace/domain-alias.test.ts +69 -0
- package/src/google-workspace/domain-alias.ts +116 -0
- package/src/google-workspace/fake-google-workspace.ts +54 -0
- package/src/google-workspace/group-member.test.ts +91 -0
- package/src/google-workspace/group-member.ts +126 -0
- package/src/google-workspace/group.test.ts +93 -0
- package/src/google-workspace/group.ts +128 -0
- package/src/google-workspace/index.ts +54 -0
- package/src/google-workspace/org-unit.test.ts +81 -0
- package/src/google-workspace/org-unit.ts +143 -0
- package/src/google-workspace/providers.ts +26 -0
- package/src/google-workspace/resource.test.ts +186 -0
- package/src/google-workspace/resource.ts +162 -0
- package/src/google-workspace/values.ts +36 -0
- package/src/grafana/credentials.ts +65 -0
- package/src/grafana/datasource.test.ts +183 -0
- package/src/grafana/datasource.ts +171 -0
- package/src/grafana/fake-grafana.ts +64 -0
- package/src/grafana/index.ts +22 -0
- package/src/grafana/providers.ts +19 -0
- package/src/grafana/resource.ts +120 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `GoogleWorkspace.DomainAlias` — one secondary-domain alias on the Workspace account
|
|
3
|
+
* (`admin/directory/v1/customer/{customer}/domainaliases`).
|
|
4
|
+
*
|
|
5
|
+
* ⛔ NO `update` OPERATION EXISTS. The Directory API ships `get`/`insert`/`delete`/`list` for
|
|
6
|
+
* domain aliases and nothing else — measured against `admin_directory_v1.ts`: there is no
|
|
7
|
+
* `patchDomainAliases` or `updateDomainAliases`. `spec.update` is intentionally absent, so
|
|
8
|
+
* `resource.ts`'s `diff` reports `replace` for any drift rather than an unsendable PATCH — the
|
|
9
|
+
* same fallback NetBox's `netboxOperations` and Forgejo's `forgejoOperations` give a spec with
|
|
10
|
+
* no `update`.
|
|
11
|
+
*
|
|
12
|
+
* ★ `domainAliasName` IS THE WHOLE IDENTITY. `parentDomainName` only matters at create time —
|
|
13
|
+
* Google refuses reparenting an alias in place, so a declared `parentDomainName` that no longer
|
|
14
|
+
* matches the live row is exactly the identity change `replace` exists for.
|
|
15
|
+
*
|
|
16
|
+
* ⚠️ THE HOUSE'S CLOUDFLARE ZONES (`homeflare.dev`, `aimto.app`, `schenanigans.com`, `taslabs.net`
|
|
17
|
+
* — `site/live.site.json` in homeflare-landscape) are DNS zones, not Workspace domains by
|
|
18
|
+
* themselves; a zone only becomes a valid `parentDomainName` once it is added and verified as a
|
|
19
|
+
* primary or secondary domain in the Workspace admin console. This family does not verify a
|
|
20
|
+
* domain — Google's verification is a TXT/CNAME challenge outside the Directory API's write
|
|
21
|
+
* surface — so `create` will fail with `BadRequest` against an unverified domain, which is
|
|
22
|
+
* correct: nothing here should silently paper over a step the account owner has to do by hand.
|
|
23
|
+
*/
|
|
24
|
+
import { Resource } from 'alchemy';
|
|
25
|
+
import * as Provider from 'alchemy/Provider';
|
|
26
|
+
import * as directory from '@distilled.cloud/google-workspace/unstable/admin_directory_v1';
|
|
27
|
+
import * as Effect from 'effect/Effect';
|
|
28
|
+
import type { GoogleWorkspaceRequirements, GoogleWorkspaceSpec } from './resource.ts';
|
|
29
|
+
export interface DomainAliasProps {
|
|
30
|
+
/** The unique id for the Workspace account, or `my_customer` for the caller's own account. */
|
|
31
|
+
customer?: string;
|
|
32
|
+
/** The alias domain name — primary key for locate. */
|
|
33
|
+
domainAliasName: string;
|
|
34
|
+
/** The primary or secondary domain this alias resolves to. Immutable — a change replaces. */
|
|
35
|
+
parentDomainName: string;
|
|
36
|
+
}
|
|
37
|
+
export interface DomainAliasAttributes {
|
|
38
|
+
customer: string;
|
|
39
|
+
domainAliasName: string;
|
|
40
|
+
parentDomainName: string;
|
|
41
|
+
/** Read-only. */
|
|
42
|
+
verified: boolean;
|
|
43
|
+
/** Read-only, ISO-ish epoch-ms string as Google returns it. */
|
|
44
|
+
creationTime: string;
|
|
45
|
+
}
|
|
46
|
+
export interface GoogleWorkspaceDomainAlias extends Resource<'GoogleWorkspace.DomainAlias', DomainAliasProps, DomainAliasAttributes, never, GoogleWorkspaceRequirements> {
|
|
47
|
+
}
|
|
48
|
+
export declare const GoogleWorkspaceDomainAlias: import("alchemy").ResourceClass<GoogleWorkspaceDomainAlias>;
|
|
49
|
+
/** ★ EXPORTED for direct testing — see domain-alias.test.ts. */
|
|
50
|
+
export declare const spec: GoogleWorkspaceSpec<DomainAliasProps, directory.DomainAlias, DomainAliasAttributes, directory.GetDomainAliasesError | directory.InsertDomainAliasesError | directory.DeleteDomainAliasesError>;
|
|
51
|
+
export declare const handlers: {
|
|
52
|
+
list: () => Effect.Effect<never[], never, never>;
|
|
53
|
+
read: ({ olds }: {
|
|
54
|
+
olds: DomainAliasProps;
|
|
55
|
+
}) => Effect.Effect<DomainAliasAttributes | undefined, import("@distilled.cloud/caddy").BadGateway | directory.BadRequest | directory.Conflict | directory.Forbidden | import("@distilled.cloud/caddy").GatewayTimeout | import("@distilled.cloud/google-workspace/Errors").GoogleWorkspaceParseError | import("effect/unstable/http/HttpClientError").HttpClientError | import("@distilled.cloud/caddy").InternalServerError | directory.NotFound | import("@distilled.cloud/caddy").ServiceUnavailable | import("@distilled.cloud/caddy").TooManyRequests | import("@distilled.cloud/caddy").Unauthorized | import("@distilled.cloud/google-workspace/Errors").UnknownGoogleWorkspaceError, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
56
|
+
diff: ({ news, output }: {
|
|
57
|
+
news: import("alchemy").Input<DomainAliasProps>;
|
|
58
|
+
output: DomainAliasAttributes | undefined;
|
|
59
|
+
}) => Effect.Effect<{
|
|
60
|
+
readonly action: 'update';
|
|
61
|
+
} | {
|
|
62
|
+
readonly action: 'noop';
|
|
63
|
+
} | {
|
|
64
|
+
readonly action: 'replace';
|
|
65
|
+
} | undefined, import("@distilled.cloud/caddy").BadGateway | directory.BadRequest | directory.Conflict | directory.Forbidden | import("@distilled.cloud/caddy").GatewayTimeout | import("@distilled.cloud/google-workspace/Errors").GoogleWorkspaceParseError | import("effect/unstable/http/HttpClientError").HttpClientError | import("@distilled.cloud/caddy").InternalServerError | directory.NotFound | import("@distilled.cloud/caddy").ServiceUnavailable | import("@distilled.cloud/caddy").TooManyRequests | import("@distilled.cloud/caddy").Unauthorized | import("@distilled.cloud/google-workspace/Errors").UnknownGoogleWorkspaceError, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
66
|
+
reconcile: ({ news }: {
|
|
67
|
+
news: DomainAliasProps;
|
|
68
|
+
}) => Effect.Effect<DomainAliasAttributes, import("@distilled.cloud/caddy").BadGateway | directory.BadRequest | directory.Conflict | directory.Forbidden | import("@distilled.cloud/caddy").GatewayTimeout | import("@distilled.cloud/google-workspace/Errors").GoogleWorkspaceParseError | import("effect/unstable/http/HttpClientError").HttpClientError | import("@distilled.cloud/caddy").InternalServerError | directory.NotFound | import("@distilled.cloud/caddy").ServiceUnavailable | import("@distilled.cloud/caddy").TooManyRequests | import("@distilled.cloud/caddy").Unauthorized | import("@distilled.cloud/google-workspace/Errors").UnknownGoogleWorkspaceError, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
69
|
+
delete: ({ olds }: {
|
|
70
|
+
olds: DomainAliasProps;
|
|
71
|
+
}) => Effect.Effect<void, import("@distilled.cloud/caddy").BadGateway | directory.BadRequest | directory.Conflict | directory.Forbidden | import("@distilled.cloud/caddy").GatewayTimeout | import("@distilled.cloud/google-workspace/Errors").GoogleWorkspaceParseError | import("effect/unstable/http/HttpClientError").HttpClientError | import("@distilled.cloud/caddy").InternalServerError | directory.NotFound | import("@distilled.cloud/caddy").ServiceUnavailable | import("@distilled.cloud/caddy").TooManyRequests | import("@distilled.cloud/caddy").Unauthorized | import("@distilled.cloud/google-workspace/Errors").UnknownGoogleWorkspaceError, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
72
|
+
};
|
|
73
|
+
export declare const GoogleWorkspaceDomainAliasProvider: () => import("effect/Layer").Layer<Provider.Provider<GoogleWorkspaceDomainAlias>, never, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
74
|
+
//# sourceMappingURL=domain-alias.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domain-alias.d.ts","sourceRoot":"","sources":["../../src/google-workspace/domain-alias.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,SAAS,MAAM,+DAA+D,CAAC;AAC3F,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,EAAE,2BAA2B,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAItF,MAAM,WAAW,gBAAgB;IAC/B,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sDAAsD;IACtD,eAAe,EAAE,MAAM,CAAC;IACxB,6FAA6F;IAC7F,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,+DAA+D;IAC/D,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,0BAA2B,SAAQ,QAAQ,CAC1D,6BAA6B,EAC7B,gBAAgB,EAChB,qBAAqB,EACrB,KAAK,EACL,2BAA2B,CAC5B;CAAG;AAEJ,eAAO,MAAM,0BAA0B,6DAGtC,CAAC;AAoBF,gEAAgE;AAChE,eAAO,MAAM,IAAI,EAAE,mBAAmB,CACpC,gBAAgB,EAChB,SAAS,CAAC,WAAW,EACrB,qBAAqB,EACnB,SAAS,CAAC,qBAAqB,GAC/B,SAAS,CAAC,wBAAwB,GAClC,SAAS,CAAC,wBAAwB,CAmBrC,CAAC;AAEF,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;CAAgC,CAAC;AAEtD,eAAO,MAAM,kCAAkC,gJAI5C,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A minimal in-memory Directory API for this family's tests — an in-memory `fetch` handed to
|
|
3
|
+
* Effect's real `FetchHttpClient` through its `Fetch` reference, mirroring
|
|
4
|
+
* `../netbox/fake-netbox.ts` and `../forgejo/fake-forgejo.ts`. Tests exercise distilled's REAL
|
|
5
|
+
* path assembly, JSON encode/decode and status→typed-error matching (`404` → `NotFound`, `403` →
|
|
6
|
+
* `Forbidden`, …) — nothing about that matching is re-implemented here, only the wire responses a
|
|
7
|
+
* scenario needs.
|
|
8
|
+
*
|
|
9
|
+
* ⛔ TEST-ONLY, AND NO NETWORK. The token is a placeholder that is never a real bearer token, and
|
|
10
|
+
* no request this fake answers ever leaves the process.
|
|
11
|
+
*/
|
|
12
|
+
import { type Credentials } from '@distilled.cloud/google-workspace/Credentials';
|
|
13
|
+
import * as Layer from 'effect/Layer';
|
|
14
|
+
export declare const FAKE_TOKEN = "placeholder-google-workspace-token";
|
|
15
|
+
export type Seen = {
|
|
16
|
+
readonly method: string;
|
|
17
|
+
readonly path: string;
|
|
18
|
+
};
|
|
19
|
+
/** Google's own `{ error: { code, message, status } }` failure envelope — matching protocol.ts. */
|
|
20
|
+
export declare const fakeFailure: (status: number, message: string, googleStatus: string) => Response;
|
|
21
|
+
/**
|
|
22
|
+
* `route` decides every response; this wrapper only records what was sent and adapts to
|
|
23
|
+
* `globalThis.fetch`'s signature. A test that expects no request at all asserts `seen` is empty.
|
|
24
|
+
*/
|
|
25
|
+
export declare const fakeGoogleWorkspace: (route: (method: string, url: URL) => Response) => {
|
|
26
|
+
fetch: typeof fetch;
|
|
27
|
+
seen: Seen[];
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* distilled Credentials + the real FetchHttpClient over the fake — `GoogleWorkspaceOpContext`,
|
|
31
|
+
* what every resource file's exported `spec.fetchLive` needs.
|
|
32
|
+
*
|
|
33
|
+
* ⚠️ NOT PAIRED WITH `handlers` (each resource file's `googleWorkspaceHandlers(spec)` export).
|
|
34
|
+
* `handlers` bakes in `CredentialsFromEnv`, which resolves `GOOGLE_ACCESS_TOKEN` through
|
|
35
|
+
* Effect's `Config` — whose default provider snapshots `process.env` once and never re-reads it
|
|
36
|
+
* (measured in the forgejo migration, kit PR 180). So a test cannot point `handlers` at a fake
|
|
37
|
+
* server; it calls `spec.fetchLive`/`spec.attributes` directly instead, with this explicit
|
|
38
|
+
* layer, exercising exactly the same production code without that env seam.
|
|
39
|
+
*/
|
|
40
|
+
export declare const fakeGoogleWorkspaceLayer: (fetchFn: typeof globalThis.fetch, creds?: Layer.Layer<Credentials>) => Layer.Layer<Credentials | import("effect/unstable/http/HttpClient").HttpClient, never, never>;
|
|
41
|
+
//# sourceMappingURL=fake-google-workspace.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fake-google-workspace.d.ts","sourceRoot":"","sources":["../../src/google-workspace/fake-google-workspace.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,KAAK,WAAW,EAAmB,MAAM,+CAA+C,CAAC;AAClG,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAGtC,eAAO,MAAM,UAAU,uCAAuC,CAAC;AAE/D,MAAM,MAAM,IAAI,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtE,mGAAmG;AACnG,eAAO,MAAM,WAAW,WAAY,MAAM,WAAW,MAAM,gBAAgB,MAAM,aACM,CAAC;AAExF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,UAAW,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,QAAQ;;;CAUhF,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,wBAAwB,YAC1B,OAAO,UAAU,CAAC,KAAK,UACzB,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,kGAC+D,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `GoogleWorkspace.GroupMember` — one user or group's membership in one Directory group.
|
|
3
|
+
*
|
|
4
|
+
* ★ WHY THIS DOES NOT MODEL A `GoogleWorkspace.User`. `email` here identifies whoever the group
|
|
5
|
+
* member is (a user's primary email, or another group's), the same way Forgejo's
|
|
6
|
+
* `TeamMember.username` (team-member.ts) never creates the account it references. The Directory
|
|
7
|
+
* API needs no user resource to exist in this family for a membership to be declared — see
|
|
8
|
+
* group.ts's own note on why users are out of scope.
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ UNLIKE `Forgejo.TeamMember`, A MEMBERSHIP HERE IS NOT EXISTENCE-ONLY. `role`
|
|
11
|
+
* (`OWNER`/`MANAGER`/`MEMBER`) and `deliverySettings` (`ALL_MAIL`/`DIGEST`/`DAILY`/`NONE`/
|
|
12
|
+
* `DISABLED`) are both mutable in place via `patchMembers` — so this spec declares `update`,
|
|
13
|
+
* not `upsert`, the same shape `group.ts`/`domain-alias.ts` use.
|
|
14
|
+
*
|
|
15
|
+
* ⛔ GROUP CYCLES ARE THE VENDOR'S TO REFUSE, NOT THIS FAMILY'S. `insertMembers`/`patchMembers`
|
|
16
|
+
* surface a cycle as `BadRequest`, which this spec's error union already carries un-narrowed —
|
|
17
|
+
* nothing here guesses at "member is itself an ancestor group".
|
|
18
|
+
*/
|
|
19
|
+
import { Resource } from 'alchemy';
|
|
20
|
+
import * as Provider from 'alchemy/Provider';
|
|
21
|
+
import * as directory from '@distilled.cloud/google-workspace/unstable/admin_directory_v1';
|
|
22
|
+
import * as Effect from 'effect/Effect';
|
|
23
|
+
import type { GoogleWorkspaceRequirements, GoogleWorkspaceSpec } from './resource.ts';
|
|
24
|
+
export type MemberRole = 'OWNER' | 'MANAGER' | 'MEMBER';
|
|
25
|
+
export type DeliverySettings = 'ALL_MAIL' | 'DIGEST' | 'DAILY' | 'NONE' | 'DISABLED';
|
|
26
|
+
export interface GroupMemberProps {
|
|
27
|
+
/** The group's email, alias or id — same key `Group.email` declares. */
|
|
28
|
+
group: string;
|
|
29
|
+
/** The member's email (a user or another group). Primary key alongside `group`. */
|
|
30
|
+
email: string;
|
|
31
|
+
/** @default 'MEMBER' */
|
|
32
|
+
role?: MemberRole;
|
|
33
|
+
/** @default 'ALL_MAIL' */
|
|
34
|
+
deliverySettings?: DeliverySettings;
|
|
35
|
+
}
|
|
36
|
+
export interface GroupMemberAttributes {
|
|
37
|
+
group: string;
|
|
38
|
+
email: string;
|
|
39
|
+
memberId: string;
|
|
40
|
+
role: string;
|
|
41
|
+
deliverySettings: string;
|
|
42
|
+
/** Read-only — `ACTIVE` for a normal member, `PENDING` while an invite is outstanding. */
|
|
43
|
+
status: string;
|
|
44
|
+
}
|
|
45
|
+
export interface GoogleWorkspaceGroupMember extends Resource<'GoogleWorkspace.GroupMember', GroupMemberProps, GroupMemberAttributes, never, GoogleWorkspaceRequirements> {
|
|
46
|
+
}
|
|
47
|
+
export declare const GoogleWorkspaceGroupMember: import("alchemy").ResourceClass<GoogleWorkspaceGroupMember>;
|
|
48
|
+
/** ★ EXPORTED for direct testing — see group-member.test.ts. */
|
|
49
|
+
export declare const spec: GoogleWorkspaceSpec<GroupMemberProps, directory.Member, GroupMemberAttributes, directory.GetMembersError | directory.InsertMembersError | directory.PatchMembersError | directory.DeleteMembersError>;
|
|
50
|
+
export declare const handlers: {
|
|
51
|
+
list: () => Effect.Effect<never[], never, never>;
|
|
52
|
+
read: ({ olds }: {
|
|
53
|
+
olds: GroupMemberProps;
|
|
54
|
+
}) => Effect.Effect<GroupMemberAttributes | undefined, import("@distilled.cloud/caddy").BadGateway | directory.BadRequest | directory.Conflict | directory.Forbidden | import("@distilled.cloud/caddy").GatewayTimeout | import("@distilled.cloud/google-workspace/Errors").GoogleWorkspaceParseError | import("effect/unstable/http/HttpClientError").HttpClientError | import("@distilled.cloud/caddy").InternalServerError | directory.NotFound | import("@distilled.cloud/caddy").ServiceUnavailable | import("@distilled.cloud/caddy").TooManyRequests | import("@distilled.cloud/caddy").Unauthorized | import("@distilled.cloud/google-workspace/Errors").UnknownGoogleWorkspaceError, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
55
|
+
diff: ({ news, output }: {
|
|
56
|
+
news: import("alchemy").Input<GroupMemberProps>;
|
|
57
|
+
output: GroupMemberAttributes | undefined;
|
|
58
|
+
}) => Effect.Effect<{
|
|
59
|
+
readonly action: 'update';
|
|
60
|
+
} | {
|
|
61
|
+
readonly action: 'noop';
|
|
62
|
+
} | {
|
|
63
|
+
readonly action: 'replace';
|
|
64
|
+
} | undefined, import("@distilled.cloud/caddy").BadGateway | directory.BadRequest | directory.Conflict | directory.Forbidden | import("@distilled.cloud/caddy").GatewayTimeout | import("@distilled.cloud/google-workspace/Errors").GoogleWorkspaceParseError | import("effect/unstable/http/HttpClientError").HttpClientError | import("@distilled.cloud/caddy").InternalServerError | directory.NotFound | import("@distilled.cloud/caddy").ServiceUnavailable | import("@distilled.cloud/caddy").TooManyRequests | import("@distilled.cloud/caddy").Unauthorized | import("@distilled.cloud/google-workspace/Errors").UnknownGoogleWorkspaceError, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
65
|
+
reconcile: ({ news }: {
|
|
66
|
+
news: GroupMemberProps;
|
|
67
|
+
}) => Effect.Effect<GroupMemberAttributes, import("@distilled.cloud/caddy").BadGateway | directory.BadRequest | directory.Conflict | directory.Forbidden | import("@distilled.cloud/caddy").GatewayTimeout | import("@distilled.cloud/google-workspace/Errors").GoogleWorkspaceParseError | import("effect/unstable/http/HttpClientError").HttpClientError | import("@distilled.cloud/caddy").InternalServerError | directory.NotFound | import("@distilled.cloud/caddy").ServiceUnavailable | import("@distilled.cloud/caddy").TooManyRequests | import("@distilled.cloud/caddy").Unauthorized | import("@distilled.cloud/google-workspace/Errors").UnknownGoogleWorkspaceError, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
68
|
+
delete: ({ olds }: {
|
|
69
|
+
olds: GroupMemberProps;
|
|
70
|
+
}) => Effect.Effect<void, import("@distilled.cloud/caddy").BadGateway | directory.BadRequest | directory.Conflict | directory.Forbidden | import("@distilled.cloud/caddy").GatewayTimeout | import("@distilled.cloud/google-workspace/Errors").GoogleWorkspaceParseError | import("effect/unstable/http/HttpClientError").HttpClientError | import("@distilled.cloud/caddy").InternalServerError | directory.NotFound | import("@distilled.cloud/caddy").ServiceUnavailable | import("@distilled.cloud/caddy").TooManyRequests | import("@distilled.cloud/caddy").Unauthorized | import("@distilled.cloud/google-workspace/Errors").UnknownGoogleWorkspaceError, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
71
|
+
};
|
|
72
|
+
export declare const GoogleWorkspaceGroupMemberProvider: () => import("effect/Layer").Layer<Provider.Provider<GoogleWorkspaceGroupMember>, never, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
73
|
+
//# sourceMappingURL=group-member.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"group-member.d.ts","sourceRoot":"","sources":["../../src/google-workspace/group-member.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,SAAS,MAAM,+DAA+D,CAAC;AAC3F,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,EAAE,2BAA2B,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAItF,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,CAAC;AACxD,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,UAAU,CAAC;AAErF,MAAM,WAAW,gBAAgB;IAC/B,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;IACd,mFAAmF;IACnF,KAAK,EAAE,MAAM,CAAC;IACd,wBAAwB;IACxB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,0BAA0B;IAC1B,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,CAAC;IACzB,0FAA0F;IAC1F,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,0BAA2B,SAAQ,QAAQ,CAC1D,6BAA6B,EAC7B,gBAAgB,EAChB,qBAAqB,EACrB,KAAK,EACL,2BAA2B,CAC5B;CAAG;AAEJ,eAAO,MAAM,0BAA0B,6DAEtC,CAAC;AAqBF,gEAAgE;AAChE,eAAO,MAAM,IAAI,EAAE,mBAAmB,CACpC,gBAAgB,EAChB,SAAS,CAAC,MAAM,EAChB,qBAAqB,EACnB,SAAS,CAAC,eAAe,GACzB,SAAS,CAAC,kBAAkB,GAC5B,SAAS,CAAC,iBAAiB,GAC3B,SAAS,CAAC,kBAAkB,CA4B/B,CAAC;AAEF,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;CAAgC,CAAC;AAEtD,eAAO,MAAM,kCAAkC,gJAI5C,CAAC"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `GoogleWorkspace.Group` — one Admin SDK Directory group (`admin/directory/v1/groups`).
|
|
3
|
+
*
|
|
4
|
+
* ★ WHY A GROUP IS THE FIRST OBJECT CLASS, AND NOT A USER. The estate's own Cloudflare Access
|
|
5
|
+
* policies already reference Workspace groups by name ("Schenanigans - Admin", "Schenanigans -
|
|
6
|
+
* Google" — `docs/zero-trust-access-applications.md` in homeflare-landscape) as the Google IdP
|
|
7
|
+
* groups an Access rule allows. A group is also the one Directory object with no foreign-key
|
|
8
|
+
* prerequisite: `email` is the whole identity, the same reasoning `netbox/prefix.ts` gives for
|
|
9
|
+
* starting with `Prefix` over `Device`.
|
|
10
|
+
*
|
|
11
|
+
* ⛔ USERS ARE OUT OF SCOPE FOR THIS FAMILY. `insertUsers`/`patchUsers`/`updateUsers` all accept a
|
|
12
|
+
* `password` field on the wire `User` schema — modelling `GoogleWorkspace.User` here would put a
|
|
13
|
+
* credential-shaped field one keystroke away from becoming a prop, and Alchemy persists props
|
|
14
|
+
* unencrypted (S25). No stack in this estate declares a Workspace user today (measured
|
|
15
|
+
* 2026-09-24 — see docs/upstream-conformance.md), so there is no house need forcing the
|
|
16
|
+
* question. `GroupMember` (group-member.ts) references a user by email without ever creating,
|
|
17
|
+
* reading or storing one.
|
|
18
|
+
*
|
|
19
|
+
* ★ ADOPT-FRIENDLY BY CONSTRUCTION. `groupKey` accepts an email, alias or id; this family always
|
|
20
|
+
* uses `email` so `fetchLive` binds the live group by the same key a human would type. An
|
|
21
|
+
* instance that already has `admin@schenanigans.com`'s "Schenanigans - Admin" group is BOUND,
|
|
22
|
+
* never duplicated, and `attributes` maps the full live row — the exact-as-live half of
|
|
23
|
+
* `adopt(true)` (resource.ts's own note).
|
|
24
|
+
*
|
|
25
|
+
* ⛔ THIS RESOURCE DOES NOT DELETE BY DEFAULT. `defaultRemovalPolicy: 'retain'` — removing a group
|
|
26
|
+
* an Access policy still names would break sign-in for everyone that rule allows. `delete` stays
|
|
27
|
+
* fully implemented; a caller opts in with `.pipe(RemovalPolicy.destroy())`.
|
|
28
|
+
*
|
|
29
|
+
* ⛔ NO CREDENTIAL IS A PROP. `GOOGLE_ACCESS_TOKEN` is read from the environment at call time,
|
|
30
|
+
* through `@distilled.cloud/google-workspace`'s `CredentialsFromEnv` (resource.ts) — see
|
|
31
|
+
* credentials.ts for where that token comes from.
|
|
32
|
+
*/
|
|
33
|
+
import { Resource } from 'alchemy';
|
|
34
|
+
import * as Provider from 'alchemy/Provider';
|
|
35
|
+
import * as directory from '@distilled.cloud/google-workspace/unstable/admin_directory_v1';
|
|
36
|
+
import * as Effect from 'effect/Effect';
|
|
37
|
+
import type { GoogleWorkspaceRequirements, GoogleWorkspaceSpec } from './resource.ts';
|
|
38
|
+
export interface GroupProps {
|
|
39
|
+
/** The group's email address — the primary key for locate, create and update alike. */
|
|
40
|
+
email: string;
|
|
41
|
+
/** Display name. @default the local part of `email` */
|
|
42
|
+
name?: string;
|
|
43
|
+
/** Free text, up to 4,096 characters per the Directory API. */
|
|
44
|
+
description?: string;
|
|
45
|
+
}
|
|
46
|
+
export interface GroupAttributes {
|
|
47
|
+
email: string;
|
|
48
|
+
name: string;
|
|
49
|
+
description: string;
|
|
50
|
+
groupId: string;
|
|
51
|
+
/** Read-only — direct members, as a decimal string on the wire (Directory's own choice). */
|
|
52
|
+
directMembersCount: string;
|
|
53
|
+
/** Read-only. Aliases outside the primary domain/subdomains; edited only via alias endpoints. */
|
|
54
|
+
nonEditableAliases: string[];
|
|
55
|
+
/** Read-only. Aliases within the account's domains; edited only via alias endpoints. */
|
|
56
|
+
aliases: string[];
|
|
57
|
+
adminCreated: boolean;
|
|
58
|
+
}
|
|
59
|
+
export interface GoogleWorkspaceGroup extends Resource<'GoogleWorkspace.Group', GroupProps, GroupAttributes, never, GoogleWorkspaceRequirements> {
|
|
60
|
+
}
|
|
61
|
+
export declare const GoogleWorkspaceGroup: import("alchemy").ResourceClass<GoogleWorkspaceGroup>;
|
|
62
|
+
/** ★ EXPORTED for direct testing with an explicit fake `Credentials` layer — see group.test.ts. */
|
|
63
|
+
export declare const spec: GoogleWorkspaceSpec<GroupProps, directory.Group, GroupAttributes, directory.GetGroupsError | directory.InsertGroupsError | directory.PatchGroupsError | directory.DeleteGroupsError>;
|
|
64
|
+
export declare const handlers: {
|
|
65
|
+
list: () => Effect.Effect<never[], never, never>;
|
|
66
|
+
read: ({ olds }: {
|
|
67
|
+
olds: GroupProps;
|
|
68
|
+
}) => Effect.Effect<GroupAttributes | undefined, import("@distilled.cloud/caddy").BadGateway | directory.BadRequest | directory.Conflict | directory.Forbidden | import("@distilled.cloud/caddy").GatewayTimeout | import("@distilled.cloud/google-workspace/Errors").GoogleWorkspaceParseError | import("effect/unstable/http/HttpClientError").HttpClientError | import("@distilled.cloud/caddy").InternalServerError | directory.NotFound | import("@distilled.cloud/caddy").ServiceUnavailable | import("@distilled.cloud/caddy").TooManyRequests | import("@distilled.cloud/caddy").Unauthorized | import("@distilled.cloud/google-workspace/Errors").UnknownGoogleWorkspaceError, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
69
|
+
diff: ({ news, output }: {
|
|
70
|
+
news: import("alchemy").Input<GroupProps>;
|
|
71
|
+
output: GroupAttributes | undefined;
|
|
72
|
+
}) => Effect.Effect<{
|
|
73
|
+
readonly action: 'update';
|
|
74
|
+
} | {
|
|
75
|
+
readonly action: 'noop';
|
|
76
|
+
} | {
|
|
77
|
+
readonly action: 'replace';
|
|
78
|
+
} | undefined, import("@distilled.cloud/caddy").BadGateway | directory.BadRequest | directory.Conflict | directory.Forbidden | import("@distilled.cloud/caddy").GatewayTimeout | import("@distilled.cloud/google-workspace/Errors").GoogleWorkspaceParseError | import("effect/unstable/http/HttpClientError").HttpClientError | import("@distilled.cloud/caddy").InternalServerError | directory.NotFound | import("@distilled.cloud/caddy").ServiceUnavailable | import("@distilled.cloud/caddy").TooManyRequests | import("@distilled.cloud/caddy").Unauthorized | import("@distilled.cloud/google-workspace/Errors").UnknownGoogleWorkspaceError, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
79
|
+
reconcile: ({ news }: {
|
|
80
|
+
news: GroupProps;
|
|
81
|
+
}) => Effect.Effect<GroupAttributes, import("@distilled.cloud/caddy").BadGateway | directory.BadRequest | directory.Conflict | directory.Forbidden | import("@distilled.cloud/caddy").GatewayTimeout | import("@distilled.cloud/google-workspace/Errors").GoogleWorkspaceParseError | import("effect/unstable/http/HttpClientError").HttpClientError | import("@distilled.cloud/caddy").InternalServerError | directory.NotFound | import("@distilled.cloud/caddy").ServiceUnavailable | import("@distilled.cloud/caddy").TooManyRequests | import("@distilled.cloud/caddy").Unauthorized | import("@distilled.cloud/google-workspace/Errors").UnknownGoogleWorkspaceError, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
82
|
+
delete: ({ olds }: {
|
|
83
|
+
olds: GroupProps;
|
|
84
|
+
}) => Effect.Effect<void, import("@distilled.cloud/caddy").BadGateway | directory.BadRequest | directory.Conflict | directory.Forbidden | import("@distilled.cloud/caddy").GatewayTimeout | import("@distilled.cloud/google-workspace/Errors").GoogleWorkspaceParseError | import("effect/unstable/http/HttpClientError").HttpClientError | import("@distilled.cloud/caddy").InternalServerError | directory.NotFound | import("@distilled.cloud/caddy").ServiceUnavailable | import("@distilled.cloud/caddy").TooManyRequests | import("@distilled.cloud/caddy").Unauthorized | import("@distilled.cloud/google-workspace/Errors").UnknownGoogleWorkspaceError, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
85
|
+
};
|
|
86
|
+
export declare const GoogleWorkspaceGroupProvider: () => import("effect/Layer").Layer<Provider.Provider<GoogleWorkspaceGroup>, never, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
87
|
+
//# sourceMappingURL=group.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"group.d.ts","sourceRoot":"","sources":["../../src/google-workspace/group.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,SAAS,MAAM,+DAA+D,CAAC;AAC3F,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,EAAE,2BAA2B,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAItF,MAAM,WAAW,UAAU;IACzB,uFAAuF;IACvF,KAAK,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,4FAA4F;IAC5F,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iGAAiG;IACjG,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,wFAAwF;IACxF,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,oBAAqB,SAAQ,QAAQ,CACpD,uBAAuB,EACvB,UAAU,EACV,eAAe,EACf,KAAK,EACL,2BAA2B,CAC5B;CAAG;AAEJ,eAAO,MAAM,oBAAoB,uDAE/B,CAAC;AAoBH,mGAAmG;AACnG,eAAO,MAAM,IAAI,EAAE,mBAAmB,CACpC,UAAU,EACV,SAAS,CAAC,KAAK,EACf,eAAe,EACb,SAAS,CAAC,cAAc,GACxB,SAAS,CAAC,iBAAiB,GAC3B,SAAS,CAAC,gBAAgB,GAC1B,SAAS,CAAC,iBAAiB,CAqB9B,CAAC;AAEF,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;CAAgC,CAAC;AAEtD,eAAO,MAAM,4BAA4B,0IAC0D,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Workspace providers for Alchemy.
|
|
3
|
+
*
|
|
4
|
+
* ⛔ THIS BARREL IS THE PUBLIC API, AND IT IS DELIBERATELY SMALLER THAN THE DIRECTORY. These are
|
|
5
|
+
* the symbols a real stack consumes; the rest of the files are internals a provider needs but a
|
|
6
|
+
* consumer should not depend on. An `export *` here would publish every helper as API and make
|
|
7
|
+
* the next refactor a breaking change.
|
|
8
|
+
* ★ `credentials.ts`'s re-exports ARE published, because a stack needs `CredentialsFromEnv` (or
|
|
9
|
+
* `fromAccessToken` in a test) to build a runtime layer, and `GoogleWorkspaceKeyRef` /
|
|
10
|
+
* `describeKeyRef` are the documentation seam credential setup is meant to go through.
|
|
11
|
+
*/
|
|
12
|
+
export { type GoogleWorkspaceConfig, Credentials, CredentialsFromEnv, describeKeyRef, fromAccessToken, GOOGLE_ACCESS_TOKEN_ENV, GOOGLE_PROJECT_ID_ENV, type GoogleWorkspaceKeyRef, } from './credentials.ts';
|
|
13
|
+
export { GoogleWorkspaceDomainAlias, GoogleWorkspaceDomainAliasProvider, type DomainAliasAttributes, type DomainAliasProps, } from './domain-alias.ts';
|
|
14
|
+
export { GoogleWorkspaceGroup, GoogleWorkspaceGroupProvider, type GroupAttributes, type GroupProps, } from './group.ts';
|
|
15
|
+
export { GoogleWorkspaceGroupMember, GoogleWorkspaceGroupMemberProvider, type DeliverySettings, type GroupMemberAttributes, type GroupMemberProps, type MemberRole, } from './group-member.ts';
|
|
16
|
+
export { GoogleWorkspaceOrgUnit, GoogleWorkspaceOrgUnitProvider, type OrgUnitAttributes, type OrgUnitProps, } from './org-unit.ts';
|
|
17
|
+
export { googleWorkspaceProviders } from './providers.ts';
|
|
18
|
+
export { googleWorkspaceHandlers, googleWorkspaceOperations, type GoogleWorkspaceRequirements, type GoogleWorkspaceSpec, } from './resource.ts';
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/google-workspace/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EACL,KAAK,qBAAqB,EAC1B,WAAW,EACX,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,uBAAuB,EACvB,qBAAqB,EACrB,KAAK,qBAAqB,GAC3B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,0BAA0B,EAC1B,kCAAkC,EAClC,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,GACtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,oBAAoB,EACpB,4BAA4B,EAC5B,KAAK,eAAe,EACpB,KAAK,UAAU,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,0BAA0B,EAC1B,kCAAkC,EAClC,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,UAAU,GAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,sBAAsB,EACtB,8BAA8B,EAC9B,KAAK,iBAAiB,EACtB,KAAK,YAAY,GAClB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EACL,uBAAuB,EACvB,yBAAyB,EACzB,KAAK,2BAA2B,EAChC,KAAK,mBAAmB,GACzB,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
// src/google-workspace/credentials.ts
|
|
2
|
+
import {
|
|
3
|
+
Credentials,
|
|
4
|
+
CredentialsFromEnv,
|
|
5
|
+
fromAccessToken
|
|
6
|
+
} from "@distilled.cloud/google-workspace/Credentials";
|
|
7
|
+
var GOOGLE_ACCESS_TOKEN_ENV = "GOOGLE_ACCESS_TOKEN";
|
|
8
|
+
var GOOGLE_PROJECT_ID_ENV = "GOOGLE_PROJECT_ID";
|
|
9
|
+
var KEY_SHAPED = /-----BEGIN|private_key|"type":\s*"service_account"/i;
|
|
10
|
+
var describeKeyRef = (ref) => {
|
|
11
|
+
for (const [field, value] of Object.entries(ref)) {
|
|
12
|
+
const text = Array.isArray(value) ? value.join(",") : String(value);
|
|
13
|
+
if (KEY_SHAPED.test(text)) {
|
|
14
|
+
throw new Error(`GoogleWorkspaceKeyRef.${field} looks like key material, not a reference`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return `service account at OpenBao:${ref.openBaoPath}, delegating as ${ref.delegatedAdmin}, ` + `scopes: ${ref.scopes.join(" ")}`;
|
|
18
|
+
};
|
|
19
|
+
// src/google-workspace/domain-alias.ts
|
|
20
|
+
import { Resource } from "alchemy";
|
|
21
|
+
import * as Provider from "alchemy/Provider";
|
|
22
|
+
import * as directory from "@distilled.cloud/google-workspace/unstable/admin_directory_v1";
|
|
23
|
+
import * as Effect2 from "effect/Effect";
|
|
24
|
+
|
|
25
|
+
// src/google-workspace/resource.ts
|
|
26
|
+
import { isResolved } from "alchemy/Diff";
|
|
27
|
+
import { CredentialsFromEnv as CredentialsFromEnv2 } from "@distilled.cloud/google-workspace/Credentials";
|
|
28
|
+
import * as Effect from "effect/Effect";
|
|
29
|
+
var googleWorkspaceOperations = (spec) => {
|
|
30
|
+
const read = (props) => spec.fetchLive(props).pipe(Effect.map((live) => live === undefined ? undefined : spec.attributes(live, props)));
|
|
31
|
+
return {
|
|
32
|
+
read,
|
|
33
|
+
diff: (news, output) => Effect.gen(function* () {
|
|
34
|
+
if (output === undefined || !isResolved(news))
|
|
35
|
+
return;
|
|
36
|
+
const live = yield* read(news);
|
|
37
|
+
if (live === undefined)
|
|
38
|
+
return { action: "update" };
|
|
39
|
+
if (spec.matches(live, news))
|
|
40
|
+
return { action: "noop" };
|
|
41
|
+
return spec.update === undefined ? { action: "replace" } : { action: "update" };
|
|
42
|
+
}),
|
|
43
|
+
reconcile: (news) => Effect.gen(function* () {
|
|
44
|
+
const before = yield* spec.fetchLive(news);
|
|
45
|
+
if (before === undefined) {
|
|
46
|
+
if (spec.create === undefined) {
|
|
47
|
+
return yield* Effect.die(new Error(`${spec.describe(news)}: spec declares no create for a missing object`));
|
|
48
|
+
}
|
|
49
|
+
yield* spec.create(news);
|
|
50
|
+
} else if (spec.update !== undefined) {
|
|
51
|
+
const mapped = spec.attributes(before, news);
|
|
52
|
+
if (mapped !== undefined && !spec.matches(mapped, news)) {
|
|
53
|
+
yield* spec.update(news, before);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const after = yield* read(news);
|
|
57
|
+
if (after === undefined) {
|
|
58
|
+
return yield* Effect.die(new Error(`${spec.describe(news)}: the write returned no error but the object is still ` + "absent. Directory returns the object it wrote — read back rather than trusting " + "the status code."));
|
|
59
|
+
}
|
|
60
|
+
return after;
|
|
61
|
+
}),
|
|
62
|
+
destroy: (olds) => Effect.gen(function* () {
|
|
63
|
+
const live = yield* spec.fetchLive(olds);
|
|
64
|
+
if (live === undefined)
|
|
65
|
+
return;
|
|
66
|
+
yield* spec.destroy(olds, live);
|
|
67
|
+
})
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
var googleWorkspaceHandlers = (spec) => {
|
|
71
|
+
const ops = googleWorkspaceOperations(spec);
|
|
72
|
+
const withCredentials = (effect) => Effect.provide(effect, CredentialsFromEnv2);
|
|
73
|
+
return {
|
|
74
|
+
list: () => Effect.succeed([]),
|
|
75
|
+
read: ({ olds }) => withCredentials(ops.read(olds)),
|
|
76
|
+
diff: ({ news, output }) => withCredentials(ops.diff(news, output)),
|
|
77
|
+
reconcile: ({ news }) => withCredentials(ops.reconcile(news)),
|
|
78
|
+
delete: ({ olds }) => withCredentials(ops.destroy(olds))
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// src/google-workspace/values.ts
|
|
83
|
+
var text = (value) => value ?? "";
|
|
84
|
+
var bool = (value) => value === true;
|
|
85
|
+
var stringArray = (value) => value === undefined ? [] : [...value].toSorted();
|
|
86
|
+
var ciEqual = (left, right) => left.toLowerCase() === right.toLowerCase();
|
|
87
|
+
var stripLeadingSlash = (path) => path.replace(/^\/+/, "");
|
|
88
|
+
var withLeadingSlash = (path) => path.startsWith("/") ? path : `/${path}`;
|
|
89
|
+
|
|
90
|
+
// src/google-workspace/domain-alias.ts
|
|
91
|
+
var GoogleWorkspaceDomainAlias = Resource("GoogleWorkspace.DomainAlias", { defaultRemovalPolicy: "retain" });
|
|
92
|
+
var customerOf = (props) => props.customer ?? "my_customer";
|
|
93
|
+
var attributesOf = (live, props) => {
|
|
94
|
+
if (live.domainAliasName === undefined || !ciEqual(live.domainAliasName, props.domainAliasName)) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
creationTime: text(live.creationTime),
|
|
99
|
+
customer: customerOf(props),
|
|
100
|
+
domainAliasName: live.domainAliasName,
|
|
101
|
+
parentDomainName: text(live.parentDomainName),
|
|
102
|
+
verified: bool(live.verified)
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
var spec = {
|
|
106
|
+
attributes: attributesOf,
|
|
107
|
+
create: (props) => directory.insertDomainAliases({
|
|
108
|
+
body: { domainAliasName: props.domainAliasName, parentDomainName: props.parentDomainName },
|
|
109
|
+
customer: customerOf(props)
|
|
110
|
+
}),
|
|
111
|
+
describe: (props) => `customer/${customerOf(props)}/domainaliases/${props.domainAliasName}`,
|
|
112
|
+
destroy: (props, live) => directory.deleteDomainAliases({
|
|
113
|
+
customer: customerOf(props),
|
|
114
|
+
domainAliasName: text(live.domainAliasName) || props.domainAliasName
|
|
115
|
+
}),
|
|
116
|
+
fetchLive: (props) => directory.getDomainAliases({ customer: customerOf(props), domainAliasName: props.domainAliasName }).pipe(Effect2.catchTag("NotFound", () => Effect2.succeed(undefined))),
|
|
117
|
+
matches: (attributes, props) => attributes.parentDomainName === props.parentDomainName
|
|
118
|
+
};
|
|
119
|
+
var handlers = googleWorkspaceHandlers(spec);
|
|
120
|
+
var GoogleWorkspaceDomainAliasProvider = () => Provider.effect(GoogleWorkspaceDomainAlias, Effect2.succeed(GoogleWorkspaceDomainAlias.Provider.of(handlers)));
|
|
121
|
+
// src/google-workspace/group.ts
|
|
122
|
+
import { Resource as Resource2 } from "alchemy";
|
|
123
|
+
import * as Provider2 from "alchemy/Provider";
|
|
124
|
+
import * as directory2 from "@distilled.cloud/google-workspace/unstable/admin_directory_v1";
|
|
125
|
+
import * as Effect3 from "effect/Effect";
|
|
126
|
+
var GoogleWorkspaceGroup = Resource2("GoogleWorkspace.Group", {
|
|
127
|
+
defaultRemovalPolicy: "retain"
|
|
128
|
+
});
|
|
129
|
+
var defaultName = (props) => props.name ?? props.email.split("@")[0] ?? props.email;
|
|
130
|
+
var attributesOf2 = (live, props) => {
|
|
131
|
+
if (live.email === undefined || !ciEqual(live.email, props.email))
|
|
132
|
+
return;
|
|
133
|
+
return {
|
|
134
|
+
adminCreated: live.adminCreated === true,
|
|
135
|
+
aliases: stringArray(live.aliases),
|
|
136
|
+
description: text(live.description),
|
|
137
|
+
directMembersCount: text(live.directMembersCount),
|
|
138
|
+
email: live.email,
|
|
139
|
+
groupId: text(live.id),
|
|
140
|
+
name: text(live.name),
|
|
141
|
+
nonEditableAliases: stringArray(live.nonEditableAliases)
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
var spec2 = {
|
|
145
|
+
attributes: attributesOf2,
|
|
146
|
+
create: (props) => directory2.insertGroups({
|
|
147
|
+
body: { description: props.description ?? "", email: props.email, name: defaultName(props) }
|
|
148
|
+
}),
|
|
149
|
+
describe: (props) => `groups/${props.email}`,
|
|
150
|
+
destroy: (_props, live) => directory2.deleteGroups({ groupKey: text(live.id) || text(live.email) }),
|
|
151
|
+
fetchLive: (props) => directory2.getGroups({ groupKey: props.email }).pipe(Effect3.catchTag("NotFound", () => Effect3.succeed(undefined))),
|
|
152
|
+
matches: (attributes, props) => attributes.description === (props.description ?? "") && attributes.name === defaultName(props),
|
|
153
|
+
update: (props, live) => directory2.patchGroups({
|
|
154
|
+
body: { description: props.description ?? "", name: defaultName(props) },
|
|
155
|
+
groupKey: text(live.id) || text(live.email)
|
|
156
|
+
})
|
|
157
|
+
};
|
|
158
|
+
var handlers2 = googleWorkspaceHandlers(spec2);
|
|
159
|
+
var GoogleWorkspaceGroupProvider = () => Provider2.effect(GoogleWorkspaceGroup, Effect3.succeed(GoogleWorkspaceGroup.Provider.of(handlers2)));
|
|
160
|
+
// src/google-workspace/group-member.ts
|
|
161
|
+
import { Resource as Resource3 } from "alchemy";
|
|
162
|
+
import * as Provider3 from "alchemy/Provider";
|
|
163
|
+
import * as directory3 from "@distilled.cloud/google-workspace/unstable/admin_directory_v1";
|
|
164
|
+
import * as Effect4 from "effect/Effect";
|
|
165
|
+
var GoogleWorkspaceGroupMember = Resource3("GoogleWorkspace.GroupMember");
|
|
166
|
+
var defaultRole = (props) => props.role ?? "MEMBER";
|
|
167
|
+
var defaultDelivery = (props) => props.deliverySettings ?? "ALL_MAIL";
|
|
168
|
+
var attributesOf3 = (live, props) => {
|
|
169
|
+
if (live.email === undefined || !ciEqual(live.email, props.email))
|
|
170
|
+
return;
|
|
171
|
+
return {
|
|
172
|
+
deliverySettings: text(live.delivery_settings),
|
|
173
|
+
email: live.email,
|
|
174
|
+
group: props.group,
|
|
175
|
+
memberId: text(live.id),
|
|
176
|
+
role: text(live.role),
|
|
177
|
+
status: text(live.status)
|
|
178
|
+
};
|
|
179
|
+
};
|
|
180
|
+
var spec3 = {
|
|
181
|
+
attributes: attributesOf3,
|
|
182
|
+
create: (props) => directory3.insertMembers({
|
|
183
|
+
body: {
|
|
184
|
+
delivery_settings: defaultDelivery(props),
|
|
185
|
+
email: props.email,
|
|
186
|
+
role: defaultRole(props)
|
|
187
|
+
},
|
|
188
|
+
groupKey: props.group
|
|
189
|
+
}),
|
|
190
|
+
describe: (props) => `groups/${props.group}/members/${props.email}`,
|
|
191
|
+
destroy: (props, live) => directory3.deleteMembers({ groupKey: props.group, memberKey: text(live.id) || props.email }),
|
|
192
|
+
fetchLive: (props) => directory3.getMembers({ groupKey: props.group, memberKey: props.email }).pipe(Effect4.catchTag("NotFound", () => Effect4.succeed(undefined))),
|
|
193
|
+
matches: (attributes, props) => attributes.role === defaultRole(props) && attributes.deliverySettings === defaultDelivery(props),
|
|
194
|
+
update: (props, live) => directory3.patchMembers({
|
|
195
|
+
body: { delivery_settings: defaultDelivery(props), role: defaultRole(props) },
|
|
196
|
+
groupKey: props.group,
|
|
197
|
+
memberKey: text(live.id) || props.email
|
|
198
|
+
})
|
|
199
|
+
};
|
|
200
|
+
var handlers3 = googleWorkspaceHandlers(spec3);
|
|
201
|
+
var GoogleWorkspaceGroupMemberProvider = () => Provider3.effect(GoogleWorkspaceGroupMember, Effect4.succeed(GoogleWorkspaceGroupMember.Provider.of(handlers3)));
|
|
202
|
+
// src/google-workspace/org-unit.ts
|
|
203
|
+
import { Resource as Resource4 } from "alchemy";
|
|
204
|
+
import * as Provider4 from "alchemy/Provider";
|
|
205
|
+
import * as directory4 from "@distilled.cloud/google-workspace/unstable/admin_directory_v1";
|
|
206
|
+
import * as Effect5 from "effect/Effect";
|
|
207
|
+
var GoogleWorkspaceOrgUnit = Resource4("GoogleWorkspace.OrgUnit", {
|
|
208
|
+
defaultRemovalPolicy: "retain"
|
|
209
|
+
});
|
|
210
|
+
var customerOf2 = (props) => props.customerId ?? "my_customer";
|
|
211
|
+
var nameOf = (props) => props.orgUnitPath.split("/").filter(Boolean).pop() ?? props.orgUnitPath;
|
|
212
|
+
var parentOf = (props) => props.parentOrgUnitPath ?? "/";
|
|
213
|
+
var attributesOf4 = (live, props) => {
|
|
214
|
+
const livePath = live.orgUnitPath === undefined ? undefined : withLeadingSlash(live.orgUnitPath);
|
|
215
|
+
if (livePath === undefined || livePath.toLowerCase() !== props.orgUnitPath.toLowerCase()) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
customerId: customerOf2(props),
|
|
220
|
+
description: text(live.description),
|
|
221
|
+
name: text(live.name),
|
|
222
|
+
orgUnitId: text(live.orgUnitId),
|
|
223
|
+
orgUnitPath: livePath,
|
|
224
|
+
parentOrgUnitPath: text(live.parentOrgUnitPath)
|
|
225
|
+
};
|
|
226
|
+
};
|
|
227
|
+
var spec4 = {
|
|
228
|
+
attributes: attributesOf4,
|
|
229
|
+
create: (props) => directory4.insertOrgunits({
|
|
230
|
+
body: {
|
|
231
|
+
description: props.description ?? "",
|
|
232
|
+
name: nameOf(props),
|
|
233
|
+
parentOrgUnitPath: parentOf(props)
|
|
234
|
+
},
|
|
235
|
+
customerId: customerOf2(props)
|
|
236
|
+
}),
|
|
237
|
+
describe: (props) => `customer/${customerOf2(props)}/orgunits${props.orgUnitPath}`,
|
|
238
|
+
destroy: (props, live) => directory4.deleteOrgunits({
|
|
239
|
+
customerId: customerOf2(props),
|
|
240
|
+
orgUnitPath: stripLeadingSlash(text(live.orgUnitPath) || props.orgUnitPath)
|
|
241
|
+
}),
|
|
242
|
+
fetchLive: (props) => directory4.getOrgunits({
|
|
243
|
+
customerId: customerOf2(props),
|
|
244
|
+
orgUnitPath: stripLeadingSlash(props.orgUnitPath)
|
|
245
|
+
}).pipe(Effect5.catchTag("NotFound", () => Effect5.succeed(undefined))),
|
|
246
|
+
matches: (attributes, props) => attributes.description === (props.description ?? "") && attributes.parentOrgUnitPath === parentOf(props) && attributes.name === nameOf(props),
|
|
247
|
+
update: (props, live) => directory4.patchOrgunits({
|
|
248
|
+
body: {
|
|
249
|
+
description: props.description ?? "",
|
|
250
|
+
name: nameOf(props),
|
|
251
|
+
parentOrgUnitPath: parentOf(props)
|
|
252
|
+
},
|
|
253
|
+
customerId: customerOf2(props),
|
|
254
|
+
orgUnitPath: stripLeadingSlash(text(live.orgUnitPath) || props.orgUnitPath)
|
|
255
|
+
})
|
|
256
|
+
};
|
|
257
|
+
var handlers4 = googleWorkspaceHandlers(spec4);
|
|
258
|
+
var GoogleWorkspaceOrgUnitProvider = () => Provider4.effect(GoogleWorkspaceOrgUnit, Effect5.succeed(GoogleWorkspaceOrgUnit.Provider.of(handlers4)));
|
|
259
|
+
// src/google-workspace/providers.ts
|
|
260
|
+
import * as Layer from "effect/Layer";
|
|
261
|
+
var googleWorkspaceProviders = () => Layer.mergeAll(GoogleWorkspaceGroupProvider(), GoogleWorkspaceGroupMemberProvider(), GoogleWorkspaceDomainAliasProvider(), GoogleWorkspaceOrgUnitProvider());
|
|
262
|
+
export {
|
|
263
|
+
Credentials,
|
|
264
|
+
CredentialsFromEnv,
|
|
265
|
+
GOOGLE_ACCESS_TOKEN_ENV,
|
|
266
|
+
GOOGLE_PROJECT_ID_ENV,
|
|
267
|
+
GoogleWorkspaceDomainAlias,
|
|
268
|
+
GoogleWorkspaceDomainAliasProvider,
|
|
269
|
+
GoogleWorkspaceGroup,
|
|
270
|
+
GoogleWorkspaceGroupMember,
|
|
271
|
+
GoogleWorkspaceGroupMemberProvider,
|
|
272
|
+
GoogleWorkspaceGroupProvider,
|
|
273
|
+
GoogleWorkspaceOrgUnit,
|
|
274
|
+
GoogleWorkspaceOrgUnitProvider,
|
|
275
|
+
describeKeyRef,
|
|
276
|
+
fromAccessToken,
|
|
277
|
+
googleWorkspaceHandlers,
|
|
278
|
+
googleWorkspaceOperations,
|
|
279
|
+
googleWorkspaceProviders
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
//# debugId=B08C209C914E559B64756E2164756E21
|
|
283
|
+
//# sourceMappingURL=index.js.map
|