@knpkv/clockify-api-client 2.0.0 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/dist/generated/ClockifyApi.d.ts +191 -1619
- package/dist/generated/ClockifyApi.d.ts.map +1 -1
- package/dist/generated/ClockifyApi.js +4 -4
- package/dist/generated/ClockifyApi.js.map +1 -1
- package/package.json +2 -2
- package/scripts/regenerate.ts +1 -0
- package/src/generated/ClockifyApi.ts +8 -8
- package/test/ClockifyApiClient.test.ts +19 -0
- package/test/GeneratedSchemas.test.ts +42 -26
- package/test/WorkspaceSubscription.test.ts +11 -0
- package/test/fixtures/clockify-workspaces.json +144 -0
- package/test/tsconfig.json +6 -0
- package/vitest.config.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knpkv/clockify-api-client",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "Effect-based Clockify REST API client",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "knpkv",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
],
|
|
59
59
|
"scripts": {
|
|
60
60
|
"build": "tsc",
|
|
61
|
-
"check": "tsc --noEmit && tsc -p scripts/tsconfig.json",
|
|
61
|
+
"check": "tsc --noEmit && tsc -p scripts/tsconfig.json && tsc -p test/tsconfig.json --noEmit",
|
|
62
62
|
"test": "vitest run",
|
|
63
63
|
"test:watch": "vitest",
|
|
64
64
|
"lint": "eslint src",
|
package/scripts/regenerate.ts
CHANGED
|
@@ -194,6 +194,7 @@ const root = Command.make("clockify-api-regenerate", { check, local }).pipe(
|
|
|
194
194
|
const current = yield* fs.readFileString(scriptPaths.specFile).pipe(
|
|
195
195
|
Effect.flatMap(Schema.decodeUnknownEffect(JsonString)),
|
|
196
196
|
Effect.flatMap(canonicalJson),
|
|
197
|
+
// ast-grep-ignore: no-silent-catch-all -- follow-up: silent fallback; fail with a typed error, log it, or mark it best-effort
|
|
197
198
|
Effect.orElseSucceed(() => "")
|
|
198
199
|
)
|
|
199
200
|
|
|
@@ -25,8 +25,8 @@ export type FeaturePlan = { readonly "addonSubscriptionPlan"?: "FREE" | "BASIC"
|
|
|
25
25
|
export const FeaturePlan = Schema.Struct({ "addonSubscriptionPlan": Schema.optionalKey(Schema.Literals(["FREE", "BASIC", "STANDARD", "PRO", "ENTERPRISE"])), "featurePermissions": Schema.optionalKey(Schema.Array(Schema.Literals(["ACTIVITY_MONITORING", "ADD_TIME_FOR_OTHERS", "ADMIN_PANEL", "ALERTS", "APPROVAL", "AUDIT_LOG", "AUTOMATIC_LOCK", "BRANDED_REPORTS", "BULK_EDIT", "CUSTOM_FIELDS", "CUSTOM_REPORTING", "CUSTOM_SUBDOMAIN", "CREATION_PERMISSIONS", "DECIMAL_FORMAT", "DISABLE_MANUAL_MODE", "EDIT_MEMBER_PROFILE", "EXCLUDE_NON_BILLABLE_FROM_ESTIMATE", "EXPENSES", "FILE_IMPORT", "TIMESHEET_IMPORT", "USER_IMPORT", "HIDE_PAGES", "HISTORIC_RATES", "INVOICING", "INVOICE_EMAILS", "INVOICE_REMINDERS", "LABOR_COST", "LOCATIONS", "MANAGER_ROLE", "MULTI_FACTOR_AUTHENTICATION", "PROJECT_BUDGET", "PROJECT_TEMPLATES", "GRANT_PROJECT_MANAGER_ROLE", "PRIVATE_PROJECT_ACCESS", "QUICKBOOKS_INTEGRATION", "RECURRING_ESTIMATES", "RECURRING_INVOICES", "REQUIRED_FIELDS", "SCHEDULED_REPORTS", "SCHEDULING", "SCHEDULING_BILLABLE_AND_COST_AMOUNT", "SCREENSHOTS", "SSO", "SUMMARY_ESTIMATE", "TARGETS_AND_REMINDERS", "TASK_RATES", "TASK_COST_RATES", "TASK_HOURLY_RATES", "TIME_OFF", "UNLIMITED_REPORTS", "USER_CUSTOM_FIELDS", "WHO_CAN_CHANGE_TIMEENTRY_BILLABILITY", "BREAKS", "KIOSK_SESSION_DURATION", "KIOSK_PIN_REQUIRED", "WHO_CAN_SEE_ALL_TIME_ENTRIES", "WHO_CAN_SEE_PROJECT_STATUS", "WHO_CAN_SEE_PUBLIC_PROJECTS_ENTRIES", "WHO_CAN_SEE_TEAMS_DASHBOARD", "WORKSPACE_LOCK_TIMEENTRIES", "WORKSPACE_TIME_AUDIT", "WORKSPACE_TIME_ROUNDING", "KIOSK", "KIOSK_SIX_DIGIT_PIN", "KIOSK_QR_CODE", "KIOSK_PHOTO_CAPTURE", "LIMITED_USERS", "FORECASTING", "TIME_TRACKING", "ATTENDANCE_REPORT", "WEEKLY_OVERTIME_CALCULATION_PERIOD", "MONTHLY_OVERTIME_CALCULATION_PERIOD", "WORKSPACE_TRANSFER", "FAVORITE_ENTRIES", "SPLIT_TIME_ENTRY", "CLIENT_CURRENCY", "SCHEDULING_FORECASTING", "SCIM", "UNLIMITED_USER_SEATS", "BILLABLE_HOURS", "PROJECT_ESTIMATE", "CSV_EXPORT", "XLSX_EXPORT", "ONE_MONTH_RANGE_REPORTS", "ONE_YEAR_RANGE_REPORTS", "SHARED_REPORTS"])).check(Schema.isUnique().annotate({ "expected": "an array with unique items" }))), "legacy": Schema.optionalKey(Schema.Boolean), "paidPlan": Schema.optionalKey(Schema.Boolean), "regionalAllowed": Schema.optionalKey(Schema.Boolean), "weight": Schema.optionalKey(Schema.Number.annotate({ "format": "int32" }).check(Schema.isInt().annotate({ "expected": "an integer" }))) }).annotate({ "description": "Represents a feature plan type enum.", "identifier": "FeaturePlan" })
|
|
26
26
|
export type Feature = never
|
|
27
27
|
export const Feature = Schema.Never.annotate({ "identifier": "Feature" })
|
|
28
|
-
export type WorkspaceSubdomainDtoV1 = { readonly "enabled"?: boolean, readonly "name"?: string }
|
|
29
|
-
export const WorkspaceSubdomainDtoV1 = Schema.Struct({ "enabled": Schema.optionalKey(Schema.Boolean.annotate({ "description": "Indicates whether subdomain is enabled on workspace" })), "name": Schema.optionalKey(Schema.String.annotate({ "description": "Represents subdomain name" })) }).annotate({ "description": "Represents the workspace subdomain", "identifier": "WorkspaceSubdomainDtoV1" })
|
|
28
|
+
export type WorkspaceSubdomainDtoV1 = { readonly "enabled"?: boolean, readonly "name"?: string | null }
|
|
29
|
+
export const WorkspaceSubdomainDtoV1 = Schema.Struct({ "enabled": Schema.optionalKey(Schema.Boolean.annotate({ "description": "Indicates whether subdomain is enabled on workspace" })), "name": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Represents subdomain name" })) }).annotate({ "description": "Represents the workspace subdomain", "identifier": "WorkspaceSubdomainDtoV1" })
|
|
30
30
|
export type TimeApprovalSettingsDtoV1 = { readonly "approvalEnabled"?: boolean, readonly "approvalPeriod"?: "WEEKLY" | "SEMI_MONTHLY" | "MONTHLY", readonly "approvalRoles"?: ReadonlyArray<"ADMIN" | "TEAM_MANAGER" | "PROJECT_MANAGER"> }
|
|
31
31
|
export const TimeApprovalSettingsDtoV1 = Schema.Struct({ "approvalEnabled": Schema.optionalKey(Schema.Boolean.annotate({ "description": "Indicates whether approval is enabled." })), "approvalPeriod": Schema.optionalKey(Schema.Literals(["WEEKLY", "SEMI_MONTHLY", "MONTHLY"]).annotate({ "description": "Represents the approval period." })), "approvalRoles": Schema.optionalKey(Schema.Array(Schema.Literals(["ADMIN", "TEAM_MANAGER", "PROJECT_MANAGER"]).annotate({ "description": "Represents the list of roles who can approve." })).annotate({ "description": "Represents the list of roles who can approve." }).check(Schema.isUnique().annotate({ "expected": "an array with unique items" }))) }).annotate({ "description": "Represents the settings for time approval.", "identifier": "TimeApprovalSettingsDtoV1" })
|
|
32
32
|
export type AuditLogSettingsDtoV1 = { readonly "auditEnabled"?: boolean, readonly "clientAuditing"?: boolean, readonly "clientStartedRecording"?: string, readonly "expensesAuditing"?: boolean, readonly "expensesStartedRecording"?: string, readonly "projectAuditing"?: boolean, readonly "projectStartedRecording"?: string, readonly "tagAuditing"?: boolean, readonly "tagStartedRecording"?: string, readonly "timeEntryAuditing"?: boolean, readonly "timeEntryStartedRecording"?: string }
|
|
@@ -367,8 +367,8 @@ export type AuditLogDtoV1 = { readonly "action"?: "CREATE_TIME_PERSONAL_TIMER" |
|
|
|
367
367
|
export const AuditLogDtoV1 = Schema.Struct({ "action": Schema.optionalKey(Schema.Literals(["CREATE_TIME_PERSONAL_TIMER", "CREATE_TIME_PERSONAL_MANUAL", "CREATE_TIME_IMPORT", "CREATE_TIME_KIOSK", "CREATE_TIME_FOR_OTHER", "RESTORE_TIME", "RESTORE_TIME_FOR_OTHER", "UPDATE_TIME_PERSONAL", "UPDATE_TIME_FOR_OTHER", "DELETE_TIME_PERSONAL", "DELETE_TIME_FOR_OTHER", "CREATE_PROJECT", "CREATE_PROJECT_IMPORT", "CREATE_PROJECT_QUICKBOOKS", "UPDATE_PROJECT", "DELETE_PROJECT", "CREATE_TASK", "CREATE_TASK_IMPORT", "UPDATE_TASK", "DELETE_TASK", "CREATE_CLIENT", "CREATE_CLIENT_IMPORT", "CREATE_CLIENT_QUICKBOOKS", "UPDATE_CLIENT", "DELETE_CLIENT", "CREATE_TAG", "CREATE_TAG_IMPORT", "UPDATE_TAG", "DELETE_TAG", "CREATE_EXPENSE", "CREATE_EXPENSE_FOR_OTHER", "RESTORE_EXPENSE", "RESTORE_EXPENSE_FOR_OTHER", "UPDATE_EXPENSE", "UPDATE_EXPENSE_FOR_OTHER", "DELETE_EXPENSE", "DELETE_EXPENSE_FOR_OTHER"]).annotate({ "description": "Represents an audit log action type." })), "content": Schema.optionalKey(Schema.String.annotate({ "description": "Represents the current value of the modified entity" })), "previousContent": Schema.optionalKey(Schema.String.annotate({ "description": "Represents the previous value of the modified entity." })), "timestamp": Schema.optionalKey(Schema.String.annotate({ "description": "Represents a timestamp of when the audit log entry was created." })), "userEmail": Schema.optionalKey(Schema.String.annotate({ "description": "Represents the email address of the user." })), "userId": Schema.optionalKey(Schema.String.annotate({ "description": "Represents a user identifier across the system." })), "userName": Schema.optionalKey(Schema.String.annotate({ "description": "Represents the name of the user." })), "workspaceId": Schema.optionalKey(Schema.String.annotate({ "description": "Represents a workspace identifier across the system." })) }).annotate({ "identifier": "AuditLogDtoV1" })
|
|
368
368
|
export type UserCustomFieldValueDtoV1 = { readonly "customFieldId"?: string, readonly "customFieldName"?: string, readonly "customFieldType"?: CustomFieldType, readonly "userId"?: string, readonly "value"?: { } }
|
|
369
369
|
export const UserCustomFieldValueDtoV1 = Schema.Struct({ "customFieldId": Schema.optionalKey(Schema.String.annotate({ "description": "Represents custom field identifier across the system." })), "customFieldName": Schema.optionalKey(Schema.String.annotate({ "description": "Represents custom field name." })), "customFieldType": Schema.optionalKey(CustomFieldType), "userId": Schema.optionalKey(Schema.String.annotate({ "description": "Represents user identifier across the system." })), "value": Schema.optionalKey(Schema.Struct({ }).annotate({ "description": "Represents custom field value." })) }).annotate({ "description": "Represents a list of value objects for user’s custom fields.", "identifier": "UserCustomFieldValueDtoV1" })
|
|
370
|
-
export type MembershipDtoV1 = { readonly "costRate"?: RateDtoV1, readonly "hourlyRate"?: HourlyRateDtoV1, readonly "membershipStatus"?: "PENDING" | "ACTIVE" | "DECLINED" | "INACTIVE" | "ALL", readonly "membershipType"?: "WORKSPACE" | "PROJECT" | "USERGROUP", readonly "targetId"?: string, readonly "userId"?: string }
|
|
371
|
-
export const MembershipDtoV1 = Schema.Struct({ "costRate": Schema.optionalKey(RateDtoV1), "hourlyRate": Schema.optionalKey(HourlyRateDtoV1), "membershipStatus": Schema.optionalKey(Schema.Literals(["PENDING", "ACTIVE", "DECLINED", "INACTIVE", "ALL"]).annotate({ "description": "Represents a membership status enum." })), "membershipType": Schema.optionalKey(Schema.Literals(["WORKSPACE", "PROJECT", "USERGROUP"]).annotate({ "description": "Represents membership type enum." })), "targetId": Schema.optionalKey(Schema.String.annotate({ "description": "Represents target identifier across the system." })), "userId": Schema.optionalKey(Schema.String.annotate({ "description": "Represents user identifier across the system." })) }).annotate({ "description": "Represents a list of membership objects.", "identifier": "MembershipDtoV1" })
|
|
370
|
+
export type MembershipDtoV1 = { readonly "costRate"?: RateDtoV1 | null, readonly "hourlyRate"?: HourlyRateDtoV1 | null, readonly "membershipStatus"?: "PENDING" | "ACTIVE" | "DECLINED" | "INACTIVE" | "ALL", readonly "membershipType"?: "WORKSPACE" | "PROJECT" | "USERGROUP", readonly "targetId"?: string, readonly "userId"?: string }
|
|
371
|
+
export const MembershipDtoV1 = Schema.Struct({ "costRate": Schema.optionalKey(Schema.Union([RateDtoV1, Schema.Null])), "hourlyRate": Schema.optionalKey(Schema.Union([HourlyRateDtoV1, Schema.Null])), "membershipStatus": Schema.optionalKey(Schema.Literals(["PENDING", "ACTIVE", "DECLINED", "INACTIVE", "ALL"]).annotate({ "description": "Represents a membership status enum." })), "membershipType": Schema.optionalKey(Schema.Literals(["WORKSPACE", "PROJECT", "USERGROUP"]).annotate({ "description": "Represents membership type enum." })), "targetId": Schema.optionalKey(Schema.String.annotate({ "description": "Represents target identifier across the system." })), "userId": Schema.optionalKey(Schema.String.annotate({ "description": "Represents user identifier across the system." })) }).annotate({ "description": "Represents a list of membership objects.", "identifier": "MembershipDtoV1" })
|
|
372
372
|
export type UserSettingsDtoV1 = { readonly "alerts"?: boolean, readonly "approval"?: boolean, readonly "collapseAllProjectLists"?: boolean, readonly "dashboardPinToTop"?: boolean, readonly "dashboardSelection"?: "ME" | "TEAM", readonly "dashboardViewType"?: "PROJECT" | "BILLABILITY", readonly "dateFormat": string, readonly "groupSimilarEntriesDisabled"?: boolean, readonly "invoiceReminders"?: boolean, readonly "isCompactViewOn"?: boolean, readonly "lang"?: string, readonly "longRunning"?: boolean, readonly "multiFactorEnabled"?: boolean, readonly "myStartOfDay"?: string, readonly "onboarding"?: boolean, readonly "projectListCollapse"?: number, readonly "projectPickerTaskFilter"?: boolean, readonly "pto"?: boolean, readonly "reminders"?: boolean, readonly "scheduledReports"?: boolean, readonly "scheduling"?: boolean, readonly "sendNewsletter"?: boolean, readonly "showOnlyWorkingDays"?: boolean, readonly "summaryReportSettings"?: SummaryReportSettingsDtoV1, readonly "theme"?: "DARK" | "DEFAULT", readonly "timeFormat": "HOUR12" | "HOUR24", readonly "timeTrackingManual"?: boolean, readonly "timeZone": string, readonly "weekStart"?: "MONDAY" | "TUESDAY" | "WEDNESDAY" | "THURSDAY" | "FRIDAY" | "SATURDAY" | "SUNDAY", readonly "weeklyUpdates"?: boolean }
|
|
373
373
|
export const UserSettingsDtoV1 = Schema.Struct({ "alerts": Schema.optionalKey(Schema.Boolean), "approval": Schema.optionalKey(Schema.Boolean), "collapseAllProjectLists": Schema.optionalKey(Schema.Boolean), "dashboardPinToTop": Schema.optionalKey(Schema.Boolean), "dashboardSelection": Schema.optionalKey(Schema.Literals(["ME", "TEAM"])), "dashboardViewType": Schema.optionalKey(Schema.Literals(["PROJECT", "BILLABILITY"])), "dateFormat": Schema.String.annotate({ "description": "Represents a date format." }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), "groupSimilarEntriesDisabled": Schema.optionalKey(Schema.Boolean), "invoiceReminders": Schema.optionalKey(Schema.Boolean), "isCompactViewOn": Schema.optionalKey(Schema.Boolean), "lang": Schema.optionalKey(Schema.String), "longRunning": Schema.optionalKey(Schema.Boolean), "multiFactorEnabled": Schema.optionalKey(Schema.Boolean), "myStartOfDay": Schema.optionalKey(Schema.String), "onboarding": Schema.optionalKey(Schema.Boolean), "projectListCollapse": Schema.optionalKey(Schema.Number.annotate({ "format": "int32" }).check(Schema.isInt().annotate({ "expected": "an integer" }))), "projectPickerTaskFilter": Schema.optionalKey(Schema.Boolean), "pto": Schema.optionalKey(Schema.Boolean), "reminders": Schema.optionalKey(Schema.Boolean), "scheduledReports": Schema.optionalKey(Schema.Boolean), "scheduling": Schema.optionalKey(Schema.Boolean), "sendNewsletter": Schema.optionalKey(Schema.Boolean), "showOnlyWorkingDays": Schema.optionalKey(Schema.Boolean), "summaryReportSettings": Schema.optionalKey(SummaryReportSettingsDtoV1), "theme": Schema.optionalKey(Schema.Literals(["DARK", "DEFAULT"])), "timeFormat": Schema.Literals(["HOUR12", "HOUR24"]).annotate({ "description": "Represents a time format enum." }), "timeTrackingManual": Schema.optionalKey(Schema.Boolean), "timeZone": Schema.String.annotate({ "description": "Represents a valid timezone ID" }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), "weekStart": Schema.optionalKey(Schema.Literals(["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"]).annotate({ "description": "Represents a day of the week." })), "weeklyUpdates": Schema.optionalKey(Schema.Boolean) }).annotate({ "description": "Represents user settings object.", "identifier": "UserSettingsDtoV1" })
|
|
374
374
|
export type BreakSettingsDtoV1 = { readonly "breaksEnabled"?: boolean, readonly "defaultBreakEntities"?: DefaultBreakEntitiesDtoV1 }
|
|
@@ -461,8 +461,8 @@ export type UpdateTimeEntryBulkRequest = { readonly "billable"?: boolean, readon
|
|
|
461
461
|
export const UpdateTimeEntryBulkRequest = Schema.Struct({ "billable": Schema.optionalKey(Schema.Boolean.annotate({ "description": "Indicates whether a time entry is billable or not." })), "customFields": Schema.optionalKey(Schema.Array(UpdateCustomFieldRequest).check(Schema.isMinLength(0).annotate({ "expected": "a value with a length of at least 0" })).check(Schema.isMaxLength(50).annotate({ "expected": "a value with a length of at most 50" }))), "description": Schema.optionalKey(Schema.String.annotate({ "description": "Represents a time entry description." }).check(Schema.isMinLength(0).annotate({ "expected": "a value with a length of at least 0" })).check(Schema.isMaxLength(3000).annotate({ "expected": "a value with a length of at most 3000" }))), "end": Schema.optionalKey(Schema.String.annotate({ "description": "Represents an end date in the yyyy-MM-ddThh:mm:ssZ format.", "format": "date-time" })), "id": Schema.String.annotate({ "description": "Represents a time entry identifier across the system." }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), "projectId": Schema.optionalKey(Schema.String.annotate({ "description": "Represents a project identifier across the system." })), "start": Schema.optionalKey(Schema.String.annotate({ "description": "Represents a start date in the yyyy-MM-ddThh:mm:ssZ format.", "format": "date-time" })), "tagIds": Schema.optionalKey(Schema.Array(Schema.String.annotate({ "description": "Represents a list of tag ids." })).annotate({ "description": "Represents a list of tag ids." })), "taskId": Schema.optionalKey(Schema.String.annotate({ "description": "Represents a task identifier across the system." })), "type": Schema.optionalKey(Schema.Literals(["REGULAR", "BREAK"])) }).annotate({ "identifier": "UpdateTimeEntryBulkRequest" })
|
|
462
462
|
export type TimeEntryDtoImplV1 = { readonly "billable": boolean, readonly "customFieldValues"?: ReadonlyArray<CustomFieldValueDtoV1>, readonly "description": string, readonly "id": string, readonly "isLocked"?: boolean, readonly "kioskId"?: string | null, readonly "projectId"?: string | null, readonly "tagIds"?: ReadonlyArray<string> | null, readonly "taskId"?: string | null, readonly "timeInterval": TimeIntervalDtoV1, readonly "type"?: "REGULAR" | "BREAK" | "HOLIDAY" | "TIME_OFF", readonly "userId": string, readonly "workspaceId": string }
|
|
463
463
|
export const TimeEntryDtoImplV1 = Schema.Struct({ "billable": Schema.Boolean.annotate({ "description": "Indicates whether a time entry is billable." }), "customFieldValues": Schema.optionalKey(Schema.Array(CustomFieldValueDtoV1).annotate({ "description": "Represents a list of custom field value objects." })), "description": Schema.String.annotate({ "description": "Represents time entry description." }), "id": Schema.String.annotate({ "description": "Represents time entry identifier across the system." }), "isLocked": Schema.optionalKey(Schema.Boolean.annotate({ "description": "Represents whether time entry is locked for modification." })), "kioskId": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Represents kiosk identifier across the system." })), "projectId": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Represents project identifier across the system." })), "tagIds": Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ "description": "Represents a list of tag identifiers across the system." })), "taskId": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Represents task identifier across the system." })), "timeInterval": TimeIntervalDtoV1, "type": Schema.optionalKey(Schema.Literals(["REGULAR", "BREAK", "HOLIDAY", "TIME_OFF"]).annotate({ "description": "Represents a time entry type enum." })), "userId": Schema.String.annotate({ "description": "Represents user identifier across the system." }), "workspaceId": Schema.String.annotate({ "description": "Represents workspace identifier across the system." }) }).annotate({ "identifier": "TimeEntryDtoImplV1" })
|
|
464
|
-
export type TimeEntryWithRatesDtoV1 = { readonly "billable": boolean, readonly "costRate"?: RateDtoV1, readonly "customFieldValues"?: ReadonlyArray<CustomFieldValueDtoV1>, readonly "description": string, readonly "hourlyRate"?: RateDtoV1, readonly "id": string, readonly "isLocked"?: boolean, readonly "kioskId"?: string | null, readonly "projectId"?: string | null, readonly "tagIds"?: ReadonlyArray<string> | null, readonly "taskId"?: string | null, readonly "timeInterval": TimeIntervalDtoV1, readonly "type"?: "REGULAR" | "BREAK" | "HOLIDAY" | "TIME_OFF", readonly "userId": string, readonly "workspaceId": string }
|
|
465
|
-
export const TimeEntryWithRatesDtoV1 = Schema.Struct({ "billable": Schema.Boolean.annotate({ "description": "Indicates whether a time entry is billable." }), "costRate": Schema.optionalKey(RateDtoV1), "customFieldValues": Schema.optionalKey(Schema.Array(CustomFieldValueDtoV1).annotate({ "description": "Represents a list of custom field value objects." })), "description": Schema.String.annotate({ "description": "Represents time entry description." }), "hourlyRate": Schema.optionalKey(RateDtoV1), "id": Schema.String.annotate({ "description": "Represents time entry identifier across the system." }), "isLocked": Schema.optionalKey(Schema.Boolean.annotate({ "description": "Represents whether time entry is locked for modification." })), "kioskId": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Represents kiosk identifier across the system." })), "projectId": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Represents project identifier across the system." })), "tagIds": Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ "description": "Represents a list of tag identifiers across the system." })), "taskId": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Represents task identifier across the system." })), "timeInterval": TimeIntervalDtoV1, "type": Schema.optionalKey(Schema.Literals(["REGULAR", "BREAK", "HOLIDAY", "TIME_OFF"]).annotate({ "description": "Represents a time entry type enum." })), "userId": Schema.String.annotate({ "description": "Represents user identifier across the system." }), "workspaceId": Schema.String.annotate({ "description": "Represents workspace identifier across the system." }) }).annotate({ "identifier": "TimeEntryWithRatesDtoV1" })
|
|
464
|
+
export type TimeEntryWithRatesDtoV1 = { readonly "billable": boolean, readonly "costRate"?: RateDtoV1 | null, readonly "customFieldValues"?: ReadonlyArray<CustomFieldValueDtoV1>, readonly "description": string, readonly "hourlyRate"?: RateDtoV1 | null, readonly "id": string, readonly "isLocked"?: boolean, readonly "kioskId"?: string | null, readonly "projectId"?: string | null, readonly "tagIds"?: ReadonlyArray<string> | null, readonly "taskId"?: string | null, readonly "timeInterval": TimeIntervalDtoV1, readonly "type"?: "REGULAR" | "BREAK" | "HOLIDAY" | "TIME_OFF", readonly "userId": string, readonly "workspaceId": string }
|
|
465
|
+
export const TimeEntryWithRatesDtoV1 = Schema.Struct({ "billable": Schema.Boolean.annotate({ "description": "Indicates whether a time entry is billable." }), "costRate": Schema.optionalKey(Schema.Union([RateDtoV1, Schema.Null])), "customFieldValues": Schema.optionalKey(Schema.Array(CustomFieldValueDtoV1).annotate({ "description": "Represents a list of custom field value objects." })), "description": Schema.String.annotate({ "description": "Represents time entry description." }), "hourlyRate": Schema.optionalKey(Schema.Union([RateDtoV1, Schema.Null])), "id": Schema.String.annotate({ "description": "Represents time entry identifier across the system." }), "isLocked": Schema.optionalKey(Schema.Boolean.annotate({ "description": "Represents whether time entry is locked for modification." })), "kioskId": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Represents kiosk identifier across the system." })), "projectId": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Represents project identifier across the system." })), "tagIds": Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ "description": "Represents a list of tag identifiers across the system." })), "taskId": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Represents task identifier across the system." })), "timeInterval": TimeIntervalDtoV1, "type": Schema.optionalKey(Schema.Literals(["REGULAR", "BREAK", "HOLIDAY", "TIME_OFF"]).annotate({ "description": "Represents a time entry type enum." })), "userId": Schema.String.annotate({ "description": "Represents user identifier across the system." }), "workspaceId": Schema.String.annotate({ "description": "Represents workspace identifier across the system." }) }).annotate({ "identifier": "TimeEntryWithRatesDtoV1" })
|
|
466
466
|
export type TimeEntryDtoV1 = { readonly "billable"?: boolean, readonly "customFieldValues"?: ReadonlyArray<CustomFieldValueDtoV1>, readonly "description"?: string, readonly "id"?: string, readonly "isLocked"?: boolean, readonly "kioskId"?: string | null, readonly "projectId"?: string | null, readonly "tagIds"?: ReadonlyArray<string> | null, readonly "taskId"?: string | null, readonly "timeInterval"?: TimeIntervalDtoV1, readonly "type"?: "REGULAR" | "BREAK" | "HOLIDAY" | "TIME_OFF", readonly "userId"?: string, readonly "workspaceId"?: string }
|
|
467
467
|
export const TimeEntryDtoV1 = Schema.Struct({ "billable": Schema.optionalKey(Schema.Boolean.annotate({ "description": "Indicates whether a time entry is billable." })), "customFieldValues": Schema.optionalKey(Schema.Array(CustomFieldValueDtoV1).annotate({ "description": "Represents a list of custom field value objects." })), "description": Schema.optionalKey(Schema.String.annotate({ "description": "Represents time entry description." })), "id": Schema.optionalKey(Schema.String.annotate({ "description": "Represents time entry identifier across the system." })), "isLocked": Schema.optionalKey(Schema.Boolean.annotate({ "description": "Represents whether time entry is locked for modification." })), "kioskId": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Represents kiosk identifier across the system." })), "projectId": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Represents project identifier across the system." })), "tagIds": Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ "description": "Represents a list of tag identifiers across the system." })), "taskId": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Represents task identifier across the system." })), "timeInterval": Schema.optionalKey(TimeIntervalDtoV1), "type": Schema.optionalKey(Schema.Literals(["REGULAR", "BREAK", "HOLIDAY", "TIME_OFF"]).annotate({ "description": "Represents a time entry type enum." })), "userId": Schema.optionalKey(Schema.String.annotate({ "description": "Represents user identifier across the system." })), "workspaceId": Schema.optionalKey(Schema.String.annotate({ "description": "Represents workspace identifier across the system." })) }).annotate({ "identifier": "TimeEntryDtoV1" })
|
|
468
468
|
export type UpdateInvoicedStatusRequest = { readonly "invoiced": boolean, readonly "timeEntryIds": ReadonlyArray<TimeEntryId> }
|
|
@@ -557,8 +557,8 @@ export type ReportFilterV1 = { readonly "amountShown"?: "EARNED" | "COST" | "PRO
|
|
|
557
557
|
export const ReportFilterV1 = Schema.Struct({ "amountShown": Schema.optionalKey(Schema.Literals(["EARNED", "COST", "PROFIT", "HIDE_AMOUNT", "EXPORT"]).annotate({ "description": "If provided, you'll get filtered result including reports with provided amount shown." })), "amounts": Schema.optionalKey(Schema.Array(Schema.Literals(["EARNED", "COST", "PROFIT", "HIDE_AMOUNT", "EXPORT"]))), "approvalState": Schema.optionalKey(Schema.Literals(["APPROVED", "UNAPPROVED", "ALL"]).annotate({ "description": "If provided, you'll get filtered result including reports with provided approval state." })), "archived": Schema.optionalKey(Schema.Boolean.annotate({ "description": "Indicates whether the report is archived" })), "attendanceFilter": Schema.optionalKey(AttendanceFilterV1), "billable": Schema.optionalKey(Schema.Boolean.annotate({ "description": "Indicates whether the report is billable" })), "clients": Schema.optionalKey(ContainsArchivedFilterV1), "currency": Schema.optionalKey(ContainsArchivedFilterV1), "customFields": Schema.optionalKey(Schema.Array(CustomFieldFilterV1)), "dateFormat": Schema.optionalKey(Schema.String.annotate({ "description": "Provide date in format YYYY-MM-DD" })), "dateRangeEnd": Schema.String.annotate({ "description": "Provide date in format YYYY-MM-DDTHH:MM:SS.ssssss. The system interprets this value based on the user's timezone (provided in the timeZone request parameter or the timezone configured in the user profile)" }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), "dateRangeStart": Schema.String.annotate({ "description": "Provide date in format YYYY-MM-DDTHH:MM:SS.ssssss. The system interprets this value based on the user's timezone (provided in the timeZone request parameter or the timezone configured in the user profile)" }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), "dateRangeType": Schema.optionalKey(Schema.Literals(["ABSOLUTE", "TODAY", "YESTERDAY", "THIS_WEEK", "LAST_WEEK", "PAST_TWO_WEEKS", "THIS_MONTH", "LAST_MONTH", "THIS_YEAR", "LAST_YEAR"]).annotate({ "description": "Provide the date range type" })), "description": Schema.optionalKey(Schema.String.annotate({ "description": "Represents search term for filtering report entries by description" })), "detailedFilter": Schema.optionalKey(DetailedFilterV1), "exportType": Schema.optionalKey(Schema.Literals(["JSON", "JSON_V1", "PDF", "CSV", "XLSX", "ZIP"]).annotate({ "description": "If provided, you'll get filtered result including reports with provided export type." })), "invoicingState": Schema.optionalKey(Schema.Literals(["INVOICED", "UNINVOICED", "ALL"]).annotate({ "description": "If provided, you'll get filtered result including reports with provided invoicing state." })), "projects": Schema.optionalKey(ContainsArchivedFilterV1), "rounding": Schema.optionalKey(Schema.Boolean.annotate({ "description": "Indicates whether the report filter is rounding" })), "sortOrder": Schema.optionalKey(Schema.Literals(["ASCENDING", "DESCENDING"]).annotate({ "description": "If provided, you'll get sorted result by provided sort order." })), "summaryFilter": Schema.optionalKey(SummaryFilterV1), "tags": Schema.optionalKey(ContainsTagFilterV1), "tasks": Schema.optionalKey(ContainsTaskFilterV1), "timeFormat": Schema.optionalKey(Schema.String.annotate({ "description": "Provide time in format THH:MM:SS.ssssss" })), "timeZone": Schema.optionalKey(Schema.String.annotate({ "description": "If provided, you'll get filtered result including reports with provided time zone." })), "userCustomFields": Schema.optionalKey(Schema.Array(CustomFieldFilterV1).annotate({ "writeOnly": true })), "userGroups": Schema.optionalKey(ContainsUsersFilterV1), "userLocale": Schema.optionalKey(Schema.String.annotate({ "description": "If provided, you'll get filtered result including reports with provided user locale." })), "users": Schema.optionalKey(ContainsUsersFilterV1), "weekStart": Schema.optionalKey(Schema.Literals(["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"]).annotate({ "description": "If provided, you'll get filtered result including reports with provided week start." })), "weeklyFilter": Schema.optionalKey(WeeklyFilterV1), "withoutDescription": Schema.optionalKey(Schema.Boolean.annotate({ "description": "If set to 'true', report will only include entries with empty description" })), "zoomLevel": Schema.optionalKey(Schema.Literals(["WEEK", "MONTH", "YEAR"]).annotate({ "description": "If provided, you'll get filtered result including reports with provided zoom level." })) }).annotate({ "identifier": "ReportFilterV1" })
|
|
558
558
|
export type ExpenseDetailedReportDtoV1 = { readonly "expenses"?: ReadonlyArray<ExpenseReportDtoV1>, readonly "totals"?: ExpenseTotalsDtoV1 }
|
|
559
559
|
export const ExpenseDetailedReportDtoV1 = Schema.Struct({ "expenses": Schema.optionalKey(Schema.Array(ExpenseReportDtoV1).annotate({ "description": "Represents list of expenses" })), "totals": Schema.optionalKey(ExpenseTotalsDtoV1) }).annotate({ "description": "report", "identifier": "ExpenseDetailedReportDtoV1" })
|
|
560
|
-
export type WorkspaceDtoV1 = { readonly "cakeOrganizationId"?: string, readonly "costRate"?: RateDtoV1, readonly "currencies"?: ReadonlyArray<CurrencyWithDefaultInfoDtoV1>, readonly "featureSubscriptionType"?:
|
|
561
|
-
export const WorkspaceDtoV1 = Schema.Struct({ "cakeOrganizationId": Schema.optionalKey(Schema.String.annotate({ "description": "Represents the Cake organization identifier across the system." })), "costRate": Schema.optionalKey(RateDtoV1), "currencies": Schema.optionalKey(Schema.Array(CurrencyWithDefaultInfoDtoV1).annotate({ "description": "Represents currency with default info object." })), "featureSubscriptionType": Schema.optionalKey(FeaturePlan), "features": Schema.optionalKey(Feature), "hourlyRate": Schema.optionalKey(HourlyRateDtoV1), "id": Schema.String.annotate({ "description": "Represents workspace identifier across the system." }), "imageUrl": Schema.optionalKey(Schema.String.annotate({ "description": "Represents an image url." })), "memberships": Schema.optionalKey(Schema.Array(MembershipDtoV1).annotate({ "description": "Represents a list of membership objects." })), "name": Schema.String.annotate({ "description": "Represents workspace name." }), "subdomain": Schema.optionalKey(WorkspaceSubdomainDtoV1), "workspaceSettings": Schema.optionalKey(
|
|
560
|
+
export type WorkspaceDtoV1 = { readonly "cakeOrganizationId"?: string, readonly "costRate"?: RateDtoV1, readonly "currencies"?: ReadonlyArray<CurrencyWithDefaultInfoDtoV1>, readonly "featureSubscriptionType"?: string, readonly "features"?: ReadonlyArray<string>, readonly "hourlyRate"?: HourlyRateDtoV1, readonly "id": string, readonly "imageUrl"?: string, readonly "memberships"?: ReadonlyArray<MembershipDtoV1>, readonly "name": string, readonly "subdomain"?: WorkspaceSubdomainDtoV1, readonly "workspaceSettings"?: { readonly [x: string]: Schema.Json } }
|
|
561
|
+
export const WorkspaceDtoV1 = Schema.Struct({ "cakeOrganizationId": Schema.optionalKey(Schema.String.annotate({ "description": "Represents the Cake organization identifier across the system." })), "costRate": Schema.optionalKey(RateDtoV1), "currencies": Schema.optionalKey(Schema.Array(CurrencyWithDefaultInfoDtoV1).annotate({ "description": "Represents currency with default info object." })), "featureSubscriptionType": Schema.optionalKey(Schema.String.annotate({ "description": "The workspace's subscription plan, e.g. FREE_2026. The upstream spec references FeaturePlan, an object, but the API returns this string; it is left open because Clockify adds plan names." })), "features": Schema.optionalKey(Schema.Array(Schema.String).annotate({ "description": "The workspace's enabled features, e.g. TIME_TRACKING. The upstream spec references Feature, which has no type, but the API returns these strings; they are left open because Clockify adds features." })), "hourlyRate": Schema.optionalKey(HourlyRateDtoV1), "id": Schema.String.annotate({ "description": "Represents workspace identifier across the system." }), "imageUrl": Schema.optionalKey(Schema.String.annotate({ "description": "Represents an image url." })), "memberships": Schema.optionalKey(Schema.Array(MembershipDtoV1).annotate({ "description": "Represents a list of membership objects." })), "name": Schema.String.annotate({ "description": "Represents workspace name." }), "subdomain": Schema.optionalKey(WorkspaceSubdomainDtoV1), "workspaceSettings": Schema.optionalKey(Schema.Record(Schema.String, Schema.Json).annotate({ "description": "Workspace settings, not modelled: the upstream schema disagrees with live responses on many nullable and enum fields." })) }).annotate({ "identifier": "WorkspaceDtoV1" })
|
|
562
562
|
export type ExpensesAndTotalsDtoV1 = { readonly "dailyTotals"?: ReadonlyArray<ExpenseDailyTotalsDtoV1>, readonly "expenses"?: ExpensesWithCountDtoV1, readonly "weeklyTotals"?: ReadonlyArray<ExpenseWeeklyTotalsDtoV1> }
|
|
563
563
|
export const ExpensesAndTotalsDtoV1 = Schema.Struct({ "dailyTotals": Schema.optionalKey(Schema.Array(ExpenseDailyTotalsDtoV1).annotate({ "description": "Represents a list of expense daily total data transfer objects." })), "expenses": Schema.optionalKey(ExpensesWithCountDtoV1), "weeklyTotals": Schema.optionalKey(Schema.Array(ExpenseWeeklyTotalsDtoV1).annotate({ "description": "Represents a list of expense weekly total data transfer objects." })) }).annotate({ "identifier": "ExpensesAndTotalsDtoV1" })
|
|
564
564
|
export type MemberProfileDtoV1 = { readonly "email"?: string, readonly "hasPassword"?: boolean, readonly "hasPendingApprovalRequest"?: boolean, readonly "imageUrl"?: string, readonly "name"?: string, readonly "userCustomFieldValues"?: ReadonlyArray<UserCustomFieldValueFullDtoV1>, readonly "weekStart"?: "MONDAY" | "TUESDAY" | "WEDNESDAY" | "THURSDAY" | "FRIDAY" | "SATURDAY" | "SUNDAY", readonly "workCapacity"?: string, readonly "workingDays"?: "MONDAY" | "TUESDAY" | "WEDNESDAY" | "THURSDAY" | "FRIDAY" | "SATURDAY" | "SUNDAY", readonly "workspaceNumber"?: number }
|
|
@@ -196,6 +196,25 @@ it.layer(suite)("ClockifyApiClient", (it) => {
|
|
|
196
196
|
expect(entries[0]?.tagIds).toBeNull()
|
|
197
197
|
}))
|
|
198
198
|
|
|
199
|
+
it.effect("decodes a time-entries list whose entries have null rates", () =>
|
|
200
|
+
Effect.gen(function*() {
|
|
201
|
+
// Clockify returns costRate/hourlyRate as explicit null when no rate applies.
|
|
202
|
+
reply(200, [{
|
|
203
|
+
id: "entry-1",
|
|
204
|
+
description: "[ENG-6169] review transaction mapper",
|
|
205
|
+
billable: true,
|
|
206
|
+
userId: "user-1",
|
|
207
|
+
workspaceId: "workspace-1",
|
|
208
|
+
costRate: null,
|
|
209
|
+
hourlyRate: null,
|
|
210
|
+
timeInterval: { start: "2026-07-20T09:00:00Z", end: "2026-07-20T11:00:00Z", duration: "PT2H" }
|
|
211
|
+
}])
|
|
212
|
+
const client = yield* ClockifyApiClient
|
|
213
|
+
const entries = yield* client.getTimeEntries("workspace-1", "user-1")
|
|
214
|
+
expect(entries[0]?.costRate).toBeNull()
|
|
215
|
+
expect(entries[0]?.hourlyRate).toBeNull()
|
|
216
|
+
}))
|
|
217
|
+
|
|
199
218
|
it.effect("fails when a successful response violates the generated schema", () =>
|
|
200
219
|
Effect.gen(function*() {
|
|
201
220
|
reply(200, { id: "user-1" })
|
|
@@ -1,37 +1,31 @@
|
|
|
1
1
|
import { describe, expect, it } from "@effect/vitest"
|
|
2
2
|
import * as Effect from "effect/Effect"
|
|
3
3
|
import * as Schema from "effect/Schema"
|
|
4
|
-
import {
|
|
4
|
+
import { FeaturePlan, WorkspaceDtoV1, WorkspaceSettingsDtoV1 } from "../src/generated/ClockifyApi.js"
|
|
5
5
|
|
|
6
|
-
describe("generated workspace schema", () => {
|
|
6
|
+
describe("generated workspace settings schema", () => {
|
|
7
7
|
// Preserve both upstream spellings in responses without rewriting either value.
|
|
8
8
|
it.effect.each(["ADMINS", "ADMINS_AND_PROJECT_MANAGERS", "ANYONE", "EVERYONE"])(
|
|
9
|
-
"preserves scheduling assignment creator %s
|
|
9
|
+
"preserves scheduling assignment creator %s",
|
|
10
10
|
(creator) =>
|
|
11
11
|
Effect.gen(function*() {
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
name: "Delivery",
|
|
15
|
-
workspaceSettings: {
|
|
16
|
-
schedulingSettings: { whoCanCreateAssignments: creator }
|
|
17
|
-
}
|
|
12
|
+
const settings = yield* Schema.decodeUnknownEffect(WorkspaceSettingsDtoV1)({
|
|
13
|
+
schedulingSettings: { whoCanCreateAssignments: creator }
|
|
18
14
|
})
|
|
19
|
-
expect(
|
|
15
|
+
expect(settings.schedulingSettings?.whoCanCreateAssignments).toBe(creator)
|
|
20
16
|
})
|
|
21
17
|
)
|
|
22
18
|
|
|
23
19
|
it.effect("rejects undocumented scheduling assignment creators", () =>
|
|
24
20
|
Effect.gen(function*() {
|
|
25
|
-
const failure = yield* Schema.decodeUnknownEffect(
|
|
26
|
-
|
|
27
|
-
name: "Delivery",
|
|
28
|
-
workspaceSettings: {
|
|
29
|
-
schedulingSettings: { whoCanCreateAssignments: "OWNER_ONLY" }
|
|
30
|
-
}
|
|
21
|
+
const failure = yield* Schema.decodeUnknownEffect(WorkspaceSettingsDtoV1)({
|
|
22
|
+
schedulingSettings: { whoCanCreateAssignments: "OWNER_ONLY" }
|
|
31
23
|
}).pipe(Effect.flip)
|
|
32
24
|
expect(failure._tag).toBe("SchemaError")
|
|
33
25
|
}))
|
|
26
|
+
})
|
|
34
27
|
|
|
28
|
+
describe("generated feature plan schema", () => {
|
|
35
29
|
it.effect("decodes feature plans after removing the upstream recursive oneOf", () =>
|
|
36
30
|
Effect.gen(function*() {
|
|
37
31
|
const plan: FeaturePlan = {
|
|
@@ -40,21 +34,43 @@ describe("generated workspace schema", () => {
|
|
|
40
34
|
paidPlan: true,
|
|
41
35
|
weight: 3
|
|
42
36
|
}
|
|
43
|
-
|
|
44
|
-
id: "workspace-1",
|
|
45
|
-
name: "Delivery",
|
|
46
|
-
featureSubscriptionType: plan
|
|
47
|
-
})
|
|
48
|
-
expect(workspace.featureSubscriptionType).toEqual(plan)
|
|
37
|
+
expect(yield* Schema.decodeUnknownEffect(FeaturePlan)(plan)).toEqual(plan)
|
|
49
38
|
}))
|
|
50
39
|
|
|
51
40
|
it.effect("rejects feature plans with a fractional weight", () =>
|
|
52
41
|
Effect.gen(function*() {
|
|
53
|
-
const failure = yield* Schema.decodeUnknownEffect(
|
|
42
|
+
const failure = yield* Schema.decodeUnknownEffect(FeaturePlan)({ weight: 1.5 }).pipe(Effect.flip)
|
|
43
|
+
expect(failure._tag).toBe("SchemaError")
|
|
44
|
+
}))
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
describe("generated workspace schema", () => {
|
|
48
|
+
// Live /v1/workspaces responses: the plan is a string, features are strings, settings are not modelled.
|
|
49
|
+
it.effect("accepts the plan, features and settings shapes the API returns", () =>
|
|
50
|
+
Effect.gen(function*() {
|
|
51
|
+
const workspace = yield* Schema.decodeUnknownEffect(WorkspaceDtoV1)({
|
|
54
52
|
id: "workspace-1",
|
|
55
53
|
name: "Delivery",
|
|
56
|
-
featureSubscriptionType:
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
featureSubscriptionType: "FREE_2026",
|
|
55
|
+
features: ["TIME_TRACKING"],
|
|
56
|
+
workspaceSettings: { automaticLock: null, adminOnlyPages: [] },
|
|
57
|
+
memberships: [{ userId: "u1", costRate: null, hourlyRate: null }],
|
|
58
|
+
subdomain: { enabled: false, name: null }
|
|
59
|
+
})
|
|
60
|
+
expect(workspace.featureSubscriptionType).toBe("FREE_2026")
|
|
61
|
+
expect(workspace.features).toEqual(["TIME_TRACKING"])
|
|
59
62
|
}))
|
|
63
|
+
|
|
64
|
+
it.effect.each([42, "oops", [], true])(
|
|
65
|
+
"rejects workspace settings that are not an object: %s",
|
|
66
|
+
(settings) =>
|
|
67
|
+
Effect.gen(function*() {
|
|
68
|
+
const failure = yield* Schema.decodeUnknownEffect(WorkspaceDtoV1)({
|
|
69
|
+
id: "workspace-1",
|
|
70
|
+
name: "Delivery",
|
|
71
|
+
workspaceSettings: settings
|
|
72
|
+
}).pipe(Effect.flip)
|
|
73
|
+
expect(failure._tag).toBe("SchemaError")
|
|
74
|
+
})
|
|
75
|
+
)
|
|
60
76
|
})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { describe, expect, it } from "@effect/vitest"
|
|
2
|
+
import { Schema } from "effect"
|
|
3
|
+
import { GetWorkspacesOfUser200 } from "../src/generated/ClockifyApi.js"
|
|
4
|
+
import workspaces from "./fixtures/clockify-workspaces.json" with { type: "json" }
|
|
5
|
+
|
|
6
|
+
describe("workspace subscription plan", () => {
|
|
7
|
+
it("decodes a real-shaped /v1/workspaces response, whose featureSubscriptionType is a string", () => {
|
|
8
|
+
const [workspace] = Schema.decodeUnknownSync(GetWorkspacesOfUser200)(workspaces)
|
|
9
|
+
expect(workspace?.featureSubscriptionType).toBe("FREE_2026")
|
|
10
|
+
})
|
|
11
|
+
})
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "64f0c0ffee0000000000aa01",
|
|
4
|
+
"name": "Example Workspace",
|
|
5
|
+
"hourlyRate": {
|
|
6
|
+
"amount": 0,
|
|
7
|
+
"currency": "USD"
|
|
8
|
+
},
|
|
9
|
+
"costRate": {
|
|
10
|
+
"amount": 0,
|
|
11
|
+
"currency": "USD"
|
|
12
|
+
},
|
|
13
|
+
"memberships": [
|
|
14
|
+
{
|
|
15
|
+
"userId": "redacted",
|
|
16
|
+
"hourlyRate": null,
|
|
17
|
+
"costRate": null,
|
|
18
|
+
"targetId": "redacted",
|
|
19
|
+
"membershipType": "WORKSPACE",
|
|
20
|
+
"membershipStatus": "ACTIVE"
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"workspaceSettings": {
|
|
24
|
+
"timeRoundingInReports": false,
|
|
25
|
+
"onlyAdminsSeeBillableRates": true,
|
|
26
|
+
"activeBillableHours": false,
|
|
27
|
+
"onlyAdminsCanChangeBillableStatus": false,
|
|
28
|
+
"onlyAdminsCreateProject": false,
|
|
29
|
+
"onlyAdminsSeeDashboard": false,
|
|
30
|
+
"defaultBillableProjects": true,
|
|
31
|
+
"lockTimeEntries": null,
|
|
32
|
+
"lockTimeZone": null,
|
|
33
|
+
"round": {
|
|
34
|
+
"round": "redacted",
|
|
35
|
+
"minutes": "redacted"
|
|
36
|
+
},
|
|
37
|
+
"projectFavorites": true,
|
|
38
|
+
"canSeeTimeSheet": false,
|
|
39
|
+
"canSeeTracker": true,
|
|
40
|
+
"projectPickerSpecialFilter": false,
|
|
41
|
+
"forceProjects": false,
|
|
42
|
+
"forceTasks": false,
|
|
43
|
+
"forceTags": false,
|
|
44
|
+
"forceDescription": false,
|
|
45
|
+
"onlyAdminsSeeAllTimeEntries": false,
|
|
46
|
+
"onlyAdminsSeePublicProjectsEntries": false,
|
|
47
|
+
"onlyAdminsSeeProjectStatus": false,
|
|
48
|
+
"trackTimeDownToSecond": true,
|
|
49
|
+
"projectGroupingLabel": "CLIENT",
|
|
50
|
+
"adminOnlyPages": [],
|
|
51
|
+
"automaticLock": null,
|
|
52
|
+
"onlyAdminsCreateTag": false,
|
|
53
|
+
"timeApprovalEnabled": false,
|
|
54
|
+
"approvalSettings": {
|
|
55
|
+
"approvalEnabled": false,
|
|
56
|
+
"approvalPeriod": "WEEKLY",
|
|
57
|
+
"approvalRoles": ["TEAM_MANAGER", "ADMIN"]
|
|
58
|
+
},
|
|
59
|
+
"screenshotsEnabled": false,
|
|
60
|
+
"highResolutionScreenshots": false,
|
|
61
|
+
"locationsEnabled": false,
|
|
62
|
+
"onlyAdminsCreateTask": false,
|
|
63
|
+
"timeTrackingMode": "DEFAULT",
|
|
64
|
+
"taskBillableEnabled": false,
|
|
65
|
+
"taskRateEnabled": false,
|
|
66
|
+
"invoicingEnabled": false,
|
|
67
|
+
"expensesEnabled": false,
|
|
68
|
+
"schedulingEnabled": false,
|
|
69
|
+
"companyAddress": "redacted",
|
|
70
|
+
"canManagerEditUsersTime": false,
|
|
71
|
+
"timeOff": {
|
|
72
|
+
"active": false,
|
|
73
|
+
"regularUserCanSeeOtherUsersTimeOff": false,
|
|
74
|
+
"showDefaultPoliciesMessageToUsers": []
|
|
75
|
+
},
|
|
76
|
+
"auditLogSettings": null,
|
|
77
|
+
"schedulingSettings": {
|
|
78
|
+
"enabled": false,
|
|
79
|
+
"everyoneCanSeeAllAssignments": false,
|
|
80
|
+
"whoCanCreateAssignments": "ADMINS",
|
|
81
|
+
"canSeeBillableAndCostAmount": false
|
|
82
|
+
},
|
|
83
|
+
"kioskEnabled": false,
|
|
84
|
+
"kioskAuthType": null,
|
|
85
|
+
"kioskProjectsAndTasksEnabled": false,
|
|
86
|
+
"kioskAutologinEnabled": false,
|
|
87
|
+
"canManagerLaunchKiosk": false,
|
|
88
|
+
"kioskPhotoCaptureEnabled": false,
|
|
89
|
+
"multiFactorEnabled": false,
|
|
90
|
+
"numberFormat": "PERIOD_COMMA",
|
|
91
|
+
"currencyFormat": "VALUE_SPACE_CURRENCY",
|
|
92
|
+
"favoriteEntriesEnabled": false,
|
|
93
|
+
"breakSettings": {
|
|
94
|
+
"breaksEnabled": false,
|
|
95
|
+
"defaultBreakEntities": {
|
|
96
|
+
"projectId": null,
|
|
97
|
+
"taskId": null
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
"weekStart": "MONDAY",
|
|
101
|
+
"timeViewMode": "TIME_SENSITIVE_VIEW",
|
|
102
|
+
"whitelistedForAggregateView": false,
|
|
103
|
+
"workCapacity": "PT8H",
|
|
104
|
+
"overtimeCalculationPeriod": "DAILY",
|
|
105
|
+
"decimalFormat": false,
|
|
106
|
+
"durationFormat": "FULL",
|
|
107
|
+
"entityCreationPermissions": {
|
|
108
|
+
"whoCanCreateTasks": "EVERYONE",
|
|
109
|
+
"whoCanCreateProjectsAndClients": "EVERYONE",
|
|
110
|
+
"whoCanCreateTags": "EVERYONE"
|
|
111
|
+
},
|
|
112
|
+
"projectLabel": "redacted",
|
|
113
|
+
"taskLabel": "redacted",
|
|
114
|
+
"customLabels": {
|
|
115
|
+
"projectGroupingLabel": "redacted",
|
|
116
|
+
"projectLabel": "redacted",
|
|
117
|
+
"taskLabel": "redacted"
|
|
118
|
+
},
|
|
119
|
+
"workingDays": ["THURSDAY", "FRIDAY", "MONDAY", "TUESDAY", "WEDNESDAY"],
|
|
120
|
+
"isCostRateActive": false,
|
|
121
|
+
"expenseApprovalSettings": {
|
|
122
|
+
"approvalEnabled": false,
|
|
123
|
+
"approvalPeriod": "WEEKLY",
|
|
124
|
+
"approvalRoles": ["TEAM_MANAGER", "ADMIN"]
|
|
125
|
+
},
|
|
126
|
+
"isProjectPublicByDefault": true
|
|
127
|
+
},
|
|
128
|
+
"imageUrl": "redacted",
|
|
129
|
+
"featureSubscriptionType": "FREE_2026",
|
|
130
|
+
"features": ["TIME_TRACKING", "ONE_MONTH_RANGE_REPORTS"],
|
|
131
|
+
"currencies": [
|
|
132
|
+
{
|
|
133
|
+
"id": "redacted",
|
|
134
|
+
"code": "USD",
|
|
135
|
+
"isDefault": true
|
|
136
|
+
}
|
|
137
|
+
],
|
|
138
|
+
"subdomain": {
|
|
139
|
+
"name": null,
|
|
140
|
+
"enabled": false
|
|
141
|
+
},
|
|
142
|
+
"cakeOrganizationId": "redacted"
|
|
143
|
+
}
|
|
144
|
+
]
|
package/vitest.config.ts
CHANGED