@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.
Files changed (56) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/LICENSE +110 -0
  3. package/README.md +169 -0
  4. package/convex.config.ts +22 -0
  5. package/dist/_generated/api.d.ts +3 -0
  6. package/dist/_generated/api.d.ts.map +1 -0
  7. package/dist/_generated/api.js +13 -0
  8. package/dist/_generated/dataModel.d.ts +13 -0
  9. package/dist/_generated/dataModel.d.ts.map +1 -0
  10. package/dist/_generated/dataModel.js +1 -0
  11. package/dist/_generated/server.d.ts +20 -0
  12. package/dist/_generated/server.d.ts.map +1 -0
  13. package/dist/_generated/server.js +8 -0
  14. package/dist/actions.d.ts +20 -0
  15. package/dist/actions.d.ts.map +1 -0
  16. package/dist/actions.js +176 -0
  17. package/dist/convex.config.d.ts +20 -0
  18. package/dist/convex.config.d.ts.map +1 -0
  19. package/dist/convex.config.js +20 -0
  20. package/dist/index.d.ts +10 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +9 -0
  23. package/dist/mutations.d.ts +84 -0
  24. package/dist/mutations.d.ts.map +1 -0
  25. package/dist/mutations.js +166 -0
  26. package/dist/queries.d.ts +30 -0
  27. package/dist/queries.d.ts.map +1 -0
  28. package/dist/queries.js +117 -0
  29. package/dist/schema.d.ts +97 -0
  30. package/dist/schema.d.ts.map +1 -0
  31. package/dist/schema.js +59 -0
  32. package/dist/src/_generated/api.d.ts +3 -0
  33. package/dist/src/_generated/api.d.ts.map +1 -0
  34. package/dist/src/_generated/api.js +13 -0
  35. package/dist/src/_generated/dataModel.d.ts +13 -0
  36. package/dist/src/_generated/dataModel.d.ts.map +1 -0
  37. package/dist/src/_generated/dataModel.js +1 -0
  38. package/dist/src/_generated/server.d.ts +20 -0
  39. package/dist/src/_generated/server.d.ts.map +1 -0
  40. package/dist/src/_generated/server.js +8 -0
  41. package/dist/src/actions.d.ts +20 -0
  42. package/dist/src/actions.d.ts.map +1 -0
  43. package/dist/src/actions.js +176 -0
  44. package/dist/src/index.d.ts +10 -0
  45. package/dist/src/index.d.ts.map +1 -0
  46. package/dist/src/index.js +9 -0
  47. package/dist/src/mutations.d.ts +84 -0
  48. package/dist/src/mutations.d.ts.map +1 -0
  49. package/dist/src/mutations.js +166 -0
  50. package/dist/src/queries.d.ts +30 -0
  51. package/dist/src/queries.d.ts.map +1 -0
  52. package/dist/src/queries.js +117 -0
  53. package/dist/src/schema.d.ts +97 -0
  54. package/dist/src/schema.d.ts.map +1 -0
  55. package/dist/src/schema.js +59 -0
  56. 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
@@ -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,3 @@
1
+ export declare const api: any;
2
+ export declare const internal: any;
3
+ //# sourceMappingURL=api.d.ts.map
@@ -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"}
@@ -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;
@@ -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"}