@vantageos/convex-doc-forge 0.1.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.
- package/CHANGELOG.md +22 -0
- package/LICENSE +110 -0
- package/README.md +169 -0
- package/convex.config.ts +22 -0
- package/dist/_generated/api.d.ts +3 -0
- package/dist/_generated/api.d.ts.map +1 -0
- package/dist/_generated/api.js +13 -0
- package/dist/_generated/dataModel.d.ts +13 -0
- package/dist/_generated/dataModel.d.ts.map +1 -0
- package/dist/_generated/dataModel.js +1 -0
- package/dist/_generated/server.d.ts +20 -0
- package/dist/_generated/server.d.ts.map +1 -0
- package/dist/_generated/server.js +8 -0
- package/dist/actions.d.ts +20 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +176 -0
- package/dist/convex.config.d.ts +20 -0
- package/dist/convex.config.d.ts.map +1 -0
- package/dist/convex.config.js +20 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/mutations.d.ts +84 -0
- package/dist/mutations.d.ts.map +1 -0
- package/dist/mutations.js +166 -0
- package/dist/queries.d.ts +30 -0
- package/dist/queries.d.ts.map +1 -0
- package/dist/queries.js +117 -0
- package/dist/schema.d.ts +97 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +59 -0
- package/dist/src/_generated/api.d.ts +3 -0
- package/dist/src/_generated/api.d.ts.map +1 -0
- package/dist/src/_generated/api.js +13 -0
- package/dist/src/_generated/dataModel.d.ts +13 -0
- package/dist/src/_generated/dataModel.d.ts.map +1 -0
- package/dist/src/_generated/dataModel.js +1 -0
- package/dist/src/_generated/server.d.ts +20 -0
- package/dist/src/_generated/server.d.ts.map +1 -0
- package/dist/src/_generated/server.js +8 -0
- package/dist/src/actions.d.ts +20 -0
- package/dist/src/actions.d.ts.map +1 -0
- package/dist/src/actions.js +176 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +9 -0
- package/dist/src/mutations.d.ts +84 -0
- package/dist/src/mutations.d.ts.map +1 -0
- package/dist/src/mutations.js +166 -0
- package/dist/src/queries.d.ts +30 -0
- package/dist/src/queries.d.ts.map +1 -0
- package/dist/src/queries.js +117 -0
- package/dist/src/schema.d.ts +97 -0
- package/dist/src/schema.d.ts.map +1 -0
- package/dist/src/schema.js +59 -0
- package/package.json +44 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Changelog — @vantageos/convex-doc-forge
|
|
2
|
+
|
|
3
|
+
All notable changes to this package follow [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) + [SemVer](https://semver.org/spec/v2.0.0.html).
|
|
4
|
+
|
|
5
|
+
## [0.1.0] — 2026-06-19 (P1 data layer)
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `convex.config.ts` — `defineComponent("docForge")`, install via `app.use(docForge)`.
|
|
10
|
+
- `schema.ts` — 3 tables: `templates`, `render_jobs`, `render_audit` with full indexes (`by_tenant`, `by_tenant_team`, `by_tenant_type`, `by_template`, `by_job`, `by_tenant_status`).
|
|
11
|
+
- `queries.ts` — `list_templates`, `get_template`, `list_render_history` (all with `returns` validators).
|
|
12
|
+
- `mutations.ts` — `upsert_template`, `queue_render` (public); `_set_job_rendering`, `_complete_render_job`, `_fail_render_job`, `_write_audit` (internal).
|
|
13
|
+
- `actions.ts` — `render_now` (`"use node"`) — fetches Railway renderer, stores output in Convex storage, writes audit row, returns signed URL TTL 7 days. Env: `CONVEX_DOC_FORGE_RENDERER_URL` + `CONVEX_DOC_FORGE_RENDERER_KEY`.
|
|
14
|
+
- `src/__tests__/` — 30 vitest tests covering all queries, mutations, and the action with mocked fetch (0 network calls).
|
|
15
|
+
- `src/_generated/` — generated stubs (`dataModel.ts`, `server.ts`, `api.ts`) compatible with Convex 1.41.
|
|
16
|
+
|
|
17
|
+
### Notes
|
|
18
|
+
|
|
19
|
+
- tsc: 0 errors (`npx tsc --noEmit`).
|
|
20
|
+
- Test ratio: 30/30 PASS (`npm test`).
|
|
21
|
+
- No secrets in source. All renderer credentials via env vars.
|
|
22
|
+
- `v.any()` used only for `input_schema` and `context` fields (JSON Schema + render context are unbounded by design — documented trade-off).
|
package/LICENSE
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
Functional Source License, Version 1.1, Apache 2.0 Future License
|
|
2
|
+
|
|
3
|
+
Abbreviation
|
|
4
|
+
|
|
5
|
+
FSL-1.1-Apache-2.0
|
|
6
|
+
|
|
7
|
+
Notice
|
|
8
|
+
|
|
9
|
+
Copyright 2026 VantageOS (ElPi Corp)
|
|
10
|
+
|
|
11
|
+
Terms and Conditions
|
|
12
|
+
|
|
13
|
+
Licensor: VantageOS (ElPi Corp)
|
|
14
|
+
|
|
15
|
+
Licensed Work: VantagePeers
|
|
16
|
+
The Licensed Work is (c) 2026 VantageOS (ElPi Corp)
|
|
17
|
+
|
|
18
|
+
Additional Use Grant: You may make production use of the Licensed Work,
|
|
19
|
+
provided Your use does not include offering the Licensed
|
|
20
|
+
Work to third parties as a hosted or managed service
|
|
21
|
+
where the service provides users with access to any
|
|
22
|
+
substantial set of the features or functionality of the
|
|
23
|
+
Licensed Work.
|
|
24
|
+
|
|
25
|
+
Change Date: 2028-04-03 (two years from publication)
|
|
26
|
+
|
|
27
|
+
Change License: Apache License, Version 2.0
|
|
28
|
+
|
|
29
|
+
For information about alternative licensing arrangements for the Licensed Work,
|
|
30
|
+
please contact: contact@vantageos.com
|
|
31
|
+
|
|
32
|
+
License text below is provided for informational purposes only.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
Functional Source License, Version 1.1, Apache 2.0 Future License
|
|
37
|
+
|
|
38
|
+
1. Purpose
|
|
39
|
+
|
|
40
|
+
This license gives you broad permission to use, modify, and share this
|
|
41
|
+
software, with one important condition: you may not use it to compete with us.
|
|
42
|
+
|
|
43
|
+
2. Acceptance
|
|
44
|
+
|
|
45
|
+
To use the software, you must agree to these terms. If you do not agree, you
|
|
46
|
+
may not use the software.
|
|
47
|
+
|
|
48
|
+
3. Copyright License
|
|
49
|
+
|
|
50
|
+
The licensor grants you a non-exclusive, royalty-free, worldwide, non-
|
|
51
|
+
sublicensable, non-transferable license to use, copy, distribute, make
|
|
52
|
+
available, and prepare derivative works of the software, in each case subject
|
|
53
|
+
to the limitations below.
|
|
54
|
+
|
|
55
|
+
4. Limitations
|
|
56
|
+
|
|
57
|
+
You may not make the functionality of the software available to third parties
|
|
58
|
+
as a service, or otherwise use the software to provide a service to third
|
|
59
|
+
parties that competes with the licensor.
|
|
60
|
+
|
|
61
|
+
5. Patents
|
|
62
|
+
|
|
63
|
+
The licensor grants you a license, under any patent claims the licensor can
|
|
64
|
+
license or becomes able to license, to make, have made, use, sell, offer for
|
|
65
|
+
sale, import, and have imported the software, in each case subject to the
|
|
66
|
+
limitations and conditions in this license.
|
|
67
|
+
|
|
68
|
+
6. Fair Use
|
|
69
|
+
|
|
70
|
+
This license is not intended to limit any rights you may have under applicable
|
|
71
|
+
fair use or other laws.
|
|
72
|
+
|
|
73
|
+
7. No Other Rights
|
|
74
|
+
|
|
75
|
+
These terms do not grant any other rights. The licensor reserves all rights not
|
|
76
|
+
expressly granted.
|
|
77
|
+
|
|
78
|
+
8. Termination
|
|
79
|
+
|
|
80
|
+
Your license is automatically terminated if you violate the terms. However, if
|
|
81
|
+
you cure the violation within 30 days of discovering it, your license is
|
|
82
|
+
reinstated retroactively.
|
|
83
|
+
|
|
84
|
+
9. Disclaimer
|
|
85
|
+
|
|
86
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
87
|
+
IMPLIED.
|
|
88
|
+
|
|
89
|
+
10. Limitation of Liability
|
|
90
|
+
|
|
91
|
+
TO THE EXTENT PERMITTED BY LAW, THE LICENSOR IS NOT LIABLE FOR ANY DAMAGES
|
|
92
|
+
ARISING FROM THE USE OF THE SOFTWARE.
|
|
93
|
+
|
|
94
|
+
11. Change Date and License
|
|
95
|
+
|
|
96
|
+
After the Change Date, the licensor grants you the rights under the Change
|
|
97
|
+
License.
|
|
98
|
+
|
|
99
|
+
12. Definitions
|
|
100
|
+
|
|
101
|
+
"Compete" means providing a product or service that is substantially similar
|
|
102
|
+
to, or a replacement for, all or a significant portion of the Licensed Work.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
Apache License, Version 2.0
|
|
107
|
+
|
|
108
|
+
After the Change Date (2028-04-03), this software will be available under the
|
|
109
|
+
Apache License, Version 2.0. See https://www.apache.org/licenses/LICENSE-2.0
|
|
110
|
+
for full text.
|
package/README.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# @vantageos/convex-doc-forge
|
|
2
|
+
|
|
3
|
+
Convex component — **data layer** for the VantageDocForge trinity.
|
|
4
|
+
|
|
5
|
+
Stores templates, render jobs, and audit records. Calls the Railway renderer sidecar (`vantage-doc-forge-renderer`) via a Convex action over HTTP. Pattern: `@convex-dev/rag` (Convex handles data; external service handles compute).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
Consumer (vantage-immo / vCRM / MCP server)
|
|
13
|
+
│
|
|
14
|
+
▼ ctx.runAction(components.docForge.actions.render_now, {...})
|
|
15
|
+
@vantageos/convex-doc-forge (this package)
|
|
16
|
+
- templates table ← template metadata + asset_storage_id
|
|
17
|
+
- render_jobs table ← job status machine (queued → rendering → done|failed)
|
|
18
|
+
- render_audit table ← immutable audit log per render
|
|
19
|
+
- render_now action → HTTP POST /v1/render → Railway renderer sidecar
|
|
20
|
+
│
|
|
21
|
+
▼
|
|
22
|
+
vantage-doc-forge-renderer (Python Railway)
|
|
23
|
+
docxtpl + python-pptx + LibreOffice headless
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install @vantageos/convex-doc-forge
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
In your Convex deployment's `convex/convex.config.ts`:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { defineApp } from "convex/server";
|
|
38
|
+
import docForge from "@vantageos/convex-doc-forge/convex.config";
|
|
39
|
+
|
|
40
|
+
const app = defineApp();
|
|
41
|
+
app.use(docForge);
|
|
42
|
+
export default app;
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Required env vars (set in Convex dashboard)
|
|
48
|
+
|
|
49
|
+
| Variable | Description |
|
|
50
|
+
|---|---|
|
|
51
|
+
| `CONVEX_DOC_FORGE_RENDERER_URL` | Base URL of the Railway renderer (e.g. `https://doc-forge.railway.app`) |
|
|
52
|
+
| `CONVEX_DOC_FORGE_RENDERER_KEY` | Bearer token for renderer auth |
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Schema
|
|
57
|
+
|
|
58
|
+
### `templates`
|
|
59
|
+
|
|
60
|
+
| Field | Type | Notes |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `name` | string | Template name (unique per tenant+team) |
|
|
63
|
+
| `team` | string | Logical group (e.g. `iris-rh`, `pujol`) |
|
|
64
|
+
| `tenant_id` | string | Clerk org ID |
|
|
65
|
+
| `template_type` | `"doc-binary"` | Always `doc-binary` (V1) |
|
|
66
|
+
| `renderer_kind` | `"docxtpl" \| "python-pptx" \| "jinja2-html"` | Which renderer to use |
|
|
67
|
+
| `input_schema` | object | JSON Schema for context validation |
|
|
68
|
+
| `asset_storage_id` | Id<"_storage"> | Convex file storage reference to the .docx/.pptx asset |
|
|
69
|
+
| `output_formats` | string[] | e.g. `["docx", "pdf"]` |
|
|
70
|
+
| `version` | string | SemVer |
|
|
71
|
+
|
|
72
|
+
### `render_jobs`
|
|
73
|
+
|
|
74
|
+
Status machine: `queued` → `rendering` → `done` | `failed`
|
|
75
|
+
|
|
76
|
+
### `render_audit`
|
|
77
|
+
|
|
78
|
+
Immutable log. One row per successful render. `context_hash` = SHA-256 of `JSON.stringify(context)`.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Functions
|
|
83
|
+
|
|
84
|
+
### Queries (public)
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
// List templates for a tenant (optionally filter by team or type)
|
|
88
|
+
docForge.queries.list_templates({ tenant_id, team?, type? })
|
|
89
|
+
|
|
90
|
+
// Get a single template by ID
|
|
91
|
+
docForge.queries.get_template({ template_id })
|
|
92
|
+
|
|
93
|
+
// Audit history for a tenant (optionally filter by template or timestamp)
|
|
94
|
+
docForge.queries.list_render_history({ tenant_id, template_id?, since? })
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Mutations (public)
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
// Insert or update a template (upsert by name+team+tenant_id)
|
|
101
|
+
docForge.mutations.upsert_template({
|
|
102
|
+
name, team, tenant_id, renderer_kind,
|
|
103
|
+
input_schema, asset_storage_id, output_formats, version, createdBy
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
// Queue an async render job (returns job ID, use list_render_history to poll)
|
|
107
|
+
docForge.mutations.queue_render({ template_id, context, output_format, createdBy })
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Actions (public)
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
// Synchronous render: calls Railway renderer, stores output, writes audit
|
|
114
|
+
// Returns signed URL (TTL 7 days)
|
|
115
|
+
docForge.actions.render_now({
|
|
116
|
+
template_id, context, output_format,
|
|
117
|
+
actor, // Clerk subject
|
|
118
|
+
source? // "manual" | "workflow" | "agent" (default "manual")
|
|
119
|
+
})
|
|
120
|
+
// → { output_url, render_job_id, duration_ms, pages? }
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Renderer contract
|
|
126
|
+
|
|
127
|
+
`render_now` calls the sidecar:
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
POST {CONVEX_DOC_FORGE_RENDERER_URL}/v1/render
|
|
131
|
+
Authorization: Bearer {CONVEX_DOC_FORGE_RENDERER_KEY}
|
|
132
|
+
Content-Type: application/json
|
|
133
|
+
|
|
134
|
+
{
|
|
135
|
+
"template_url": "<Convex signed URL for the .docx asset>",
|
|
136
|
+
"context": { ...JSON data matching input_schema... },
|
|
137
|
+
"output_format": "pdf" | "docx" | "pptx" | "doc"
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
→ {
|
|
141
|
+
"output_bytes": "<base64>", // OR
|
|
142
|
+
"output_url": "<url>",
|
|
143
|
+
"duration_ms": 420,
|
|
144
|
+
"pages": 3
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Development
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
npm install
|
|
154
|
+
npm run typecheck # tsc --noEmit
|
|
155
|
+
npm test # vitest run (30 tests, no network)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Open decisions (see `decisions/rfc-rendering-engine.md`)
|
|
161
|
+
|
|
162
|
+
1. **npm scope**: `@vantageos/convex-doc-forge` (internal) — migrate to `@convex-dev/doc-forge` if submitted upstream.
|
|
163
|
+
2. **Asset storage**: Convex `_storage` (default) — hook for R2/S3 when volume justifies.
|
|
164
|
+
3. **Sync vs async**: `render_now` is sync (<10s); `queue_render` + subscription for async flows.
|
|
165
|
+
4. **Consumer install**: pnpm workspace monorepo (fleet) vs npm public (external).
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
Orchestrator: Hephaistos — VantageOS Team | 2026-06-19
|
package/convex.config.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineComponent } from "convex/server";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @vantageos/convex-doc-forge — data layer component.
|
|
5
|
+
*
|
|
6
|
+
* Install in a consumer Convex deployment:
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* // convex/convex.config.ts (consumer app)
|
|
10
|
+
* import { defineApp } from "convex/server";
|
|
11
|
+
* import docForge from "@vantageos/convex-doc-forge/convex.config";
|
|
12
|
+
*
|
|
13
|
+
* const app = defineApp();
|
|
14
|
+
* app.use(docForge);
|
|
15
|
+
* export default app;
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* Then reference via `components.docForge` in consumer functions.
|
|
19
|
+
*/
|
|
20
|
+
const docForge = defineComponent("docForge");
|
|
21
|
+
|
|
22
|
+
export default docForge;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/_generated/api.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,GAAG,EAAE,GAAY,CAAC;AAG/B,eAAO,MAAM,QAAQ,EAAE,GAAY,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
/**
|
|
3
|
+
* Generated API utility for @vantageos/convex-doc-forge.
|
|
4
|
+
*
|
|
5
|
+
* In a real deployment, regenerate with `npx convex dev`.
|
|
6
|
+
*/
|
|
7
|
+
import { anyApi } from "convex/server";
|
|
8
|
+
// anyApi is typed as `any` at runtime so it satisfies all function reference lookups.
|
|
9
|
+
// This mirrors the canonical Convex generated pattern.
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
+
export const api = anyApi;
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
+
export const internal = anyApi;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generated data model types for @vantageos/convex-doc-forge.
|
|
3
|
+
*
|
|
4
|
+
* In a real deployment, regenerate with `npx convex dev`.
|
|
5
|
+
*/
|
|
6
|
+
import type { DataModelFromSchemaDefinition, DocumentByName, TableNamesInDataModel, SystemTableNames } from "convex/server";
|
|
7
|
+
import type { GenericId } from "convex/values";
|
|
8
|
+
import schema from "../schema.js";
|
|
9
|
+
export type TableNames = TableNamesInDataModel<DataModel>;
|
|
10
|
+
export type Doc<TableName extends TableNames> = DocumentByName<DataModel, TableName>;
|
|
11
|
+
export type Id<TableName extends TableNames | SystemTableNames> = GenericId<TableName>;
|
|
12
|
+
export type DataModel = DataModelFromSchemaDefinition<typeof schema>;
|
|
13
|
+
//# sourceMappingURL=dataModel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataModel.d.ts","sourceRoot":"","sources":["../../src/_generated/dataModel.ts"],"names":[],"mappings":"AACA;;;;GAIG;AACH,OAAO,KAAK,EACV,6BAA6B,EAC7B,cAAc,EACd,qBAAqB,EACrB,gBAAgB,EACjB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,MAAM,MAAM,cAAc,CAAC;AAElC,MAAM,MAAM,UAAU,GAAG,qBAAqB,CAAC,SAAS,CAAC,CAAC;AAE1D,MAAM,MAAM,GAAG,CAAC,SAAS,SAAS,UAAU,IAAI,cAAc,CAC5D,SAAS,EACT,SAAS,CACV,CAAC;AAEF,MAAM,MAAM,EAAE,CAAC,SAAS,SAAS,UAAU,GAAG,gBAAgB,IAC5D,SAAS,CAAC,SAAS,CAAC,CAAC;AAEvB,MAAM,MAAM,SAAS,GAAG,6BAA6B,CAAC,OAAO,MAAM,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generated server utilities for @vantageos/convex-doc-forge.
|
|
3
|
+
*
|
|
4
|
+
* In a real deployment, regenerate with `npx convex dev`.
|
|
5
|
+
*/
|
|
6
|
+
import type { ActionBuilder, HttpActionBuilder, MutationBuilder, QueryBuilder, GenericActionCtx, GenericMutationCtx, GenericQueryCtx, GenericDatabaseReader, GenericDatabaseWriter } from "convex/server";
|
|
7
|
+
import type { DataModel } from "./dataModel.js";
|
|
8
|
+
export declare const query: QueryBuilder<DataModel, "public">;
|
|
9
|
+
export declare const internalQuery: QueryBuilder<DataModel, "internal">;
|
|
10
|
+
export declare const mutation: MutationBuilder<DataModel, "public">;
|
|
11
|
+
export declare const internalMutation: MutationBuilder<DataModel, "internal">;
|
|
12
|
+
export declare const action: ActionBuilder<DataModel, "public">;
|
|
13
|
+
export declare const internalAction: ActionBuilder<DataModel, "internal">;
|
|
14
|
+
export declare const httpAction: HttpActionBuilder;
|
|
15
|
+
export type QueryCtx = GenericQueryCtx<DataModel>;
|
|
16
|
+
export type MutationCtx = GenericMutationCtx<DataModel>;
|
|
17
|
+
export type ActionCtx = GenericActionCtx<DataModel>;
|
|
18
|
+
export type DatabaseReader = GenericDatabaseReader<DataModel>;
|
|
19
|
+
export type DatabaseWriter = GenericDatabaseWriter<DataModel>;
|
|
20
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/_generated/server.ts"],"names":[],"mappings":"AACA;;;;GAIG;AACH,OAAO,KAAK,EACV,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,qBAAqB,EACrB,qBAAqB,EACtB,MAAM,eAAe,CAAC;AAUvB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,eAAO,MAAM,KAAK,EAAE,YAAY,CAAC,SAAS,EAAE,QAAQ,CAAgB,CAAC;AACrE,eAAO,MAAM,aAAa,EAAE,YAAY,CAAC,SAAS,EAAE,UAAU,CACxC,CAAC;AACvB,eAAO,MAAM,QAAQ,EAAE,eAAe,CAAC,SAAS,EAAE,QAAQ,CAAmB,CAAC;AAC9E,eAAO,MAAM,gBAAgB,EAAE,eAAe,CAAC,SAAS,EAAE,UAAU,CAC3C,CAAC;AAC1B,eAAO,MAAM,MAAM,EAAE,aAAa,CAAC,SAAS,EAAE,QAAQ,CAAiB,CAAC;AACxE,eAAO,MAAM,cAAc,EAAE,aAAa,CAAC,SAAS,EAAE,UAAU,CACzC,CAAC;AACxB,eAAO,MAAM,UAAU,EAAE,iBAAqC,CAAC;AAE/D,MAAM,MAAM,QAAQ,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC;AAClD,MAAM,MAAM,WAAW,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAC;AACxD,MAAM,MAAM,SAAS,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;AACpD,MAAM,MAAM,cAAc,GAAG,qBAAqB,CAAC,SAAS,CAAC,CAAC;AAC9D,MAAM,MAAM,cAAc,GAAG,qBAAqB,CAAC,SAAS,CAAC,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { actionGeneric, httpActionGeneric, queryGeneric, mutationGeneric, internalActionGeneric, internalMutationGeneric, internalQueryGeneric, } from "convex/server";
|
|
2
|
+
export const query = queryGeneric;
|
|
3
|
+
export const internalQuery = internalQueryGeneric;
|
|
4
|
+
export const mutation = mutationGeneric;
|
|
5
|
+
export const internalMutation = internalMutationGeneric;
|
|
6
|
+
export const action = actionGeneric;
|
|
7
|
+
export const internalAction = internalActionGeneric;
|
|
8
|
+
export const httpAction = httpActionGeneric;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare function renderNowHandler(ctx: any, args: {
|
|
2
|
+
template_id: string;
|
|
3
|
+
context: unknown;
|
|
4
|
+
output_format: string;
|
|
5
|
+
actor: string;
|
|
6
|
+
source?: "manual" | "workflow" | "agent";
|
|
7
|
+
}): Promise<{
|
|
8
|
+
output_url: string;
|
|
9
|
+
render_job_id: string;
|
|
10
|
+
duration_ms: number;
|
|
11
|
+
pages?: number;
|
|
12
|
+
}>;
|
|
13
|
+
export declare const render_now: import("convex/server").RegisteredAction<"public", {
|
|
14
|
+
source?: "manual" | "workflow" | "agent" | undefined;
|
|
15
|
+
template_id: import("convex/values").GenericId<"templates">;
|
|
16
|
+
context: any;
|
|
17
|
+
output_format: string;
|
|
18
|
+
actor: string;
|
|
19
|
+
}, any>;
|
|
20
|
+
//# sourceMappingURL=actions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AAgCA,wBAAsB,gBAAgB,CAEpC,GAAG,EAAE,GAAG,EACR,IAAI,EAAE;IACJ,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;CAC1C,GACA,OAAO,CAAC;IACT,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC,CAoID;AAMD,eAAO,MAAM,UAAU;;;;;;OAsBrB,CAAC"}
|
package/dist/actions.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"use node";
|
|
2
|
+
import { v } from "convex/values";
|
|
3
|
+
import { action } from "./_generated/server";
|
|
4
|
+
import { internal } from "./_generated/api";
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// SHA-256 helper (Node built-in crypto)
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
async function sha256hex(input) {
|
|
9
|
+
const { createHash } = await import("node:crypto");
|
|
10
|
+
return createHash("sha256").update(input).digest("hex");
|
|
11
|
+
}
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// renderNowHandler — exported for unit testing
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
const SIGNED_URL_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
16
|
+
export async function renderNowHandler(
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
18
|
+
ctx, args) {
|
|
19
|
+
const rendererUrl = process.env.CONVEX_DOC_FORGE_RENDERER_URL;
|
|
20
|
+
const rendererKey = process.env.CONVEX_DOC_FORGE_RENDERER_KEY;
|
|
21
|
+
if (!rendererUrl)
|
|
22
|
+
throw new Error("CONVEX_DOC_FORGE_RENDERER_URL not set");
|
|
23
|
+
if (!rendererKey)
|
|
24
|
+
throw new Error("CONVEX_DOC_FORGE_RENDERER_KEY not set");
|
|
25
|
+
// 1. Load template
|
|
26
|
+
const template = await ctx.runQuery(internal.queries.get_template, {
|
|
27
|
+
template_id: args.template_id,
|
|
28
|
+
});
|
|
29
|
+
if (template === null)
|
|
30
|
+
throw new Error("Template not found");
|
|
31
|
+
if (!template.output_formats.includes(args.output_format)) {
|
|
32
|
+
throw new Error(`output_format '${args.output_format}' not supported. ` +
|
|
33
|
+
`Supported: ${template.output_formats.join(", ")}`);
|
|
34
|
+
}
|
|
35
|
+
// 2. Get signed URL for the binary asset
|
|
36
|
+
const assetUrl = await ctx.storage.getUrl(template.asset_storage_id);
|
|
37
|
+
if (!assetUrl)
|
|
38
|
+
throw new Error("Asset storage URL unavailable");
|
|
39
|
+
// 3. Create a queued job
|
|
40
|
+
const jobId = await ctx.runMutation(internal.mutations.queue_render, {
|
|
41
|
+
template_id: args.template_id,
|
|
42
|
+
context: args.context,
|
|
43
|
+
output_format: args.output_format,
|
|
44
|
+
createdBy: args.actor,
|
|
45
|
+
});
|
|
46
|
+
// 4. Transition to rendering
|
|
47
|
+
await ctx.runMutation(internal.mutations._set_job_rendering, {
|
|
48
|
+
job_id: jobId,
|
|
49
|
+
});
|
|
50
|
+
const contextHash = await sha256hex(JSON.stringify(args.context));
|
|
51
|
+
const startTs = Date.now();
|
|
52
|
+
try {
|
|
53
|
+
// 5. Call renderer Railway
|
|
54
|
+
const rendererRes = await fetch(`${rendererUrl}/v1/render`, {
|
|
55
|
+
method: "POST",
|
|
56
|
+
headers: {
|
|
57
|
+
"Content-Type": "application/json",
|
|
58
|
+
Authorization: `Bearer ${rendererKey}`,
|
|
59
|
+
},
|
|
60
|
+
body: JSON.stringify({
|
|
61
|
+
template_url: assetUrl,
|
|
62
|
+
context: args.context,
|
|
63
|
+
output_format: args.output_format,
|
|
64
|
+
}),
|
|
65
|
+
});
|
|
66
|
+
if (!rendererRes.ok) {
|
|
67
|
+
const errText = await rendererRes.text().catch(() => "<no body>");
|
|
68
|
+
throw new Error(`Renderer returned ${rendererRes.status}: ${errText}`);
|
|
69
|
+
}
|
|
70
|
+
const rendererJson = (await rendererRes.json());
|
|
71
|
+
const duration_ms = rendererJson.duration_ms ?? Date.now() - startTs;
|
|
72
|
+
// 6. Store output in Convex storage
|
|
73
|
+
let outputStorageId;
|
|
74
|
+
let signedUrl;
|
|
75
|
+
if (rendererJson.output_bytes) {
|
|
76
|
+
const binaryStr = atob(rendererJson.output_bytes);
|
|
77
|
+
const bytes = new Uint8Array(binaryStr.length);
|
|
78
|
+
for (let i = 0; i < binaryStr.length; i++) {
|
|
79
|
+
bytes[i] = binaryStr.charCodeAt(i);
|
|
80
|
+
}
|
|
81
|
+
const mimeType = mimeForFormat(args.output_format);
|
|
82
|
+
const blob = new Blob([bytes], { type: mimeType });
|
|
83
|
+
outputStorageId = await ctx.storage.store(blob);
|
|
84
|
+
const url = await ctx.storage.getUrl(outputStorageId);
|
|
85
|
+
if (!url)
|
|
86
|
+
throw new Error("Failed to get signed URL after storage");
|
|
87
|
+
signedUrl = url;
|
|
88
|
+
}
|
|
89
|
+
else if (rendererJson.output_url) {
|
|
90
|
+
const downloadRes = await fetch(rendererJson.output_url);
|
|
91
|
+
if (!downloadRes.ok)
|
|
92
|
+
throw new Error(`Failed to download output from renderer URL: ${downloadRes.status}`);
|
|
93
|
+
const mimeType = mimeForFormat(args.output_format);
|
|
94
|
+
const blob = new Blob([await downloadRes.arrayBuffer()], {
|
|
95
|
+
type: mimeType,
|
|
96
|
+
});
|
|
97
|
+
outputStorageId = await ctx.storage.store(blob);
|
|
98
|
+
const url = await ctx.storage.getUrl(outputStorageId);
|
|
99
|
+
if (!url)
|
|
100
|
+
throw new Error("Failed to get signed URL after storage");
|
|
101
|
+
signedUrl = url;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
throw new Error("Renderer response missing both output_bytes and output_url");
|
|
105
|
+
}
|
|
106
|
+
// 7. Mark job done
|
|
107
|
+
await ctx.runMutation(internal.mutations._complete_render_job, {
|
|
108
|
+
job_id: jobId,
|
|
109
|
+
output_storage_id: outputStorageId,
|
|
110
|
+
});
|
|
111
|
+
// 8. Write audit record
|
|
112
|
+
const expiresAt = Date.now() + SIGNED_URL_TTL_MS;
|
|
113
|
+
await ctx.runMutation(internal.mutations._write_audit, {
|
|
114
|
+
render_job_id: jobId,
|
|
115
|
+
tenant_id: template.tenant_id,
|
|
116
|
+
template_id: args.template_id,
|
|
117
|
+
template_version: template.version,
|
|
118
|
+
context_hash: contextHash,
|
|
119
|
+
output_signed_url: signedUrl,
|
|
120
|
+
expires_at: expiresAt,
|
|
121
|
+
actor: args.actor,
|
|
122
|
+
source: args.source ?? "manual",
|
|
123
|
+
});
|
|
124
|
+
return {
|
|
125
|
+
output_url: signedUrl,
|
|
126
|
+
render_job_id: jobId,
|
|
127
|
+
duration_ms,
|
|
128
|
+
pages: rendererJson.pages,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
133
|
+
await ctx.runMutation(internal.mutations._fail_render_job, {
|
|
134
|
+
job_id: jobId,
|
|
135
|
+
error: message,
|
|
136
|
+
});
|
|
137
|
+
throw err;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
// Convex registration
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
export const render_now = action({
|
|
144
|
+
args: {
|
|
145
|
+
template_id: v.id("templates"),
|
|
146
|
+
context: v.any(),
|
|
147
|
+
output_format: v.string(),
|
|
148
|
+
actor: v.string(),
|
|
149
|
+
source: v.optional(v.union(v.literal("manual"), v.literal("workflow"), v.literal("agent"))),
|
|
150
|
+
},
|
|
151
|
+
returns: v.object({
|
|
152
|
+
output_url: v.string(),
|
|
153
|
+
render_job_id: v.id("render_jobs"),
|
|
154
|
+
duration_ms: v.number(),
|
|
155
|
+
pages: v.optional(v.number()),
|
|
156
|
+
}),
|
|
157
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
158
|
+
handler: renderNowHandler,
|
|
159
|
+
});
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
// Helpers
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
function mimeForFormat(format) {
|
|
164
|
+
switch (format.toLowerCase()) {
|
|
165
|
+
case "pdf":
|
|
166
|
+
return "application/pdf";
|
|
167
|
+
case "docx":
|
|
168
|
+
return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
|
169
|
+
case "pptx":
|
|
170
|
+
return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
|
|
171
|
+
case "doc":
|
|
172
|
+
return "application/msword";
|
|
173
|
+
default:
|
|
174
|
+
return "application/octet-stream";
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vantageos/convex-doc-forge — data layer component.
|
|
3
|
+
*
|
|
4
|
+
* Install in a consumer Convex deployment:
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* // convex/convex.config.ts (consumer app)
|
|
8
|
+
* import { defineApp } from "convex/server";
|
|
9
|
+
* import docForge from "@vantageos/convex-doc-forge/convex.config";
|
|
10
|
+
*
|
|
11
|
+
* const app = defineApp();
|
|
12
|
+
* app.use(docForge);
|
|
13
|
+
* export default app;
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* Then reference via `components.docForge` in consumer functions.
|
|
17
|
+
*/
|
|
18
|
+
declare const docForge: import("convex/server").ComponentDefinition<any, {}>;
|
|
19
|
+
export default docForge;
|
|
20
|
+
//# sourceMappingURL=convex.config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"convex.config.d.ts","sourceRoot":"","sources":["../convex.config.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;GAgBG;AACH,QAAA,MAAM,QAAQ,sDAA8B,CAAC;AAE7C,eAAe,QAAQ,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defineComponent } from "convex/server";
|
|
2
|
+
/**
|
|
3
|
+
* @vantageos/convex-doc-forge — data layer component.
|
|
4
|
+
*
|
|
5
|
+
* Install in a consumer Convex deployment:
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* // convex/convex.config.ts (consumer app)
|
|
9
|
+
* import { defineApp } from "convex/server";
|
|
10
|
+
* import docForge from "@vantageos/convex-doc-forge/convex.config";
|
|
11
|
+
*
|
|
12
|
+
* const app = defineApp();
|
|
13
|
+
* app.use(docForge);
|
|
14
|
+
* export default app;
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* Then reference via `components.docForge` in consumer functions.
|
|
18
|
+
*/
|
|
19
|
+
const docForge = defineComponent("docForge");
|
|
20
|
+
export default docForge;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vantageos/convex-doc-forge — public re-exports.
|
|
3
|
+
*
|
|
4
|
+
* Consumer apps import functions via the component API object, not directly.
|
|
5
|
+
* This barrel is provided for type-only imports.
|
|
6
|
+
*/
|
|
7
|
+
export * from "./queries";
|
|
8
|
+
export * from "./mutations";
|
|
9
|
+
export * from "./actions";
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC"}
|