# Google Docs

Lightweight, dependency-free in-process fake of the Google Docs API v1 for zero-cost parlel-pool tests.

Default port: `4616`

## Implemented Operations

Documents:

- `POST /v1/documents` (`documents.create`)
- `GET /v1/documents/{documentId}` (`documents.get`)
- `POST /v1/documents/{documentId}:batchUpdate` (`documents.batchUpdate`)

Aliases:

- `GET /v1`
- `GET /docs/v1`
- `GET /docs/v1/documents/{documentId}`
- `POST /docs/v1/documents/{documentId}:batchUpdate`

Parlel control endpoints:

- `GET /_parlel/health`
- `POST /_parlel/reset`

Batch update request types:

- Text: `insertText`, `deleteContentRange`, `replaceAllText`, `updateTextStyle`
- Paragraphs: `createParagraphBullets`, `deleteParagraphBullets`, `updateParagraphStyle`
- Named ranges: `createNamedRange`, `deleteNamedRange`, `replaceNamedRangeContent`
- Images and positioned objects: `insertInlineImage`, `replaceImage`, `deletePositionedObject`
- Tables: `insertTable`, `insertTableRow`, `insertTableColumn`, `deleteTableRow`, `deleteTableColumn`, `deleteTable`, `mergeTableCells`, `unmergeTableCells`, `updateTableCellStyle`, `updateTableColumnProperties`, `updateTableRowStyle`, `pinTableHeaderRows`
- Layout: `insertPageBreak`, `insertSectionBreak`, `updateDocumentStyle`, `updateSectionStyle`
- Headers, footers, footnotes: `createHeader`, `deleteHeader`, `createFooter`, `deleteFooter`, `createFootnote`
- Tabs and named styles: `addDocumentTab`, `deleteTab`, `updateDocumentTabProperties`, `updateNamedStyle`
- Smart chips: `insertDate`, `insertPerson`, `insertRichLink`

## Quick Start

```js
import { google } from "googleapis";
import { GoogleDocsServer } from "./services/google-docs/src/server.js";

const server = new GoogleDocsServer(4616);
await server.start();

const docs = google.docs({ version: "v1", auth: "test" });
docs.context._options.rootUrl = "http://127.0.0.1:4616/";

const created = await docs.documents.create({ requestBody: { title: "Parlel Notes" } });
await docs.documents.batchUpdate({
  documentId: created.data.documentId,
  requestBody: {
    requests: [{ insertText: { location: { index: 1 }, text: "Hello from parlel\n" } }],
  },
});

const got = await docs.documents.get({ documentId: created.data.documentId });
console.log(got.data.body.content[0].paragraph.elements[0].textRun.content);

await server.stop();
```

## Support Matrix

| Feature | Status | Notes |
| --- | --- | --- |
| `googleapis` REST wire paths | Supported | Use `rootUrl = "http://127.0.0.1:4616/"`. |
| In-memory documents | Supported | Ephemeral state; reset with `server.reset()` or `POST /_parlel/reset`. |
| Document create/get | Supported | Returns Docs-shaped document JSON with body content and metadata maps. |
| Batch update request union | Supported | All public Docs API v1 request variants exposed by googleapis are accepted. |
| Text mutation | Supported | Maintains a canonical plain-text body and returns paragraph text runs. |
| Tables/images/headers/footers/footnotes | Supported | Lightweight metadata model, enough for application tests. |
| Tabs, named styles, dates, people, rich links | Supported | Lightweight metadata and text insertion model. |
| Auth, OAuth scopes, IAM | Intentionally unsupported | The fake accepts local test traffic without credentials. |
| Persistence | Intentionally unsupported | State is process-local and cleared on restart/reset. |
| Suggestions, collaborative editing, revision history | Intentionally unsupported | `revisionId` changes after batch updates, but historical revisions are not retained. |
| Full Docs rendering fidelity | Intentionally unsupported | This is a protocol fake, not a visual renderer. |

## Error Shapes

Errors use Google-style JSON envelopes:

```json
{
  "error": {
    "code": 404,
    "message": "Requested entity was not found.",
    "status": "NOT_FOUND",
    "errors": [
      {
        "message": "Requested entity was not found.",
        "domain": "global",
        "reason": "notFound"
      }
    ]
  }
}
```

Returned codes include:

| HTTP | Status | Common reason |
| --- | --- | --- |
| `400` | `INVALID_ARGUMENT` | `invalidArgument`, `parseError` |
| `404` | `NOT_FOUND` | `notFound` |
| `405` | `METHOD_NOT_ALLOWED` | `methodNotAllowed` |
| `409` | `ALREADY_EXISTS` | `alreadyExists` |
| `500` | `INTERNAL` | `backendError` |
