@elevasis/core 0.22.0 → 0.24.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/index.d.ts +3214 -2501
- package/dist/index.js +3112 -1222
- package/dist/knowledge/index.d.ts +1108 -1264
- package/dist/knowledge/index.js +112 -9
- package/dist/organization-model/index.d.ts +3214 -2501
- package/dist/organization-model/index.js +3112 -1222
- package/dist/test-utils/index.d.ts +985 -1103
- package/dist/test-utils/index.js +2464 -1165
- package/package.json +5 -5
- package/src/README.md +14 -14
- package/src/__tests__/publish.test.ts +24 -24
- package/src/__tests__/template-core-compatibility.test.ts +9 -80
- package/src/_gen/__tests__/__snapshots__/contracts.md.snap +2389 -2121
- package/src/_gen/__tests__/scaffold-contracts.test.ts +30 -30
- package/src/auth/multi-tenancy/credentials/__tests__/encryption.test.ts +217 -217
- package/src/auth/multi-tenancy/credentials/server/encryption.ts +69 -69
- package/src/auth/multi-tenancy/credentials/server/kek-loader.ts +37 -37
- package/src/auth/multi-tenancy/index.ts +26 -26
- package/src/auth/multi-tenancy/invitations/api-schemas.ts +104 -104
- package/src/auth/multi-tenancy/memberships/api-schemas.ts +143 -143
- package/src/auth/multi-tenancy/memberships/index.ts +26 -26
- package/src/auth/multi-tenancy/memberships/membership.ts +130 -130
- package/src/auth/multi-tenancy/organizations/__tests__/api-schemas.test.ts +194 -194
- package/src/auth/multi-tenancy/organizations/api-schemas.ts +136 -136
- package/src/auth/multi-tenancy/permissions.test.ts +42 -42
- package/src/auth/multi-tenancy/permissions.ts +123 -123
- package/src/auth/multi-tenancy/role-management/api-schemas.ts +78 -78
- package/src/auth/multi-tenancy/role-management/index.ts +16 -16
- package/src/auth/multi-tenancy/theme-presets.ts +45 -45
- package/src/auth/multi-tenancy/types.ts +57 -57
- package/src/auth/multi-tenancy/users/api-schemas.ts +165 -165
- package/src/business/README.md +2 -2
- package/src/business/acquisition/activity-events.test.ts +250 -250
- package/src/business/acquisition/activity-events.ts +93 -93
- package/src/business/acquisition/api-schemas.test.ts +1883 -1843
- package/src/business/acquisition/api-schemas.ts +1493 -1500
- package/src/business/acquisition/build-templates.test.ts +240 -240
- package/src/business/acquisition/build-templates.ts +83 -41
- package/src/business/acquisition/crm-next-action.test.ts +262 -262
- package/src/business/acquisition/crm-next-action.ts +220 -220
- package/src/business/acquisition/crm-priority.test.ts +216 -216
- package/src/business/acquisition/crm-priority.ts +349 -349
- package/src/business/acquisition/crm-state-actions.test.ts +153 -151
- package/src/business/acquisition/deal-ownership.test.ts +351 -351
- package/src/business/acquisition/deal-ownership.ts +120 -120
- package/src/business/acquisition/derive-actions.test.ts +129 -104
- package/src/business/acquisition/derive-actions.ts +74 -84
- package/src/business/acquisition/index.ts +171 -170
- package/src/business/acquisition/ontology-validation.ts +309 -0
- package/src/business/acquisition/stateful.ts +30 -30
- package/src/business/acquisition/types.ts +396 -392
- package/src/business/clients/api-schemas.test.ts +115 -115
- package/src/business/clients/api-schemas.ts +158 -158
- package/src/business/clients/index.ts +1 -1
- package/src/business/crm/api-schemas.ts +40 -40
- package/src/business/crm/index.ts +1 -1
- package/src/business/deals/api-schemas.ts +87 -87
- package/src/business/deals/index.ts +1 -1
- package/src/business/index.ts +5 -5
- package/src/business/projects/types.ts +144 -144
- package/src/commands/queue/types/task.ts +15 -15
- package/src/execution/core/runner-types.ts +61 -61
- package/src/execution/core/sse-executions.ts +7 -7
- package/src/execution/engine/__tests__/fixtures/test-agents.ts +10 -10
- package/src/execution/engine/agent/core/__tests__/agent.test.ts +16 -16
- package/src/execution/engine/agent/core/__tests__/error-passthrough.test.ts +4 -4
- package/src/execution/engine/agent/core/types.ts +25 -25
- package/src/execution/engine/agent/index.ts +6 -6
- package/src/execution/engine/agent/reasoning/__tests__/request-builder.test.ts +24 -24
- package/src/execution/engine/index.ts +443 -443
- package/src/execution/engine/tools/integration/server/adapters/apify/__tests__/apify-run-actor.integration.test.ts +298 -298
- package/src/execution/engine/tools/integration/server/adapters/apify/apify-adapter.test.ts +55 -55
- package/src/execution/engine/tools/integration/server/adapters/apify/apify-adapter.ts +107 -107
- package/src/execution/engine/tools/integration/server/adapters/apollo/apollo-adapter.test.ts +48 -48
- package/src/execution/engine/tools/integration/server/adapters/apollo/apollo-adapter.ts +99 -99
- package/src/execution/engine/tools/integration/server/adapters/apollo/index.ts +1 -1
- package/src/execution/engine/tools/integration/server/adapters/attio/__tests__/attio-crud.integration.test.ts +363 -363
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/get-record/index.test.ts +162 -162
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/list-records/index.test.ts +316 -316
- package/src/execution/engine/tools/integration/server/adapters/clickup/clickup-adapter.test.ts +18 -18
- package/src/execution/engine/tools/integration/server/adapters/clickup/clickup-adapter.ts +194 -194
- package/src/execution/engine/tools/integration/server/adapters/clickup/index.ts +7 -7
- package/src/execution/engine/tools/integration/server/adapters/gmail/gmail-adapter.ts +204 -204
- package/src/execution/engine/tools/integration/server/adapters/gmail/gmail-tools.ts +105 -105
- package/src/execution/engine/tools/integration/server/adapters/google-calendar/google-calendar-adapter.ts +428 -428
- package/src/execution/engine/tools/integration/server/adapters/google-calendar/index.ts +2 -2
- package/src/execution/engine/tools/integration/server/adapters/google-sheets/__tests__/google-sheets.integration.test.ts +261 -261
- package/src/execution/engine/tools/integration/server/adapters/instantly/instantly-tools.ts +1474 -1474
- package/src/execution/engine/tools/integration/server/adapters/millionverifier/millionverifier-tools.ts +103 -103
- package/src/execution/engine/tools/integration/server/adapters/resend/fetch/send-email/index.test.ts +88 -88
- package/src/execution/engine/tools/integration/server/adapters/resend/fetch/send-email/index.ts +141 -141
- package/src/execution/engine/tools/integration/server/adapters/resend/fetch/utils/types.ts +76 -76
- package/src/execution/engine/tools/integration/server/adapters/signature-api/signature-api-tools.ts +182 -182
- package/src/execution/engine/tools/integration/server/adapters/stripe/stripe-tools.ts +310 -310
- package/src/execution/engine/tools/integration/service.test.ts +239 -239
- package/src/execution/engine/tools/integration/service.ts +172 -172
- package/src/execution/engine/tools/integration/tool.ts +255 -255
- package/src/execution/engine/tools/lead-service-types.ts +1005 -1005
- package/src/execution/engine/tools/messages.ts +43 -43
- package/src/execution/engine/tools/platform/acquisition/company-tools.ts +7 -7
- package/src/execution/engine/tools/platform/acquisition/contact-tools.ts +6 -6
- package/src/execution/engine/tools/platform/acquisition/list-tools.ts +6 -6
- package/src/execution/engine/tools/platform/acquisition/types.ts +280 -280
- package/src/execution/engine/tools/platform/email/types.ts +97 -97
- package/src/execution/engine/tools/registry.ts +704 -704
- package/src/execution/engine/tools/tool-maps.ts +831 -831
- package/src/execution/engine/tools/types.ts +234 -234
- package/src/execution/engine/workflow/types.ts +202 -202
- package/src/execution/external/__tests__/api-schemas.test.ts +127 -127
- package/src/execution/external/api-schemas.ts +40 -40
- package/src/execution/external/index.ts +1 -1
- package/src/index.ts +18 -18
- package/src/integrations/credentials/__tests__/api-schemas.test.ts +420 -420
- package/src/integrations/credentials/api-schemas.ts +146 -146
- package/src/integrations/credentials/schemas.ts +200 -200
- package/src/integrations/oauth/__tests__/provider-registry.test.ts +7 -7
- package/src/integrations/oauth/provider-registry.ts +74 -74
- package/src/integrations/oauth/server/credentials.ts +43 -43
- package/src/integrations/webhook-endpoints/__tests__/api-schemas.test.ts +327 -327
- package/src/integrations/webhook-endpoints/api-schemas.ts +103 -103
- package/src/integrations/webhook-endpoints/types.ts +58 -58
- package/src/knowledge/README.md +33 -32
- package/src/knowledge/__tests__/queries.test.ts +633 -541
- package/src/knowledge/format.ts +100 -99
- package/src/knowledge/index.ts +5 -5
- package/src/knowledge/published.ts +5 -5
- package/src/knowledge/queries.ts +274 -222
- package/src/operations/activities/api-schemas.ts +80 -80
- package/src/operations/activities/types.ts +64 -64
- package/src/organization-model/README.md +149 -109
- package/src/organization-model/__tests__/content-kinds-registry.test.ts +210 -0
- package/src/organization-model/__tests__/defaults.test.ts +168 -194
- package/src/organization-model/__tests__/domains/actions.test.ts +78 -0
- package/src/organization-model/__tests__/domains/customers.test.ts +48 -44
- package/src/organization-model/__tests__/domains/entities.test.ts +56 -0
- package/src/organization-model/__tests__/domains/goals.test.ts +110 -96
- package/src/organization-model/__tests__/domains/identity.test.ts +4 -3
- package/src/organization-model/__tests__/domains/navigation.test.ts +222 -166
- package/src/organization-model/__tests__/domains/offerings.test.ts +83 -88
- package/src/organization-model/__tests__/domains/policies.test.ts +323 -0
- package/src/organization-model/__tests__/domains/resource-mappings.test.ts +30 -30
- package/src/organization-model/__tests__/domains/resources.test.ts +396 -175
- package/src/organization-model/__tests__/domains/roles.test.ts +463 -402
- package/src/organization-model/__tests__/domains/statuses.test.ts +13 -10
- package/src/organization-model/__tests__/domains/systems.test.ts +209 -193
- package/src/organization-model/__tests__/flatten-additive-merge.test.ts +362 -0
- package/src/organization-model/__tests__/foundation.test.ts +47 -75
- package/src/organization-model/__tests__/get-resources-for-system.test.ts +144 -0
- package/src/organization-model/__tests__/graph.test.ts +1336 -149
- package/src/organization-model/__tests__/icons.test.ts +10 -1
- package/src/organization-model/__tests__/knowledge.test.ts +418 -61
- package/src/organization-model/__tests__/lookup-helpers.test.ts +438 -0
- package/src/organization-model/__tests__/migration-helpers.test.ts +591 -0
- package/src/organization-model/__tests__/prospecting-ssot.test.ts +103 -94
- package/src/organization-model/__tests__/recursive-system-schema.test.ts +549 -0
- package/src/organization-model/__tests__/resolve.test.ts +303 -42
- package/src/organization-model/__tests__/schema.test.ts +863 -153
- package/src/organization-model/__tests__/surface-projection.test.ts +284 -174
- package/src/organization-model/catalogs/lead-gen.ts +144 -0
- package/src/organization-model/content-kinds/config.ts +36 -0
- package/src/organization-model/content-kinds/index.ts +78 -0
- package/src/organization-model/content-kinds/pipeline.ts +68 -0
- package/src/organization-model/content-kinds/registry.ts +44 -0
- package/src/organization-model/content-kinds/status.ts +71 -0
- package/src/organization-model/content-kinds/template.ts +83 -0
- package/src/organization-model/content-kinds/types.ts +117 -0
- package/src/organization-model/contracts.ts +27 -17
- package/src/organization-model/defaults.ts +489 -107
- package/src/organization-model/domains/actions.ts +333 -0
- package/src/organization-model/domains/customers.ts +10 -7
- package/src/organization-model/domains/entities.ts +144 -0
- package/src/organization-model/domains/goals.ts +9 -6
- package/src/organization-model/domains/knowledge.ts +128 -54
- package/src/organization-model/domains/navigation.ts +139 -416
- package/src/organization-model/domains/offerings.ts +15 -10
- package/src/organization-model/domains/policies.ts +102 -0
- package/src/organization-model/domains/projects.ts +6 -40
- package/src/organization-model/domains/prospecting.ts +395 -514
- package/src/organization-model/domains/resources.ts +173 -81
- package/src/organization-model/domains/roles.ts +96 -93
- package/src/organization-model/domains/sales.test.ts +218 -218
- package/src/organization-model/domains/sales.ts +380 -589
- package/src/organization-model/domains/shared.ts +8 -8
- package/src/organization-model/domains/statuses.ts +298 -89
- package/src/organization-model/domains/systems.ts +240 -38
- package/src/organization-model/foundation.ts +35 -48
- package/src/organization-model/graph/build.ts +1035 -279
- package/src/organization-model/graph/index.ts +4 -4
- package/src/organization-model/graph/link.ts +10 -10
- package/src/organization-model/graph/schema.ts +77 -56
- package/src/organization-model/graph/types.ts +75 -56
- package/src/organization-model/helpers.ts +312 -59
- package/src/organization-model/icons.ts +78 -66
- package/src/organization-model/index.ts +129 -16
- package/src/organization-model/migration-helpers.ts +252 -0
- package/src/organization-model/ontology.ts +661 -0
- package/src/organization-model/organization-graph.mdx +110 -89
- package/src/organization-model/organization-model.mdx +226 -171
- package/src/organization-model/published.ts +295 -139
- package/src/organization-model/resolve.ts +139 -21
- package/src/organization-model/schema.ts +841 -301
- package/src/organization-model/surface-projection.ts +212 -218
- package/src/organization-model/types.ts +181 -90
- package/src/platform/api/types.ts +38 -38
- package/src/platform/constants/versions.ts +3 -3
- package/src/platform/index.ts +23 -23
- package/src/platform/registry/__tests__/command-view.test.ts +5 -7
- package/src/platform/registry/__tests__/resource-link.test.ts +35 -30
- package/src/platform/registry/__tests__/resource-registry.integration.test.ts +17 -32
- package/src/platform/registry/__tests__/resource-registry.nested-systems.test.ts +245 -0
- package/src/platform/registry/__tests__/resource-registry.test.ts +2053 -2051
- package/src/platform/registry/__tests__/validation.test.ts +1347 -1343
- package/src/platform/registry/command-view.ts +10 -10
- package/src/platform/registry/index.ts +103 -103
- package/src/platform/registry/resource-link.ts +32 -32
- package/src/platform/registry/resource-registry.ts +890 -878
- package/src/platform/registry/serialization.ts +295 -295
- package/src/platform/registry/serialized-types.ts +166 -166
- package/src/platform/registry/stats-types.ts +68 -68
- package/src/platform/registry/types.ts +425 -425
- package/src/platform/registry/validation.ts +745 -743
- package/src/platform/utils/__tests__/validation.test.ts +1084 -1084
- package/src/platform/utils/validation.ts +425 -425
- package/src/projects/api-schemas.test.ts +39 -39
- package/src/projects/api-schemas.ts +291 -291
- package/src/reference/_generated/contracts.md +2389 -2121
- package/src/reference/glossary.md +76 -76
- package/src/scaffold-registry/__tests__/index.test.ts +206 -206
- package/src/scaffold-registry/__tests__/schema.test.ts +166 -166
- package/src/scaffold-registry/index.ts +392 -392
- package/src/scaffold-registry/schema.ts +243 -243
- package/src/server.ts +289 -289
- package/src/supabase/database.types.ts +3153 -3093
- package/src/test-utils/README.md +37 -37
- package/src/test-utils/entities.ts +108 -108
- package/src/test-utils/fixtures/memberships.ts +82 -82
- package/src/test-utils/index.ts +12 -12
- package/src/test-utils/organization-model.ts +65 -65
- package/src/test-utils/published.ts +6 -6
- package/src/test-utils/rls/RLSTestContext.ts +588 -588
- package/src/test-utils/test-utils.test.ts +44 -49
- package/src/organization-model/__tests__/domains/operations.test.ts +0 -203
- package/src/organization-model/domains/features.ts +0 -31
- package/src/organization-model/domains/operations.ts +0 -85
|
@@ -1,194 +1,194 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { CreateOrganizationSchema, ListOrganizationsQuerySchema, UpdateOrganizationSchema } from '../api-schemas'
|
|
3
|
-
|
|
4
|
-
// ---------------------------------------------------------------------------
|
|
5
|
-
// CreateOrganizationSchema
|
|
6
|
-
// ---------------------------------------------------------------------------
|
|
7
|
-
|
|
8
|
-
describe('CreateOrganizationSchema', () => {
|
|
9
|
-
it('accepts a minimal valid payload (name only)', () => {
|
|
10
|
-
expect(CreateOrganizationSchema.safeParse({ name: 'Acme Corp' }).success).toBe(true)
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
it('accepts name at minimum length boundary (2 chars)', () => {
|
|
14
|
-
expect(CreateOrganizationSchema.safeParse({ name: 'AB' }).success).toBe(true)
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
it('accepts name at maximum length boundary (100 chars)', () => {
|
|
18
|
-
expect(CreateOrganizationSchema.safeParse({ name: 'A'.repeat(100) }).success).toBe(true)
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
it('rejects name shorter than 2 characters', () => {
|
|
22
|
-
expect(CreateOrganizationSchema.safeParse({ name: 'A' }).success).toBe(false)
|
|
23
|
-
expect(CreateOrganizationSchema.safeParse({ name: '' }).success).toBe(false)
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
it('rejects name longer than 100 characters', () => {
|
|
27
|
-
expect(CreateOrganizationSchema.safeParse({ name: 'A'.repeat(101) }).success).toBe(false)
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
it('accepts names with letters, numbers, spaces, hyphens, and underscores', () => {
|
|
31
|
-
expect(CreateOrganizationSchema.safeParse({ name: 'My Org-123_Test' }).success).toBe(true)
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
it('rejects names with disallowed characters (special symbols)', () => {
|
|
35
|
-
expect(CreateOrganizationSchema.safeParse({ name: 'Acme@Corp' }).success).toBe(false)
|
|
36
|
-
expect(CreateOrganizationSchema.safeParse({ name: 'Acme.Corp' }).success).toBe(false)
|
|
37
|
-
expect(CreateOrganizationSchema.safeParse({ name: 'Acme/Corp' }).success).toBe(false)
|
|
38
|
-
expect(CreateOrganizationSchema.safeParse({ name: 'Acme+Corp' }).success).toBe(false)
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
it('accepts optional domainData array with valid entries', () => {
|
|
42
|
-
const result = CreateOrganizationSchema.safeParse({
|
|
43
|
-
name: 'Acme Corp',
|
|
44
|
-
domainData: [{ domain: 'acme.com', state: 'verified' }]
|
|
45
|
-
})
|
|
46
|
-
expect(result.success).toBe(true)
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
it('accepts domainData up to 10 entries', () => {
|
|
50
|
-
const domains = Array.from({ length: 10 }, (_, i) => ({ domain: `domain${i}.com` }))
|
|
51
|
-
expect(CreateOrganizationSchema.safeParse({ name: 'Acme Corp', domainData: domains }).success).toBe(true)
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
it('rejects domainData with more than 10 entries', () => {
|
|
55
|
-
const domains = Array.from({ length: 11 }, (_, i) => ({ domain: `domain${i}.com` }))
|
|
56
|
-
expect(CreateOrganizationSchema.safeParse({ name: 'Acme Corp', domainData: domains }).success).toBe(false)
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
it('accepts optional metadata as a record under 10KB', () => {
|
|
60
|
-
const result = CreateOrganizationSchema.safeParse({
|
|
61
|
-
name: 'Acme Corp',
|
|
62
|
-
metadata: { plan: 'pro', region: 'us-west' }
|
|
63
|
-
})
|
|
64
|
-
expect(result.success).toBe(true)
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
it('rejects metadata exceeding 10KB (10240 bytes)', () => {
|
|
68
|
-
// Build a metadata object whose JSON representation exceeds 10240 bytes
|
|
69
|
-
const largeValue = 'x'.repeat(10241)
|
|
70
|
-
const result = CreateOrganizationSchema.safeParse({
|
|
71
|
-
name: 'Acme Corp',
|
|
72
|
-
metadata: { big: largeValue }
|
|
73
|
-
})
|
|
74
|
-
expect(result.success).toBe(false)
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
it('rejects unknown top-level fields (.strict() mode)', () => {
|
|
78
|
-
expect(CreateOrganizationSchema.safeParse({ name: 'Acme Corp', unknownField: 'value' }).success).toBe(false)
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
it('rejects missing name field', () => {
|
|
82
|
-
expect(CreateOrganizationSchema.safeParse({}).success).toBe(false)
|
|
83
|
-
})
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
// ---------------------------------------------------------------------------
|
|
87
|
-
// UpdateOrganizationSchema
|
|
88
|
-
// ---------------------------------------------------------------------------
|
|
89
|
-
|
|
90
|
-
describe('UpdateOrganizationSchema', () => {
|
|
91
|
-
it('rejects an empty object (at least one field required)', () => {
|
|
92
|
-
// UpdateOrganizationSchema is CreateOrganizationSchema.partial().strict().refine(...)
|
|
93
|
-
// The .refine() enforces the documented "at least one field" convention
|
|
94
|
-
// (see .claude/rules/core-package.md → "api-schemas.ts Pattern")
|
|
95
|
-
const result = UpdateOrganizationSchema.safeParse({})
|
|
96
|
-
expect(result.success).toBe(false)
|
|
97
|
-
if (!result.success) {
|
|
98
|
-
expect(result.error.issues[0].message).toMatch(/at least one field/i)
|
|
99
|
-
}
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
it('accepts updating only name', () => {
|
|
103
|
-
expect(UpdateOrganizationSchema.safeParse({ name: 'New Name' }).success).toBe(true)
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
it('accepts updating only domainData', () => {
|
|
107
|
-
expect(UpdateOrganizationSchema.safeParse({ domainData: [{ domain: 'newdomain.com' }] }).success).toBe(true)
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
it('accepts updating only metadata', () => {
|
|
111
|
-
expect(UpdateOrganizationSchema.safeParse({ metadata: { key: 'value' } }).success).toBe(true)
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
it('applies same name validation as CreateOrganizationSchema when name is provided', () => {
|
|
115
|
-
// Too short
|
|
116
|
-
expect(UpdateOrganizationSchema.safeParse({ name: 'A' }).success).toBe(false)
|
|
117
|
-
// Too long
|
|
118
|
-
expect(UpdateOrganizationSchema.safeParse({ name: 'A'.repeat(101) }).success).toBe(false)
|
|
119
|
-
// Invalid charset
|
|
120
|
-
expect(UpdateOrganizationSchema.safeParse({ name: 'Bad@Name' }).success).toBe(false)
|
|
121
|
-
// Valid
|
|
122
|
-
expect(UpdateOrganizationSchema.safeParse({ name: 'Good Name-123' }).success).toBe(true)
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
it('applies same domainData max-10 validation when provided', () => {
|
|
126
|
-
const tooMany = Array.from({ length: 11 }, (_, i) => ({ domain: `d${i}.com` }))
|
|
127
|
-
expect(UpdateOrganizationSchema.safeParse({ domainData: tooMany }).success).toBe(false)
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
it('applies same metadata 10KB cap validation when provided', () => {
|
|
131
|
-
const largeValue = 'x'.repeat(10241)
|
|
132
|
-
expect(UpdateOrganizationSchema.safeParse({ metadata: { big: largeValue } }).success).toBe(false)
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
it('rejects unknown top-level fields (.strict() mode)', () => {
|
|
136
|
-
expect(UpdateOrganizationSchema.safeParse({ name: 'Valid Name', unknownField: 'bad' }).success).toBe(false)
|
|
137
|
-
})
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
// ---------------------------------------------------------------------------
|
|
141
|
-
// ListOrganizationsQuerySchema
|
|
142
|
-
// ---------------------------------------------------------------------------
|
|
143
|
-
|
|
144
|
-
describe('ListOrganizationsQuerySchema', () => {
|
|
145
|
-
it('accepts an empty query and applies default limit of 20', () => {
|
|
146
|
-
const result = ListOrganizationsQuerySchema.safeParse({})
|
|
147
|
-
expect(result.success).toBe(true)
|
|
148
|
-
if (result.success) {
|
|
149
|
-
expect(result.data.limit).toBe(20)
|
|
150
|
-
}
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
it('coerces limit from string "50" to number 50', () => {
|
|
154
|
-
const result = ListOrganizationsQuerySchema.safeParse({ limit: '50' })
|
|
155
|
-
expect(result.success).toBe(true)
|
|
156
|
-
if (result.success) expect(result.data.limit).toBe(50)
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
it('accepts limit at upper boundary (100)', () => {
|
|
160
|
-
const result = ListOrganizationsQuerySchema.safeParse({ limit: '100' })
|
|
161
|
-
expect(result.success).toBe(true)
|
|
162
|
-
if (result.success) expect(result.data.limit).toBe(100)
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
it('accepts limit at lower boundary (1)', () => {
|
|
166
|
-
const result = ListOrganizationsQuerySchema.safeParse({ limit: '1' })
|
|
167
|
-
expect(result.success).toBe(true)
|
|
168
|
-
if (result.success) expect(result.data.limit).toBe(1)
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
it('rejects limit of 101 (above max 100)', () => {
|
|
172
|
-
expect(ListOrganizationsQuerySchema.safeParse({ limit: '101' }).success).toBe(false)
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
it('rejects limit of 0 (below min 1)', () => {
|
|
176
|
-
expect(ListOrganizationsQuerySchema.safeParse({ limit: '0' }).success).toBe(false)
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
it('accepts optional before cursor string', () => {
|
|
180
|
-
const result = ListOrganizationsQuerySchema.safeParse({ before: 'cursor_abc123' })
|
|
181
|
-
expect(result.success).toBe(true)
|
|
182
|
-
if (result.success) expect(result.data.before).toBe('cursor_abc123')
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
it('accepts optional after cursor string', () => {
|
|
186
|
-
const result = ListOrganizationsQuerySchema.safeParse({ after: 'cursor_xyz789' })
|
|
187
|
-
expect(result.success).toBe(true)
|
|
188
|
-
if (result.success) expect(result.data.after).toBe('cursor_xyz789')
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
it('accepts both before and after cursors together', () => {
|
|
192
|
-
expect(ListOrganizationsQuerySchema.safeParse({ before: 'a', after: 'b' }).success).toBe(true)
|
|
193
|
-
})
|
|
194
|
-
})
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { CreateOrganizationSchema, ListOrganizationsQuerySchema, UpdateOrganizationSchema } from '../api-schemas'
|
|
3
|
+
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// CreateOrganizationSchema
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
describe('CreateOrganizationSchema', () => {
|
|
9
|
+
it('accepts a minimal valid payload (name only)', () => {
|
|
10
|
+
expect(CreateOrganizationSchema.safeParse({ name: 'Acme Corp' }).success).toBe(true)
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('accepts name at minimum length boundary (2 chars)', () => {
|
|
14
|
+
expect(CreateOrganizationSchema.safeParse({ name: 'AB' }).success).toBe(true)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('accepts name at maximum length boundary (100 chars)', () => {
|
|
18
|
+
expect(CreateOrganizationSchema.safeParse({ name: 'A'.repeat(100) }).success).toBe(true)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('rejects name shorter than 2 characters', () => {
|
|
22
|
+
expect(CreateOrganizationSchema.safeParse({ name: 'A' }).success).toBe(false)
|
|
23
|
+
expect(CreateOrganizationSchema.safeParse({ name: '' }).success).toBe(false)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('rejects name longer than 100 characters', () => {
|
|
27
|
+
expect(CreateOrganizationSchema.safeParse({ name: 'A'.repeat(101) }).success).toBe(false)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('accepts names with letters, numbers, spaces, hyphens, and underscores', () => {
|
|
31
|
+
expect(CreateOrganizationSchema.safeParse({ name: 'My Org-123_Test' }).success).toBe(true)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('rejects names with disallowed characters (special symbols)', () => {
|
|
35
|
+
expect(CreateOrganizationSchema.safeParse({ name: 'Acme@Corp' }).success).toBe(false)
|
|
36
|
+
expect(CreateOrganizationSchema.safeParse({ name: 'Acme.Corp' }).success).toBe(false)
|
|
37
|
+
expect(CreateOrganizationSchema.safeParse({ name: 'Acme/Corp' }).success).toBe(false)
|
|
38
|
+
expect(CreateOrganizationSchema.safeParse({ name: 'Acme+Corp' }).success).toBe(false)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('accepts optional domainData array with valid entries', () => {
|
|
42
|
+
const result = CreateOrganizationSchema.safeParse({
|
|
43
|
+
name: 'Acme Corp',
|
|
44
|
+
domainData: [{ domain: 'acme.com', state: 'verified' }]
|
|
45
|
+
})
|
|
46
|
+
expect(result.success).toBe(true)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('accepts domainData up to 10 entries', () => {
|
|
50
|
+
const domains = Array.from({ length: 10 }, (_, i) => ({ domain: `domain${i}.com` }))
|
|
51
|
+
expect(CreateOrganizationSchema.safeParse({ name: 'Acme Corp', domainData: domains }).success).toBe(true)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('rejects domainData with more than 10 entries', () => {
|
|
55
|
+
const domains = Array.from({ length: 11 }, (_, i) => ({ domain: `domain${i}.com` }))
|
|
56
|
+
expect(CreateOrganizationSchema.safeParse({ name: 'Acme Corp', domainData: domains }).success).toBe(false)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('accepts optional metadata as a record under 10KB', () => {
|
|
60
|
+
const result = CreateOrganizationSchema.safeParse({
|
|
61
|
+
name: 'Acme Corp',
|
|
62
|
+
metadata: { plan: 'pro', region: 'us-west' }
|
|
63
|
+
})
|
|
64
|
+
expect(result.success).toBe(true)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('rejects metadata exceeding 10KB (10240 bytes)', () => {
|
|
68
|
+
// Build a metadata object whose JSON representation exceeds 10240 bytes
|
|
69
|
+
const largeValue = 'x'.repeat(10241)
|
|
70
|
+
const result = CreateOrganizationSchema.safeParse({
|
|
71
|
+
name: 'Acme Corp',
|
|
72
|
+
metadata: { big: largeValue }
|
|
73
|
+
})
|
|
74
|
+
expect(result.success).toBe(false)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('rejects unknown top-level fields (.strict() mode)', () => {
|
|
78
|
+
expect(CreateOrganizationSchema.safeParse({ name: 'Acme Corp', unknownField: 'value' }).success).toBe(false)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('rejects missing name field', () => {
|
|
82
|
+
expect(CreateOrganizationSchema.safeParse({}).success).toBe(false)
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// UpdateOrganizationSchema
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
describe('UpdateOrganizationSchema', () => {
|
|
91
|
+
it('rejects an empty object (at least one field required)', () => {
|
|
92
|
+
// UpdateOrganizationSchema is CreateOrganizationSchema.partial().strict().refine(...)
|
|
93
|
+
// The .refine() enforces the documented "at least one field" convention
|
|
94
|
+
// (see .claude/rules/core-package.md → "api-schemas.ts Pattern")
|
|
95
|
+
const result = UpdateOrganizationSchema.safeParse({})
|
|
96
|
+
expect(result.success).toBe(false)
|
|
97
|
+
if (!result.success) {
|
|
98
|
+
expect(result.error.issues[0].message).toMatch(/at least one field/i)
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('accepts updating only name', () => {
|
|
103
|
+
expect(UpdateOrganizationSchema.safeParse({ name: 'New Name' }).success).toBe(true)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('accepts updating only domainData', () => {
|
|
107
|
+
expect(UpdateOrganizationSchema.safeParse({ domainData: [{ domain: 'newdomain.com' }] }).success).toBe(true)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('accepts updating only metadata', () => {
|
|
111
|
+
expect(UpdateOrganizationSchema.safeParse({ metadata: { key: 'value' } }).success).toBe(true)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('applies same name validation as CreateOrganizationSchema when name is provided', () => {
|
|
115
|
+
// Too short
|
|
116
|
+
expect(UpdateOrganizationSchema.safeParse({ name: 'A' }).success).toBe(false)
|
|
117
|
+
// Too long
|
|
118
|
+
expect(UpdateOrganizationSchema.safeParse({ name: 'A'.repeat(101) }).success).toBe(false)
|
|
119
|
+
// Invalid charset
|
|
120
|
+
expect(UpdateOrganizationSchema.safeParse({ name: 'Bad@Name' }).success).toBe(false)
|
|
121
|
+
// Valid
|
|
122
|
+
expect(UpdateOrganizationSchema.safeParse({ name: 'Good Name-123' }).success).toBe(true)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('applies same domainData max-10 validation when provided', () => {
|
|
126
|
+
const tooMany = Array.from({ length: 11 }, (_, i) => ({ domain: `d${i}.com` }))
|
|
127
|
+
expect(UpdateOrganizationSchema.safeParse({ domainData: tooMany }).success).toBe(false)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('applies same metadata 10KB cap validation when provided', () => {
|
|
131
|
+
const largeValue = 'x'.repeat(10241)
|
|
132
|
+
expect(UpdateOrganizationSchema.safeParse({ metadata: { big: largeValue } }).success).toBe(false)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('rejects unknown top-level fields (.strict() mode)', () => {
|
|
136
|
+
expect(UpdateOrganizationSchema.safeParse({ name: 'Valid Name', unknownField: 'bad' }).success).toBe(false)
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
// ListOrganizationsQuerySchema
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
describe('ListOrganizationsQuerySchema', () => {
|
|
145
|
+
it('accepts an empty query and applies default limit of 20', () => {
|
|
146
|
+
const result = ListOrganizationsQuerySchema.safeParse({})
|
|
147
|
+
expect(result.success).toBe(true)
|
|
148
|
+
if (result.success) {
|
|
149
|
+
expect(result.data.limit).toBe(20)
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('coerces limit from string "50" to number 50', () => {
|
|
154
|
+
const result = ListOrganizationsQuerySchema.safeParse({ limit: '50' })
|
|
155
|
+
expect(result.success).toBe(true)
|
|
156
|
+
if (result.success) expect(result.data.limit).toBe(50)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it('accepts limit at upper boundary (100)', () => {
|
|
160
|
+
const result = ListOrganizationsQuerySchema.safeParse({ limit: '100' })
|
|
161
|
+
expect(result.success).toBe(true)
|
|
162
|
+
if (result.success) expect(result.data.limit).toBe(100)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('accepts limit at lower boundary (1)', () => {
|
|
166
|
+
const result = ListOrganizationsQuerySchema.safeParse({ limit: '1' })
|
|
167
|
+
expect(result.success).toBe(true)
|
|
168
|
+
if (result.success) expect(result.data.limit).toBe(1)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('rejects limit of 101 (above max 100)', () => {
|
|
172
|
+
expect(ListOrganizationsQuerySchema.safeParse({ limit: '101' }).success).toBe(false)
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('rejects limit of 0 (below min 1)', () => {
|
|
176
|
+
expect(ListOrganizationsQuerySchema.safeParse({ limit: '0' }).success).toBe(false)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('accepts optional before cursor string', () => {
|
|
180
|
+
const result = ListOrganizationsQuerySchema.safeParse({ before: 'cursor_abc123' })
|
|
181
|
+
expect(result.success).toBe(true)
|
|
182
|
+
if (result.success) expect(result.data.before).toBe('cursor_abc123')
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('accepts optional after cursor string', () => {
|
|
186
|
+
const result = ListOrganizationsQuerySchema.safeParse({ after: 'cursor_xyz789' })
|
|
187
|
+
expect(result.success).toBe(true)
|
|
188
|
+
if (result.success) expect(result.data.after).toBe('cursor_xyz789')
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
it('accepts both before and after cursors together', () => {
|
|
192
|
+
expect(ListOrganizationsQuerySchema.safeParse({ before: 'a', after: 'b' }).success).toBe(true)
|
|
193
|
+
})
|
|
194
|
+
})
|
|
@@ -1,136 +1,136 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Organizations Domain - Zod Validation Schemas
|
|
3
|
-
*
|
|
4
|
-
* Validation schemas for organization management endpoints.
|
|
5
|
-
* Includes request bodies, query params, and path params.
|
|
6
|
-
*
|
|
7
|
-
* Security:
|
|
8
|
-
* - All schemas use .strict() to prevent mass assignment attacks
|
|
9
|
-
* - UUID/WorkOS ID validation prevents invalid references
|
|
10
|
-
* - String length limits prevent DoS
|
|
11
|
-
* - Domain and metadata size limits
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { z } from 'zod'
|
|
15
|
-
import { UuidSchema } from '../../../platform/utils/validation'
|
|
16
|
-
|
|
17
|
-
// ============================================================================
|
|
18
|
-
// Shared Schemas
|
|
19
|
-
// ============================================================================
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Organization name validation
|
|
23
|
-
* - Alphanumeric, spaces, hyphens, underscores only
|
|
24
|
-
* - 2-100 characters
|
|
25
|
-
*
|
|
26
|
-
* Security: Prevents injection, DoS via long names
|
|
27
|
-
*/
|
|
28
|
-
export const OrganizationNameSchema = z
|
|
29
|
-
.string()
|
|
30
|
-
.min(2, 'Organization name must be at least 2 characters')
|
|
31
|
-
.max(100, 'Organization name must be at most 100 characters')
|
|
32
|
-
.trim()
|
|
33
|
-
.regex(
|
|
34
|
-
/^[a-zA-Z0-9\s\-_]+$/,
|
|
35
|
-
'Organization name must contain only letters, numbers, spaces, hyphens, and underscores'
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Organization ID validation
|
|
40
|
-
* Supports both UUID and WorkOS org_ prefixed IDs
|
|
41
|
-
*/
|
|
42
|
-
export const OrganizationIdSchema = z.union([
|
|
43
|
-
UuidSchema,
|
|
44
|
-
z.string().regex(/^org_[a-zA-Z0-9]+$/, 'Invalid WorkOS organization ID')
|
|
45
|
-
])
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Organization domain data schema
|
|
49
|
-
*/
|
|
50
|
-
export const OrganizationDomainSchema = z.object({
|
|
51
|
-
domain: z.string().min(3).max(255),
|
|
52
|
-
state: z.enum(['verified', 'pending', 'failed']).optional()
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
// ============================================================================
|
|
56
|
-
// Path Parameters
|
|
57
|
-
// ============================================================================
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Validate organization ID in URL path
|
|
61
|
-
* Used by: GET/PUT/DELETE /organizations/:id
|
|
62
|
-
*/
|
|
63
|
-
export const OrganizationIdParamSchema = z
|
|
64
|
-
.object({
|
|
65
|
-
id: OrganizationIdSchema
|
|
66
|
-
})
|
|
67
|
-
.strict()
|
|
68
|
-
|
|
69
|
-
// ============================================================================
|
|
70
|
-
// Request Bodies
|
|
71
|
-
// ============================================================================
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Create new organization
|
|
75
|
-
* POST /organizations
|
|
76
|
-
*
|
|
77
|
-
* Security:
|
|
78
|
-
* - Name format validated (alphanumeric + spaces + hyphens + underscores)
|
|
79
|
-
* - Domain array size limited (max 10)
|
|
80
|
-
* - Metadata size limited (10KB)
|
|
81
|
-
* - Strict mode prevents unknown field injection
|
|
82
|
-
*/
|
|
83
|
-
export const CreateOrganizationSchema = z
|
|
84
|
-
.object({
|
|
85
|
-
name: OrganizationNameSchema,
|
|
86
|
-
domainData: z.array(OrganizationDomainSchema).max(10).optional(),
|
|
87
|
-
metadata: z
|
|
88
|
-
.record(z.string(), z.unknown())
|
|
89
|
-
.refine((val) => JSON.stringify(val).length <= 10240, 'Metadata must be under 10KB')
|
|
90
|
-
.optional()
|
|
91
|
-
})
|
|
92
|
-
.strict()
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Update organization
|
|
96
|
-
* PUT /organizations/:id
|
|
97
|
-
*
|
|
98
|
-
* Security:
|
|
99
|
-
* - All fields optional (partial update)
|
|
100
|
-
* - Same validation as create
|
|
101
|
-
* - At least one field required (matches documented Update schema convention,
|
|
102
|
-
* see .claude/rules/core-package.md → "api-schemas.ts Pattern")
|
|
103
|
-
*/
|
|
104
|
-
export const UpdateOrganizationSchema = CreateOrganizationSchema.partial()
|
|
105
|
-
.strict()
|
|
106
|
-
.refine((data) => Object.keys(data).length > 0, {
|
|
107
|
-
message: 'At least one field (name, domainData, or metadata) must be provided'
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
// ============================================================================
|
|
111
|
-
// Query Parameters
|
|
112
|
-
// ============================================================================
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* List organizations with filters
|
|
116
|
-
* GET /organizations
|
|
117
|
-
*
|
|
118
|
-
* Security:
|
|
119
|
-
* - Limit bounded (prevents DoS)
|
|
120
|
-
* - WorkOS pagination cursors
|
|
121
|
-
*/
|
|
122
|
-
export const ListOrganizationsQuerySchema = z.object({
|
|
123
|
-
limit: z.coerce.number().int().min(1).max(100).default(20),
|
|
124
|
-
before: z.string().optional(), // WorkOS pagination cursor
|
|
125
|
-
after: z.string().optional() // WorkOS pagination cursor
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
// ============================================================================
|
|
129
|
-
// TypeScript Type Exports
|
|
130
|
-
// ============================================================================
|
|
131
|
-
|
|
132
|
-
// Export inferred types for use in route handlers
|
|
133
|
-
export type CreateOrganizationInput = z.infer<typeof CreateOrganizationSchema>
|
|
134
|
-
export type UpdateOrganizationInput = z.infer<typeof UpdateOrganizationSchema>
|
|
135
|
-
export type ListOrganizationsQuery = z.infer<typeof ListOrganizationsQuerySchema>
|
|
136
|
-
export type OrganizationIdParam = z.infer<typeof OrganizationIdParamSchema>
|
|
1
|
+
/**
|
|
2
|
+
* Organizations Domain - Zod Validation Schemas
|
|
3
|
+
*
|
|
4
|
+
* Validation schemas for organization management endpoints.
|
|
5
|
+
* Includes request bodies, query params, and path params.
|
|
6
|
+
*
|
|
7
|
+
* Security:
|
|
8
|
+
* - All schemas use .strict() to prevent mass assignment attacks
|
|
9
|
+
* - UUID/WorkOS ID validation prevents invalid references
|
|
10
|
+
* - String length limits prevent DoS
|
|
11
|
+
* - Domain and metadata size limits
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { z } from 'zod'
|
|
15
|
+
import { UuidSchema } from '../../../platform/utils/validation'
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Shared Schemas
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Organization name validation
|
|
23
|
+
* - Alphanumeric, spaces, hyphens, underscores only
|
|
24
|
+
* - 2-100 characters
|
|
25
|
+
*
|
|
26
|
+
* Security: Prevents injection, DoS via long names
|
|
27
|
+
*/
|
|
28
|
+
export const OrganizationNameSchema = z
|
|
29
|
+
.string()
|
|
30
|
+
.min(2, 'Organization name must be at least 2 characters')
|
|
31
|
+
.max(100, 'Organization name must be at most 100 characters')
|
|
32
|
+
.trim()
|
|
33
|
+
.regex(
|
|
34
|
+
/^[a-zA-Z0-9\s\-_]+$/,
|
|
35
|
+
'Organization name must contain only letters, numbers, spaces, hyphens, and underscores'
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Organization ID validation
|
|
40
|
+
* Supports both UUID and WorkOS org_ prefixed IDs
|
|
41
|
+
*/
|
|
42
|
+
export const OrganizationIdSchema = z.union([
|
|
43
|
+
UuidSchema,
|
|
44
|
+
z.string().regex(/^org_[a-zA-Z0-9]+$/, 'Invalid WorkOS organization ID')
|
|
45
|
+
])
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Organization domain data schema
|
|
49
|
+
*/
|
|
50
|
+
export const OrganizationDomainSchema = z.object({
|
|
51
|
+
domain: z.string().min(3).max(255),
|
|
52
|
+
state: z.enum(['verified', 'pending', 'failed']).optional()
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
// ============================================================================
|
|
56
|
+
// Path Parameters
|
|
57
|
+
// ============================================================================
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Validate organization ID in URL path
|
|
61
|
+
* Used by: GET/PUT/DELETE /organizations/:id
|
|
62
|
+
*/
|
|
63
|
+
export const OrganizationIdParamSchema = z
|
|
64
|
+
.object({
|
|
65
|
+
id: OrganizationIdSchema
|
|
66
|
+
})
|
|
67
|
+
.strict()
|
|
68
|
+
|
|
69
|
+
// ============================================================================
|
|
70
|
+
// Request Bodies
|
|
71
|
+
// ============================================================================
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Create new organization
|
|
75
|
+
* POST /organizations
|
|
76
|
+
*
|
|
77
|
+
* Security:
|
|
78
|
+
* - Name format validated (alphanumeric + spaces + hyphens + underscores)
|
|
79
|
+
* - Domain array size limited (max 10)
|
|
80
|
+
* - Metadata size limited (10KB)
|
|
81
|
+
* - Strict mode prevents unknown field injection
|
|
82
|
+
*/
|
|
83
|
+
export const CreateOrganizationSchema = z
|
|
84
|
+
.object({
|
|
85
|
+
name: OrganizationNameSchema,
|
|
86
|
+
domainData: z.array(OrganizationDomainSchema).max(10).optional(),
|
|
87
|
+
metadata: z
|
|
88
|
+
.record(z.string(), z.unknown())
|
|
89
|
+
.refine((val) => JSON.stringify(val).length <= 10240, 'Metadata must be under 10KB')
|
|
90
|
+
.optional()
|
|
91
|
+
})
|
|
92
|
+
.strict()
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Update organization
|
|
96
|
+
* PUT /organizations/:id
|
|
97
|
+
*
|
|
98
|
+
* Security:
|
|
99
|
+
* - All fields optional (partial update)
|
|
100
|
+
* - Same validation as create
|
|
101
|
+
* - At least one field required (matches documented Update schema convention,
|
|
102
|
+
* see .claude/rules/core-package.md → "api-schemas.ts Pattern")
|
|
103
|
+
*/
|
|
104
|
+
export const UpdateOrganizationSchema = CreateOrganizationSchema.partial()
|
|
105
|
+
.strict()
|
|
106
|
+
.refine((data) => Object.keys(data).length > 0, {
|
|
107
|
+
message: 'At least one field (name, domainData, or metadata) must be provided'
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
// ============================================================================
|
|
111
|
+
// Query Parameters
|
|
112
|
+
// ============================================================================
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* List organizations with filters
|
|
116
|
+
* GET /organizations
|
|
117
|
+
*
|
|
118
|
+
* Security:
|
|
119
|
+
* - Limit bounded (prevents DoS)
|
|
120
|
+
* - WorkOS pagination cursors
|
|
121
|
+
*/
|
|
122
|
+
export const ListOrganizationsQuerySchema = z.object({
|
|
123
|
+
limit: z.coerce.number().int().min(1).max(100).default(20),
|
|
124
|
+
before: z.string().optional(), // WorkOS pagination cursor
|
|
125
|
+
after: z.string().optional() // WorkOS pagination cursor
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
// ============================================================================
|
|
129
|
+
// TypeScript Type Exports
|
|
130
|
+
// ============================================================================
|
|
131
|
+
|
|
132
|
+
// Export inferred types for use in route handlers
|
|
133
|
+
export type CreateOrganizationInput = z.infer<typeof CreateOrganizationSchema>
|
|
134
|
+
export type UpdateOrganizationInput = z.infer<typeof UpdateOrganizationSchema>
|
|
135
|
+
export type ListOrganizationsQuery = z.infer<typeof ListOrganizationsQuerySchema>
|
|
136
|
+
export type OrganizationIdParam = z.infer<typeof OrganizationIdParamSchema>
|