@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.
- package/dist/auth/index.d.ts +68 -2
- package/dist/index.d.ts +652 -416
- package/dist/index.js +82 -5
- package/dist/knowledge/index.d.ts +69 -5
- package/dist/organization-model/index.d.ts +652 -416
- package/dist/organization-model/index.js +82 -5
- package/dist/test-utils/index.d.ts +103 -37
- package/dist/test-utils/index.js +69 -2
- package/package.json +1 -1
- package/src/_gen/__tests__/__snapshots__/contracts.md.snap +54 -0
- package/src/business/clients/api-schemas.test.ts +63 -29
- package/src/business/clients/api-schemas.ts +41 -29
- package/src/integrations/credentials/index.ts +3 -4
- package/src/organization-model/__tests__/clients.test.ts +146 -0
- package/src/organization-model/contracts.ts +27 -27
- package/src/organization-model/cross-ref.ts +4 -0
- package/src/organization-model/defaults.ts +2 -2
- package/src/organization-model/domains/knowledge.ts +1 -0
- package/src/organization-model/graph/build.ts +23 -6
- package/src/organization-model/graph/schema.ts +4 -3
- package/src/organization-model/graph/types.ts +4 -3
- package/src/organization-model/helpers.ts +15 -0
- package/src/organization-model/published.ts +19 -1
- package/src/organization-model/schema-refinements.ts +2 -2
- package/src/organization-model/schema.ts +97 -0
- package/src/organization-model/snapshot-hash.ts +0 -2
- package/src/organization-model/types.ts +19 -1
- package/src/platform/constants/versions.ts +1 -1
- package/src/reference/_generated/contracts.md +54 -0
- package/src/supabase/database.types.ts +3 -0
- package/src/supabase/index.ts +0 -6
- 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:
|
|
@@ -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
|
package/src/supabase/index.ts
CHANGED
|
@@ -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()
|