Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook. | 1 | /** |
| 2 | * Who and where, for the artifacts service's folio code: the workspace by | |
| Chat, artifacts and notifications no longer serve g1t's own agents. Direct messages and conversation invites take people only, and a space or folio is shared with people and teams. Chat stops waking agents, drops the RPCs agents posted, read, searched and handed off through, and stops answering buttons on agent cards; old cards keep their links. An agent's old messages still show, under its saved name or as "A deleted agent". Artifacts drops the folio and docs RPCs agents read and edited pages with, and an old agent member of a space can still be removed. Notifications drop "an agent is waiting on you". A deployment no longer trusts a pull request with secrets just because g1t's agent opened it, and the sign-up card no longer promises a team of agents. | 3 | * slug, its people and teams, how member keys show, and the |
| The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook. | 4 | * spaces with a person's role in each. Cached per request (one instance |
| 5 | * per request). Docs' page code (src/index.ts, `Docs`) keeps its own copy | |
| 6 | * of these until it is removed. | |
| 7 | */ | |
| 8 | import { | |
| 9 | fail, | |
| 10 | identityClient, | |
| 11 | newId, | |
| 12 | ok, | |
| 13 | parsePrincipalKey, | |
| 14 | principalKey, | |
| 15 | type DocAgentMode, | |
| 16 | type DocRole, | |
| 17 | type DocSpace, | |
| 18 | type DocSpaceKind, | |
| 19 | type Member, | |
| 20 | type MemberProfile, | |
| 21 | type Principal, | |
| 22 | type Result, | |
| 23 | type ServiceBinding, | |
| 24 | type User, | |
| 25 | type Viewer, | |
| 26 | type Workspace, | |
| 27 | } from "@g1t/contracts"; | |
| 28 | ||
| 29 | import { roleOf, type Person, type SpaceRules } from "./access.ts"; | |
| 30 | import { freeSlug } from "./slugs.ts"; | |
| 31 | ||
| Chat, artifacts and notifications no longer serve g1t's own agents. Direct messages and conversation invites take people only, and a space or folio is shared with people and teams. Chat stops waking agents, drops the RPCs agents posted, read, searched and handed off through, and stops answering buttons on agent cards; old cards keep their links. An agent's old messages still show, under its saved name or as "A deleted agent". Artifacts drops the folio and docs RPCs agents read and edited pages with, and an old agent member of a space can still be removed. Notifications drop "an agent is waiting on you". A deployment no longer trusts a pull request with secrets just because g1t's agent opened it, and the sign-up card no longer promises a team of agents. | 32 | export type WhoEnv = { DB: D1Database; IDENTITY: ServiceBinding }; |
| The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook. | 33 | |
| 34 | export type SpaceRow = { | |
| 35 | id: string; | |
| 36 | workspace_id: string; | |
| 37 | slug: string; | |
| 38 | name: string; | |
| 39 | description: string | null; | |
| 40 | icon: string | null; | |
| 41 | kind: DocSpaceKind; | |
| 42 | team: string | null; | |
| 43 | default_role: DocRole | null; | |
| 44 | agent_mode: DocAgentMode; | |
| 45 | /** 1: people with edit access may share what is in it (migration 0005). */ | |
| 46 | editors_can_share: number; | |
| 47 | is_default: number; | |
| 48 | created_by: string; | |
| 49 | created_at: string; | |
| 50 | archived_at: string | null; | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 51 | /** |
| 52 | * Set when the space was deleted (migration 0007). The row stays, so the | |
| 53 | * trash's rows that point at it keep their space's name and rules (whoever | |
| 54 | * could edit them still restores them) and General, once deleted, is not | |
| 55 | * made again; listings leave deleted spaces out. | |
| 56 | */ | |
| 57 | deleted_at: string | null; | |
| 58 | deleted_by: string | null; | |
| The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook. | 59 | }; |
| 60 | ||
| 61 | /** A space, with who is in it and the viewer's role (null: they can't read it). */ | |
| 62 | export type Space = { row: SpaceRow; members: { principal: string; role: DocRole }[]; projects: string[]; role: DocRole | null }; | |
| 63 | ||
| 64 | export const now = () => new Date().toISOString(); | |
| 65 | ||
| A doc opens at once: the page carries the document's saved state, so the editor mounts without waiting for its live room, which connects in the background and sends only what changed, with a quiet dot that turns green when it's synced; edits made before then are kept and sent once. The editor's code is preloaded from the page and fetched early when you hover a doc, the app's shared code ships in a few chunks instead of a hundred small ones, socket tickets come with the page instead of a round trip, and the artifacts service answers a page, a sidebar and a room in parallel lookups instead of a dozen in a row, reporting its database round trips in Server-Timing. The artifacts guide says how a doc opens, and CONTRIBUTING's Speed section has the numbers. | 66 | /** |
| 67 | * Kept across requests in this isolate: the workspace behind a slug for | |
| 68 | * half a minute (identity answers it in ~80 ms, and every folio call | |
| 69 | * starts with it), and which workspaces already have their General | |
| 70 | * space (made once, never unmade). A rename reaches the old slug within | |
| 71 | * that half minute, which is what the site's own redirect allows. | |
| 72 | */ | |
| 73 | const WORKSPACE_TTL_MS = 30_000; | |
| 74 | const workspaces = new Map<string, { at: number; value: Promise<Workspace | null> }>(); | |
| 75 | const defaultsMade = new Set<string>(); | |
| 76 | ||
| 77 | /** For tests: forgets everything kept across requests. */ | |
| 78 | export function forgetKept(): void { | |
| 79 | workspaces.clear(); | |
| 80 | defaultsMade.clear(); | |
| 81 | } | |
| 82 | ||
| The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook. | 83 | export function rulesOf(space: Pick<Space, "row" | "members">): SpaceRules { |
| 84 | return { kind: space.row.kind, team: space.row.team, default_role: space.row.default_role, members: space.members }; | |
| 85 | } | |
| 86 | ||
| 87 | export function isMember(viewer: Viewer, workspace: string): boolean { | |
| 88 | return !!viewer?.workspaces?.some((m) => m.slug === String(workspace ?? "").toLowerCase()); | |
| 89 | } | |
| 90 | ||
| 91 | export function userKey(viewer: Pick<User, "id">): string { | |
| 92 | return principalKey({ kind: "user", id: viewer.id }); | |
| 93 | } | |
| 94 | ||
| 95 | export class Who { | |
| 96 | private readonly workspaces = new Map<string, Promise<Workspace | null>>(); | |
| 97 | private readonly people = new Map<string, Promise<Map<string, Member>>>(); | |
| 98 | private readonly teams = new Map<string, Promise<Map<string, Set<string>>>>(); | |
| 99 | private readonly spaces = new Map<string, Promise<Omit<Space, "role">[]>>(); | |
| 100 | readonly usernames = new Map<string, string>(); | |
| 101 | ||
| 102 | constructor(private readonly env: WhoEnv) {} | |
| 103 | ||
| 104 | workspace(slug: string): Promise<Workspace | null> { | |
| 105 | const key = String(slug ?? "").toLowerCase(); | |
| 106 | let found = this.workspaces.get(key); | |
| 107 | if (!found) { | |
| A doc opens at once: the page carries the document's saved state, so the editor mounts without waiting for its live room, which connects in the background and sends only what changed, with a quiet dot that turns green when it's synced; edits made before then are kept and sent once. The editor's code is preloaded from the page and fetched early when you hover a doc, the app's shared code ships in a few chunks instead of a hundred small ones, socket tickets come with the page instead of a round trip, and the artifacts service answers a page, a sidebar and a room in parallel lookups instead of a dozen in a row, reporting its database round trips in Server-Timing. The artifacts guide says how a doc opens, and CONTRIBUTING's Speed section has the numbers. | 108 | const kept = workspaces.get(key); |
| 109 | if (kept && Date.now() - kept.at < WORKSPACE_TTL_MS) found = kept.value; | |
| 110 | else { | |
| 111 | found = identityClient(this.env.IDENTITY).getWorkspace(key).catch(() => null); | |
| 112 | workspaces.set(key, { at: Date.now(), value: found }); | |
| 113 | // Only an answer is kept: a miss or a failure is asked again next time. | |
| 114 | void found.then((w) => { | |
| 115 | if (!w) workspaces.delete(key); | |
| 116 | }); | |
| 117 | } | |
| The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook. | 118 | this.workspaces.set(key, found); |
| 119 | } | |
| 120 | return found; | |
| 121 | } | |
| 122 | ||
| 123 | /** The workspace acting for itself: how this service asks identity about its members. */ | |
| 124 | actor(workspace: Workspace): User { | |
| 125 | return { id: workspace.id, username: workspace.slug, kind: "workspace", verified: true, workspaces: [{ slug: workspace.slug, role: "member" }] }; | |
| 126 | } | |
| 127 | ||
| 128 | /** The workspace's people by username (lowercased). */ | |
| 129 | members(workspace: Workspace): Promise<Map<string, Member>> { | |
| 130 | let found = this.people.get(workspace.id); | |
| 131 | if (!found) { | |
| 132 | found = identityClient(this.env.IDENTITY) | |
| 133 | .listMembers(workspace.slug, this.actor(workspace)) | |
| 134 | .then((r) => new Map(r.ok ? r.value.map((m) => [m.username.toLowerCase(), m]) : [])) | |
| 135 | .catch(() => new Map<string, Member>()); | |
| 136 | this.people.set(workspace.id, found); | |
| 137 | } | |
| 138 | return found; | |
| 139 | } | |
| 140 | ||
| 141 | /** Each member's teams (slugs, lowercased), by username. */ | |
| 142 | teamsOf(workspace: Workspace): Promise<Map<string, Set<string>>> { | |
| 143 | let found = this.teams.get(workspace.id); | |
| 144 | if (!found) { | |
| 145 | found = identityClient(this.env.IDENTITY) | |
| 146 | .teamMemberships(this.actor(workspace), workspace.slug) | |
| 147 | .then((r) => new Map(r.ok ? r.value.map((m) => [m.username.toLowerCase(), new Set(m.teams.map((t) => t.slug.toLowerCase()))]) : [])) | |
| 148 | .catch(() => new Map<string, Set<string>>()); | |
| 149 | this.teams.set(workspace.id, found); | |
| 150 | } | |
| 151 | return found; | |
| 152 | } | |
| 153 | ||
| 154 | async nameUsers(ids: string[]): Promise<void> { | |
| 155 | const unnamed = [...new Set(ids)].filter((id) => !this.usernames.has(id)); | |
| 156 | if (!unnamed.length) return; | |
| 157 | const named = await identityClient(this.env.IDENTITY) | |
| 158 | .usernames(unnamed) | |
| 159 | .catch(() => ({}) as Record<string, string>); | |
| 160 | for (const [id, username] of Object.entries(named)) this.usernames.set(id, username); | |
| 161 | } | |
| 162 | ||
| Chat, artifacts and notifications no longer serve g1t's own agents. Direct messages and conversation invites take people only, and a space or folio is shared with people and teams. Chat stops waking agents, drops the RPCs agents posted, read, searched and handed off through, and stops answering buttons on agent cards; old cards keep their links. An agent's old messages still show, under its saved name or as "A deleted agent". Artifacts drops the folio and docs RPCs agents read and edited pages with, and an old agent member of a space can still be removed. Notifications drop "an agent is waiting on you". A deployment no longer trusts a pull request with secrets just because g1t's agent opened it, and the sign-up card no longer promises a team of agents. | 163 | /** |
| 164 | * How member keys show. An agent's key (workspaces no longer have | |
| 165 | * agents) shows as a deleted agent; anything else that isn't a person | |
| 166 | * shows as g1t. | |
| 167 | */ | |
| The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook. | 168 | async profiles(workspace: Workspace, keys: string[]): Promise<Map<string, MemberProfile>> { |
| 169 | const principals = [...new Set(keys)].map((k) => parsePrincipalKey(k)).filter((p): p is Principal => !!p); | |
| 170 | const userIds = principals.filter((p) => p.kind === "user").map((p) => p.id); | |
| Chat, artifacts and notifications no longer serve g1t's own agents. Direct messages and conversation invites take people only, and a space or folio is shared with people and teams. Chat stops waking agents, drops the RPCs agents posted, read, searched and handed off through, and stops answering buttons on agent cards; old cards keep their links. An agent's old messages still show, under its saved name or as "A deleted agent". Artifacts drops the folio and docs RPCs agents read and edited pages with, and an old agent member of a space can still be removed. Notifications drop "an agent is waiting on you". A deployment no longer trusts a pull request with secrets just because g1t's agent opened it, and the sign-up card no longer promises a team of agents. | 171 | const [, people] = await Promise.all([this.nameUsers(userIds), userIds.length ? this.members(workspace) : new Map<string, Member>()]); |
| The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook. | 172 | const out = new Map<string, MemberProfile>(); |
| 173 | for (const p of principals) { | |
| 174 | if (p.kind === "user") { | |
| 175 | const username = this.usernames.get(p.id) ?? null; | |
| 176 | const person = username ? people.get(username.toLowerCase()) : undefined; | |
| 177 | out.set(principalKey(p), { ...p, name: username ?? "ghost", display_name: person?.name || username || "Former member", avatar: person?.avatar ?? null, role: null, title: null, avatar_seed: null }); | |
| 178 | } else { | |
| Chat, artifacts and notifications no longer serve g1t's own agents. Direct messages and conversation invites take people only, and a space or folio is shared with people and teams. Chat stops waking agents, drops the RPCs agents posted, read, searched and handed off through, and stops answering buttons on agent cards; old cards keep their links. An agent's old messages still show, under its saved name or as "A deleted agent". Artifacts drops the folio and docs RPCs agents read and edited pages with, and an old agent member of a space can still be removed. Notifications drop "an agent is waiting on you". A deployment no longer trusts a pull request with secrets just because g1t's agent opened it, and the sign-up card no longer promises a team of agents. | 179 | out.set(principalKey(p), { ...p, name: p.id, display_name: "A deleted agent", avatar: null, role: null, title: null, avatar_seed: null, deleted: true }); |
| The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook. | 180 | } |
| 181 | } | |
| 182 | for (const key of keys) { | |
| 183 | if (!out.has(key)) out.set(key, { kind: "user", id: key, name: "g1t", display_name: "g1t", avatar: null, role: null, title: null, avatar_seed: null }); | |
| 184 | } | |
| 185 | return out; | |
| 186 | } | |
| 187 | ||
| 188 | async viewerWorkspace(slug: string, viewer: Viewer): Promise<Result<Workspace>> { | |
| 189 | if (!viewer?.id) return fail("unauthenticated", "Sign in to use Artifacts."); | |
| 190 | if (!slug || !isMember(viewer, slug)) return fail("forbidden", "Only members of a workspace can use its Artifacts."); | |
| 191 | const workspace = await this.workspace(slug); | |
| 192 | return workspace ? ok(workspace) : fail("not_found", "No such workspace."); | |
| 193 | } | |
| 194 | ||
| 195 | viewerOwner(viewer: User, slug: string): boolean { | |
| 196 | return !!viewer.workspaces?.some((m) => m.slug === slug.toLowerCase() && m.role === "owner"); | |
| 197 | } | |
| 198 | ||
| 199 | /** A person as access sees them: their teams, and whether they own the workspace. */ | |
| 200 | async personOf(workspace: Workspace, user: Pick<User, "id" | "username">, owner: boolean): Promise<Person> { | |
| 201 | const teams = (await this.teamsOf(workspace)).get(String(user.username ?? "").toLowerCase()) ?? new Set<string>(); | |
| 202 | return { user_id: user.id, owner, teams }; | |
| 203 | } | |
| 204 | ||
| 205 | /** The viewer as access sees them. */ | |
| 206 | viewerPerson(workspace: Workspace, viewer: User): Promise<Person> { | |
| 207 | return this.personOf(workspace, viewer, this.viewerOwner(viewer, workspace.slug)); | |
| 208 | } | |
| 209 | ||
| 210 | /** People by user id as access sees them: members' teams and ownership; anyone else reads nothing. */ | |
| 211 | async peopleByIds(workspace: Workspace, ids: string[]): Promise<Person[]> { | |
| 212 | const unique = [...new Set(ids.map(String))].slice(0, 200); | |
| 213 | await this.nameUsers(unique); | |
| 214 | const [members, teams] = await Promise.all([this.members(workspace), this.teamsOf(workspace)]); | |
| 215 | return unique.map((id) => { | |
| 216 | const username = this.usernames.get(id)?.toLowerCase() ?? ""; | |
| 217 | const member = members.get(username); | |
| 218 | return { user_id: member ? id : `outside:${id}`, owner: member?.role === "owner", teams: member ? (teams.get(username) ?? new Set()) : new Set() }; | |
| 219 | }); | |
| 220 | } | |
| 221 | ||
| 222 | /** Every space in the workspace (archived too), with members and projects. */ | |
| 223 | allSpaces(workspace: Workspace): Promise<Omit<Space, "role">[]> { | |
| 224 | let found = this.spaces.get(workspace.id); | |
| 225 | if (!found) { | |
| 226 | found = (async () => { | |
| 227 | const db = this.env.DB; | |
| 228 | const [spaces, members, projects] = await Promise.all([ | |
| 229 | db.prepare("SELECT * FROM spaces WHERE workspace_id = ? ORDER BY is_default DESC, name COLLATE NOCASE").bind(workspace.id).all<SpaceRow>(), | |
| 230 | db | |
| 231 | .prepare("SELECT m.space_id, m.principal, m.role FROM space_members m JOIN spaces s ON s.id = m.space_id WHERE s.workspace_id = ?") | |
| 232 | .bind(workspace.id) | |
| 233 | .all<{ space_id: string; principal: string; role: DocRole }>(), | |
| 234 | db.prepare("SELECT p.space_id, p.repo FROM space_projects p JOIN spaces s ON s.id = p.space_id WHERE s.workspace_id = ?").bind(workspace.id).all<{ space_id: string; repo: string }>(), | |
| 235 | ]); | |
| 236 | return spaces.results.map((row) => ({ | |
| 237 | row, | |
| 238 | members: members.results.filter((m) => m.space_id === row.id).map((m) => ({ principal: m.principal, role: m.role })), | |
| 239 | projects: projects.results.filter((p) => p.space_id === row.id).map((p) => p.repo), | |
| 240 | })); | |
| 241 | })(); | |
| 242 | this.spaces.set(workspace.id, found); | |
| 243 | } | |
| 244 | return found; | |
| 245 | } | |
| 246 | ||
| 247 | /** Forget cached spaces after one changed. */ | |
| 248 | forgetSpaces(): void { | |
| 249 | this.spaces.clear(); | |
| 250 | } | |
| 251 | ||
| 252 | /** The spaces with `person`'s role in each (null: they can't read it). Archived spaces too. */ | |
| 253 | async spacesFor(workspace: Workspace, person: Person): Promise<Space[]> { | |
| 254 | const spaces = await this.allSpaces(workspace); | |
| 255 | return spaces.map((s) => ({ ...s, role: roleOf(rulesOf(s), person) })); | |
| 256 | } | |
| 257 | ||
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 258 | /** |
| 259 | * Makes the workspace's General space, once. A deleted General still | |
| 260 | * has its row (`deleted_at` set), so it is never made again: a | |
| 261 | * workspace may have no spaces. | |
| 262 | */ | |
| The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook. | 263 | async ensureDefault(workspace: Workspace, viewer: User): Promise<void> { |
| A doc opens at once: the page carries the document's saved state, so the editor mounts without waiting for its live room, which connects in the background and sends only what changed, with a quiet dot that turns green when it's synced; edits made before then are kept and sent once. The editor's code is preloaded from the page and fetched early when you hover a doc, the app's shared code ships in a few chunks instead of a hundred small ones, socket tickets come with the page instead of a round trip, and the artifacts service answers a page, a sidebar and a room in parallel lookups instead of a dozen in a row, reporting its database round trips in Server-Timing. The artifacts guide says how a doc opens, and CONTRIBUTING's Speed section has the numbers. | 264 | if (defaultsMade.has(workspace.id)) return; |
| The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook. | 265 | const db = this.env.DB; |
| 266 | const found = await db.prepare("SELECT id FROM spaces WHERE workspace_id = ? AND is_default = 1").bind(workspace.id).first<{ id: string }>(); | |
| A doc opens at once: the page carries the document's saved state, so the editor mounts without waiting for its live room, which connects in the background and sends only what changed, with a quiet dot that turns green when it's synced; edits made before then are kept and sent once. The editor's code is preloaded from the page and fetched early when you hover a doc, the app's shared code ships in a few chunks instead of a hundred small ones, socket tickets come with the page instead of a round trip, and the artifacts service answers a page, a sidebar and a room in parallel lookups instead of a dozen in a row, reporting its database round trips in Server-Timing. The artifacts guide says how a doc opens, and CONTRIBUTING's Speed section has the numbers. | 267 | if (found) { |
| 268 | defaultsMade.add(workspace.id); | |
| 269 | return; | |
| 270 | } | |
| The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook. | 271 | const taken = new Set((await db.prepare("SELECT slug FROM spaces WHERE workspace_id = ?").bind(workspace.id).all<{ slug: string }>()).results.map((r) => r.slug)); |
| 272 | await db | |
| 273 | .prepare( | |
| 274 | "INSERT OR IGNORE INTO spaces (id, workspace_id, slug, name, description, icon, kind, team, default_role, agent_mode, is_default, created_by, created_at) VALUES (?, ?, ?, 'General', 'Everything the whole workspace should know.', '📚', 'workspace', NULL, 'edit', 'suggest', 1, ?, ?)", | |
| 275 | ) | |
| 276 | .bind(newId("spc"), workspace.id, freeSlug("general", taken), userKey(viewer), now()) | |
| 277 | .run(); | |
| A doc opens at once: the page carries the document's saved state, so the editor mounts without waiting for its live room, which connects in the background and sends only what changed, with a quiet dot that turns green when it's synced; edits made before then are kept and sent once. The editor's code is preloaded from the page and fetched early when you hover a doc, the app's shared code ships in a few chunks instead of a hundred small ones, socket tickets come with the page instead of a round trip, and the artifacts service answers a page, a sidebar and a room in parallel lookups instead of a dozen in a row, reporting its database round trips in Server-Timing. The artifacts guide says how a doc opens, and CONTRIBUTING's Speed section has the numbers. | 278 | defaultsMade.add(workspace.id); |
| The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook. | 279 | this.forgetSpaces(); |
| 280 | } | |
| 281 | ||
| 282 | toSpace(space: Space, pageCount = 0): DocSpace { | |
| 283 | const created = parsePrincipalKey(space.row.created_by) ?? { kind: "user" as const, id: space.row.created_by }; | |
| 284 | return { | |
| 285 | id: space.row.id, | |
| 286 | workspace_id: space.row.workspace_id, | |
| 287 | slug: space.row.slug, | |
| 288 | name: space.row.name, | |
| 289 | description: space.row.description, | |
| 290 | icon: space.row.icon, | |
| 291 | kind: space.row.kind, | |
| 292 | team: space.row.team, | |
| 293 | default_role: space.row.kind === "private" ? null : space.row.default_role, | |
| 294 | agent_mode: space.row.agent_mode, | |
| 295 | editors_can_share: !!space.row.editors_can_share, | |
| 296 | is_default: !!space.row.is_default, | |
| 297 | projects: space.projects, | |
| 298 | created_by: created, | |
| 299 | created_at: space.row.created_at, | |
| 300 | archived_at: space.row.archived_at, | |
| 301 | viewer_role: space.role ?? "view", | |
| 302 | page_count: pageCount, | |
| 303 | }; | |
| 304 | } | |
| 305 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.