Skip to content

πŸ—οΈ PUT-1735 + PUT-1737 + PUT-1738 + PUT-1739: the puter.teams SDK module, suite and docs - #3741

Open
jfcastro92 wants to merge 1 commit into
juancastro/put-1732-hard-delete-on-requestfrom
juancastro/put-1735-puter-teams-sdk-module
Open

πŸ—οΈ PUT-1735 + PUT-1737 + PUT-1738 + PUT-1739: the puter.teams SDK module, suite and docs#3741
jfcastro92 wants to merge 1 commit into
juancastro/put-1732-hard-delete-on-requestfrom
juancastro/put-1735-puter-teams-sdk-module

Conversation

@jfcastro92

Copy link
Copy Markdown
Collaborator

Closes PUT-1735, PUT-1737, PUT-1738 and PUT-1739. Stacked on #3740.

Four tickets, one surface: the module, the suite that exercises it, the pages that document it, and the limits those pages publish. Splitting them would mean shipping a module with no docs, then docs describing a module already merged.

Why routes and not a driver

Workspace administration reaches the backend through /teams rather than a driver interface, because the gates it needs are route-level: a user actor, a verified account, and a dual-window rate limit. A driver has none of those.

Layout

Follows apps/ and perms/ β€” one file per method, a thin index.js, a JSDoc-only types.js, and the METHODS rebinding so a destructured method keeps its this.

It deliberately does not follow perms/lib/req.js. Those endpoints resolve { error: true } for backward compatibility; nothing here has callers to keep compatible, so this throws PuterJSError carrying the backend's own code.

Two decisions worth a reviewer's eye

Every method takes a uid, never a handle. A handle is a mutable label that deleting the workspace releases, so a stored handle can later resolve to a different workspace. Accepting one would make that a silent wrong-target bug rather than a 404.

The list methods refuse offset. They offer the three forms puter.apps.list() does β€” array by default, page envelope under cursor/includeTotal, async iterator under stream. These routes are keyset-only, so an accepted offset would return page one however far you asked to skip. Refusing beats lying.

Scope

The surface covers the routes that exist today. Usage totals and member-email correction have no backend route yet and are absent rather than shipped as methods that 404.

deleteMember() is here β€” the route it needs lands in #3740, directly below this. Worth flagging: this PR is unmergeable ahead of that one, by design.

Docs

Teams.md plus 14 method pages, each carrying the beta banner in the same position Events/ puts it β€” on every page, not only the index, since a reader arriving from search lands on a method page.

Limits published in rate-limits-and-quotas.md (PUT-1739), including that both buckets are per account rather than per workspace, so administering several workspaces spends one budget.

Verification

Typecheck clean. check:puterjs:types β€” declarations generate and type-check cleanly. Backend suite 7887 passed | 26 skipped. SDK unit tests 23 passed, and every commit in the stack typechecks on its own rather than just the tips.

teams.suite.ts (PUT-1737) is registered in suites/index.ts and gated on the teams capability, so it skips where a deployment has teams off instead of failing.

⚠ npm run check-translations could not be run: the script points at tools/check-translations.js, which does not exist on main either. Pre-existing, unrelated to this PR, and worth its own ticket.

@jfcastro92

Copy link
Copy Markdown
Collaborator Author

Terminology: "master account" is now "owner account" throughout the workspace surface β€” docs, SDK JSDoc, GUI strings and backend comments. 24 files, no behavior change.

not_the_workspace_owner was already the error code, so the prose now matches the API rather than contradicting it.

@jfcastro92
jfcastro92 force-pushed the juancastro/put-1735-puter-teams-sdk-module branch from 6f375d8 to 5d0e430 Compare September 4, 2026 13:36
@jfcastro92

Copy link
Copy Markdown
Collaborator Author

Terminology: "workspace" is now "team", everywhere

The feature said puter.teams in the namespace and "workspace" in every string, comment and error code. That divergence was deliberate at design time; it is now settled the other way, and the whole stack says team.

Applied to every commit rather than added on top, so each PR shows its own code already named correctly and no intermediate commit references a name a later one changes. All 13 commits still typecheck individually.

The contract-bearing renames

These were free to change now and expensive after release, which is why they went in this pass:

