@elevasis/core 0.40.0 → 0.42.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 (32) hide show
  1. package/dist/auth/index.d.ts +68 -2
  2. package/dist/index.d.ts +652 -416
  3. package/dist/index.js +82 -5
  4. package/dist/knowledge/index.d.ts +69 -5
  5. package/dist/organization-model/index.d.ts +652 -416
  6. package/dist/organization-model/index.js +82 -5
  7. package/dist/test-utils/index.d.ts +103 -37
  8. package/dist/test-utils/index.js +69 -2
  9. package/package.json +1 -1
  10. package/src/_gen/__tests__/__snapshots__/contracts.md.snap +54 -0
  11. package/src/business/clients/api-schemas.test.ts +63 -29
  12. package/src/business/clients/api-schemas.ts +41 -29
  13. package/src/integrations/credentials/index.ts +3 -4
  14. package/src/organization-model/__tests__/clients.test.ts +146 -0
  15. package/src/organization-model/contracts.ts +27 -27
  16. package/src/organization-model/cross-ref.ts +4 -0
  17. package/src/organization-model/defaults.ts +2 -2
  18. package/src/organization-model/domains/knowledge.ts +1 -0
  19. package/src/organization-model/graph/build.ts +23 -6
  20. package/src/organization-model/graph/schema.ts +4 -3
  21. package/src/organization-model/graph/types.ts +4 -3
  22. package/src/organization-model/helpers.ts +15 -0
  23. package/src/organization-model/published.ts +19 -1
  24. package/src/organization-model/schema-refinements.ts +2 -2
  25. package/src/organization-model/schema.ts +97 -0
  26. package/src/organization-model/snapshot-hash.ts +0 -2
  27. package/src/organization-model/types.ts +19 -1
  28. package/src/platform/constants/versions.ts +1 -1
  29. package/src/reference/_generated/contracts.md +54 -0
  30. package/src/supabase/database.types.ts +3 -0
  31. package/src/supabase/index.ts +0 -6
  32. package/src/business/pdf/server/__tests__/pdfmake-test.ts +0 -219
