@homeflare/alchemy 0.29.1 → 0.31.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 (118) hide show
  1. package/README.md +42 -0
  2. package/dist/caddy/config-reconcile.d.ts.map +1 -1
  3. package/dist/caddy/config.d.ts.map +1 -1
  4. package/dist/caddy/format-warnings.d.ts +29 -0
  5. package/dist/caddy/format-warnings.d.ts.map +1 -0
  6. package/dist/caddy/format.d.ts +50 -0
  7. package/dist/caddy/format.d.ts.map +1 -0
  8. package/dist/caddy/index.d.ts +1 -0
  9. package/dist/caddy/index.d.ts.map +1 -1
  10. package/dist/caddy/index.js +78 -10
  11. package/dist/caddy/index.js.map +7 -5
  12. package/dist/discord/application-command.d.ts +52 -0
  13. package/dist/discord/application-command.d.ts.map +1 -0
  14. package/dist/discord/command-form.d.ts +62 -0
  15. package/dist/discord/command-form.d.ts.map +1 -0
  16. package/dist/discord/credentials.d.ts +35 -0
  17. package/dist/discord/credentials.d.ts.map +1 -0
  18. package/dist/discord/fake-discord.d.ts +44 -0
  19. package/dist/discord/fake-discord.d.ts.map +1 -0
  20. package/dist/discord/guild-application-command.d.ts +53 -0
  21. package/dist/discord/guild-application-command.d.ts.map +1 -0
  22. package/dist/discord/index.d.ts +12 -0
  23. package/dist/discord/index.d.ts.map +1 -0
  24. package/dist/discord/index.js +193 -0
  25. package/dist/discord/index.js.map +15 -0
  26. package/dist/discord/providers.d.ts +15 -0
  27. package/dist/discord/providers.d.ts.map +1 -0
  28. package/dist/discord/resource.d.ts +75 -0
  29. package/dist/discord/resource.d.ts.map +1 -0
  30. package/dist/google-workspace/credentials.d.ts +54 -0
  31. package/dist/google-workspace/credentials.d.ts.map +1 -0
  32. package/dist/google-workspace/domain-alias.d.ts +74 -0
  33. package/dist/google-workspace/domain-alias.d.ts.map +1 -0
  34. package/dist/google-workspace/fake-google-workspace.d.ts +41 -0
  35. package/dist/google-workspace/fake-google-workspace.d.ts.map +1 -0
  36. package/dist/google-workspace/group-member.d.ts +73 -0
  37. package/dist/google-workspace/group-member.d.ts.map +1 -0
  38. package/dist/google-workspace/group.d.ts +87 -0
  39. package/dist/google-workspace/group.d.ts.map +1 -0
  40. package/dist/google-workspace/index.d.ts +19 -0
  41. package/dist/google-workspace/index.d.ts.map +1 -0
  42. package/dist/google-workspace/index.js +283 -0
  43. package/dist/google-workspace/index.js.map +17 -0
  44. package/dist/google-workspace/org-unit.d.ts +75 -0
  45. package/dist/google-workspace/org-unit.d.ts.map +1 -0
  46. package/dist/google-workspace/providers.d.ts +16 -0
  47. package/dist/google-workspace/providers.d.ts.map +1 -0
  48. package/dist/google-workspace/resource.d.ts +80 -0
  49. package/dist/google-workspace/resource.d.ts.map +1 -0
  50. package/dist/google-workspace/values.d.ts +28 -0
  51. package/dist/google-workspace/values.d.ts.map +1 -0
  52. package/dist/grafana/credentials.d.ts +38 -0
  53. package/dist/grafana/credentials.d.ts.map +1 -0
  54. package/dist/grafana/datasource.d.ts +90 -0
  55. package/dist/grafana/datasource.d.ts.map +1 -0
  56. package/dist/grafana/fake-grafana.d.ts +48 -0
  57. package/dist/grafana/fake-grafana.d.ts.map +1 -0
  58. package/dist/grafana/index.d.ts +17 -0
  59. package/dist/grafana/index.d.ts.map +1 -0
  60. package/dist/grafana/index.js +149 -0
  61. package/dist/grafana/index.js.map +13 -0
  62. package/dist/grafana/providers.d.ts +17 -0
  63. package/dist/grafana/providers.d.ts.map +1 -0
  64. package/dist/grafana/resource.d.ts +68 -0
  65. package/dist/grafana/resource.d.ts.map +1 -0
  66. package/docs/caddy-fmt.md +59 -0
  67. package/docs/caddy.md +7 -0
  68. package/docs/credentials.md +20 -0
  69. package/docs/discord.md +129 -0
  70. package/docs/google-workspace.md +105 -0
  71. package/docs/grafana.md +102 -0
  72. package/docs/peers.md +9 -0
  73. package/docs/upstream-conformance.md +53 -0
  74. package/package.json +13 -1
  75. package/src/caddy/config-format-warning.test.ts +91 -0
  76. package/src/caddy/config-reconcile.ts +6 -0
  77. package/src/caddy/config.ts +8 -1
  78. package/src/caddy/format-warnings.test.ts +50 -0
  79. package/src/caddy/format-warnings.ts +39 -0
  80. package/src/caddy/format.test.ts +105 -0
  81. package/src/caddy/format.ts +121 -0
  82. package/src/caddy/index.ts +7 -0
  83. package/src/caddy/providers.test.ts +8 -0
  84. package/src/discord/application-command.test.ts +103 -0
  85. package/src/discord/application-command.ts +109 -0
  86. package/src/discord/command-form.test.ts +144 -0
  87. package/src/discord/command-form.ts +133 -0
  88. package/src/discord/credentials.ts +36 -0
  89. package/src/discord/fake-discord.ts +70 -0
  90. package/src/discord/guild-application-command.test.ts +215 -0
  91. package/src/discord/guild-application-command.ts +129 -0
  92. package/src/discord/index.ts +31 -0
  93. package/src/discord/providers.ts +18 -0
  94. package/src/discord/resource.ts +164 -0
  95. package/src/discord/retry.test.ts +74 -0
  96. package/src/google-workspace/credentials.test.ts +40 -0
  97. package/src/google-workspace/credentials.ts +75 -0
  98. package/src/google-workspace/domain-alias.test.ts +69 -0
  99. package/src/google-workspace/domain-alias.ts +116 -0
  100. package/src/google-workspace/fake-google-workspace.ts +54 -0
  101. package/src/google-workspace/group-member.test.ts +91 -0
  102. package/src/google-workspace/group-member.ts +126 -0
  103. package/src/google-workspace/group.test.ts +93 -0
  104. package/src/google-workspace/group.ts +128 -0
  105. package/src/google-workspace/index.ts +54 -0
  106. package/src/google-workspace/org-unit.test.ts +81 -0
  107. package/src/google-workspace/org-unit.ts +143 -0
  108. package/src/google-workspace/providers.ts +26 -0
  109. package/src/google-workspace/resource.test.ts +186 -0
  110. package/src/google-workspace/resource.ts +162 -0
  111. package/src/google-workspace/values.ts +36 -0
  112. package/src/grafana/credentials.ts +65 -0
  113. package/src/grafana/datasource.test.ts +183 -0
  114. package/src/grafana/datasource.ts +171 -0
  115. package/src/grafana/fake-grafana.ts +64 -0
  116. package/src/grafana/index.ts +22 -0
  117. package/src/grafana/providers.ts +19 -0
  118. package/src/grafana/resource.ts +120 -0
@@ -0,0 +1,17 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/google-workspace/credentials.ts", "../src/google-workspace/domain-alias.ts", "../src/google-workspace/resource.ts", "../src/google-workspace/values.ts", "../src/google-workspace/group.ts", "../src/google-workspace/group-member.ts", "../src/google-workspace/org-unit.ts", "../src/google-workspace/providers.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * Google Workspace credentials for the house — a lazy reference, never the key.\n *\n * ⛔ THE DISTILLED SDK'S OWN `Credentials` SERVICE TAKES ONE THING: an already-minted OAuth2\n * bearer access token (`@distilled.cloud/google-workspace/Credentials` — read at\n * `node_modules/@distilled.cloud/google-workspace/src/credentials.ts`, 2026-09-24: \"API-compatible\n * port of the distilled gcp credentials module … no ADC, no service-account signing, no refresh\n * flow\"). It has no service-account JSON shape and no domain-wide-delegation JWT exchange built\n * in — minting the token is entirely outside the SDK's surface.\n *\n * ★ SO THE HOUSE PATTERN IS H8'S, UNCHANGED: a short-lived credential is minted from OpenBao and\n * exported into the DEPLOY PROCESS's environment only, by a Bun wrapper the operator runs — the\n * same shape the kit's Proxmox subpath already uses for its own OpenBao-sourced token. This\n * family adds nothing that signs a JWT or talks to Google's token endpoint; that stays Tim's\n * wrapper script, documented in the README, because building it here would be a second,\n * hand-rolled auth flow next to the one the SDK already exposes (S23).\n *\n * ⛔ NEVER THE KEY. `GoogleWorkspaceKeyRef` below is a REFERENCE to where a service-account key\n * lives — an OpenBao path, a delegated admin subject and the scopes the wrapper requests — not\n * the key itself. Nothing in this family reads, parses or holds private key material; `describeKeyRef`\n * is pure string formatting for docs and the census handoff header, and it is tested (below) to\n * never accept or echo anything shaped like a key.\n *\n * Re-exported so a consumer imports one module for both the distilled service and the house\n * reference type: `import { CredentialsFromEnv, fromAccessToken, type GoogleWorkspaceKeyRef } from\n * '@homeflare/alchemy/google-workspace'`.\n */\nexport {\n type Config as GoogleWorkspaceConfig,\n Credentials,\n CredentialsFromEnv,\n fromAccessToken,\n} from '@distilled.cloud/google-workspace/Credentials';\n\n/** Env var the SDK's `CredentialsFromEnv` reads — the wrapper mints into THIS var, nothing else. */\nexport const GOOGLE_ACCESS_TOKEN_ENV = 'GOOGLE_ACCESS_TOKEN';\n/** Optional — only meaningful if a resource ever needs to scope a call to a GCP project. */\nexport const GOOGLE_PROJECT_ID_ENV = 'GOOGLE_PROJECT_ID';\n\n/**\n * Where the service-account key that a wrapper exchanges for `GOOGLE_ACCESS_TOKEN` lives —\n * documentation and typing only. A stack file may keep one of these next to its declarations so\n * the OpenBao path, delegated admin and scopes are reviewable in the same diff as the resources\n * they authorize — nothing here reads OpenBao or Google, and no field may hold a key, a token or\n * a private key fragment.\n */\nexport interface GoogleWorkspaceKeyRef {\n /** OpenBao KV path, e.g. `kv/google-workspace/service-accounts/directory-admin`. */\n readonly openBaoPath: string;\n /** The Workspace admin the service account impersonates via domain-wide delegation. */\n readonly delegatedAdmin: string;\n /** OAuth scopes the wrapper requests — keep this the least the declared resources need. */\n readonly scopes: readonly string[];\n}\n\n/** Substrings that must never appear in a `GoogleWorkspaceKeyRef` field — this is a reference. */\nconst KEY_SHAPED = /-----BEGIN|private_key|\"type\":\\s*\"service_account\"/i;\n\n/**\n * A one-line, key-free description for docs and the census handoff header. Throws rather than\n * formatting a ref whose fields look like they hold key material — a `GoogleWorkspaceKeyRef` is\n * a pointer, and this is the one place that assumption is checked instead of assumed.\n */\nexport const describeKeyRef = (ref: GoogleWorkspaceKeyRef): string => {\n for (const [field, value] of Object.entries(ref)) {\n const text = Array.isArray(value) ? value.join(',') : String(value);\n if (KEY_SHAPED.test(text)) {\n throw new Error(`GoogleWorkspaceKeyRef.${field} looks like key material, not a reference`);\n }\n }\n return (\n `service account at OpenBao:${ref.openBaoPath}, delegating as ${ref.delegatedAdmin}, ` +\n `scopes: ${ref.scopes.join(' ')}`\n );\n};\n",
6
+ "/**\n * `GoogleWorkspace.DomainAlias` — one secondary-domain alias on the Workspace account\n * (`admin/directory/v1/customer/{customer}/domainaliases`).\n *\n * ⛔ NO `update` OPERATION EXISTS. The Directory API ships `get`/`insert`/`delete`/`list` for\n * domain aliases and nothing else — measured against `admin_directory_v1.ts`: there is no\n * `patchDomainAliases` or `updateDomainAliases`. `spec.update` is intentionally absent, so\n * `resource.ts`'s `diff` reports `replace` for any drift rather than an unsendable PATCH — the\n * same fallback NetBox's `netboxOperations` and Forgejo's `forgejoOperations` give a spec with\n * no `update`.\n *\n * ★ `domainAliasName` IS THE WHOLE IDENTITY. `parentDomainName` only matters at create time —\n * Google refuses reparenting an alias in place, so a declared `parentDomainName` that no longer\n * matches the live row is exactly the identity change `replace` exists for.\n *\n * ⚠️ THE HOUSE'S CLOUDFLARE ZONES (`homeflare.dev`, `aimto.app`, `schenanigans.com`, `taslabs.net`\n * — `site/live.site.json` in homeflare-landscape) are DNS zones, not Workspace domains by\n * themselves; a zone only becomes a valid `parentDomainName` once it is added and verified as a\n * primary or secondary domain in the Workspace admin console. This family does not verify a\n * domain — Google's verification is a TXT/CNAME challenge outside the Directory API's write\n * surface — so `create` will fail with `BadRequest` against an unverified domain, which is\n * correct: nothing here should silently paper over a step the account owner has to do by hand.\n */\nimport { Resource } from 'alchemy';\nimport * as Provider from 'alchemy/Provider';\nimport * as directory from '@distilled.cloud/google-workspace/unstable/admin_directory_v1';\nimport * as Effect from 'effect/Effect';\nimport type { GoogleWorkspaceRequirements, GoogleWorkspaceSpec } from './resource.ts';\nimport { googleWorkspaceHandlers } from './resource.ts';\nimport { bool, ciEqual, text } from './values.ts';\n\nexport interface DomainAliasProps {\n /** The unique id for the Workspace account, or `my_customer` for the caller's own account. */\n customer?: string;\n /** The alias domain name — primary key for locate. */\n domainAliasName: string;\n /** The primary or secondary domain this alias resolves to. Immutable — a change replaces. */\n parentDomainName: string;\n}\n\nexport interface DomainAliasAttributes {\n customer: string;\n domainAliasName: string;\n parentDomainName: string;\n /** Read-only. */\n verified: boolean;\n /** Read-only, ISO-ish epoch-ms string as Google returns it. */\n creationTime: string;\n}\n\nexport interface GoogleWorkspaceDomainAlias extends Resource<\n 'GoogleWorkspace.DomainAlias',\n DomainAliasProps,\n DomainAliasAttributes,\n never,\n GoogleWorkspaceRequirements\n> {}\n\nexport const GoogleWorkspaceDomainAlias = Resource<GoogleWorkspaceDomainAlias>(\n 'GoogleWorkspace.DomainAlias',\n { defaultRemovalPolicy: 'retain' },\n);\n\nconst customerOf = (props: DomainAliasProps): string => props.customer ?? 'my_customer';\n\nconst attributesOf = (\n live: directory.DomainAlias,\n props: DomainAliasProps,\n): DomainAliasAttributes | undefined => {\n if (live.domainAliasName === undefined || !ciEqual(live.domainAliasName, props.domainAliasName)) {\n return undefined;\n }\n return {\n creationTime: text(live.creationTime),\n customer: customerOf(props),\n domainAliasName: live.domainAliasName,\n parentDomainName: text(live.parentDomainName),\n verified: bool(live.verified),\n };\n};\n\n/** ★ EXPORTED for direct testing — see domain-alias.test.ts. */\nexport const spec: GoogleWorkspaceSpec<\n DomainAliasProps,\n directory.DomainAlias,\n DomainAliasAttributes,\n | directory.GetDomainAliasesError\n | directory.InsertDomainAliasesError\n | directory.DeleteDomainAliasesError\n> = {\n attributes: attributesOf,\n create: (props) =>\n directory.insertDomainAliases({\n body: { domainAliasName: props.domainAliasName, parentDomainName: props.parentDomainName },\n customer: customerOf(props),\n }),\n describe: (props) => `customer/${customerOf(props)}/domainaliases/${props.domainAliasName}`,\n destroy: (props, live) =>\n directory.deleteDomainAliases({\n customer: customerOf(props),\n domainAliasName: text(live.domainAliasName) || props.domainAliasName,\n }),\n fetchLive: (props) =>\n directory\n .getDomainAliases({ customer: customerOf(props), domainAliasName: props.domainAliasName })\n .pipe(Effect.catchTag('NotFound', () => Effect.succeed(undefined))),\n matches: (attributes, props) => attributes.parentDomainName === props.parentDomainName,\n};\n\nexport const handlers = googleWorkspaceHandlers(spec);\n\nexport const GoogleWorkspaceDomainAliasProvider = () =>\n Provider.effect(\n GoogleWorkspaceDomainAlias,\n Effect.succeed(GoogleWorkspaceDomainAlias.Provider.of(handlers)),\n );\n",
7
+ "/**\n * One shape for every Google Workspace Directory object — the same four operations, different\n * distilled call.\n *\n * ⛔ THE ALTERNATIVE IS COPYING THE SAME HANDLERS PER RESOURCE. Groups, group members, domain\n * aliases and org units are all create / read / update / delete against\n * `@distilled.cloud/google-workspace`'s typed `admin_directory_v1` operations. Hand-rolling the\n * fourth copy is where someone drops the read-back or the `isResolved` guard and the plan lies.\n *\n * ★ SO THE FOUR OPERATIONS LIVE HERE, ONCE — mirroring `../forgejo/resource.ts`'s engine exactly.\n * The Directory API addresses every object this family models by a stable key (an email, a\n * domain alias name, an org unit path), so — unlike NetBox's page-then-disambiguate `locateOne`\n * — `fetchLive` is always a plain get-by-key.\n *\n * ⚠️ `NotFound` IS FOLDED TO \"ABSENT\" INSIDE EACH RESOURCE FILE'S OWN `fetchLive`, NOT HERE — the\n * same split forgejo's and netbox's engines use. `Effect.catchTag`'s tag-literal inference needs\n * a concrete error union to resolve `\"NotFound\"` against; a spec's `E` is only concrete at each\n * resource file's own call site.\n *\n * ★ ADOPT IS THE DEFAULT POSTURE, NOT A FLAG. `reconcile` LOCATES before it writes: a group or org\n * unit the Workspace already has is bound, not duplicated — Tim's own domain (schenanigans.com)\n * predates this code. `spec.attributes` always maps the FULL live object, so a bound row reads\n * back byte-identical to what Workspace actually holds — the exact-as-live half of `adopt(true)`;\n * the other half (refusing to bind without it) is upstream's `AdoptPolicy`, unchanged here.\n *\n * ⛔ CREDENTIALS ARE NEVER A PROP (S24/S25). See `credentials.ts` for what a stack passes instead.\n */\nimport { isResolved } from 'alchemy/Diff';\nimport type { Input } from 'alchemy/Input';\nimport { CredentialsFromEnv } from '@distilled.cloud/google-workspace/Credentials';\nimport type { GoogleWorkspaceOpContext } from '@distilled.cloud/google-workspace/Protocol';\nimport * as Effect from 'effect/Effect';\nimport type * as HttpClient from 'effect/unstable/http/HttpClient';\n\n/**\n * ★ WHAT EVERY HANDLER NEEDS FROM THE CALLER'S RUNTIME, after Credentials are provided below.\n * `GoogleWorkspaceOpContext` is `Credentials | HttpClient.HttpClient`; `googleWorkspaceHandlers`\n * closes over `CredentialsFromEnv` itself (`GOOGLE_ACCESS_TOKEN` / `GOOGLE_PROJECT_ID` — see\n * credentials.ts), so a consuming stack only has to provide `HttpClient.HttpClient`, the same\n * shape every other distilled-backed family in this kit asks for.\n */\nexport type GoogleWorkspaceRequirements = HttpClient.HttpClient;\n\n/**\n * One Directory object's distilled calls. `Live` is whatever the SDK decodes (a `Group`, a\n * `Member`, a `DomainAlias`, an `OrgUnit`) — never an untyped response body.\n *\n * `E` is left to each resource file to declare (the union of the distilled operations it\n * actually calls), so a call site's precise per-operation error union flows straight through\n * instead of being widened by hand here.\n */\nexport type GoogleWorkspaceSpec<Props extends object, Live, Attributes, E> = {\n /** Already folds a real \"absent\" outcome — a `catchTag('NotFound', ...)` at the call site. */\n readonly fetchLive: (\n props: Props,\n ) => Effect.Effect<Live | undefined, E, GoogleWorkspaceOpContext>;\n readonly attributes: (live: Live, props: Props) => Attributes | undefined;\n readonly matches: (attributes: Attributes, props: Props) => boolean;\n /** Absent for an existence-only family (none here yet, but `GroupMember` could become one). */\n readonly create?: (props: Props) => Effect.Effect<unknown, E, GoogleWorkspaceOpContext>;\n readonly update?: (\n props: Props,\n live: Live,\n ) => Effect.Effect<unknown, E, GoogleWorkspaceOpContext>;\n readonly destroy: (\n props: Props,\n live: Live,\n ) => Effect.Effect<unknown, E, GoogleWorkspaceOpContext>;\n /** For the read-back-failure message. */\n readonly describe: (props: Props) => string;\n};\n\nexport const googleWorkspaceOperations = <Props extends object, Live, Attributes, E>(\n spec: GoogleWorkspaceSpec<Props, Live, Attributes, E>,\n) => {\n const read = (props: Props) =>\n spec\n .fetchLive(props)\n .pipe(Effect.map((live) => (live === undefined ? undefined : spec.attributes(live, props))));\n\n return {\n read,\n\n diff: (news: Input<Props>, output: Attributes | undefined) =>\n Effect.gen(function* () {\n if (output === undefined || !isResolved(news)) return undefined;\n const live = yield* read(news);\n if (live === undefined) return { action: 'update' } as const;\n if (spec.matches(live, news)) return { action: 'noop' } as const;\n return spec.update === undefined\n ? ({ action: 'replace' } as const)\n : ({ action: 'update' } as const);\n }),\n\n reconcile: (news: Props) =>\n Effect.gen(function* () {\n const before = yield* spec.fetchLive(news);\n if (before === undefined) {\n if (spec.create === undefined) {\n return yield* Effect.die(\n new Error(`${spec.describe(news)}: spec declares no create for a missing object`),\n );\n }\n yield* spec.create(news);\n } else if (spec.update !== undefined) {\n const mapped = spec.attributes(before, news);\n // ⚠️ ADOPTION IS THE `mapped === undefined` CASE TOO: a row this spec cannot map is not\n // a row to PATCH blindly.\n if (mapped !== undefined && !spec.matches(mapped, news)) {\n yield* spec.update(news, before);\n }\n }\n const after = yield* read(news);\n if (after === undefined) {\n return yield* Effect.die(\n new Error(\n `${spec.describe(news)}: the write returned no error but the object is still ` +\n 'absent. Directory returns the object it wrote — read back rather than trusting ' +\n 'the status code.',\n ),\n );\n }\n return after;\n }),\n\n destroy: (olds: Props) =>\n Effect.gen(function* () {\n const live = yield* spec.fetchLive(olds);\n if (live === undefined) return;\n yield* spec.destroy(olds, live);\n }),\n };\n};\n\n/**\n * The five provider handlers for a spec'd Directory object, wired once.\n *\n * ★ CREDENTIALS ARE PROVIDED HERE, ONCE, NOT BY EVERY CALLER. `CredentialsFromEnv` resolves\n * `GOOGLE_ACCESS_TOKEN` on the calling fiber per request (credentials.ts's own note), so this\n * closure keeps that laziness — nothing is captured at module load.\n *\n * ⛔ IT STOPS AT THE HANDLERS AND DOES NOT RETURN THE LAYER — same reasoning as `forgejoHandlers`.\n *\n * ⚠️ `list` answers empty. The Directory API's list endpoints page the whole domain — every group,\n * every org unit — so adoption stays explicit rather than sweeping (same posture as NetBox and\n * Forgejo; the census handoff script, not this provider, is where a full inventory belongs).\n */\nexport const googleWorkspaceHandlers = <Props extends object, Live, Attributes extends object, E>(\n spec: GoogleWorkspaceSpec<Props, Live, Attributes, E>,\n) => {\n const ops = googleWorkspaceOperations(spec);\n const withCredentials = <A>(effect: Effect.Effect<A, E, GoogleWorkspaceOpContext>) =>\n Effect.provide(effect, CredentialsFromEnv);\n return {\n list: () => Effect.succeed([]),\n read: ({ olds }: { olds: Props }) => withCredentials(ops.read(olds)),\n diff: ({ news, output }: { news: Input<Props>; output: Attributes | undefined }) =>\n withCredentials(ops.diff(news, output)),\n reconcile: ({ news }: { news: Props }) => withCredentials(ops.reconcile(news)),\n delete: ({ olds }: { olds: Props }) => withCredentials(ops.destroy(olds)),\n };\n};\n",
8
+ "/**\n * Wire coercions shared across Google Workspace resources.\n *\n * ⚠️ EVERY FIELD ON A GENERATED DIRECTORY TYPE IS `S.optional(...)` (the SDK does not know which\n * fields Google always sends), so a live read is `string | undefined` even for a field the API\n * guarantees in practice. These helpers turn that into the settled value a `matches()` compares\n * against, mirroring `../netbox/values.ts` and `../forgejo/values.ts` for the same reason: a\n * `diff` that compared `undefined` against a declared default would see drift forever.\n */\n\n/** `''` for absent — Directory objects that omit a text field mean \"no value\", not `undefined`. */\nexport const text = (value: string | undefined): string => value ?? '';\n\nexport const bool = (value: boolean | undefined): boolean => value === true;\n\n/**\n * Wire arrays compared in sorted order. `StringList` (group aliases, org unit paths in a search)\n * decodes to `string[] | undefined`; Directory does not promise an order.\n */\nexport const stringArray = (value: readonly string[] | undefined): string[] =>\n value === undefined ? [] : [...value].toSorted();\n\n/**\n * Every user- and group-facing key in this family is case-insensitive on the wire (Google lower-\n * cases email addresses and org unit path segments internally) but a declaration may be typed in\n * mixed case. Compare through this, never with `===`, or an adopted object drifts on every plan.\n */\nexport const ciEqual = (left: string, right: string): boolean =>\n left.toLowerCase() === right.toLowerCase();\n\n/** An org unit path as the API wants it for a path segment: no leading `/`. */\nexport const stripLeadingSlash = (path: string): string => path.replace(/^\\/+/, '');\n\n/** An org unit path as attributes should read it back: exactly one leading `/`. */\nexport const withLeadingSlash = (path: string): string =>\n path.startsWith('/') ? path : `/${path}`;\n",
9
+ "/**\n * `GoogleWorkspace.Group` — one Admin SDK Directory group (`admin/directory/v1/groups`).\n *\n * ★ WHY A GROUP IS THE FIRST OBJECT CLASS, AND NOT A USER. The estate's own Cloudflare Access\n * policies already reference Workspace groups by name (\"Schenanigans - Admin\", \"Schenanigans -\n * Google\" — `docs/zero-trust-access-applications.md` in homeflare-landscape) as the Google IdP\n * groups an Access rule allows. A group is also the one Directory object with no foreign-key\n * prerequisite: `email` is the whole identity, the same reasoning `netbox/prefix.ts` gives for\n * starting with `Prefix` over `Device`.\n *\n * ⛔ USERS ARE OUT OF SCOPE FOR THIS FAMILY. `insertUsers`/`patchUsers`/`updateUsers` all accept a\n * `password` field on the wire `User` schema — modelling `GoogleWorkspace.User` here would put a\n * credential-shaped field one keystroke away from becoming a prop, and Alchemy persists props\n * unencrypted (S25). No stack in this estate declares a Workspace user today (measured\n * 2026-09-24 — see docs/upstream-conformance.md), so there is no house need forcing the\n * question. `GroupMember` (group-member.ts) references a user by email without ever creating,\n * reading or storing one.\n *\n * ★ ADOPT-FRIENDLY BY CONSTRUCTION. `groupKey` accepts an email, alias or id; this family always\n * uses `email` so `fetchLive` binds the live group by the same key a human would type. An\n * instance that already has `admin@schenanigans.com`'s \"Schenanigans - Admin\" group is BOUND,\n * never duplicated, and `attributes` maps the full live row — the exact-as-live half of\n * `adopt(true)` (resource.ts's own note).\n *\n * ⛔ THIS RESOURCE DOES NOT DELETE BY DEFAULT. `defaultRemovalPolicy: 'retain'` — removing a group\n * an Access policy still names would break sign-in for everyone that rule allows. `delete` stays\n * fully implemented; a caller opts in with `.pipe(RemovalPolicy.destroy())`.\n *\n * ⛔ NO CREDENTIAL IS A PROP. `GOOGLE_ACCESS_TOKEN` is read from the environment at call time,\n * through `@distilled.cloud/google-workspace`'s `CredentialsFromEnv` (resource.ts) — see\n * credentials.ts for where that token comes from.\n */\nimport { Resource } from 'alchemy';\nimport * as Provider from 'alchemy/Provider';\nimport * as directory from '@distilled.cloud/google-workspace/unstable/admin_directory_v1';\nimport * as Effect from 'effect/Effect';\nimport type { GoogleWorkspaceRequirements, GoogleWorkspaceSpec } from './resource.ts';\nimport { googleWorkspaceHandlers } from './resource.ts';\nimport { ciEqual, stringArray, text } from './values.ts';\n\nexport interface GroupProps {\n /** The group's email address — the primary key for locate, create and update alike. */\n email: string;\n /** Display name. @default the local part of `email` */\n name?: string;\n /** Free text, up to 4,096 characters per the Directory API. */\n description?: string;\n}\n\nexport interface GroupAttributes {\n email: string;\n name: string;\n description: string;\n groupId: string;\n /** Read-only — direct members, as a decimal string on the wire (Directory's own choice). */\n directMembersCount: string;\n /** Read-only. Aliases outside the primary domain/subdomains; edited only via alias endpoints. */\n nonEditableAliases: string[];\n /** Read-only. Aliases within the account's domains; edited only via alias endpoints. */\n aliases: string[];\n adminCreated: boolean;\n}\n\nexport interface GoogleWorkspaceGroup extends Resource<\n 'GoogleWorkspace.Group',\n GroupProps,\n GroupAttributes,\n never,\n GoogleWorkspaceRequirements\n> {}\n\nexport const GoogleWorkspaceGroup = Resource<GoogleWorkspaceGroup>('GoogleWorkspace.Group', {\n defaultRemovalPolicy: 'retain',\n});\n\n/** Un-declared `name` converges here — deterministic, so an undeclared field never drifts. */\nconst defaultName = (props: GroupProps): string =>\n props.name ?? props.email.split('@')[0] ?? props.email;\n\nconst attributesOf = (live: directory.Group, props: GroupProps): GroupAttributes | undefined => {\n if (live.email === undefined || !ciEqual(live.email, props.email)) return undefined;\n return {\n adminCreated: live.adminCreated === true,\n aliases: stringArray(live.aliases),\n description: text(live.description),\n directMembersCount: text(live.directMembersCount),\n email: live.email,\n groupId: text(live.id),\n name: text(live.name),\n nonEditableAliases: stringArray(live.nonEditableAliases),\n };\n};\n\n/** ★ EXPORTED for direct testing with an explicit fake `Credentials` layer — see group.test.ts. */\nexport const spec: GoogleWorkspaceSpec<\n GroupProps,\n directory.Group,\n GroupAttributes,\n | directory.GetGroupsError\n | directory.InsertGroupsError\n | directory.PatchGroupsError\n | directory.DeleteGroupsError\n> = {\n attributes: attributesOf,\n create: (props) =>\n directory.insertGroups({\n body: { description: props.description ?? '', email: props.email, name: defaultName(props) },\n }),\n describe: (props) => `groups/${props.email}`,\n destroy: (_props, live) =>\n directory.deleteGroups({ groupKey: text(live.id) || text(live.email) }),\n fetchLive: (props) =>\n directory\n .getGroups({ groupKey: props.email })\n .pipe(Effect.catchTag('NotFound', () => Effect.succeed(undefined))),\n matches: (attributes, props) =>\n attributes.description === (props.description ?? '') && attributes.name === defaultName(props),\n update: (props, live) =>\n directory.patchGroups({\n body: { description: props.description ?? '', name: defaultName(props) },\n groupKey: text(live.id) || text(live.email),\n }),\n};\n\nexport const handlers = googleWorkspaceHandlers(spec);\n\nexport const GoogleWorkspaceGroupProvider = () =>\n Provider.effect(GoogleWorkspaceGroup, Effect.succeed(GoogleWorkspaceGroup.Provider.of(handlers)));\n",
10
+ "/**\n * `GoogleWorkspace.GroupMember` — one user or group's membership in one Directory group.\n *\n * ★ WHY THIS DOES NOT MODEL A `GoogleWorkspace.User`. `email` here identifies whoever the group\n * member is (a user's primary email, or another group's), the same way Forgejo's\n * `TeamMember.username` (team-member.ts) never creates the account it references. The Directory\n * API needs no user resource to exist in this family for a membership to be declared — see\n * group.ts's own note on why users are out of scope.\n *\n * ⚠️ UNLIKE `Forgejo.TeamMember`, A MEMBERSHIP HERE IS NOT EXISTENCE-ONLY. `role`\n * (`OWNER`/`MANAGER`/`MEMBER`) and `deliverySettings` (`ALL_MAIL`/`DIGEST`/`DAILY`/`NONE`/\n * `DISABLED`) are both mutable in place via `patchMembers` — so this spec declares `update`,\n * not `upsert`, the same shape `group.ts`/`domain-alias.ts` use.\n *\n * ⛔ GROUP CYCLES ARE THE VENDOR'S TO REFUSE, NOT THIS FAMILY'S. `insertMembers`/`patchMembers`\n * surface a cycle as `BadRequest`, which this spec's error union already carries un-narrowed —\n * nothing here guesses at \"member is itself an ancestor group\".\n */\nimport { Resource } from 'alchemy';\nimport * as Provider from 'alchemy/Provider';\nimport * as directory from '@distilled.cloud/google-workspace/unstable/admin_directory_v1';\nimport * as Effect from 'effect/Effect';\nimport type { GoogleWorkspaceRequirements, GoogleWorkspaceSpec } from './resource.ts';\nimport { googleWorkspaceHandlers } from './resource.ts';\nimport { ciEqual, text } from './values.ts';\n\nexport type MemberRole = 'OWNER' | 'MANAGER' | 'MEMBER';\nexport type DeliverySettings = 'ALL_MAIL' | 'DIGEST' | 'DAILY' | 'NONE' | 'DISABLED';\n\nexport interface GroupMemberProps {\n /** The group's email, alias or id — same key `Group.email` declares. */\n group: string;\n /** The member's email (a user or another group). Primary key alongside `group`. */\n email: string;\n /** @default 'MEMBER' */\n role?: MemberRole;\n /** @default 'ALL_MAIL' */\n deliverySettings?: DeliverySettings;\n}\n\nexport interface GroupMemberAttributes {\n group: string;\n email: string;\n memberId: string;\n role: string;\n deliverySettings: string;\n /** Read-only — `ACTIVE` for a normal member, `PENDING` while an invite is outstanding. */\n status: string;\n}\n\nexport interface GoogleWorkspaceGroupMember extends Resource<\n 'GoogleWorkspace.GroupMember',\n GroupMemberProps,\n GroupMemberAttributes,\n never,\n GoogleWorkspaceRequirements\n> {}\n\nexport const GoogleWorkspaceGroupMember = Resource<GoogleWorkspaceGroupMember>(\n 'GoogleWorkspace.GroupMember',\n);\n\nconst defaultRole = (props: GroupMemberProps): MemberRole => props.role ?? 'MEMBER';\nconst defaultDelivery = (props: GroupMemberProps): DeliverySettings =>\n props.deliverySettings ?? 'ALL_MAIL';\n\nconst attributesOf = (\n live: directory.Member,\n props: GroupMemberProps,\n): GroupMemberAttributes | undefined => {\n if (live.email === undefined || !ciEqual(live.email, props.email)) return undefined;\n return {\n deliverySettings: text(live.delivery_settings),\n email: live.email,\n group: props.group,\n memberId: text(live.id),\n role: text(live.role),\n status: text(live.status),\n };\n};\n\n/** ★ EXPORTED for direct testing — see group-member.test.ts. */\nexport const spec: GoogleWorkspaceSpec<\n GroupMemberProps,\n directory.Member,\n GroupMemberAttributes,\n | directory.GetMembersError\n | directory.InsertMembersError\n | directory.PatchMembersError\n | directory.DeleteMembersError\n> = {\n attributes: attributesOf,\n create: (props) =>\n directory.insertMembers({\n body: {\n delivery_settings: defaultDelivery(props),\n email: props.email,\n role: defaultRole(props),\n },\n groupKey: props.group,\n }),\n describe: (props) => `groups/${props.group}/members/${props.email}`,\n destroy: (props, live) =>\n directory.deleteMembers({ groupKey: props.group, memberKey: text(live.id) || props.email }),\n fetchLive: (props) =>\n directory\n .getMembers({ groupKey: props.group, memberKey: props.email })\n .pipe(Effect.catchTag('NotFound', () => Effect.succeed(undefined))),\n matches: (attributes, props) =>\n attributes.role === defaultRole(props) &&\n attributes.deliverySettings === defaultDelivery(props),\n update: (props, live) =>\n directory.patchMembers({\n body: { delivery_settings: defaultDelivery(props), role: defaultRole(props) },\n groupKey: props.group,\n memberKey: text(live.id) || props.email,\n }),\n};\n\nexport const handlers = googleWorkspaceHandlers(spec);\n\nexport const GoogleWorkspaceGroupMemberProvider = () =>\n Provider.effect(\n GoogleWorkspaceGroupMember,\n Effect.succeed(GoogleWorkspaceGroupMember.Provider.of(handlers)),\n );\n",
11
+ "/**\n * `GoogleWorkspace.OrgUnit` — one organizational unit (`admin/directory/v1/customer/{id}/orgunits`).\n *\n * ★ `orgUnitPath` IS THE IDENTITY the way `NetboxPrefix.prefix` is. Google's own doc line is\n * copied onto `OrgUnitProps.orgUnitPath` below because it is the one field this resource cannot\n * get wrong: it is DERIVED on read (`parentOrgUnitPath` + `name`) but AUTHORED as a full path on\n * write, and the API's own path-parameter form drops the leading `/` (`GetOrgunitsRequest`'s\n * `orgUnitPath` doc: \"the full path … minus the leading `/`\"). `values.ts`'s\n * `stripLeadingSlash`/`withLeadingSlash` are the one place that asymmetry is handled.\n *\n * ⛔ THIS RESOURCE DOES NOT REMOVE SUB-UNITS OR MOVE USERS. Deleting an org unit that still has\n * children or members fails server-side (Directory refuses rather than cascading) — nothing\n * here retries around that refusal, matching S11's \"observe before deleting\", not \"force\n * through\".\n *\n * ⛔ `defaultRemovalPolicy: 'retain'` — an org unit governs which Workspace services a user can\n * reach (Google's own doc line on `orgUnitPath`); deleting one out from under live users is\n * exactly the kind of hard-to-reverse action this policy exists to require an explicit opt-in\n * for.\n */\nimport { Resource } from 'alchemy';\nimport * as Provider from 'alchemy/Provider';\nimport * as directory from '@distilled.cloud/google-workspace/unstable/admin_directory_v1';\nimport * as Effect from 'effect/Effect';\nimport type { GoogleWorkspaceRequirements, GoogleWorkspaceSpec } from './resource.ts';\nimport { googleWorkspaceHandlers } from './resource.ts';\nimport { stripLeadingSlash, text, withLeadingSlash } from './values.ts';\n\nexport interface OrgUnitProps {\n /** The unique id for the Workspace account, or `my_customer` for the caller's own account. */\n customerId?: string;\n /**\n * The full path to the organizational unit, e.g. `/Contractors/2026`. Required. In order to\n * edit an `orgUnitPath`, either update `name` or `parentOrgUnitPath` — this field is derived on\n * read, not itself patchable, so declaring a different path here plans a `replace`.\n */\n orgUnitPath: string;\n /** The parent's full path, e.g. `/Contractors`. Omit for a top-level unit under `/`. */\n parentOrgUnitPath?: string;\n description?: string;\n}\n\nexport interface OrgUnitAttributes {\n customerId: string;\n orgUnitPath: string;\n parentOrgUnitPath: string;\n name: string;\n description: string;\n orgUnitId: string;\n}\n\nexport interface GoogleWorkspaceOrgUnit extends Resource<\n 'GoogleWorkspace.OrgUnit',\n OrgUnitProps,\n OrgUnitAttributes,\n never,\n GoogleWorkspaceRequirements\n> {}\n\nexport const GoogleWorkspaceOrgUnit = Resource<GoogleWorkspaceOrgUnit>('GoogleWorkspace.OrgUnit', {\n defaultRemovalPolicy: 'retain',\n});\n\nconst customerOf = (props: OrgUnitProps): string => props.customerId ?? 'my_customer';\n/** The path segment the API wants, split from its own leading-name component for `name`/`insert`. */\nconst nameOf = (props: OrgUnitProps): string =>\n props.orgUnitPath.split('/').filter(Boolean).pop() ?? props.orgUnitPath;\nconst parentOf = (props: OrgUnitProps): string => props.parentOrgUnitPath ?? '/';\n\nconst attributesOf = (\n live: directory.OrgUnit,\n props: OrgUnitProps,\n): OrgUnitAttributes | undefined => {\n const livePath = live.orgUnitPath === undefined ? undefined : withLeadingSlash(live.orgUnitPath);\n if (livePath === undefined || livePath.toLowerCase() !== props.orgUnitPath.toLowerCase()) {\n return undefined;\n }\n return {\n customerId: customerOf(props),\n description: text(live.description),\n name: text(live.name),\n orgUnitId: text(live.orgUnitId),\n orgUnitPath: livePath,\n parentOrgUnitPath: text(live.parentOrgUnitPath),\n };\n};\n\n/** ★ EXPORTED for direct testing — see org-unit.test.ts. */\nexport const spec: GoogleWorkspaceSpec<\n OrgUnitProps,\n directory.OrgUnit,\n OrgUnitAttributes,\n | directory.GetOrgunitsError\n | directory.InsertOrgunitsError\n | directory.PatchOrgunitsError\n | directory.DeleteOrgunitsError\n> = {\n attributes: attributesOf,\n create: (props) =>\n directory.insertOrgunits({\n body: {\n description: props.description ?? '',\n name: nameOf(props),\n parentOrgUnitPath: parentOf(props),\n },\n customerId: customerOf(props),\n }),\n describe: (props) => `customer/${customerOf(props)}/orgunits${props.orgUnitPath}`,\n destroy: (props, live) =>\n directory.deleteOrgunits({\n customerId: customerOf(props),\n orgUnitPath: stripLeadingSlash(text(live.orgUnitPath) || props.orgUnitPath),\n }),\n fetchLive: (props) =>\n directory\n .getOrgunits({\n customerId: customerOf(props),\n orgUnitPath: stripLeadingSlash(props.orgUnitPath),\n })\n .pipe(Effect.catchTag('NotFound', () => Effect.succeed(undefined))),\n matches: (attributes, props) =>\n attributes.description === (props.description ?? '') &&\n attributes.parentOrgUnitPath === parentOf(props) &&\n attributes.name === nameOf(props),\n update: (props, live) =>\n directory.patchOrgunits({\n body: {\n description: props.description ?? '',\n name: nameOf(props),\n parentOrgUnitPath: parentOf(props),\n },\n customerId: customerOf(props),\n orgUnitPath: stripLeadingSlash(text(live.orgUnitPath) || props.orgUnitPath),\n }),\n};\n\nexport const handlers = googleWorkspaceHandlers(spec);\n\nexport const GoogleWorkspaceOrgUnitProvider = () =>\n Provider.effect(\n GoogleWorkspaceOrgUnit,\n Effect.succeed(GoogleWorkspaceOrgUnit.Provider.of(handlers)),\n );\n",
12
+ "/**\n * Every Google Workspace provider, wired once — the same role `litellmProviders`/`netboxProviders`-\n * style helpers play elsewhere in this kit (S16: \"a subpath's `providers()` plays the same role\" as\n * upstream's `Providers.ts`).\n *\n * Layer.mergeAll(googleWorkspaceProviders(), …the stack's other providers)\n *\n * ⚠️ CREDENTIALS ARE NOT A PARAMETER HERE. Every handler already closes over\n * `CredentialsFromEnv` inside `resource.ts` (`GOOGLE_ACCESS_TOKEN`, resolved at call time — see\n * credentials.ts), so this layer only wires the four resource providers together. `HttpClient`\n * is NOT provided here either — it is ambient in every Alchemy runtime, the same note\n * `litellm/providers.ts` makes for its own family.\n */\nimport * as Layer from 'effect/Layer';\nimport { GoogleWorkspaceDomainAliasProvider } from './domain-alias.ts';\nimport { GoogleWorkspaceGroupProvider } from './group.ts';\nimport { GoogleWorkspaceGroupMemberProvider } from './group-member.ts';\nimport { GoogleWorkspaceOrgUnitProvider } from './org-unit.ts';\n\nexport const googleWorkspaceProviders = () =>\n Layer.mergeAll(\n GoogleWorkspaceGroupProvider(),\n GoogleWorkspaceGroupMemberProvider(),\n GoogleWorkspaceDomainAliasProvider(),\n GoogleWorkspaceOrgUnitProvider(),\n );\n"
13
+ ],
14
+ "mappings": "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",
15
+ "debugId": "B08C209C914E559B64756E2164756E21",
16
+ "names": []
17
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * `GoogleWorkspace.OrgUnit` — one organizational unit (`admin/directory/v1/customer/{id}/orgunits`).
3
+ *
4
+ * ★ `orgUnitPath` IS THE IDENTITY the way `NetboxPrefix.prefix` is. Google's own doc line is
5
+ * copied onto `OrgUnitProps.orgUnitPath` below because it is the one field this resource cannot
6
+ * get wrong: it is DERIVED on read (`parentOrgUnitPath` + `name`) but AUTHORED as a full path on
7
+ * write, and the API's own path-parameter form drops the leading `/` (`GetOrgunitsRequest`'s
8
+ * `orgUnitPath` doc: "the full path … minus the leading `/`"). `values.ts`'s
9
+ * `stripLeadingSlash`/`withLeadingSlash` are the one place that asymmetry is handled.
10
+ *
11
+ * ⛔ THIS RESOURCE DOES NOT REMOVE SUB-UNITS OR MOVE USERS. Deleting an org unit that still has
12
+ * children or members fails server-side (Directory refuses rather than cascading) — nothing
13
+ * here retries around that refusal, matching S11's "observe before deleting", not "force
14
+ * through".
15
+ *
16
+ * ⛔ `defaultRemovalPolicy: 'retain'` — an org unit governs which Workspace services a user can
17
+ * reach (Google's own doc line on `orgUnitPath`); deleting one out from under live users is
18
+ * exactly the kind of hard-to-reverse action this policy exists to require an explicit opt-in
19
+ * for.
20
+ */
21
+ import { Resource } from 'alchemy';
22
+ import * as Provider from 'alchemy/Provider';
23
+ import * as directory from '@distilled.cloud/google-workspace/unstable/admin_directory_v1';
24
+ import * as Effect from 'effect/Effect';
25
+ import type { GoogleWorkspaceRequirements, GoogleWorkspaceSpec } from './resource.ts';
26
+ export interface OrgUnitProps {
27
+ /** The unique id for the Workspace account, or `my_customer` for the caller's own account. */
28
+ customerId?: string;
29
+ /**
30
+ * The full path to the organizational unit, e.g. `/Contractors/2026`. Required. In order to
31
+ * edit an `orgUnitPath`, either update `name` or `parentOrgUnitPath` — this field is derived on
32
+ * read, not itself patchable, so declaring a different path here plans a `replace`.
33
+ */
34
+ orgUnitPath: string;
35
+ /** The parent's full path, e.g. `/Contractors`. Omit for a top-level unit under `/`. */
36
+ parentOrgUnitPath?: string;
37
+ description?: string;
38
+ }
39
+ export interface OrgUnitAttributes {
40
+ customerId: string;
41
+ orgUnitPath: string;
42
+ parentOrgUnitPath: string;
43
+ name: string;
44
+ description: string;
45
+ orgUnitId: string;
46
+ }
47
+ export interface GoogleWorkspaceOrgUnit extends Resource<'GoogleWorkspace.OrgUnit', OrgUnitProps, OrgUnitAttributes, never, GoogleWorkspaceRequirements> {
48
+ }
49
+ export declare const GoogleWorkspaceOrgUnit: import("alchemy").ResourceClass<GoogleWorkspaceOrgUnit>;
50
+ /** ★ EXPORTED for direct testing — see org-unit.test.ts. */
51
+ export declare const spec: GoogleWorkspaceSpec<OrgUnitProps, directory.OrgUnit, OrgUnitAttributes, directory.GetOrgunitsError | directory.InsertOrgunitsError | directory.PatchOrgunitsError | directory.DeleteOrgunitsError>;
52
+ export declare const handlers: {
53
+ list: () => Effect.Effect<never[], never, never>;
54
+ read: ({ olds }: {
55
+ olds: OrgUnitProps;
56
+ }) => Effect.Effect<OrgUnitAttributes | 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>;
57
+ diff: ({ news, output }: {
58
+ news: import("alchemy").Input<OrgUnitProps>;
59
+ output: OrgUnitAttributes | undefined;
60
+ }) => Effect.Effect<{
61
+ readonly action: 'update';
62
+ } | {
63
+ readonly action: 'noop';
64
+ } | {
65
+ readonly action: 'replace';
66
+ } | 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>;
67
+ reconcile: ({ news }: {
68
+ news: OrgUnitProps;
69
+ }) => Effect.Effect<OrgUnitAttributes, 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>;
70
+ delete: ({ olds }: {
71
+ olds: OrgUnitProps;
72
+ }) => 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>;
73
+ };
74
+ export declare const GoogleWorkspaceOrgUnitProvider: () => import("effect/Layer").Layer<Provider.Provider<GoogleWorkspaceOrgUnit>, never, import("effect/unstable/http/HttpClient").HttpClient>;
75
+ //# sourceMappingURL=org-unit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"org-unit.d.ts","sourceRoot":"","sources":["../../src/google-workspace/org-unit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;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,YAAY;IAC3B,8FAA8F;IAC9F,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,wFAAwF;IACxF,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,sBAAuB,SAAQ,QAAQ,CACtD,yBAAyB,EACzB,YAAY,EACZ,iBAAiB,EACjB,KAAK,EACL,2BAA2B,CAC5B;CAAG;AAEJ,eAAO,MAAM,sBAAsB,yDAEjC,CAAC;AA0BH,4DAA4D;AAC5D,eAAO,MAAM,IAAI,EAAE,mBAAmB,CACpC,YAAY,EACZ,SAAS,CAAC,OAAO,EACjB,iBAAiB,EACf,SAAS,CAAC,gBAAgB,GAC1B,SAAS,CAAC,mBAAmB,GAC7B,SAAS,CAAC,kBAAkB,GAC5B,SAAS,CAAC,mBAAmB,CAuChC,CAAC;AAEF,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;CAAgC,CAAC;AAEtD,eAAO,MAAM,8BAA8B,4IAIxC,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Every Google Workspace provider, wired once — the same role `litellmProviders`/`netboxProviders`-
3
+ * style helpers play elsewhere in this kit (S16: "a subpath's `providers()` plays the same role" as
4
+ * upstream's `Providers.ts`).
5
+ *
6
+ * Layer.mergeAll(googleWorkspaceProviders(), …the stack's other providers)
7
+ *
8
+ * ⚠️ CREDENTIALS ARE NOT A PARAMETER HERE. Every handler already closes over
9
+ * `CredentialsFromEnv` inside `resource.ts` (`GOOGLE_ACCESS_TOKEN`, resolved at call time — see
10
+ * credentials.ts), so this layer only wires the four resource providers together. `HttpClient`
11
+ * is NOT provided here either — it is ambient in every Alchemy runtime, the same note
12
+ * `litellm/providers.ts` makes for its own family.
13
+ */
14
+ import * as Layer from 'effect/Layer';
15
+ export declare const googleWorkspaceProviders: () => Layer.Layer<import("alchemy").Provider<import("./domain-alias.ts").GoogleWorkspaceDomainAlias> | import("alchemy").Provider<import("./group.ts").GoogleWorkspaceGroup> | import("alchemy").Provider<import("./group-member.ts").GoogleWorkspaceGroupMember> | import("alchemy").Provider<import("./org-unit.ts").GoogleWorkspaceOrgUnit>, never, import("effect/unstable/http/HttpClient").HttpClient>;
16
+ //# sourceMappingURL=providers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../../src/google-workspace/providers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAMtC,eAAO,MAAM,wBAAwB,8YAMlC,CAAC"}
@@ -0,0 +1,80 @@
1
+ import type { Input } from 'alchemy/Input';
2
+ import type { GoogleWorkspaceOpContext } from '@distilled.cloud/google-workspace/Protocol';
3
+ import * as Effect from 'effect/Effect';
4
+ import type * as HttpClient from 'effect/unstable/http/HttpClient';
5
+ /**
6
+ * ★ WHAT EVERY HANDLER NEEDS FROM THE CALLER'S RUNTIME, after Credentials are provided below.
7
+ * `GoogleWorkspaceOpContext` is `Credentials | HttpClient.HttpClient`; `googleWorkspaceHandlers`
8
+ * closes over `CredentialsFromEnv` itself (`GOOGLE_ACCESS_TOKEN` / `GOOGLE_PROJECT_ID` — see
9
+ * credentials.ts), so a consuming stack only has to provide `HttpClient.HttpClient`, the same
10
+ * shape every other distilled-backed family in this kit asks for.
11
+ */
12
+ export type GoogleWorkspaceRequirements = HttpClient.HttpClient;
13
+ /**
14
+ * One Directory object's distilled calls. `Live` is whatever the SDK decodes (a `Group`, a
15
+ * `Member`, a `DomainAlias`, an `OrgUnit`) — never an untyped response body.
16
+ *
17
+ * `E` is left to each resource file to declare (the union of the distilled operations it
18
+ * actually calls), so a call site's precise per-operation error union flows straight through
19
+ * instead of being widened by hand here.
20
+ */
21
+ export type GoogleWorkspaceSpec<Props extends object, Live, Attributes, E> = {
22
+ /** Already folds a real "absent" outcome — a `catchTag('NotFound', ...)` at the call site. */
23
+ readonly fetchLive: (props: Props) => Effect.Effect<Live | undefined, E, GoogleWorkspaceOpContext>;
24
+ readonly attributes: (live: Live, props: Props) => Attributes | undefined;
25
+ readonly matches: (attributes: Attributes, props: Props) => boolean;
26
+ /** Absent for an existence-only family (none here yet, but `GroupMember` could become one). */
27
+ readonly create?: (props: Props) => Effect.Effect<unknown, E, GoogleWorkspaceOpContext>;
28
+ readonly update?: (props: Props, live: Live) => Effect.Effect<unknown, E, GoogleWorkspaceOpContext>;
29
+ readonly destroy: (props: Props, live: Live) => Effect.Effect<unknown, E, GoogleWorkspaceOpContext>;
30
+ /** For the read-back-failure message. */
31
+ readonly describe: (props: Props) => string;
32
+ };
33
+ export declare const googleWorkspaceOperations: <Props extends object, Live, Attributes, E>(spec: GoogleWorkspaceSpec<Props, Live, Attributes, E>) => {
34
+ read: (props: Props) => Effect.Effect<Attributes | undefined, E, GoogleWorkspaceOpContext>;
35
+ diff: (news: Input<Props>, output: Attributes | undefined) => Effect.Effect<{
36
+ readonly action: 'update';
37
+ } | {
38
+ readonly action: 'noop';
39
+ } | {
40
+ readonly action: 'replace';
41
+ } | undefined, E, GoogleWorkspaceOpContext>;
42
+ reconcile: (news: Props) => Effect.Effect<Attributes & ({} | null), E, GoogleWorkspaceOpContext>;
43
+ destroy: (olds: Props) => Effect.Effect<void, E, GoogleWorkspaceOpContext>;
44
+ };
45
+ /**
46
+ * The five provider handlers for a spec'd Directory object, wired once.
47
+ *
48
+ * ★ CREDENTIALS ARE PROVIDED HERE, ONCE, NOT BY EVERY CALLER. `CredentialsFromEnv` resolves
49
+ * `GOOGLE_ACCESS_TOKEN` on the calling fiber per request (credentials.ts's own note), so this
50
+ * closure keeps that laziness — nothing is captured at module load.
51
+ *
52
+ * ⛔ IT STOPS AT THE HANDLERS AND DOES NOT RETURN THE LAYER — same reasoning as `forgejoHandlers`.
53
+ *
54
+ * ⚠️ `list` answers empty. The Directory API's list endpoints page the whole domain — every group,
55
+ * every org unit — so adoption stays explicit rather than sweeping (same posture as NetBox and
56
+ * Forgejo; the census handoff script, not this provider, is where a full inventory belongs).
57
+ */
58
+ export declare const googleWorkspaceHandlers: <Props extends object, Live, Attributes extends object, E>(spec: GoogleWorkspaceSpec<Props, Live, Attributes, E>) => {
59
+ list: () => Effect.Effect<never[], never, never>;
60
+ read: ({ olds }: {
61
+ olds: Props;
62
+ }) => Effect.Effect<Attributes | undefined, E, HttpClient.HttpClient>;
63
+ diff: ({ news, output }: {
64
+ news: Input<Props>;
65
+ output: Attributes | undefined;
66
+ }) => Effect.Effect<{
67
+ readonly action: 'update';
68
+ } | {
69
+ readonly action: 'noop';
70
+ } | {
71
+ readonly action: 'replace';
72
+ } | undefined, E, HttpClient.HttpClient>;
73
+ reconcile: ({ news }: {
74
+ news: Props;
75
+ }) => Effect.Effect<Attributes, E, HttpClient.HttpClient>;
76
+ delete: ({ olds }: {
77
+ olds: Props;
78
+ }) => Effect.Effect<void, E, HttpClient.HttpClient>;
79
+ };
80
+ //# sourceMappingURL=resource.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../../src/google-workspace/resource.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAE3C,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,4CAA4C,CAAC;AAC3F,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,UAAU,MAAM,iCAAiC,CAAC;AAEnE;;;;;;GAMG;AACH,MAAM,MAAM,2BAA2B,GAAG,UAAU,CAAC,UAAU,CAAC;AAEhE;;;;;;;GAOG;AACH,MAAM,MAAM,mBAAmB,CAAC,KAAK,SAAS,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,IAAI;IAC3E,8FAA8F;IAC9F,QAAQ,CAAC,SAAS,EAAE,CAClB,KAAK,EAAE,KAAK,KACT,MAAM,CAAC,MAAM,CAAC,IAAI,GAAG,SAAS,EAAE,CAAC,EAAE,wBAAwB,CAAC,CAAC;IAClE,QAAQ,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,KAAK,UAAU,GAAG,SAAS,CAAC;IAC1E,QAAQ,CAAC,OAAO,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC;IACpE,+FAA+F;IAC/F,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,EAAE,wBAAwB,CAAC,CAAC;IACxF,QAAQ,CAAC,MAAM,CAAC,EAAE,CAChB,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,IAAI,KACP,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,EAAE,wBAAwB,CAAC,CAAC;IACzD,QAAQ,CAAC,OAAO,EAAE,CAChB,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,IAAI,KACP,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,EAAE,wBAAwB,CAAC,CAAC;IACzD,yCAAyC;IACzC,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,CAAC;CAC7C,CAAC;AAEF,eAAO,MAAM,yBAAyB,GAAI,KAAK,SAAS,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,QAC3E,mBAAmB,CAAC,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;kBAEhC,KAAK;iBAQX,KAAK,CAAC,KAAK,CAAC,UAAU,UAAU,GAAG,SAAS;yBAIZ,QAAQ;;yBACF,MAAM;;yBAEtC,SAAS;;sBAIV,KAAK;oBA+BP,KAAK;CAOxB,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,uBAAuB,GAAI,KAAK,SAAS,MAAM,EAAE,IAAI,EAAE,UAAU,SAAS,MAAM,EAAE,CAAC,QACxF,mBAAmB,CAAC,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;IAMnD,IAAI;IACJ,IAAI,aAAa;QAAE,IAAI,EAAE,KAAK,CAAA;KAAE;IAChC,IAAI,qBAAqB;QAAE,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;QAAC,MAAM,EAAE,UAAU,GAAG,SAAS,CAAA;KAAE;yBArElC,QAAQ;;yBACF,MAAM;;yBAEtC,SAAS;;IAoE5B,SAAS,aAAa;QAAE,IAAI,EAAE,KAAK,CAAA;KAAE;IACrC,MAAM,aAAa;QAAE,IAAI,EAAE,KAAK,CAAA;KAAE;CAErC,CAAC"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Wire coercions shared across Google Workspace resources.
3
+ *
4
+ * ⚠️ EVERY FIELD ON A GENERATED DIRECTORY TYPE IS `S.optional(...)` (the SDK does not know which
5
+ * fields Google always sends), so a live read is `string | undefined` even for a field the API
6
+ * guarantees in practice. These helpers turn that into the settled value a `matches()` compares
7
+ * against, mirroring `../netbox/values.ts` and `../forgejo/values.ts` for the same reason: a
8
+ * `diff` that compared `undefined` against a declared default would see drift forever.
9
+ */
10
+ /** `''` for absent — Directory objects that omit a text field mean "no value", not `undefined`. */
11
+ export declare const text: (value: string | undefined) => string;
12
+ export declare const bool: (value: boolean | undefined) => boolean;
13
+ /**
14
+ * Wire arrays compared in sorted order. `StringList` (group aliases, org unit paths in a search)
15
+ * decodes to `string[] | undefined`; Directory does not promise an order.
16
+ */
17
+ export declare const stringArray: (value: readonly string[] | undefined) => string[];
18
+ /**
19
+ * Every user- and group-facing key in this family is case-insensitive on the wire (Google lower-
20
+ * cases email addresses and org unit path segments internally) but a declaration may be typed in
21
+ * mixed case. Compare through this, never with `===`, or an adopted object drifts on every plan.
22
+ */
23
+ export declare const ciEqual: (left: string, right: string) => boolean;
24
+ /** An org unit path as the API wants it for a path segment: no leading `/`. */
25
+ export declare const stripLeadingSlash: (path: string) => string;
26
+ /** An org unit path as attributes should read it back: exactly one leading `/`. */
27
+ export declare const withLeadingSlash: (path: string) => string;
28
+ //# sourceMappingURL=values.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"values.d.ts","sourceRoot":"","sources":["../../src/google-workspace/values.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,mGAAmG;AACnG,eAAO,MAAM,IAAI,UAAW,MAAM,GAAG,SAAS,KAAG,MAAqB,CAAC;AAEvE,eAAO,MAAM,IAAI,UAAW,OAAO,GAAG,SAAS,KAAG,OAAyB,CAAC;AAE5E;;;GAGG;AACH,eAAO,MAAM,WAAW,UAAW,SAAS,MAAM,EAAE,GAAG,SAAS,KAAG,MAAM,EACvB,CAAC;AAEnD;;;;GAIG;AACH,eAAO,MAAM,OAAO,SAAU,MAAM,SAAS,MAAM,KAAG,OACV,CAAC;AAE7C,+EAA+E;AAC/E,eAAO,MAAM,iBAAiB,SAAU,MAAM,KAAG,MAAkC,CAAC;AAEpF,mFAAmF;AACnF,eAAO,MAAM,gBAAgB,SAAU,MAAM,KAAG,MACN,CAAC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Grafana credentials for this family — a URL plus a TOKEN REFERENCE (an env var NAME), never a
3
+ * literal secret. Wraps `@distilled.cloud/grafana`'s own `Credentials` context directly (S23 —
4
+ * reuse the SDK's own client rather than inventing a second one); this file only adds the house's
5
+ * lazy, per-instance resolution on top of it.
6
+ *
7
+ * ★ ONE `GRAFANA_TOKEN`/`GRAFANA_URL` PAIR ISN'T ENOUGH FOR THIS HOUSE. Unlike Forgejo (one
8
+ * instance), the estate runs at least two Grafanas — `grafana.homeflare.dev` and
9
+ * `teslamate-grafana` on CT100, which the unit's own comment says has "Own Access SaaS OIDC
10
+ * client, never grafana.homeflare.dev's". The SDK's `CredentialsFromEnv` reads one fixed pair
11
+ * of names, so a stack declaring against more than one instance needs a target per instance —
12
+ * `grafanaCredentials(target)` below, parameterized the way `litellm/credentials.ts` and
13
+ * `proxmox/credentials.ts` already are in this kit.
14
+ *
15
+ * ⛔ THE TOKEN NEVER TOUCHES A PROP OR AN ATTRIBUTE (S25). `tokenEnv` is a NAME, resolved from
16
+ * `process.env` fresh inside the credentials effect on every request (S24) — a layer built once
17
+ * still re-reads the environment each time an operation runs, the same laziness
18
+ * `litellm/credentials.ts` and the SDK's own `forgejo`/`netbox` `CredentialsFromEnv` have.
19
+ *
20
+ * ⚠️ THE SDK's `Credentials` SERVICE VALUE TYPE IS `Effect.Effect<Config, never, never>` — the
21
+ * error channel is `never` (credentials.d.ts). A missing env var can only surface as a defect
22
+ * here, via `Effect.orDie`, the same trade-off `forgejo/resource.ts` and `netbox/resource.ts`
23
+ * already accept for their own `CredentialsFromEnv`; it is not a choice this file introduces.
24
+ */
25
+ import { Credentials } from '@distilled.cloud/grafana/Credentials';
26
+ import * as Layer from 'effect/Layer';
27
+ export interface GrafanaTarget {
28
+ /** Instance origin, e.g. `https://tesla.homeflare.dev/dash` — never `/api`; the SDK appends it. */
29
+ readonly baseUrl: string;
30
+ /** Env var NAME holding a Grafana service-account token. Never the value itself. */
31
+ readonly tokenEnv: string;
32
+ /** Selects the org a call applies to, via `X-Grafana-Org-Id`. Omit for the default org. */
33
+ readonly orgId?: number | string;
34
+ }
35
+ /** The lazy, per-instance layer `grafanaProviders(target)` composes by default. */
36
+ export declare const grafanaCredentials: (target: GrafanaTarget) => Layer.Layer<Credentials>;
37
+ export type { Credentials };
38
+ //# sourceMappingURL=credentials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../src/grafana/credentials.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAAe,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAGhF,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAGtC,MAAM,WAAW,aAAa;IAC5B,mGAAmG;IACnG,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,oFAAoF;IACpF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,2FAA2F;IAC3F,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAClC;AAuBD,mFAAmF;AACnF,eAAO,MAAM,kBAAkB,WAAY,aAAa,KAAG,KAAK,CAAC,KAAK,CAAC,WAAW,CACrC,CAAC;AAE9C,YAAY,EAAE,WAAW,EAAE,CAAC"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * `Grafana.Datasource` — one Grafana data source, keyed by `uid`.
3
+ *
4
+ * ★ WHY `uid` IS REQUIRED, NOT GENERATED. `getDataSourceByUID`/`updateDataSourceByUID`/
5
+ * `deleteDataSourceByUID` are all UID-keyed, and `getDataSources` (list) takes no filter — so
6
+ * locating a datasource this resource didn't create means listing the whole org and matching by
7
+ * name, the same ambiguity netbox/resource.ts's `soleMatch` exists to catch. Requiring `uid` up
8
+ * front (Grafana accepts a caller-chosen one on create — `AddDataSourceRequest.uid`) sidesteps
9
+ * it entirely: `read`/`reconcile`/`destroy` all key on the one field the API itself is keyed on.
10
+ * `teslamate-grafana`'s live datasource (provisioned from a file, not this resource) has a
11
+ * Grafana-generated uid; the census in the handoff script records it so a first declaration can
12
+ * name it explicitly.
13
+ *
14
+ * ⛔ `secureJsonData` (e.g. a datasource password) NEVER APPEARS AS A LITERAL PROP (S25).
15
+ * `secureJsonDataRefs` takes env var NAMES instead; `resolveSecureJsonData` reads them from
16
+ * `process.env` fresh inside `create`/`update`'s own effect, never at plan time and never
17
+ * returned in `attributes` — Grafana's own read API agrees: `DataSource.secureJsonFields` on a
18
+ * GET is a map of booleans (is-it-set), never the value.
19
+ */
20
+ import { Resource } from 'alchemy';
21
+ import * as Provider from 'alchemy/Provider';
22
+ import * as grafana from '@distilled.cloud/grafana';
23
+ import * as Effect from 'effect/Effect';
24
+ import { type GrafanaSpec } from './resource.ts';
25
+ export interface DatasourceProps {
26
+ uid: string;
27
+ name: string;
28
+ /** e.g. `postgres`, `prometheus`, `influxdb` — the Grafana plugin id. */
29
+ type: string;
30
+ url?: string;
31
+ access?: 'proxy' | 'direct';
32
+ database?: string;
33
+ user?: string;
34
+ basicAuth?: boolean;
35
+ basicAuthUser?: string;
36
+ isDefault?: boolean;
37
+ withCredentials?: boolean;
38
+ jsonData?: Record<string, unknown>;
39
+ /** Secure field name (e.g. `password`) -> env var NAME holding its value. Never the value. */
40
+ secureJsonDataRefs?: Record<string, string>;
41
+ }
42
+ export interface DatasourceAttributes {
43
+ uid: string;
44
+ id: number;
45
+ name: string;
46
+ type: string;
47
+ url: string;
48
+ access: string;
49
+ orgId: number;
50
+ isDefault: boolean;
51
+ readOnly: boolean;
52
+ version: number;
53
+ }
54
+ export interface GrafanaDatasource extends Resource<'Grafana.Datasource', DatasourceProps, DatasourceAttributes, never> {
55
+ }
56
+ export declare const GrafanaDatasource: import("alchemy").ResourceClass<GrafanaDatasource>;
57
+ declare const GrafanaSecretRefUnsetError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
58
+ readonly _tag: "GrafanaSecretRefUnsetError";
59
+ } & Readonly<A>;
60
+ /** A named secure-field env var is unset — a domain refusal, not a distilled error. */
61
+ export declare class GrafanaSecretRefUnsetError extends GrafanaSecretRefUnsetError_base<{
62
+ readonly message: string;
63
+ }> {
64
+ }
65
+ export declare const spec: GrafanaSpec<DatasourceProps, grafana.DataSource, DatasourceAttributes, grafana.AddDataSourceError | grafana.GetDataSourceByUIDError | grafana.UpdateDataSourceByUIDError | grafana.DeleteDataSourceByUIDError | GrafanaSecretRefUnsetError>;
66
+ export declare const handlers: {
67
+ list: () => Effect.Effect<never[], never, never>;
68
+ read: ({ olds }: {
69
+ olds: DatasourceProps;
70
+ }) => Effect.Effect<DatasourceAttributes | undefined, grafana.BadGateway | import("@distilled.cloud/caddy").BadRequest | grafana.BadRequest | grafana.ConfigError | import("@distilled.cloud/caddy").Conflict | grafana.Conflict | import("@distilled.cloud/caddy").Forbidden | grafana.Forbidden | grafana.GatewayTimeout | GrafanaSecretRefUnsetError | import("effect/unstable/http/HttpClientError").HttpClientError | grafana.InternalServerError | grafana.Locked | import("@distilled.cloud/caddy").NotFound | grafana.NotFound | grafana.ServiceUnavailable | grafana.TooManyRequests | grafana.Unauthorized | grafana.UnknownGrafanaError | import("@distilled.cloud/caddy").UnprocessableEntity, grafana.GrafanaOpContext>;
71
+ diff: ({ news, output }: {
72
+ news: import("alchemy").Input<DatasourceProps>;
73
+ output: DatasourceAttributes | undefined;
74
+ }) => Effect.Effect<{
75
+ readonly action: 'update';
76
+ } | {
77
+ readonly action: 'noop';
78
+ } | {
79
+ readonly action: 'replace';
80
+ } | undefined, grafana.BadGateway | import("@distilled.cloud/caddy").BadRequest | grafana.BadRequest | grafana.ConfigError | import("@distilled.cloud/caddy").Conflict | grafana.Conflict | import("@distilled.cloud/caddy").Forbidden | grafana.Forbidden | grafana.GatewayTimeout | GrafanaSecretRefUnsetError | import("effect/unstable/http/HttpClientError").HttpClientError | grafana.InternalServerError | grafana.Locked | import("@distilled.cloud/caddy").NotFound | grafana.NotFound | grafana.ServiceUnavailable | grafana.TooManyRequests | grafana.Unauthorized | grafana.UnknownGrafanaError | import("@distilled.cloud/caddy").UnprocessableEntity, grafana.GrafanaOpContext>;
81
+ reconcile: ({ news }: {
82
+ news: DatasourceProps;
83
+ }) => Effect.Effect<DatasourceAttributes, grafana.BadGateway | import("@distilled.cloud/caddy").BadRequest | grafana.BadRequest | grafana.ConfigError | import("@distilled.cloud/caddy").Conflict | grafana.Conflict | import("@distilled.cloud/caddy").Forbidden | grafana.Forbidden | grafana.GatewayTimeout | GrafanaSecretRefUnsetError | import("effect/unstable/http/HttpClientError").HttpClientError | grafana.InternalServerError | grafana.Locked | import("@distilled.cloud/caddy").NotFound | grafana.NotFound | grafana.ServiceUnavailable | grafana.TooManyRequests | grafana.Unauthorized | grafana.UnknownGrafanaError | import("@distilled.cloud/caddy").UnprocessableEntity, grafana.GrafanaOpContext>;
84
+ delete: ({ olds }: {
85
+ olds: DatasourceProps;
86
+ }) => Effect.Effect<void, grafana.BadGateway | import("@distilled.cloud/caddy").BadRequest | grafana.BadRequest | grafana.ConfigError | import("@distilled.cloud/caddy").Conflict | grafana.Conflict | import("@distilled.cloud/caddy").Forbidden | grafana.Forbidden | grafana.GatewayTimeout | GrafanaSecretRefUnsetError | import("effect/unstable/http/HttpClientError").HttpClientError | grafana.InternalServerError | grafana.Locked | import("@distilled.cloud/caddy").NotFound | grafana.NotFound | grafana.ServiceUnavailable | grafana.TooManyRequests | grafana.Unauthorized | grafana.UnknownGrafanaError | import("@distilled.cloud/caddy").UnprocessableEntity, grafana.GrafanaOpContext>;
87
+ };
88
+ export declare const GrafanaDatasourceProvider: () => import("effect/Layer").Layer<Provider.Provider<GrafanaDatasource>, never, grafana.GrafanaOpContext>;
89
+ export {};
90
+ //# sourceMappingURL=datasource.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"datasource.d.ts","sourceRoot":"","sources":["../../src/grafana/datasource.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,OAAO,MAAM,0BAA0B,CAAC;AAEpD,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,KAAK,WAAW,EAAmB,MAAM,eAAe,CAAC;AAElE,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,8FAA8F;IAC9F,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7C;AAED,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAkB,SAAQ,QAAQ,CACjD,oBAAoB,EACpB,eAAe,EACf,oBAAoB,EACpB,KAAK,CACN;CAAG;AAEJ,eAAO,MAAM,iBAAiB,oDAAoD,CAAC;;;;AAEnF,uFAAuF;AACvF,qBAAa,0BAA2B,SAAQ,gCAA+C;IAC7F,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;CAAG;AAwCL,eAAO,MAAM,IAAI,EAAE,WAAW,CAC5B,eAAe,EACf,OAAO,CAAC,UAAU,EAClB,oBAAoB,EAClB,OAAO,CAAC,kBAAkB,GAC1B,OAAO,CAAC,uBAAuB,GAC/B,OAAO,CAAC,0BAA0B,GAClC,OAAO,CAAC,0BAA0B,GAClC,0BAA0B,CAgD7B,CAAC;AAEF,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;CAAwB,CAAC;AAE9C,eAAO,MAAM,yBAAyB,2GACuD,CAAC"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * A minimal in-memory Grafana HTTP API for this family's tests — an in-memory `fetch` handed to
3
+ * Effect's real `FetchHttpClient` through its `Fetch` reference, mirroring
4
+ * `../forgejo/fake-forgejo.ts` and `../netbox/fake-netbox.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. No provider imports this file. The base URL is RFC 2606
10
+ * `grafana.example.com`, and the token is a placeholder.
11
+ */
12
+ import { type Credentials } from '@distilled.cloud/grafana/Credentials';
13
+ import * as Layer from 'effect/Layer';
14
+ export declare const FAKE_BASE = "https://grafana.example.com";
15
+ export declare const FAKE_TOKEN = "placeholder-grafana-token";
16
+ export type Seen = {
17
+ readonly method: string;
18
+ readonly path: string;
19
+ };
20
+ /** Grafana's `{ message?, status? }` failure envelope — matching the SDK's own protocol.ts note. */
21
+ export declare const fakeFailure: (status: number, message: string) => Response;
22
+ /**
23
+ * `route` decides every response; this wrapper only records what was sent and adapts to
24
+ * `globalThis.fetch`'s signature. A test that expects no request at all asserts `seen` is empty.
25
+ *
26
+ * ★ `bodies` IS A SEPARATE, PARALLEL ARRAY (index-matched to `seen`), NOT FOLDED INTO IT — so
27
+ * every existing `expect(fake.seen).toEqual([{method, path}, …])` stays exact rather than
28
+ * growing an unrelated `body` key it would then have to repeat on every entry. A test that
29
+ * cares what was actually sent (not just that a request was) reads `bodies[n]` instead of only
30
+ * checking a request landed — the request landing is not proof of what it carried.
31
+ */
32
+ export declare const fakeGrafana: (route: (method: string, url: URL, body: unknown) => Response) => {
33
+ fetch: typeof fetch;
34
+ seen: Seen[];
35
+ bodies: unknown[];
36
+ };
37
+ /**
38
+ * distilled `Credentials` (via the SDK's own `fromApiKey`) + the real `FetchHttpClient` over the
39
+ * fake — `GrafanaOpContext`, what every resource file's exported `spec.fetchLive`/`create`/
40
+ * `update`/`destroy` needs. Unlike `grafanaHandlers`'s production callers, this never touches
41
+ * `process.env` — `grafanaCredentials` reads `tokenEnv` fresh per call (credentials.ts's own
42
+ * note), which a test-time `process.env` mutation cannot reliably reach either (measured for the
43
+ * same reason in the forgejo/netbox migrations: Effect's default `Config` provider snapshots the
44
+ * environment). Passing an explicit layer exercises exactly the same production code without
45
+ * that seam.
46
+ */
47
+ export declare const fakeGrafanaLayer: (fetchFn: typeof globalThis.fetch, creds?: Layer.Layer<Credentials>) => Layer.Layer<Credentials | import("effect/unstable/http/HttpClient").HttpClient, never, never>;
48
+ //# sourceMappingURL=fake-grafana.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fake-grafana.d.ts","sourceRoot":"","sources":["../../src/grafana/fake-grafana.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,KAAK,WAAW,EAAc,MAAM,sCAAsC,CAAC;AACpF,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAGtC,eAAO,MAAM,SAAS,gCAAgC,CAAC;AACvD,eAAO,MAAM,UAAU,8BAA8B,CAAC;AAEtD,MAAM,MAAM,IAAI,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtE,oGAAoG;AACpG,eAAO,MAAM,WAAW,WAAY,MAAM,WAAW,MAAM,aACnB,CAAC;AAEzC;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,UAAW,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,KAAK,QAAQ;;;;CAcvF,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,YAClB,OAAO,UAAU,CAAC,KAAK,UACzB,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,kGAC+D,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Grafana 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 (resource.ts's engine, fake-grafana.ts)
6
+ * are internals a provider needs but a consumer should not depend on.
7
+ *
8
+ * ⛔ ONLY `Grafana.Datasource` SHIPS HERE. `@distilled.cloud/grafana@1.0.0-rc.12` has no folder,
9
+ * dashboard, alert-rule or contact-point CRUD operations — see docs/grafana.md and
10
+ * docs/upstream-conformance.md for the measured gap. Adding those resources now would mean
11
+ * hand-rolling an `HttpClient` client for them, which S23 forbids for a vendor distilled
12
+ * already covers in part; the right fix is upstream, in distilled, not a house workaround here.
13
+ */
14
+ export { type DatasourceAttributes, type DatasourceProps, GrafanaDatasource, GrafanaDatasourceProvider, GrafanaSecretRefUnsetError, } from './datasource.ts';
15
+ export { type GrafanaTarget, grafanaCredentials } from './credentials.ts';
16
+ export { grafanaProviders } from './providers.ts';
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/grafana/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,iBAAiB,EACjB,yBAAyB,EACzB,0BAA0B,GAC3B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,KAAK,aAAa,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC"}