@voyantjs/legal 0.2.0 → 0.3.1

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 (32) hide show
  1. package/dist/contracts/routes.d.ts +94 -1
  2. package/dist/contracts/routes.d.ts.map +1 -1
  3. package/dist/contracts/routes.js +27 -1
  4. package/dist/contracts/service-contracts.d.ts +1234 -0
  5. package/dist/contracts/service-contracts.d.ts.map +1 -0
  6. package/dist/contracts/service-contracts.js +233 -0
  7. package/dist/contracts/service-series.d.ts +68 -0
  8. package/dist/contracts/service-series.d.ts.map +1 -0
  9. package/dist/contracts/service-series.js +34 -0
  10. package/dist/contracts/service-shared.d.ts +32 -0
  11. package/dist/contracts/service-shared.d.ts.map +1 -0
  12. package/dist/contracts/service-shared.js +142 -0
  13. package/dist/contracts/service-templates.d.ts +434 -0
  14. package/dist/contracts/service-templates.d.ts.map +1 -0
  15. package/dist/contracts/service-templates.js +132 -0
  16. package/dist/contracts/service.d.ts +882 -909
  17. package/dist/contracts/service.d.ts.map +1 -1
  18. package/dist/contracts/service.js +8 -568
  19. package/dist/contracts/validation.d.ts +14 -0
  20. package/dist/contracts/validation.d.ts.map +1 -1
  21. package/dist/contracts/validation.js +16 -0
  22. package/dist/policies/routes.d.ts +2 -2
  23. package/dist/policies/service-core.d.ts +1336 -0
  24. package/dist/policies/service-core.d.ts.map +1 -0
  25. package/dist/policies/service-core.js +357 -0
  26. package/dist/policies/service-shared.d.ts +43 -0
  27. package/dist/policies/service-shared.d.ts.map +1 -0
  28. package/dist/policies/service-shared.js +30 -0
  29. package/dist/policies/service.d.ts +28 -76
  30. package/dist/policies/service.d.ts.map +1 -1
  31. package/dist/policies/service.js +4 -436
  32. package/package.json +6 -6
