@knpkv/clockify-api-client 1.0.1 → 1.0.3

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/src/index.ts CHANGED
@@ -16,6 +16,7 @@ export type {
16
16
  AuthenticatedClockifyApi,
17
17
  CreateTimeEntryParams,
18
18
  GetTimeEntriesParams,
19
+ GetTimeEntryParams,
19
20
  Project,
20
21
  StopTimeEntryParams,
21
22
  Tag,
@@ -23,7 +24,8 @@ export type {
23
24
  TimeInterval,
24
25
  UpdateTimeEntryParams,
25
26
  User,
26
- Workspace
27
+ Workspace,
28
+ WorkspaceUser
27
29
  } from "./ClockifyApiClient.js"
28
30
 
29
31
  export { ClockifyApiConfig, type ClockifyApiConfigShape } from "./ClockifyApiConfig.js"
@@ -80,6 +80,107 @@ describe("ClockifyApiClient", () => {
80
80
  )
81
81
  })
82
82
 
83
+ it.effect("requests hydrated time-entry details when required by the caller", () => {
84
+ const requests: Array<HttpClientRequest.HttpClientRequest> = []
85
+ return Effect.gen(function*() {
86
+ const client = yield* ClockifyApiClient
87
+ const entry = yield* client.getTimeEntry("workspace-1", "entry-1", { hydrated: true })
88
+ yield* client.getTimeEntry("workspace-1", "entry-1")
89
+ yield* client.getTimeEntry("workspace-1", "entry-1", { hydrated: false })
90
+ expect(entry.id).toBe("entry-1")
91
+ expect(new Map(requests[0]?.urlParams ?? []).get("hydrated")).toBe("true")
92
+ expect(new Map(requests[1]?.urlParams ?? []).has("hydrated")).toBe(false)
93
+ expect(new Map(requests[2]?.urlParams ?? []).get("hydrated")).toBe("false")
94
+ }).pipe(
95
+ Effect.provide(clientLayer({
96
+ status: 200,
97
+ body: {
98
+ id: "entry-1",
99
+ workspaceId: "workspace-1",
100
+ userId: "user-1",
101
+ description: "Review payment safeguards",
102
+ billable: true,
103
+ customFieldValues: [],
104
+ projectId: "project-1",
105
+ taskId: "task-1",
106
+ tagIds: ["delivery"],
107
+ timeInterval: {
108
+ start: "2026-07-11T08:00:00Z",
109
+ end: "2026-07-11T09:00:00Z",
110
+ duration: "PT1H"
111
+ }
112
+ }
113
+ }, requests))
114
+ )
115
+ })
116
+
117
+ it.effect("forwards hydration when listing canonical time-entry snapshots", () => {
118
+ const requests: Array<HttpClientRequest.HttpClientRequest> = []
119
+ return Effect.gen(function*() {
120
+ const client = yield* ClockifyApiClient
121
+ yield* client.getTimeEntries("workspace-1", "user-1", {
122
+ hydrated: true,
123
+ page: 2,
124
+ pageSize: 10
125
+ })
126
+ expect(new Map(requests[0]?.urlParams ?? []).get("hydrated")).toBe("true")
127
+ expect(new Map(requests[0]?.urlParams ?? []).get("page")).toBe("2")
128
+ expect(new Map(requests[0]?.urlParams ?? []).get("page-size")).toBe("10")
129
+ }).pipe(
130
+ Effect.provide(clientLayer({ status: 200, body: [] }, requests))
131
+ )
132
+ })
133
+
134
+ it.effect("pages through the complete workspace user directory", () => {
135
+ const requests: Array<HttpClientRequest.HttpClientRequest> = []
136
+ const firstPage = Array.from({ length: 500 }, (_, index) => ({
137
+ id: `user-${index}`,
138
+ name: `User ${index}`,
139
+ email: `user-${index}@example.test`,
140
+ status: "ACTIVE"
141
+ }))
142
+ const layer = ClockifyApiClient.layer.pipe(
143
+ Layer.provide(Layer.succeed(ClockifyApiConfig, {
144
+ apiKey: Redacted.make("secret"),
145
+ workspaceId: "workspace-1",
146
+ userId: "user-1",
147
+ baseUrl: "https://clockify.test/api"
148
+ })),
149
+ Layer.provide(Layer.succeed(
150
+ HttpClient.HttpClient,
151
+ HttpClient.make((request) =>
152
+ Effect.sync(() => {
153
+ requests.push(request)
154
+ const body = requests.length <= 20
155
+ ? firstPage
156
+ : [{ id: "configured-user", name: "Configured User", email: "configured@example.test", status: "ACTIVE" }]
157
+ return HttpClientResponse.fromWeb(
158
+ request,
159
+ new Response(JSON.stringify(body), {
160
+ status: 200,
161
+ headers: { "content-type": "application/json" }
162
+ })
163
+ )
164
+ })
165
+ )
166
+ ))
167
+ )
168
+ return Effect.gen(function*() {
169
+ const client = yield* ClockifyApiClient
170
+ if (client.getWorkspaceUsers === undefined) return assert.fail("expected workspace user discovery")
171
+ const users = yield* client.getWorkspaceUsers("workspace-1")
172
+ expect(users).toHaveLength(10_001)
173
+ expect(users.at(-1)?.id).toBe("configured-user")
174
+ expect(requests).toHaveLength(21)
175
+ expect(new Map(requests[0]?.urlParams ?? []).get("page")).toBe("1")
176
+ expect(new Map(requests[20]?.urlParams ?? []).get("page")).toBe("21")
177
+ expect(new Map(requests[0]?.urlParams ?? []).get("account-statuses")).toBe(
178
+ "ACTIVE,PENDING_EMAIL_VERIFICATION,DELETED,NOT_REGISTERED,LIMITED,LIMITED_DELETED"
179
+ )
180
+ expect(new Map(requests[0]?.urlParams ?? []).get("memberships")).toBe("ALL")
181
+ }).pipe(Effect.provide(layer))
182
+ })
183
+
83
184
  it.effect("decodes a created time entry whose optional id references are null", () => {
84
185
  const requests: Array<HttpClientRequest.HttpClientRequest> = []
85
186
  return Effect.gen(function*() {