@@ -31,10 +31,10 @@ function asRoleHolderArray(heldBy: NonNullable<OrganizationModel['roles'][string
31
31
  function isKnowledgeKindCompatibleWithTarget(knowledgeKind: string, targetKind: string): boolean {
32
32
  if (knowledgeKind === 'reference') return true
33
33
  if (knowledgeKind === 'playbook') {
34
- return ['system', 'resource', 'stage', 'action', 'ontology'].includes(targetKind)
34
+ return ['system', 'client', 'resource', 'stage', 'action', 'ontology'].includes(targetKind)
35
35
  }
36
36
  if (knowledgeKind === 'strategy') {
37
- return ['system', 'goal', 'offering', 'customer-segment', 'ontology'].includes(targetKind)
37
+ return ['system', 'client', 'goal', 'offering', 'customer-segment', 'ontology'].includes(targetKind)
38
38
  }
39
39
  return false
40
40
  }
@@ -15,12 +15,106 @@ import { EntitiesDomainSchema } from './domains/entities'
15
15
  import { PoliciesDomainSchema, DEFAULT_ORGANIZATION_MODEL_POLICIES } from './domains/policies'
16
16
  import { DEFAULT_ONTOLOGY_SCOPE, OntologyScopeSchema } from './ontology'
17
17
  import { refineOrganizationModel } from './schema-refinements'
18
+ import { JsonValueSchema } from './domains/systems'
19
+ import { LabelSchema, ModelIdSchema } from './domains/shared'
20
+
21
+ export const ClientProfileIdSchema = z.string().uuid()
22
+ export const ClientProfileStatusSchema = z.enum(['active', 'onboarding', 'paused', 'completed', 'churned'])
23
+ export const ClientProfileSourceSchema = z
24
+ .string()
25
+ .trim()
26
+ .min(1)
27
+ .max(64)
28
+ .regex(/^[a-z][a-z0-9_]*$/, 'Source must use lowercase letters, numbers, and underscores')
29
+
30
+ export const ClientProfileIdentitySchema = z
31
+ .object({
32
+ organizationName: LabelSchema.optional(),
33
+ shortName: z.string().trim().min(1).max(40).optional(),
34
+ clientBrief: z.string().trim().max(4000).default(''),
35
+ geographicFocus: z.array(z.string().trim().min(1).max(200)).default([]),
36
+ timeZone: z.string().trim().min(1).max(100).default('UTC')
37
+ })
38
+ .passthrough()
39
+ .default({
40
+ clientBrief: '',
41
+ geographicFocus: [],
42
+ timeZone: 'UTC'
43
+ })
44
+
45
+ export const ClientProfileBrandingSchema = z
46
+ .object({
47
+ voice: z.string().trim().max(280).optional(),
48
+ tagline: z.string().trim().max(200).optional(),
49
+ values: z.array(z.string().trim().min(1).max(100)).default([])
50
+ })
51
+ .passthrough()
52
+ .default({
53
+ values: []
54
+ })
55
+
56
+ export const ClientProfileWorkspaceSchema = z
57
+ .object({
58
+ kind: z.enum(['external-project', 'internal-project', 'none']).optional(),
59
+ owner: z.enum(['developer', 'client', 'platform']).optional(),
60
+ projectId: z.string().trim().min(1).max(200).optional(),
61
+ workspacePath: z.string().trim().min(1).max(500).optional()
62
+ })
63
+ .passthrough()
64
+ .default({})
65
+
66
+ export const ClientProfileLinksSchema = z
67
+ .object({
68
+ projectIds: z.array(z.string().uuid()).default([]),
69
+ primaryCompanyId: z.string().uuid().optional(),
70
+ primaryContactId: z.string().uuid().optional(),
71
+ sourceDealId: z.string().uuid().optional()
72
+ })
73
+ .default({
74
+ projectIds: []
75
+ })
76
+
77
+ export const ClientProfilePromptsSchema = z
78
+ .object({
79
+ defaultContext: z.string().trim().max(8000).default('')
80
+ })
81
+ .passthrough()
82
+ .default({
83
+ defaultContext: ''
84
+ })
85
+
86
+ export const ClientProfileSchema = z
87
+ .object({
88
+ id: ClientProfileIdSchema,
89
+ slug: ModelIdSchema,
90
+ name: LabelSchema,
91
+ status: ClientProfileStatusSchema.default('onboarding'),
92
+ source: ClientProfileSourceSchema.optional(),
93
+ identity: ClientProfileIdentitySchema,
94
+ branding: ClientProfileBrandingSchema,
95
+ workspace: ClientProfileWorkspaceSchema,
96
+ links: ClientProfileLinksSchema,
97
+ prompts: ClientProfilePromptsSchema,
98
+ config: z.record(z.string().trim().min(1).max(200), JsonValueSchema).default({}),
99
+ customValues: z.record(z.string().trim().min(1).max(200), JsonValueSchema).default({})
100
+ })
101
+ .strict()
102
+
103
+ export const ClientProfilesDomainSchema = z
104
+ .record(ClientProfileIdSchema, ClientProfileSchema)
105
+ .refine((record) => Object.entries(record).every(([key, entry]) => entry.id === key), {
106
+ message: 'Each client profile id must match its map key'
107
+ })
108
+ .default({})
109
+
110
+ export const DEFAULT_ORGANIZATION_MODEL_CLIENTS: z.infer<typeof ClientProfilesDomainSchema> = {}
18
111
 
19
112
  // Phase 4 cut: 'sales', 'prospecting', 'projects', 'statuses' removed.
20
113
  // domainMetadata.knowledge covers versioning for the knowledge flat-map (D7).
21
114
  export const OrganizationModelDomainKeySchema = z.enum([
22
115
  'branding',
23
116
  'identity',
117
+ 'clients',
24
118
  'customers',
25
119
  'offerings',
26
120
  'roles',
@@ -46,6 +140,7 @@ export const DEFAULT_ORGANIZATION_MODEL_DOMAIN_METADATA: Record<
46
140
  > = {
47
141
  branding: { version: 1, lastModified: '2026-05-10' },
48
142
  identity: { version: 1, lastModified: '2026-05-10' },
143
+ clients: { version: 1, lastModified: '2026-05-30' },
49
144
  customers: { version: 1, lastModified: '2026-05-10' },
50
145
  offerings: { version: 1, lastModified: '2026-05-10' },
51
146
  roles: { version: 1, lastModified: '2026-05-10' },
@@ -64,6 +159,7 @@ export const OrganizationModelDomainMetadataByDomainSchema = z
64
159
  .object({
65
160
  branding: OrganizationModelDomainMetadataSchema,
66
161
  identity: OrganizationModelDomainMetadataSchema,
162
+ clients: OrganizationModelDomainMetadataSchema,
67
163
  customers: OrganizationModelDomainMetadataSchema,
68
164
  offerings: OrganizationModelDomainMetadataSchema,
69
165
  roles: OrganizationModelDomainMetadataSchema,
@@ -107,6 +203,7 @@ const OrganizationModelSchemaBase = z.object({
107
203
  branding: OrganizationModelBrandingSchema.default(DEFAULT_ORGANIZATION_MODEL_BRANDING),
108
204
  navigation: OrganizationModelNavigationSchema,
109
205
  identity: IdentityDomainSchema.default(DEFAULT_ORGANIZATION_MODEL_IDENTITY),
206
+ clients: ClientProfilesDomainSchema.default(DEFAULT_ORGANIZATION_MODEL_CLIENTS),
110
207
  customers: CustomersDomainSchema.default(DEFAULT_ORGANIZATION_MODEL_CUSTOMERS),
111
208
  offerings: OfferingsDomainSchema.default(DEFAULT_ORGANIZATION_MODEL_OFFERINGS),
112
209
  roles: RolesDomainSchema.default(DEFAULT_ORGANIZATION_MODEL_ROLES),
@@ -42,7 +42,6 @@ function deterministicStringify(value: unknown): string {
42
42
  */
43
43
  export function canonicalStringifyOrganizationModel(model: OrganizationModel): string {
44
44
  // Shallow-clone to strip snapshotHash without mutating the caller's object.
45
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
46
45
  const { snapshotHash: _snapshotHash, domainMetadata, ...rest } = model
47
46
 
48
47
  // Strip lastModified from each domain metadata entry so volatile dates do not
@@ -51,7 +50,6 @@ export function canonicalStringifyOrganizationModel(model: OrganizationModel): s
51
50
  ? Object.fromEntries(
52
51
  Object.entries(domainMetadata).map(([domain, meta]) => {
53
52
  if (meta === null || typeof meta !== 'object') return [domain, meta]
54
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
55
53
  const { lastModified: _lastModified, ...metaRest } = meta as Record<string, unknown>
56
54
  return [domain, metaRest]
57
55
  })
@@ -110,7 +110,16 @@ import {
110
110
  OrganizationModelDomainKeySchema,
111
111
  OrganizationModelDomainMetadataByDomainSchema,
112
112
  OrganizationModelDomainMetadataSchema,
113
- OrganizationModelSchema
113
+ OrganizationModelSchema,
114
+ ClientProfileBrandingSchema,
115
+ ClientProfileIdentitySchema,
116
+ ClientProfileLinksSchema,
117
+ ClientProfilePromptsSchema,
118
+ ClientProfileSchema,
119
+ ClientProfileSourceSchema,
120
+ ClientProfileStatusSchema,
121
+ ClientProfileWorkspaceSchema,
122
+ ClientProfilesDomainSchema
114
123
  } from './schema'
115
124
 
116
125
  export type OrganizationModel = z.infer<typeof OrganizationModelSchema>
@@ -118,6 +127,15 @@ export type OrganizationModelDomainKey = z.infer<typeof OrganizationModelDomainK
118
127
  export type OrganizationModelDomainMetadata = z.infer<typeof OrganizationModelDomainMetadataSchema>
119
128
  export type OrganizationModelDomainMetadataByDomain = z.infer<typeof OrganizationModelDomainMetadataByDomainSchema>
120
129
  export type OrganizationModelBranding = z.infer<typeof OrganizationModelBrandingSchema>
130
+ export type ClientProfile = z.infer<typeof ClientProfileSchema>
131
+ export type ClientProfileBranding = z.infer<typeof ClientProfileBrandingSchema>
132
+ export type ClientProfileIdentity = z.infer<typeof ClientProfileIdentitySchema>
133
+ export type ClientProfileLinks = z.infer<typeof ClientProfileLinksSchema>
134
+ export type ClientProfilePrompts = z.infer<typeof ClientProfilePromptsSchema>
135
+ export type ClientProfileSource = z.infer<typeof ClientProfileSourceSchema>
136
+ export type ClientProfileStatus = z.infer<typeof ClientProfileStatusSchema>
137
+ export type ClientProfileWorkspace = z.infer<typeof ClientProfileWorkspaceSchema>
138
+ export type OrganizationModelClients = z.infer<typeof ClientProfilesDomainSchema>
121
139
  // Phase 4: OrganizationModelSales, OrganizationModelProspecting, OrganizationModelProjects,
122
140
  // OrganizationModelNavigation removed — compound domain top-level fields deleted per D8/D1.
123
141
  // Retained as local aliases for content-kind payload shapes used in migration-helpers:
@@ -1,3 +1,3 @@
1
1
  export const VERSION = {
2
- CURRENT: '1.12.16'
2
+ CURRENT: '1.12.18'
3
3
  }
@@ -39,6 +39,60 @@ export type OrganizationModelDomainMetadataByDomain = z.infer<typeof Organizatio
39
39
  export type OrganizationModelBranding = z.infer<typeof OrganizationModelBrandingSchema>
40
40
  ```
41
41
 
42
+ ### `ClientProfile`
43
+
44
+ ```typescript
45
+ export type ClientProfile = z.infer<typeof ClientProfileSchema>
46
+ ```
47
+
48
+ ### `ClientProfileBranding`
49
+
50
+ ```typescript
51
+ export type ClientProfileBranding = z.infer<typeof ClientProfileBrandingSchema>
52
+ ```
53
+
54
+ ### `ClientProfileIdentity`
55
+
56
+ ```typescript
57
+ export type ClientProfileIdentity = z.infer<typeof ClientProfileIdentitySchema>
58
+ ```
59
+
60
+ ### `ClientProfileLinks`
61
+
62
+ ```typescript
63
+ export type ClientProfileLinks = z.infer<typeof ClientProfileLinksSchema>
64
+ ```
65
+
66
+ ### `ClientProfilePrompts`
67
+
68
+ ```typescript
69
+ export type ClientProfilePrompts = z.infer<typeof ClientProfilePromptsSchema>
70
+ ```
71
+
72
+ ### `ClientProfileSource`
73
+
74
+ ```typescript
75
+ export type ClientProfileSource = z.infer<typeof ClientProfileSourceSchema>
76
+ ```
77
+
78
+ ### `ClientProfileStatus`
79
+
80
+ ```typescript
81
+ export type ClientProfileStatus = z.infer<typeof ClientProfileStatusSchema>
82
+ ```
83
+
84
+ ### `ClientProfileWorkspace`
85
+
86
+ ```typescript
87
+ export type ClientProfileWorkspace = z.infer<typeof ClientProfileWorkspaceSchema>
88
+ ```
89
+
90
+ ### `OrganizationModelClients`
91
+
92
+ ```typescript
93
+ export type OrganizationModelClients = z.infer<typeof ClientProfilesDomainSchema>
94
+ ```
95
+
42
96
  ### `SalesPipeline`
43
97
 
44
98
  ```typescript
@@ -1256,6 +1256,7 @@ export type Database = {
1256
1256
  organization_id: string
1257
1257
  primary_company_id: string | null
1258
1258
  primary_contact_id: string | null
1259
+ source: string
1259
1260
  source_deal_id: string | null
1260
1261
  status: string
1261
1262
  updated_at: string
@@ -1269,6 +1270,7 @@ export type Database = {
1269
1270
  organization_id: string
1270
1271
  primary_company_id?: string | null
1271
1272
  primary_contact_id?: string | null
1273
+ source?: string
1272
1274
  source_deal_id?: string | null
1273
1275
  status?: string
1274
1276
  updated_at?: string
@@ -1282,6 +1284,7 @@ export type Database = {
1282
1284
  organization_id?: string
1283
1285
  primary_company_id?: string | null
1284
1286
  primary_contact_id?: string | null
1287
+ source?: string
1285
1288
  source_deal_id?: string | null
1286
1289
  status?: string
1287
1290
  updated_at?: string
@@ -13,8 +13,6 @@ export type SupabaseUserProfile = Tables<'users'>
13
13
  export type SupabaseOrganization = Tables<'organizations'>
14
14
  export type SupabaseOrgMembership = Tables<'org_memberships'>
15
15
  export type SupabaseTaskSchedule = Tables<'task_schedules'>
16
- /** @deprecated Use SupabaseTaskSchedule instead. Alias for backward compatibility. */
17
- export type SupabaseScheduledTask = SupabaseTaskSchedule
18
16
  export type SupabaseCommandQueue = Tables<'command_queue'>
19
17
  export type SupabaseExecutionLogs = Tables<'execution_logs'>
20
18
  export type SupabaseApiKey = Tables<'api_keys'>
@@ -25,8 +23,6 @@ export type SupabaseOrganizationCredential = Tables<'credentials'>
25
23
  export type SupabaseUserProfileInsert = TablesInsert<'users'>
26
24
  export type SupabaseOrganizationInsert = TablesInsert<'organizations'>
27
25
  export type SupabaseTaskScheduleInsert = TablesInsert<'task_schedules'>
28
- /** @deprecated Use SupabaseTaskScheduleInsert instead. Alias for backward compatibility. */
29
- export type SupabaseScheduledTaskInsert = SupabaseTaskScheduleInsert
30
26
  export type SupabaseCommandQueueInsert = TablesInsert<'command_queue'>
31
27
  export type SupabaseApiKeyInsert = TablesInsert<'api_keys'>
32
28
  export type SupabaseExecutionLogsInsert = TablesInsert<'execution_logs'>
@@ -35,8 +31,6 @@ export type SupabaseOrganizationCredentialInsert = TablesInsert<'credentials'>
35
31
  export type SupabaseUserProfileUpdate = TablesUpdate<'users'>
36
32
  export type SupabaseOrganizationUpdate = TablesUpdate<'organizations'>
37
33
  export type SupabaseTaskScheduleUpdate = TablesUpdate<'task_schedules'>
38
- /** @deprecated Use SupabaseTaskScheduleUpdate instead. Alias for backward compatibility. */
39
- export type SupabaseScheduledTaskUpdate = SupabaseTaskScheduleUpdate
40
34
  export type SupabaseCommandQueueUpdate = TablesUpdate<'command_queue'>
41
35
  export type SupabaseApiKeyUpdate = TablesUpdate<'api_keys'>
42
36
  export type SupabaseExecutionLogsUpdate = TablesUpdate<'execution_logs'>
@@ -1,219 +0,0 @@
1
- /**
2
- * pdfmake PDF Generation Test
3
- *
4
- * Tests that pdfmake can generate PDFs in this environment.
5
- * This is a verification test before full migration from @react-pdf/renderer.
6
- *
7
- * Run with: npx tsx packages/core/src/pdf/server/__tests__/pdfmake-test.ts
8
- */
9
- import { writeFileSync } from 'node:fs'
10
- import { join } from 'node:path'
11
- import type { PDFDocument } from '../../types'
12
- import { PdfMakeService } from '../pdfmake-service'
13
-
14
- // Mock storage service for testing (matches real StorageService interface)
15
- const mockStorageService = {
16
- async upload(params: { organizationId: string; bucket: string; path: string; file: Buffer; contentType: string; upsert?: boolean }) {
17
- const size = params.file.byteLength
18
- console.log(`[mock-storage] Would upload ${size} bytes to ${params.organizationId}/${params.bucket}/${params.path}`)
19
- return {
20
- path: params.path,
21
- fullPath: `${params.organizationId}/${params.path}`,
22
- size
23
- }
24
- },
25
- async createSignedUrl(params: { organizationId: string; bucket: string; path: string; expiresIn: number }) {
26
- console.log(`[mock-storage] Creating signed URL for ${params.bucket}/${params.path} (expires in ${params.expiresIn}s)`)
27
- return {
28
- signedUrl: `https://mock.storage/${params.bucket}/${params.organizationId}/${params.path}?token=mock`,
29
- path: params.path,
30
- expiresAt: new Date(Date.now() + params.expiresIn * 1000)
31
- }
32
- }
33
- }
34
-
35
- // Sample document matching the PDFDocument schema
36
- const sampleDocument: PDFDocument = {
37
- metadata: {
38
- title: 'pdfmake Test Document',
39
- author: 'Elevasis Platform'
40
- },
41
- pages: [
42
- // Page 1: Cover page
43
- {
44
- sections: [
45
- { type: 'text', content: 'ELEVASIS', variant: 'coverVertical' },
46
- { type: 'text', content: 'Automation Solutions Proposal', variant: 'coverScope' },
47
- { type: 'text', content: 'Le Development LLC', variant: 'coverSolutions' }
48
- ],
49
- footer: false,
50
- cover: {
51
- bottomLeft: [
52
- { type: 'text', content: 'Prepared for', variant: 'caption' },
53
- { type: 'text', content: 'Alexander Le', variant: 'body' }
54
- ],
55
- bottomRight: [
56
- { type: 'text', content: 'January 2026', variant: 'caption' }
57
- ]
58
- }
59
- },
60
- // Page 2: Executive summary with metrics
61
- {
62
- sections: [
63
- { type: 'text', content: 'Executive Summary', variant: 'title' },
64
- { type: 'text', content: 'This proposal outlines automation solutions designed to streamline your operations and reduce manual workload.', variant: 'body' },
65
- {
66
- type: 'card',
67
- title: 'Key Metrics',
68
- variant: 'accent',
69
- content: [
70
- { type: 'metric', value: '$51,506', label: 'Projected Annual Savings' },
71
- { type: 'metric', value: '16.1 hrs', label: 'Hours Reclaimed Weekly' }
72
- ]
73
- }
74
- ],
75
- header: { title: 'Executive Summary' },
76
- footer: true
77
- },
78
- // Page 3: Bottlenecks analysis
79
- {
80
- sections: [
81
- { type: 'text', content: 'Current Bottlenecks', variant: 'title' },
82
- {
83
- type: 'list',
84
- items: [
85
- { text: 'Manual client reporting - 15 hrs/week', highlight: true },
86
- { text: 'Invoice generation and follow-up - 8 hrs/week', highlight: true },
87
- 'Email campaign management',
88
- 'Data entry across platforms'
89
- ]
90
- },
91
- {
92
- type: 'table',
93
- headers: ['Bottleneck', 'Hours/Week', 'Annual Cost'],
94
- rows: [
95
- ['Client Reporting', '15', '$50,700'],
96
- ['Invoice Management', '8', '$22,880'],
97
- ['Total', '23', '$73,580']
98
- ]
99
- }
100
- ],
101
- header: { title: 'Analysis' },
102
- footer: true
103
- },
104
- // Page 4: Solutions with cards
105
- {
106
- sections: [
107
- { type: 'text', content: 'Proposed Solutions', variant: 'title' },
108
- {
109
- type: 'card',
110
- title: 'Solution 1: Automated Reporting',
111
- content: [
112
- { type: 'text', content: 'Automated data aggregation from multiple sources into unified client reports.', variant: 'body' },
113
- { type: 'list', items: ['Real-time data sync', 'Custom templates', 'Scheduled delivery'] }
114
- ]
115
- },
116
- {
117
- type: 'card',
118
- title: 'Solution 2: Invoice Automation',
119
- content: [
120
- { type: 'text', content: 'End-to-end invoice generation with automatic follow-ups and payment tracking.', variant: 'body' },
121
- { type: 'list', items: ['Auto-generation', 'Payment reminders', 'Cash flow analytics'], ordered: true }
122
- ]
123
- }
124
- ],
125
- header: { title: 'Solutions' },
126
- footer: true
127
- }
128
- ]
129
- }
130
-
131
- async function runTest() {
132
- console.log('========================================')
133
- console.log('pdfmake PDF Generation Test')
134
- console.log('========================================')
135
- console.log('')
136
-
137
- try {
138
- // Test 1: Create service
139
- console.log('[TEST 1] Creating PdfMakeService...')
140
- const service = new PdfMakeService(mockStorageService)
141
- console.log('[TEST 1] ✅ Service created successfully')
142
- console.log('')
143
-
144
- // Test 2: Render to buffer (no storage upload)
145
- console.log('[TEST 2] Rendering sample document to buffer...')
146
- console.log(` Document: ${sampleDocument.pages.length} pages`)
147
- console.log(` Title: ${sampleDocument.metadata?.title}`)
148
-
149
- const startTime = Date.now()
150
- const buffer = await service.renderToBuffer({
151
- organizationId: 'test-org',
152
- document: sampleDocument
153
- })
154
- const elapsed = Date.now() - startTime
155
-
156
- console.log(`[TEST 2] ✅ PDF rendered successfully`)
157
- console.log(` Buffer size: ${buffer.byteLength} bytes`)
158
- console.log(` Time: ${elapsed}ms`)
159
- console.log('')
160
-
161
- // Test 3: Verify it's a valid PDF (starts with %PDF-)
162
- console.log('[TEST 3] Validating PDF format...')
163
- const header = buffer.subarray(0, 5).toString('utf-8')
164
- if (header === '%PDF-') {
165
- console.log(`[TEST 3] ✅ Valid PDF header detected: ${header}`)
166
- } else {
167
- throw new Error(`Invalid PDF header: ${header}`)
168
- }
169
- console.log('')
170
-
171
- // Test 4: Write to file for manual inspection
172
- const outputPath = join(process.cwd(), 'pdfmake-test-output.pdf')
173
- console.log('[TEST 4] Writing PDF to file for inspection...')
174
- writeFileSync(outputPath, buffer)
175
- console.log(`[TEST 4] ✅ PDF written to: ${outputPath}`)
176
- console.log('')
177
-
178
- // Test 5: Test full render with mock storage
179
- console.log('[TEST 5] Testing full render with storage upload...')
180
- const result = await service.render({
181
- organizationId: 'test-org',
182
- document: sampleDocument,
183
- storage: {
184
- bucket: 'test-bucket',
185
- path: 'proposals/test/output.pdf'
186
- }
187
- })
188
- console.log(`[TEST 5] ✅ Full render completed`)
189
- console.log(` Success: ${result.success}`)
190
- console.log(` Size: ${result.size} bytes`)
191
- console.log(` URL: ${result.pdfUrl}`)
192
- console.log('')
193
-
194
- console.log('========================================')
195
- console.log('✅ ALL TESTS PASSED')
196
- console.log('========================================')
197
- console.log('')
198
- console.log('pdfmake is working correctly in this environment.')
199
- console.log(`Check the output PDF at: ${outputPath}`)
200
- console.log('')
201
-
202
- } catch (error) {
203
- console.error('')
204
- console.error('========================================')
205
- console.error('❌ TEST FAILED')
206
- console.error('========================================')
207
- console.error('')
208
- console.error('Error:', error instanceof Error ? error.message : String(error))
209
- if (error instanceof Error && error.stack) {
210
- console.error('')
211
- console.error('Stack trace:')
212
- console.error(error.stack)
213
- }
214
- process.exit(1)
215
- }
216
- }
217
-
218
- // Run the test
219
- runTest()