{"id":37614038,"url":"https://github.com/foo-ogawa/embedoc","last_synced_at":"2026-05-26T23:05:58.567Z","repository":{"id":331580312,"uuid":"1128140720","full_name":"foo-ogawa/embedoc","owner":"foo-ogawa","description":"**In-Place Document Generator** - A tool that auto-updates marker blocks in documents and source code while preserving manually edited sections.","archived":false,"fork":false,"pushed_at":"2026-05-20T14:33:51.000Z","size":538,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-20T16:15:07.544Z","etag":null,"topics":["automation","cli","code-generation","developer-tools","docs-as-code","documentation-generator","handlebars","markdown","nodejs","sqlite","template-engine","typescript"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/foo-ogawa.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-05T07:45:14.000Z","updated_at":"2026-05-20T14:48:30.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/foo-ogawa/embedoc","commit_stats":null,"previous_names":["foo-ogawa/embedoc"],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/foo-ogawa/embedoc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foo-ogawa%2Fembedoc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foo-ogawa%2Fembedoc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foo-ogawa%2Fembedoc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foo-ogawa%2Fembedoc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/foo-ogawa","download_url":"https://codeload.github.com/foo-ogawa/embedoc/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foo-ogawa%2Fembedoc/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33542350,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"ssl_error","status_checked_at":"2026-05-26T15:22:15.568Z","response_time":63,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["automation","cli","code-generation","developer-tools","docs-as-code","documentation-generator","handlebars","markdown","nodejs","sqlite","template-engine","typescript"],"created_at":"2026-01-16T10:28:06.735Z","updated_at":"2026-05-26T23:05:58.561Z","avatar_url":"https://github.com/foo-ogawa.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# embedoc\n\n[![npm version](https://badge.fury.io/js/embedoc.svg)](https://www.npmjs.com/package/embedoc)　[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n**In-Place Document Generator** - A tool that auto-updates marker blocks in documents and source code while preserving manually edited sections.\n\n\n## Overview\n\nembedoc provides \"In-Place template update\" functionality that auto-updates specific blocks (regions enclosed by markers) within documents or source code while preserving manually edited sections.\n\n```markdown\n# Manually written heading\n\nThis part can be manually edited.\n\n\u003c!--@embedoc:table_columns id=\"users\"--\u003e\n(This content is auto-generated)\n\u003c!--@embedoc:end--\u003e\n\nThis part can also be manually edited.\n```\n\n**Auto-generated and manually edited sections coexist in the same file** without separating source and built files.\n\n## Features\n\n- **In-Place Updates**: Auto-generated and manually edited sections coexist in the same file\n- **Multiple Comment Formats**: Supports HTML, block, line, hash, SQL comment formats\n- **Programmable Embeds**: Write marker embedding logic in TypeScript (no compilation required)\n- **Multiple Datasources**: SQLite, CSV, JSON, YAML, and glob support\n- **Inline Datasources**: Define data directly in documents with `@embedoc-data` markers\n- **File Generation**: Generate new files in bulk using Handlebars templates\n- **Watch Mode**: Monitor file changes and auto-rebuild with incremental builds\n- **Dependency Tracking**: Automatic dependency graph analysis for efficient rebuilds\n\n## Installation\n\n```bash\nnpm install embedoc\n# or\npnpm add embedoc\n# or\nyarn add embedoc\n```\n\n## Quick Start\n\n### 1. Initialize Project\n\n```bash\nnpx embedoc init\n```\n\nThis creates:\n- `embedoc.config.yaml` - configuration file\n- `.embedoc/renderers/index.ts` - renderer registration\n- `.embedoc/datasources/index.ts` - custom datasource registration\n- `.embedoc/templates/` - Handlebars templates directory\n\nIf `package.json` exists, npm scripts are also added:\n- `npm run embedoc:build` - build documents\n- `npm run embedoc:watch` - watch mode\n- `npm run embedoc:generate` - run generators\n\n### 2. Configure\n\nEdit `embedoc.config.yaml` to set your targets and datasources:\n\n```yaml\n# embedoc.config.yaml\nversion: \"1.0\"\n\ntargets:\n  - pattern: \"./docs/**/*.md\"\n    comment_style: html\n    exclude:\n      - \"**/node_modules/**\"\n\ndatasources:\n  metadata_db:\n    type: sqlite\n    path: \"./data/metadata.db\"\n```\n\n### 3. Create a Renderer\n\n```typescript\n// .embedoc/renderers/table_columns.ts\nimport { defineEmbed } from 'embedoc';\n\nexport default defineEmbed({\n  dependsOn: ['metadata_db'],\n\n  async render(ctx) {\n    const { id } = ctx.params;\n\n    if (!id) {\n      return { content: '❌ Error: id parameter is required' };\n    }\n\n    const columns = await ctx.datasources['metadata_db']!.query(\n      `SELECT * FROM columns WHERE table_name = ? ORDER BY ordinal_position`,\n      [id]\n    );\n\n    const markdown = ctx.markdown.table(\n      ['Column', 'Type', 'NOT NULL', 'Default', 'Comment'],\n      columns.map((col) =\u003e [\n        col['column_name'],\n        col['data_type'],\n        col['not_null'] ? '✔' : '',\n        col['default_value'] ?? 'NULL',\n        col['column_comment'] ?? '',\n      ])\n    );\n\n    return { content: markdown };\n  },\n});\n```\n\nRegister your renderer in `.embedoc/renderers/index.ts`:\n\n```typescript\n// .embedoc/renderers/index.ts\nimport tableColumns from './table_columns.ts';\n\nexport const embeds = {\n  table_columns: tableColumns,\n};\n```\n\n\u003e **Note**: embedoc can directly import TypeScript files, so **no compilation is required**.\n\n### 4. Add Markers to Your Document\n\n```markdown\n# Users Table\n\n\u003c!--@embedoc:table_columns id=\"users\"--\u003e\n\u003c!--@embedoc:end--\u003e\n```\n\n### 5. Run Build\n\n```bash\nnpx embedoc build\n# or, if scripts were added to package.json:\nnpm run embedoc:build\n```\n\n---\n\n## CLI Commands\n\n\u003e **Full CLI reference:** [docs/cli-reference.md](./docs/cli-reference.md) | **Machine-readable contract:** [cli-contract.yaml](./cli-contract.yaml)\n\n```bash\n# Initialize project (creates config, .embedoc/ directory, updates package.json)\nembedoc init\nembedoc init --force   # overwrite existing files\n\n# Build all files\nembedoc build\nembedoc build --config embedoc.config.yaml\n\n# Build specific files only\nembedoc build ./path/to/file.md\n\n# Generate new files (specific datasource)\nembedoc generate --datasource tables\n\n# Generate with specific template\nembedoc generate --datasource tables --generator table_doc.hbs\n\n# Run all datasource generators\nembedoc generate --all\n\n# Watch mode (incremental build)\nembedoc watch\nembedoc watch --config embedoc.config.yaml\n\n# Debug dependency graph\nembedoc watch --debug-deps\n\n# Dry run (no file writes)\nembedoc build --dry-run\n\n# Verbose output\nembedoc build --verbose\n```\n\nAll commands can be run directly with `npx embedoc \u003ccommand\u003e` or via package.json scripts after `embedoc init`.\n\n---\n\n## Configuration File\n\n### Full Configuration Reference\n\n```yaml\n# embedoc.config.yaml\nversion: \"1.0\"\n\n# Target files\ntargets:\n  - pattern: \"./docs/**/*.md\"\n    comment_style: html\n    exclude:\n      - \"**/node_modules/**\"\n      - \"**/.git/**\"\n  - pattern: \"./src/**/*.ts\"\n    comment_style: block\n  - pattern: \"./scripts/**/*.py\"\n    comment_style: hash\n  - pattern: \"./db/**/*.sql\"\n    comment_style: sql\n\n# Custom comment style definitions (optional)\ncomment_styles:\n  html:\n    start: \"\u003c!--\"\n    end: \"--\u003e\"\n  block:\n    start: \"/*\"\n    end: \"*/\"\n  line:\n    start: \"//\"\n    end: \"\"\n  hash:\n    start: \"#\"\n    end: \"\"\n  sql:\n    start: \"--\"\n    end: \"\"\n  # Custom formats\n  lua:\n    start: \"--[[\"\n    end: \"]]\"\n\n# Datasource definitions\ndatasources:\n  # Schema datasource with generators\n  tables:\n    type: sqlite\n    path: \"./data/metadata.db\"\n    query: \"SELECT * FROM tables\"\n    generators:\n      - output_path: \"./docs/tables/{table_name}.md\"\n        template: table_doc.hbs\n        overwrite: false\n  \n  # Connection datasource (for queries from embeds)\n  metadata_db:\n    type: sqlite\n    path: \"./data/metadata.db\"\n  \n  # CSV datasource\n  api_endpoints:\n    type: csv\n    path: \"./data/endpoints.csv\"\n    encoding: utf-8\n  \n  # JSON datasource\n  config:\n    type: json\n    path: \"./data/config.json\"\n  \n  # YAML datasource\n  settings:\n    type: yaml\n    path: \"./data/settings.yaml\"\n  \n  # Glob datasource\n  doc_files:\n    type: glob\n    pattern: \"./docs/**/*.md\"\n\n# Renderer directory (TypeScript) - default: \".embedoc/renderers\"\nrenderers_dir: \".embedoc/renderers\"\n\n# Custom datasource types directory - default: \".embedoc/datasources\"\ndatasources_dir: \".embedoc/datasources\"\n\n# Template directory (Handlebars) - default: \".embedoc/templates\"\ntemplates_dir: \".embedoc/templates\"\n\n# Output settings\noutput:\n  encoding: utf-8\n  line_ending: lf  # or \"crlf\"\n\n# Inline datasource configuration\ninline_datasource:\n  enabled: true\n  maxBytes: 10240           # Max size per inline block (default: 10KB)\n  allowedFormats:           # Allowed formats (default: all)\n    - yaml\n    - json\n    - csv\n    - table\n    - text\n  conflictPolicy: warn      # warn | error | prefer_external\n  stripCodeFences: true     # Auto-strip ```yaml ... ``` fences\n  stripPatterns:            # Custom patterns to strip (regex)\n    - '^```\\w*\\s*\\n?'\n    - '\\n?```\\s*$'\n\n# GitHub integration\n# Used as base URL when generating repository links in embeds\n# (e.g., ctx.markdown.link('file.ts', github.base_url + 'src/file.ts'))\ngithub:\n  base_url: \"https://github.com/owner/repo/blob/main/\"\n```\n\n---\n\n## Marker Syntax\n\n### Basic Syntax\n\n```\n{comment_start}@embedoc:{embed_name} {attr1}=\"{value1}\" {attr2}=\"{value2}\"{comment_end}\n(auto-generated content)\n{comment_start}@embedoc:end{comment_end}\n```\n\n### Supported Comment Formats\n\n| Format | Start Marker | End Marker | Target Files |\n|--------|-------------|------------|--------------|\n| `html` | `\u003c!--` | `--\u003e` | `.md`, `.html`, `.xml` |\n| `block` | `/*` | `*/` | `.js`, `.ts`, `.css`, `.java`, `.c` |\n| `line` | `//` | (newline) | `.js`, `.ts`, `.java`, `.c`, `.go` |\n| `hash` | `#` | (newline) | `.py`, `.rb`, `.sh`, `.yaml` |\n| `sql` | `--` | (newline) | `.sql` |\n\n### Examples by Format\n\n**Markdown / HTML**\n```markdown\n\u003c!--@embedoc:table_columns id=\"users\"--\u003e\n| Column | Type | Comment |\n| --- | --- | --- |\n| id | integer | User ID |\n\u003c!--@embedoc:end--\u003e\n```\n\n**TypeScript / JavaScript (block)**\n```typescript\n/*@embedoc:type_definition id=\"User\"*/\nexport interface User {\n  id: number;\n  name: string;\n}\n/*@embedoc:end*/\n```\n\n**TypeScript / JavaScript (line)**\n```typescript\n//@embedoc:imports id=\"api-client\"\nimport { ApiClient } from './api';\nimport { UserService } from './services/user';\n//@embedoc:end\n```\n\n**Python**\n```python\n#@embedoc:constants id=\"config\"\nAPI_URL = \"https://api.example.com\"\nTIMEOUT = 30\n#@embedoc:end\n```\n\n**SQL**\n```sql\n--@embedoc:view_definition id=\"active_users\"\nCREATE VIEW active_users AS\nSELECT * FROM users WHERE status = 'active';\n--@embedoc:end\n```\n\n### Inline Mode\n\nUse `inline=\"true\"` to prevent newlines around the generated content. Useful for embedding values within table cells or inline text:\n\n```markdown\n| Name | Value |\n|------|-------|\n| User | \u003c!--@embedoc:inline_value datasource=\"data\" path=\"name\" inline=\"true\"--\u003eJohn\u003c!--@embedoc:end--\u003e |\n```\n\nWithout `inline=\"true\"`, the output would include newlines and break the table formatting.\n\n### Variable References in Attributes\n\nUse `${...}` syntax in attribute values to reference Frontmatter properties or inline datasources.\n\n```yaml\n---\ndoc_id: \"users\"\nschema: \"public\"\n---\n```\n\n```markdown\n\u003c!--@embedoc:table_columns id=\"${doc_id}\"--\u003e\n\u003c!--@embedoc:end--\u003e\n\n\u003c!--@embedoc:table_info id=\"${schema}.${doc_id}\"--\u003e\n\u003c!--@embedoc:end--\u003e\n```\n\n---\n\n## Embed API\n\n### Basic Structure\n\n```typescript\nimport { defineEmbed } from 'embedoc';\n\nexport default defineEmbed({\n  // Datasources this embed depends on (for dependency tracking)\n  dependsOn: ['metadata_db'],\n  \n  // Render function\n  async render(ctx) {\n    // ctx.params: Marker attribute values\n    // ctx.frontmatter: Frontmatter YAML data\n    // ctx.datasources: Access to datasources\n    // ctx.markdown: Markdown helpers\n    // ctx.filePath: Current file path being processed\n    \n    return { content: 'Generated content' };\n  }\n});\n```\n\n### Context Object\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `ctx.params` | `Record\u003cstring, string\u003e` | Marker attribute values |\n| `ctx.frontmatter` | `Record\u003cstring, unknown\u003e` | Document frontmatter data |\n| `ctx.datasources` | `Record\u003cstring, Datasource\u003e` | Available datasources |\n| `ctx.markdown` | `MarkdownHelper` | Markdown generation helpers |\n| `ctx.filePath` | `string` | Current file path |\n| `ctx.existingContent` | `string \\| undefined` | Existing content between markers (for error recovery) |\n\n### Error Recovery / Graceful Degradation\n\nReturn `null` or `undefined` from `render()` to keep existing content unchanged. This is useful when external data sources are unavailable.\n\n```typescript\nasync render(ctx) {\n  try {\n    const data = await fetchFromDatabase(ctx.params['id']);\n    return { content: formatData(data) };\n  } catch (error) {\n    // On error, keep existing content\n    return { content: null };\n  }\n}\n```\n\n### Markdown Helpers\n\n```typescript\n// Table\nctx.markdown.table(\n  ['Column', 'Type', 'Description'],\n  [\n    ['id', 'integer', 'Primary key'],\n    ['name', 'varchar', 'User name'],\n  ]\n);\n\n// List\nctx.markdown.list(['Item 1', 'Item 2', 'Item 3'], false);  // unordered\nctx.markdown.list(['First', 'Second', 'Third'], true);     // ordered\n\n// Code block\nctx.markdown.codeBlock('const x = 1;', 'typescript');\n\n// Link\nctx.markdown.link('Click here', 'https://example.com');\n\n// Heading\nctx.markdown.heading('Section Title', 2);  // ## Section Title\n\n// Inline formatting\nctx.markdown.bold('Important');    // **Important**\nctx.markdown.italic('Emphasis');   // *Emphasis*\nctx.markdown.checkbox(true);       // [x]\nctx.markdown.checkbox(false);      // [ ]\n```\n\n---\n\n## Datasources\n\n### SQLite\n\n```yaml\ndatasources:\n  metadata_db:\n    type: sqlite\n    path: \"./data/metadata.db\"\n    # Optional: predefined query for generators\n    query: \"SELECT * FROM tables\"\n```\n\nUsage in embed:\n```typescript\nconst rows = await ctx.datasources['metadata_db'].query(\n  'SELECT * FROM users WHERE id = ?',\n  [userId]\n);\n```\n\n### CSV\n\n```yaml\ndatasources:\n  endpoints:\n    type: csv\n    path: \"./data/endpoints.csv\"\n    encoding: utf-8  # optional, default: utf-8\n```\n\n### JSON\n\n```yaml\ndatasources:\n  config:\n    type: json\n    path: \"./data/config.json\"\n```\n\n### YAML\n\n```yaml\ndatasources:\n  settings:\n    type: yaml\n    path: \"./data/settings.yaml\"\n```\n\n### Glob (File Listings)\n\n```yaml\ndatasources:\n  doc_files:\n    type: glob\n    pattern: \"./docs/**/*.md\"\n```\n\nReturns array of file info objects with `path`, `name`, `ext`, etc.\n\n---\n\n## Inline Datasources\n\nDefine data directly in documents using `@embedoc-data` markers.\n\n### Basic Syntax\n\n```markdown\n\u003c!--@embedoc-data:datasource_name format=\"yaml\"--\u003e\n- name: Alice\n  age: 25\n- name: Bob\n  age: 30\n\u003c!--@embedoc-data:end--\u003e\n```\n\n### Supported Formats\n\n| Format | Description |\n|--------|-------------|\n| `yaml` | YAML array or object (default) |\n| `json` | JSON array or object |\n| `csv` | CSV with header row |\n| `table` | Markdown table |\n| `text` | Plain text |\n\n### Format Examples\n\n**YAML (default)**\n```markdown\n\u003c!--@embedoc-data:users format=\"yaml\"--\u003e\n- id: 1\n  name: Alice\n  email: alice@example.com\n- id: 2\n  name: Bob\n  email: bob@example.com\n\u003c!--@embedoc-data:end--\u003e\n```\n\n**JSON**\n```markdown\n\u003c!--@embedoc-data:config format=\"json\"--\u003e\n{\n  \"api_url\": \"https://api.example.com\",\n  \"timeout\": 30\n}\n\u003c!--@embedoc-data:end--\u003e\n```\n\n**CSV**\n```markdown\n\u003c!--@embedoc-data:endpoints format=\"csv\"--\u003e\nmethod,path,description\nGET,/users,List all users\nPOST,/users,Create user\n\u003c!--@embedoc-data:end--\u003e\n```\n\n**Markdown Table**\n```markdown\n\u003c!--@embedoc-data:features format=\"table\"--\u003e\n| Feature | Status | Priority |\n|---------|--------|----------|\n| Auth    | Done   | High     |\n| API     | WIP    | High     |\n\u003c!--@embedoc-data:end--\u003e\n```\n\n### Code Fence Support\n\nFor better readability in editors, you can wrap data in code fences:\n\n````markdown\n\u003c!--@embedoc-data:config format=\"yaml\"--\u003e\n```yaml\napi_url: https://api.example.com\ntimeout: 30\nfeatures:\n  - auth\n  - logging\n```\n\u003c!--@embedoc-data:end--\u003e\n````\n\nCode fences are automatically stripped during parsing.\n\n### Dot-Path Access for Nested Data\n\nAccess nested properties using dot notation:\n\n```markdown\n\u003c!--@embedoc-data:project format=\"yaml\"--\u003e\nname: embedoc\nversion: 1.0.0\nauthor:\n  name: Jane Developer\n  email: jane@example.com\nrepository:\n  url: https://github.com/janedev/embedoc\n\u003c!--@embedoc-data:end--\u003e\n\nProject: ${project.name} v${project.version}\nAuthor: ${project.author.name} (${project.author.email})\n```\n\n### Distributed Definition Style\n\nDefine data inline where it's contextually relevant:\n\n```markdown\n# Project Documentation\n\nThis project, \u003c!--@embedoc-data:project.name--\u003eembedoc\u003c!--@embedoc-data:end--\u003e, \nversion \u003c!--@embedoc-data:project.version--\u003e1.0.0\u003c!--@embedoc-data:end--\u003e, \nprovides in-place document generation.\n\n## Author\n\nMaintained by \u003c!--@embedoc-data:project.author.name--\u003eJane Developer\u003c!--@embedoc-data:end--\u003e\n(\u003c!--@embedoc-data:project.author.email--\u003ejane@example.com\u003c!--@embedoc-data:end--\u003e).\n\n## Summary\n\n| Property | Value |\n|----------|-------|\n| Name | ${project.name} |\n| Version | ${project.version} |\n| Author | ${project.author.name} |\n```\n\nBoth YAML blocks and dot-path definitions produce the same structure and can be mixed.\n\n\u003e **Note**: If the same dot-path is defined multiple times within a document, the **last definition wins** (values are overwritten in document order).\n\n### Using Inline Datasources in Embeds\n\n```typescript\nimport { defineEmbed, InlineDatasource } from 'embedoc';\n\nexport default defineEmbed({\n  async render(ctx) {\n    const ds = ctx.datasources['my_data'] as InlineDatasource;\n    \n    // Get all data\n    const data = await ds.getAll();\n    \n    // Get nested value (for object datasources)\n    const authorName = await ds.get('author.name');\n    \n    // Get location metadata (for traceability)\n    const meta = ds.getMeta('', ctx.filePath);  // '' = root definition\n    if (meta) {\n      // meta.relativePath: relative path from current document\n      // meta.contentStartLine / contentEndLine: line numbers\n      console.log(`Defined at ${meta.relativePath}:${meta.contentStartLine}`);\n    }\n    \n    // Get location of specific property (for distributed definitions)\n    const propMeta = ds.getMeta('author.name', ctx.filePath);\n    \n    return { content: ctx.markdown.table(/* ... */) };\n  }\n});\n```\n\nSee [API Reference](./docs/api/README.md#inlinedatasource) for `InlineDatasource` details.\n\n### Inline Datasource Configuration\n\n```yaml\n# embedoc.config.yaml\ninline_datasource:\n  enabled: true              # Enable/disable (default: true)\n  maxBytes: 10240           # Max size per block (default: 10KB)\n  allowedFormats:           # Restrict formats\n    - yaml\n    - json\n  conflictPolicy: warn      # warn | error | prefer_external\n  stripCodeFences: true     # Auto-strip code fences (default: true)\n  stripPatterns:            # Custom strip patterns (regex)\n    - '^```\\w*\\s*\\n?'\n    - '\\n?```\\s*$'\n```\n\n---\n\n## Custom Datasources\n\nDefine custom datasource types in TypeScript to connect to any data source (APIs, databases, custom file formats, etc.).\n\n### Defining a Custom Datasource Type\n\n```typescript\n// .embedoc/datasources/github.ts\nimport { defineDatasource } from 'embedoc';\n\nexport default defineDatasource({\n  async create(config) {\n    const owner = config['owner'] as string;\n    const repo = config['repo'] as string;\n    const response = await fetch(\n      `https://api.github.com/repos/${owner}/${repo}/issues`\n    );\n    const issues = await response.json();\n\n    return {\n      async query() { return issues; },\n      async getAll() { return issues; },\n      async close() {},\n    };\n  }\n});\n```\n\n### Registering Custom Datasource Types\n\n```typescript\n// .embedoc/datasources/index.ts\nimport github from './github.ts';\n\nexport const datasourceTypes = {\n  github,\n};\n```\n\n### Using in Configuration\n\n```yaml\n# embedoc.config.yaml\ndatasources:\n  my_issues:\n    type: github         # matches key in datasourceTypes\n    owner: \"myorg\"\n    repo: \"myrepo\"\n```\n\nAll config properties are passed to the `create()` method, so custom datasources can accept any configuration.\n\n### Custom Inline Format Parsers\n\nRegister custom format parsers for `@embedoc-data` inline markers by exporting `inlineFormats` alongside `datasourceTypes`:\n\n```typescript\n// .embedoc/datasources/index.ts\nimport github from './github.ts';\n\nexport const datasourceTypes = {\n  github,\n};\n\nexport const inlineFormats = {\n  toml: (content: string) =\u003e parseToml(content),\n  ini: (content: string) =\u003e {\n    const result: Record\u003cstring, string\u003e = {};\n    for (const line of content.split('\\n')) {\n      const [key, ...rest] = line.split('=');\n      if (key \u0026\u0026 rest.length \u003e 0) {\n        result[key.trim()] = rest.join('=').trim();\n      }\n    }\n    return result;\n  },\n};\n```\n\nCustom formats can then be used in documents:\n\n```markdown\n\u003c!--@embedoc-data:config format=\"ini\"--\u003e\nhost=localhost\nport=5432\ndatabase=myapp\n\u003c!--@embedoc-data:end--\u003e\n```\n\n---\n\n## File Generation\n\nGenerate new files in bulk using Handlebars templates based on datasource records.\n\n### Configuration\n\n```yaml\ndatasources:\n  tables:\n    type: sqlite\n    path: \"./data/metadata.db\"\n    query: \"SELECT * FROM tables\"\n    generators:\n      - output_path: \"./docs/tables/{table_name}.md\"\n        template: table_doc.hbs\n        overwrite: false  # Don't overwrite existing files\n```\n\n### Template (Handlebars)\n\n```handlebars\n{{!-- .embedoc/templates/table_doc.hbs --}}\n---\ndoc_id: \"{{table_name}}\"\nembeds:\n  - table_columns\n  - table_relations\n---\n# Table: {{table_name}}\n\n## Columns\n\n\u003c!--@embedoc:table_columns id=\"{{table_name}}\"--\u003e\n\u003c!--@embedoc:end--\u003e\n\n## Relations\n\n\u003c!--@embedoc:table_relations id=\"{{table_name}}\"--\u003e\n\u003c!--@embedoc:end--\u003e\n\nCreated: {{today}}\n```\n\n### Built-in Handlebars Helpers\n\n| Helper | Description | Example Output |\n|--------|-------------|----------------|\n| `{{today}}` | Today's date (YYYY-MM-DD) | `YYYY-MM-DD` |\n| `{{datetime}}` | Current datetime (ISO 8601) | `YYYY-MM-DDTHH:mm:ss.sssZ` |\n| `{{#if condition}}` | Conditional | `{{#if is_primary}}✔{{/if}}` |\n| `{{#each items}}` | Loop | `{{#each columns}}{{name}}{{/each}}` |\n| `{{#unless condition}}` | Negation | `{{#unless nullable}}NOT NULL{{/unless}}` |\n\n### Run Generation\n\n```bash\n# Generate for specific datasource\nembedoc generate --datasource tables\n\n# Generate for all datasources with generators\nembedoc generate --all\n```\n\n---\n\n## Incremental Build \u0026 Dependency Tracking\n\n### Dependency Chain\n\n```\nDocument (.md) → Embed (.ts) → Datasource (.db, .csv, .json)\n```\n\n- **Document changed**: Rebuild that document only\n- **Embed changed**: Rebuild all documents using that embed\n- **Datasource changed**: Rebuild all documents using embeds that depend on that datasource\n\n### Watch Mode\n\n```bash\nembedoc watch --config embedoc.config.yaml\n```\n\n### Debug Dependency Graph\n\n```bash\nembedoc watch --debug-deps\n```\n\nExample output:\n```\n=== Dependency Graph ===\n\n[document] docs/tables/users.md\n  depends on:\n    - embed:table_columns\n    - embed:table_relations\n\n[embed] embed:table_columns\n  depends on:\n    - data/sample.db\n  depended by:\n    - docs/tables/users.md\n    - docs/tables/orders.md\n\n[datasource] data/sample.db\n  depended by:\n    - embed:table_columns\n    - embed:table_relations\n```\n\n---\n\n## Frontmatter\n\nDocuments can include YAML frontmatter for metadata and configuration:\n\n```yaml\n---\ndoc_id: \"users\"\ndoc_type: \"table\"\nschema: \"public\"\nembeds:\n  - table_columns\n  - table_relations\n---\n```\n\nFrontmatter values can be referenced in marker attributes using `${...}` syntax.\n\n---\n\n## Directory Structure\n\n### Recommended Project Structure\n\n```\nyour-project/\n├── embedoc.config.yaml        # Configuration file\n├── .embedoc/                   # All embedoc custom code\n│   ├── renderers/              # Renderer definitions (TypeScript)\n│   │   ├── index.ts            # Export all renderers\n│   │   ├── table_columns.ts\n│   │   └── table_relations.ts\n│   ├── datasources/            # Custom datasource types (TypeScript)\n│   │   ├── index.ts            # Export datasourceTypes and inlineFormats\n│   │   └── github.ts\n│   ├── templates/              # File generation templates (Handlebars)\n│   │   ├── table_doc.hbs\n│   │   └── view_doc.hbs\n│   └── package.json            # Optional: dependencies for custom code\n├── data/                       # Datasource files\n│   ├── metadata.db\n│   └── endpoints.csv\n└── docs/                       # Target documents\n    └── tables/\n        └── users.md\n```\n\n### Renderer Registration\n\n```typescript\n// .embedoc/renderers/index.ts\nimport tableColumns from './table_columns.ts';\nimport tableRelations from './table_relations.ts';\nimport customEmbed from './custom_embed.ts';\n\nexport const embeds = {\n  table_columns: tableColumns,\n  table_relations: tableRelations,\n  custom_embed: customEmbed,\n};\n```\n\n---\n\n## Development\n\n### Building from Source\n\n```bash\n# Clone repository\ngit clone https://github.com/user/embedoc.git\ncd embedoc\n\n# Install dependencies\nnpm install\n\n# Build\nnpm run build\n\n# Development mode (watch)\nnpm run dev\n\n# Run tests\nnpm test\n```\n\n### Requirements\n\n- Node.js 18+\n- npm / pnpm / yarn\n\n---\n\n## API Reference\n\n\u003e 📖 **See [docs/api/README.md](./docs/api/README.md) for detailed Embed API documentation.**\n\n### Exported Functions\n\n```typescript\nimport {\n  // Core\n  defineEmbed,\n  defineDatasource,\n  build,\n  processFile,\n  \n  // Parser\n  parseMarkers,\n  parseFrontmatter,\n  parseInlineDataMarkers,\n  \n  // Datasource utilities\n  InlineDatasource,\n  buildInlineDatasources,\n  parseDotPath,\n  resolveDotPath,\n  \n  // Helpers\n  createMarkdownHelper,\n  \n  // Constants\n  DEFAULT_COMMENT_STYLES,\n} from 'embedoc';\n```\n\n### Type Definitions\n\n```typescript\ninterface EmbedDefinition {\n  dependsOn?: string[];\n  render: (ctx: EmbedContext) =\u003e Promise\u003cEmbedResult\u003e;\n}\n\ninterface EmbedResult {\n  /** Return string to replace, or null/undefined to keep existing content */\n  content: string | null | undefined;\n}\n\ninterface EmbedContext {\n  params: Record\u003cstring, string\u003e;\n  frontmatter: Record\u003cstring, unknown\u003e;\n  datasources: Record\u003cstring, Datasource\u003e;\n  markdown: MarkdownHelper;\n  filePath: string;\n  /** Existing content between markers (for error recovery) */\n  existingContent?: string;\n}\n\ninterface Datasource {\n  query(sql: string, params?: unknown[]): Promise\u003cQueryResult\u003e;\n  getAll(): Promise\u003cQueryResult\u003e;\n  close(): Promise\u003cvoid\u003e;\n}\n\ninterface InlineDatasource extends Datasource {\n  readonly type: 'inline';\n  readonly format: string;\n  readonly locations: InlineDefinitionLocation[];\n  get(path: string): Promise\u003cunknown\u003e;\n  getMeta(propertyPath?: string, targetDocPath?: string): InlineDefinitionLocation | null;\n  getAllMeta(targetDocPath?: string): InlineDefinitionLocation[];\n}\n\ninterface InlineDefinitionLocation {\n  propertyPath: string;\n  absolutePath: string;\n  relativePath: string;\n  startLine: number;\n  endLine: number;\n  contentStartLine: number;\n  contentEndLine: number;\n  format: string;\n}\n\ninterface InlineDatasourceConfig {\n  enabled?: boolean;\n  maxBytes?: number;\n  allowedFormats?: string[];\n  conflictPolicy?: 'warn' | 'error' | 'prefer_external';\n  stripCodeFences?: boolean;\n  stripPatterns?: string[];\n}\n\ninterface CustomDatasourceDefinition {\n  create(config: DatasourceConfig): Promise\u003cDatasource\u003e;\n}\n\ntype InlineFormatParser = (content: string) =\u003e unknown;\n```\n\n---\n\n## License\n\nMIT\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffoo-ogawa%2Fembedoc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffoo-ogawa%2Fembedoc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffoo-ogawa%2Fembedoc/lists"}