Before After
not_the_workspace_owner not_the_team_owner
workspace_limit_reached team_limit_reached
workspace_deleted team_deleted
max_workspaces_per_user max_teams_per_user
max_seats_per_workspace max_seats_per_team
disabled_by_workspace disabled_by_team
createWorkspace / updateWorkspace / deleteWorkspace createTeam / updateTeam / deleteTeam

Plus twoWorkspaces.ts β†’ twoTeams.ts, shareWorkspaces.js β†’ shareTeams.js, the teams-workspace-* CSS classes, and the team_workspace_deleted email template, which the mechanical pass turned into the stutter team_team_deleted and is now team_closed.

Scoped, and checked

"workspace" means other things in this repo. These were deliberately left alone, and verified untouched:

  • kvShares.ts and the KV integration tests, where workspace:abc: is a key prefix in an example, not a team
  • Events.md and KV.md, which use it the same way
  • UIPermissionDialog tests, same
  • package.json / webpack/BaseConfig.cjs / .gitignore, where it is npm workspaces

Audited three ways: every changed line mentions team or workspace (no collateral edits), no stutters or mangled words (team_team, teamspace, …), and both old config keys are gone with the new ones consistent across all 12 sites that read them.

One thing not renamed

The git branch names still say workspace (put-1740-1762-workspace-gui). Renaming a branch changes a PR's head ref and closes the PR, so they stay as they are β€” they are refs, not product surface.

@jfcastro92
jfcastro92 force-pushed the juancastro/put-1735-puter-teams-sdk-module branch from 5d0e430 to 6bd216e Compare September 4, 2026 13:46
@jfcastro92
jfcastro92 force-pushed the juancastro/put-1735-puter-teams-sdk-module branch from 6bd216e to edb2aaf Compare September 4, 2026 14:31
@jfcastro92
jfcastro92 force-pushed the juancastro/put-1735-puter-teams-sdk-module branch from edb2aaf to 794a0ce Compare September 4, 2026 15:06
@jfcastro92
jfcastro92 force-pushed the juancastro/put-1735-puter-teams-sdk-module branch from 794a0ce to b5f905a Compare September 4, 2026 15:27
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for puter.js SDK

Status Category Percentage Covered / Total
πŸ”΅ Lines 62.5%
⬆️ +0.56%
4539 / 7262
πŸ”΅ Statements 61.05%
⬆️ +0.53%
4823 / 7899
πŸ”΅ Functions 63.01%
⬆️ +0.69%
864 / 1371
πŸ”΅ Branches 55.07%
⬆️ +0.49%
2999 / 5445
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/puter-js/src/index.js 74.86%
⬆️ +0.40%
62.78%
🟰 ±0%
81.81%
🟰 ±0%
74.85%
⬆️ +0.61%
218, 230, 359-365, 408, 421, 431, 469, 471, 479-483, 522, 528-530, 539-541, 547-549, 555, 560-562, 568, 578-580, 582, 612-614, 619-683, 709, 711, 715-719, 752-762, 777-790, 865, 876, 881, 927, 940, 973-974, 981, 987, 1023-1037, 1059, 1123-1171, 1209, 1583, 1600, 1652-1671, 1776-1816, 1824-1862
src/puter-js/src/modules/teams/create.js 100% 100% 100% 100%
src/puter-js/src/modules/teams/createMember.js 71.42% 75% 100% 71.42% 21, 24
src/puter-js/src/modules/teams/delete.js 100% 100% 100% 100%
src/puter-js/src/modules/teams/deleteMemberAccount.js 100% 100% 100% 100%
src/puter-js/src/modules/teams/disableMember.js 100% 100% 100% 100%
src/puter-js/src/modules/teams/enableMember.js 100% 100% 100% 100%
src/puter-js/src/modules/teams/get.js 100% 100% 100% 100%
src/puter-js/src/modules/teams/index.js 100% 100% 100% 100%
src/puter-js/src/modules/teams/list.js 100% 100% 100% 100%
src/puter-js/src/modules/teams/listAudit.js 100% 100% 100% 100%
src/puter-js/src/modules/teams/listMembers.js 100% 100% 100% 100%
src/puter-js/src/modules/teams/listOwnAudit.js 100% 100% 100% 100%
src/puter-js/src/modules/teams/resendActivation.js 100% 100% 100% 100%
src/puter-js/src/modules/teams/resetPassword.js 0% 100% 0% 0% 21-33
src/puter-js/src/modules/teams/update.js 100% 100% 100% 100%
src/puter-js/src/modules/teams/lib/listRoute.js 88.88% 77.27% 80% 92.85% 57
src/puter-js/src/modules/teams/lib/req.js 79.16% 63.88% 100% 86.36% 30, 44, 55, 58, 74
src/puter-js/src/modules/teams/lib/shapes.js 100% 80.95% 100% 100%
Generated in workflow #397 for commit c9b234c by the Vitest Coverage Report Action