@@ -0,0 +1,434 @@
1
+ import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
2
+ import { type ContractTemplateListQuery, type CreateContractTemplateInput, type CreateContractTemplateVersionInput, type RenderTemplateInput, type UpdateContractTemplateInput } from "./service-shared.js";
3
+ export declare const contractTemplatesService: {
4
+ listTemplates(db: PostgresJsDatabase, query: ContractTemplateListQuery): Promise<{
5
+ data: {
6
+ id: string;
7
+ name: string;
8
+ slug: string;
9
+ scope: "customer" | "partner" | "supplier" | "other" | "channel";
10
+ language: string;
11
+ description: string | null;
12
+ bodyFormat: "markdown" | "html" | "lexical_json";
13
+ body: string;
14
+ variableSchema: unknown;
15
+ currentVersionId: string | null;
16
+ active: boolean;
17
+ createdAt: Date;
18
+ updatedAt: Date;
19
+ }[];
20
+ total: number;
21
+ limit: number;
22
+ offset: number;
23
+ }>;
24
+ getTemplateById(db: PostgresJsDatabase, id: string): Promise<{
25
+ id: string;
26
+ name: string;
27
+ slug: string;
28
+ scope: "customer" | "partner" | "supplier" | "other" | "channel";
29
+ language: string;
30
+ description: string | null;
31
+ bodyFormat: "markdown" | "html" | "lexical_json";
32
+ body: string;
33
+ variableSchema: unknown;
34
+ currentVersionId: string | null;
35
+ active: boolean;
36
+ createdAt: Date;
37
+ updatedAt: Date;
38
+ } | null>;
39
+ getDefaultTemplate(db: PostgresJsDatabase, query: {
40
+ scope: "customer" | "supplier" | "partner" | "channel" | "other";
41
+ language?: string;
42
+ fallbackLanguages?: string[];
43
+ }): Promise<{
44
+ id: string;
45
+ name: string;
46
+ slug: string;
47
+ scope: "customer" | "partner" | "supplier" | "other" | "channel";
48
+ language: string;
49
+ description: string | null;
50
+ bodyFormat: "markdown" | "html" | "lexical_json";
51
+ body: string;
52
+ variableSchema: unknown;
53
+ currentVersionId: string | null;
54
+ active: boolean;
55
+ createdAt: Date;
56
+ updatedAt: Date;
57
+ } | null>;
58
+ createTemplate(db: PostgresJsDatabase, data: CreateContractTemplateInput): Promise<{
59
+ id: string;
60
+ name: string;
61
+ active: boolean;
62
+ createdAt: Date;
63
+ updatedAt: Date;
64
+ description: string | null;
65
+ slug: string;
66
+ scope: "customer" | "partner" | "supplier" | "other" | "channel";
67
+ language: string;
68
+ bodyFormat: "markdown" | "html" | "lexical_json";
69
+ body: string;
70
+ variableSchema: unknown;
71
+ currentVersionId: string | null;
72
+ } | null>;
73
+ updateTemplate(db: PostgresJsDatabase, id: string, data: UpdateContractTemplateInput): Promise<{
74
+ id: string;
75
+ name: string;
76
+ slug: string;
77
+ scope: "customer" | "partner" | "supplier" | "other" | "channel";
78
+ language: string;
79
+ description: string | null;
80
+ bodyFormat: "markdown" | "html" | "lexical_json";
81
+ body: string;
82
+ variableSchema: unknown;
83
+ currentVersionId: string | null;
84
+ active: boolean;
85
+ createdAt: Date;
86
+ updatedAt: Date;
87
+ } | null>;
88
+ deleteTemplate(db: PostgresJsDatabase, id: string): Promise<{
89
+ id: string;
90
+ } | null>;
91
+ listTemplateVersions(db: PostgresJsDatabase, templateId: string): Omit<import("drizzle-orm/pg-core").PgSelectBase<"contract_template_versions", {
92
+ id: import("drizzle-orm/pg-core").PgColumn<{
93
+ name: string;
94
+ tableName: "contract_template_versions";
95
+ dataType: "string";
96
+ columnType: "PgText";
97
+ data: string;
98
+ driverParam: string;
99
+ notNull: true;
100
+ hasDefault: true;
101
+ isPrimaryKey: true;
102
+ isAutoincrement: false;
103
+ hasRuntimeDefault: true;
104
+ enumValues: [string, ...string[]];
105
+ baseColumn: never;
106
+ identity: undefined;
107
+ generated: undefined;
108
+ }, {}, {}>;
109
+ templateId: import("drizzle-orm/pg-core").PgColumn<{
110
+ name: string;
111
+ tableName: "contract_template_versions";
112
+ dataType: "string";
113
+ columnType: "PgText";
114
+ data: string;
115
+ driverParam: string;
116
+ notNull: true;
117
+ hasDefault: false;
118
+ isPrimaryKey: false;
119
+ isAutoincrement: false;
120
+ hasRuntimeDefault: false;
121
+ enumValues: [string, ...string[]];
122
+ baseColumn: never;
123
+ identity: undefined;
124
+ generated: undefined;
125
+ }, {}, {}>;
126
+ version: import("drizzle-orm/pg-core").PgColumn<{
127
+ name: "version";
128
+ tableName: "contract_template_versions";
129
+ dataType: "number";
130
+ columnType: "PgInteger";
131
+ data: number;
132
+ driverParam: string | number;
133
+ notNull: true;
134
+ hasDefault: false;
135
+ isPrimaryKey: false;
136
+ isAutoincrement: false;
137
+ hasRuntimeDefault: false;
138
+ enumValues: undefined;
139
+ baseColumn: never;
140
+ identity: undefined;
141
+ generated: undefined;
142
+ }, {}, {}>;
143
+ bodyFormat: import("drizzle-orm/pg-core").PgColumn<{
144
+ name: "body_format";
145
+ tableName: "contract_template_versions";
146
+ dataType: "string";
147
+ columnType: "PgEnumColumn";
148
+ data: "markdown" | "html" | "lexical_json";
149
+ driverParam: string;
150
+ notNull: true;
151
+ hasDefault: true;
152
+ isPrimaryKey: false;
153
+ isAutoincrement: false;
154
+ hasRuntimeDefault: false;
155
+ enumValues: ["markdown", "html", "lexical_json"];
156
+ baseColumn: never;
157
+ identity: undefined;
158
+ generated: undefined;
159
+ }, {}, {}>;
160
+ body: import("drizzle-orm/pg-core").PgColumn<{
161
+ name: "body";
162
+ tableName: "contract_template_versions";
163
+ dataType: "string";
164
+ columnType: "PgText";
165
+ data: string;
166
+ driverParam: string;
167
+ notNull: true;
168
+ hasDefault: false;
169
+ isPrimaryKey: false;
170
+ isAutoincrement: false;
171
+ hasRuntimeDefault: false;
172
+ enumValues: [string, ...string[]];
173
+ baseColumn: never;
174
+ identity: undefined;
175
+ generated: undefined;
176
+ }, {}, {}>;
177
+ variableSchema: import("drizzle-orm/pg-core").PgColumn<{
178
+ name: "variable_schema";
179
+ tableName: "contract_template_versions";
180
+ dataType: "json";
181
+ columnType: "PgJsonb";
182
+ data: unknown;
183
+ driverParam: unknown;
184
+ notNull: false;
185
+ hasDefault: false;
186
+ isPrimaryKey: false;
187
+ isAutoincrement: false;
188
+ hasRuntimeDefault: false;
189
+ enumValues: undefined;
190
+ baseColumn: never;
191
+ identity: undefined;
192
+ generated: undefined;
193
+ }, {}, {}>;
194
+ changelog: import("drizzle-orm/pg-core").PgColumn<{
195
+ name: "changelog";
196
+ tableName: "contract_template_versions";
197
+ dataType: "string";
198
+ columnType: "PgText";
199
+ data: string;
200
+ driverParam: string;
201
+ notNull: false;
202
+ hasDefault: false;
203
+ isPrimaryKey: false;
204
+ isAutoincrement: false;
205
+ hasRuntimeDefault: false;
206
+ enumValues: [string, ...string[]];
207
+ baseColumn: never;
208
+ identity: undefined;
209
+ generated: undefined;
210
+ }, {}, {}>;
211
+ createdBy: import("drizzle-orm/pg-core").PgColumn<{
212
+ name: "created_by";
213
+ tableName: "contract_template_versions";
214
+ dataType: "string";
215
+ columnType: "PgText";
216
+ data: string;
217
+ driverParam: string;
218
+ notNull: false;
219
+ hasDefault: false;
220
+ isPrimaryKey: false;
221
+ isAutoincrement: false;
222
+ hasRuntimeDefault: false;
223
+ enumValues: [string, ...string[]];
224
+ baseColumn: never;
225
+ identity: undefined;
226
+ generated: undefined;
227
+ }, {}, {}>;
228
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
229
+ name: "created_at";
230
+ tableName: "contract_template_versions";
231
+ dataType: "date";
232
+ columnType: "PgTimestamp";
233
+ data: Date;
234
+ driverParam: string;
235
+ notNull: true;
236
+ hasDefault: true;
237
+ isPrimaryKey: false;
238
+ isAutoincrement: false;
239
+ hasRuntimeDefault: false;
240
+ enumValues: undefined;
241
+ baseColumn: never;
242
+ identity: undefined;
243
+ generated: undefined;
244
+ }, {}, {}>;
245
+ }, "single", Record<"contract_template_versions", "not-null">, false, "where" | "orderBy", {
246
+ id: string;
247
+ templateId: string;
248
+ version: number;
249
+ bodyFormat: "markdown" | "html" | "lexical_json";
250
+ body: string;
251
+ variableSchema: unknown;
252
+ changelog: string | null;
253
+ createdBy: string | null;
254
+ createdAt: Date;
255
+ }[], {
256
+ id: import("drizzle-orm/pg-core").PgColumn<{
257
+ name: string;
258
+ tableName: "contract_template_versions";
259
+ dataType: "string";
260
+ columnType: "PgText";
261
+ data: string;
262
+ driverParam: string;
263
+ notNull: true;
264
+ hasDefault: true;
265
+ isPrimaryKey: true;
266
+ isAutoincrement: false;
267
+ hasRuntimeDefault: true;
268
+ enumValues: [string, ...string[]];
269
+ baseColumn: never;
270
+ identity: undefined;
271
+ generated: undefined;
272
+ }, {}, {}>;
273
+ templateId: import("drizzle-orm/pg-core").PgColumn<{
274
+ name: string;
275
+ tableName: "contract_template_versions";
276
+ dataType: "string";
277
+ columnType: "PgText";
278
+ data: string;
279
+ driverParam: string;
280
+ notNull: true;
281
+ hasDefault: false;
282
+ isPrimaryKey: false;
283
+ isAutoincrement: false;
284
+ hasRuntimeDefault: false;
285
+ enumValues: [string, ...string[]];
286
+ baseColumn: never;
287
+ identity: undefined;
288
+ generated: undefined;
289
+ }, {}, {}>;
290
+ version: import("drizzle-orm/pg-core").PgColumn<{
291
+ name: "version";
292
+ tableName: "contract_template_versions";
293
+ dataType: "number";
294
+ columnType: "PgInteger";
295
+ data: number;
296
+ driverParam: string | number;
297
+ notNull: true;
298
+ hasDefault: false;
299
+ isPrimaryKey: false;
300
+ isAutoincrement: false;
301
+ hasRuntimeDefault: false;
302
+ enumValues: undefined;
303
+ baseColumn: never;
304
+ identity: undefined;
305
+ generated: undefined;
306
+ }, {}, {}>;
307
+ bodyFormat: import("drizzle-orm/pg-core").PgColumn<{
308
+ name: "body_format";
309
+ tableName: "contract_template_versions";
310
+ dataType: "string";
311
+ columnType: "PgEnumColumn";
312
+ data: "markdown" | "html" | "lexical_json";
313
+ driverParam: string;
314
+ notNull: true;
315
+ hasDefault: true;
316
+ isPrimaryKey: false;
317
+ isAutoincrement: false;
318
+ hasRuntimeDefault: false;
319
+ enumValues: ["markdown", "html", "lexical_json"];
320
+ baseColumn: never;
321
+ identity: undefined;
322
+ generated: undefined;
323
+ }, {}, {}>;
324
+ body: import("drizzle-orm/pg-core").PgColumn<{
325
+ name: "body";
326
+ tableName: "contract_template_versions";
327
+ dataType: "string";
328
+ columnType: "PgText";
329
+ data: string;
330
+ driverParam: string;
331
+ notNull: true;
332
+ hasDefault: false;
333
+ isPrimaryKey: false;
334
+ isAutoincrement: false;
335
+ hasRuntimeDefault: false;
336
+ enumValues: [string, ...string[]];
337
+ baseColumn: never;
338
+ identity: undefined;
339
+ generated: undefined;
340
+ }, {}, {}>;
341
+ variableSchema: import("drizzle-orm/pg-core").PgColumn<{
342
+ name: "variable_schema";
343
+ tableName: "contract_template_versions";
344
+ dataType: "json";
345
+ columnType: "PgJsonb";
346
+ data: unknown;
347
+ driverParam: unknown;
348
+ notNull: false;
349
+ hasDefault: false;
350
+ isPrimaryKey: false;
351
+ isAutoincrement: false;
352
+ hasRuntimeDefault: false;
353
+ enumValues: undefined;
354
+ baseColumn: never;
355
+ identity: undefined;
356
+ generated: undefined;
357
+ }, {}, {}>;
358
+ changelog: import("drizzle-orm/pg-core").PgColumn<{
359
+ name: "changelog";
360
+ tableName: "contract_template_versions";
361
+ dataType: "string";
362
+ columnType: "PgText";
363
+ data: string;
364
+ driverParam: string;
365
+ notNull: false;
366
+ hasDefault: false;
367
+ isPrimaryKey: false;
368
+ isAutoincrement: false;
369
+ hasRuntimeDefault: false;
370
+ enumValues: [string, ...string[]];
371
+ baseColumn: never;
372
+ identity: undefined;
373
+ generated: undefined;
374
+ }, {}, {}>;
375
+ createdBy: import("drizzle-orm/pg-core").PgColumn<{
376
+ name: "created_by";
377
+ tableName: "contract_template_versions";
378
+ dataType: "string";
379
+ columnType: "PgText";
380
+ data: string;
381
+ driverParam: string;
382
+ notNull: false;
383
+ hasDefault: false;
384
+ isPrimaryKey: false;
385
+ isAutoincrement: false;
386
+ hasRuntimeDefault: false;
387
+ enumValues: [string, ...string[]];
388
+ baseColumn: never;
389
+ identity: undefined;
390
+ generated: undefined;
391
+ }, {}, {}>;
392
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
393
+ name: "created_at";
394
+ tableName: "contract_template_versions";
395
+ dataType: "date";
396
+ columnType: "PgTimestamp";
397
+ data: Date;
398
+ driverParam: string;
399
+ notNull: true;
400
+ hasDefault: true;
401
+ isPrimaryKey: false;
402
+ isAutoincrement: false;
403
+ hasRuntimeDefault: false;
404
+ enumValues: undefined;
405
+ baseColumn: never;
406
+ identity: undefined;
407
+ generated: undefined;
408
+ }, {}, {}>;
409
+ }>, "where" | "orderBy">;
410
+ getTemplateVersionById(db: PostgresJsDatabase, id: string): Promise<{
411
+ id: string;
412
+ templateId: string;
413
+ version: number;
414
+ bodyFormat: "markdown" | "html" | "lexical_json";
415
+ body: string;
416
+ variableSchema: unknown;
417
+ changelog: string | null;
418
+ createdBy: string | null;
419
+ createdAt: Date;
420
+ } | null>;
421
+ createTemplateVersion(db: PostgresJsDatabase, templateId: string, data: CreateContractTemplateVersionInput): Promise<{
422
+ id: string;
423
+ createdAt: Date;
424
+ bodyFormat: "markdown" | "html" | "lexical_json";
425
+ body: string;
426
+ variableSchema: unknown;
427
+ templateId: string;
428
+ version: number;
429
+ changelog: string | null;
430
+ createdBy: string | null;
431
+ } | null>;
432
+ renderPreview(input: RenderTemplateInput): string;
433
+ };
434
+ //# sourceMappingURL=service-templates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-templates.d.ts","sourceRoot":"","sources":["../../src/contracts/service-templates.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAGjE,OAAO,EACL,KAAK,yBAAyB,EAC9B,KAAK,2BAA2B,EAChC,KAAK,kCAAkC,EAEvC,KAAK,mBAAmB,EAExB,KAAK,2BAA2B,EACjC,MAAM,qBAAqB,CAAA;AAE5B,eAAO,MAAM,wBAAwB;sBACX,kBAAkB,SAAS,yBAAyB;;;;;;;;;;;;;;;;;;;;wBA6BlD,kBAAkB,MAAM,MAAM;;;;;;;;;;;;;;;2BASlD,kBAAkB,SACf;QACL,KAAK,EAAE,UAAU,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAA;QAChE,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;KAC7B;;;;;;;;;;;;;;;uBAgCsB,kBAAkB,QAAQ,2BAA2B;;;;;;;;;;;;;;;uBAIrD,kBAAkB,MAAM,MAAM,QAAQ,2BAA2B;;;;;;;;;;;;;;;uBAQjE,kBAAkB,MAAM,MAAM;;;6BAO9B,kBAAkB,cAAc,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+BAO9B,kBAAkB,MAAM,MAAM;;;;;;;;;;;8BASzD,kBAAkB,cACV,MAAM,QACZ,kCAAkC;;;;;;;;;;;yBAmCrB,mBAAmB,GAAG,MAAM;CAKlD,CAAA"}
@@ -0,0 +1,132 @@
1
+ import { and, desc, eq, ilike, or, sql } from "drizzle-orm";
2
+ import { contractTemplates, contractTemplateVersions } from "./schema.js";
3
+ import { paginate, renderTemplate, } from "./service-shared.js";
4
+ export const contractTemplatesService = {
5
+ async listTemplates(db, query) {
6
+ const conditions = [];
7
+ if (query.scope)
8
+ conditions.push(eq(contractTemplates.scope, query.scope));
9
+ if (query.language)
10
+ conditions.push(eq(contractTemplates.language, query.language));
11
+ if (query.active !== undefined)
12
+ conditions.push(eq(contractTemplates.active, query.active));
13
+ if (query.search) {
14
+ const term = `%${query.search}%`;
15
+ conditions.push(or(ilike(contractTemplates.name, term), ilike(contractTemplates.slug, term), ilike(contractTemplates.description, term)));
16
+ }
17
+ const where = conditions.length ? and(...conditions) : undefined;
18
+ return paginate(db
19
+ .select()
20
+ .from(contractTemplates)
21
+ .where(where)
22
+ .limit(query.limit)
23
+ .offset(query.offset)
24
+ .orderBy(desc(contractTemplates.updatedAt)), db.select({ total: sql `count(*)::int` }).from(contractTemplates).where(where), query.limit, query.offset);
25
+ },
26
+ async getTemplateById(db, id) {
27
+ const [row] = await db
28
+ .select()
29
+ .from(contractTemplates)
30
+ .where(eq(contractTemplates.id, id))
31
+ .limit(1);
32
+ return row ?? null;
33
+ },
34
+ async getDefaultTemplate(db, query) {
35
+ const rows = await db
36
+ .select()
37
+ .from(contractTemplates)
38
+ .where(and(eq(contractTemplates.scope, query.scope), eq(contractTemplates.active, true)))
39
+ .orderBy(desc(contractTemplates.updatedAt));
40
+ if (rows.length === 0) {
41
+ return null;
42
+ }
43
+ const preferredLanguages = [
44
+ query.language?.trim().toLowerCase(),
45
+ ...(query.fallbackLanguages ?? []).map((value) => value.trim().toLowerCase()),
46
+ ].filter((value, index, values) => Boolean(value) && values.indexOf(value) === index);
47
+ if (preferredLanguages.length === 0) {
48
+ return rows[0] ?? null;
49
+ }
50
+ for (const language of preferredLanguages) {
51
+ const match = rows.find((row) => row.language.trim().toLowerCase() === language);
52
+ if (match) {
53
+ return match;
54
+ }
55
+ }
56
+ return rows[0] ?? null;
57
+ },
58
+ async createTemplate(db, data) {
59
+ const [row] = await db.insert(contractTemplates).values(data).returning();
60
+ return row ?? null;
61
+ },
62
+ async updateTemplate(db, id, data) {
63
+ const [row] = await db
64
+ .update(contractTemplates)
65
+ .set({ ...data, updatedAt: new Date() })
66
+ .where(eq(contractTemplates.id, id))
67
+ .returning();
68
+ return row ?? null;
69
+ },
70
+ async deleteTemplate(db, id) {
71
+ const [row] = await db
72
+ .delete(contractTemplates)
73
+ .where(eq(contractTemplates.id, id))
74
+ .returning({ id: contractTemplates.id });
75
+ return row ?? null;
76
+ },
77
+ listTemplateVersions(db, templateId) {
78
+ return db
79
+ .select()
80
+ .from(contractTemplateVersions)
81
+ .where(eq(contractTemplateVersions.templateId, templateId))
82
+ .orderBy(desc(contractTemplateVersions.version));
83
+ },
84
+ async getTemplateVersionById(db, id) {
85
+ const [row] = await db
86
+ .select()
87
+ .from(contractTemplateVersions)
88
+ .where(eq(contractTemplateVersions.id, id))
89
+ .limit(1);
90
+ return row ?? null;
91
+ },
92
+ async createTemplateVersion(db, templateId, data) {
93
+ return db.transaction(async (tx) => {
94
+ const [template] = await tx
95
+ .select({ id: contractTemplates.id })
96
+ .from(contractTemplates)
97
+ .where(eq(contractTemplates.id, templateId))
98
+ .limit(1);
99
+ if (!template)
100
+ return null;
101
+ const [maxRow] = await tx
102
+ .select({ max: sql `coalesce(max(${contractTemplateVersions.version}), 0)::int` })
103
+ .from(contractTemplateVersions)
104
+ .where(eq(contractTemplateVersions.templateId, templateId));
105
+ const nextVersion = (maxRow?.max ?? 0) + 1;
106
+ const [version] = await tx
107
+ .insert(contractTemplateVersions)
108
+ .values({
109
+ templateId,
110
+ version: nextVersion,
111
+ bodyFormat: data.bodyFormat,
112
+ body: data.body,
113
+ variableSchema: data.variableSchema ?? null,
114
+ changelog: data.changelog ?? null,
115
+ createdBy: data.createdBy ?? null,
116
+ })
117
+ .returning();
118
+ if (version) {
119
+ await tx
120
+ .update(contractTemplates)
121
+ .set({ currentVersionId: version.id, updatedAt: new Date() })
122
+ .where(eq(contractTemplates.id, templateId));
123
+ }
124
+ return version ?? null;
125
+ });
126
+ },
127
+ renderPreview(input) {
128
+ const body = input.body ?? "";
129
+ const format = input.bodyFormat ?? "markdown";
130
+ return renderTemplate(body, format, input.variables);
131
+ },
132
+ };