@jfcastro92
jfcastro92 requested a review from Salazareo September 4, 2026 16:27
@jfcastro92

Copy link
Copy Markdown
Collaborator Author

Local validation β€” SDK, suite and docs

Engine mysql 8, a real local instance, current tip. Nothing stubbed but the SDK origin in the browser tests.

The CI failure this PR had, and why it was mine

Every teams case in the browser runner failed with PuterJSError: You may own 1 team. The harness enables teams_enabled but never raised the cap, and the suite creates a team per case against one shared account β€” so every case after the first failed on team_limit_reached rather than on what it tests.

That is a collision between this PR and the cap in #3725, and it only appears when the two are stacked. Fixed in harness/capabilities.ts:

// The shipped default is 1, and the suite creates a team per case
// against one shared account, so every case after the first would
// fail on `team_limit_reached` rather than on what it tests.
max_teams_per_user: 100,

⚠ I had not run npm run test:puterjs locally before pushing β€” only the unit tests. That is the gap that let it through, and it is now part of the phase-6 checklist rather than something I remember to do.

After the fix, run locally:

npm run test:puterjs:node
Test Files  1 passed (1)
     Tests  488 passed | 69 skipped (557)

The API surface, exercised end to end

A 40-check transcript against the running server covers every route this SDK wraps. Extract:

=== 1. Team lifecycle ===
  PASS  create a team β€” uid=ce964c0e-…
  PASS  directory is off by default
  PASS  read it back by uid
  PASS  rename it
=== 2. Seats ===
  PASS  provision a seat β€” credential returned once
  PASS  seat appears with org_owned
  PASS  a taken username is refused β€” 409 username_already_in_use
=== 3. Authority boundaries ===
  PASS  non-member gets 404, not 403 β€” got 404
  PASS  non-member cannot provision
  PASS  the owner is not a valid member-route target

40 passed, 0 failed

Two things the run taught me about the real flows

A provisioned seat has no email_confirm_code. The administrator made the account, so no confirmation mail was ever sent. The activation order is therefore forced: sign in on the temporary password β†’ change password first (the only route the gate admits) β†’ then confirm the address. My first harness did it the other way and got nowhere, which is a fair proxy for what an integrator will hit.

/user-protected/change-password is session-cookie only and rejects a bearer token outright β€” so it is not scriptable with an API token at all. Worth knowing for anyone automating seat onboarding; the SDK method inherits this.

Neither is a defect, but neither is obvious from the docs, and both cost me a real debugging loop.

Verification

Typecheck clean Β· check:puterjs:types clean Β· backend 7897 passed | 26 skipped Β· GUI+SDK unit 275 passed Β· all 13 commits typecheck individually.

Team administration reaches the backend through the `/teams` routes
rather than a driver interface, because the gates it needs are route-level:
a user actor, a verified account, and a dual-window rate limit.

The module follows `apps/` and `perms/` in layout -- one file per method,
a thin `index.js`, a JSDoc-only `types.js`, and the `METHODS` rebinding so a
destructured method keeps its `this`. It does not follow `perms/lib/req.js`:
those endpoints resolve `{ error: true }` for backward compatibility, and
nothing here has callers to keep compatible, so this throws `PuterJSError`
with the backend's own code.

Every method takes a team `uid`. A handle is a mutable label that
deleting the team releases, so a stored handle can later resolve to a
different team.

The list methods offer the three forms `puter.apps.list()` does -- an array
by default, the page envelope under `cursor`/`includeTotal`, an async
iterator under `stream`. They refuse `offset`: these routes are keyset-only
and would otherwise return page one however far you asked to skip.

The surface covers the routes that exist today. Usage totals and member-email
correction have no backend route yet and are deliberately absent rather than
shipped as methods that 404. `deleteMember()` is here because the route it
needs lands in the commit below this one.
@jfcastro92
jfcastro92 force-pushed the juancastro/put-1735-puter-teams-sdk-module branch from b5f905a to c9b234c Compare September 4, 2026 19:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant