@knpkv/clockify-api-client 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/.specs/VERSION +1 -0
  2. package/.specs/clockify-v1.json +31670 -0
  3. package/.specs/clockify-v1.patch.json +48 -0
  4. package/CHANGELOG.md +11 -0
  5. package/LICENSE +21 -0
  6. package/README.md +121 -0
  7. package/dist/ClockifyApiClient.d.ts +67 -0
  8. package/dist/ClockifyApiClient.d.ts.map +1 -0
  9. package/dist/ClockifyApiClient.js +141 -0
  10. package/dist/ClockifyApiClient.js.map +1 -0
  11. package/dist/ClockifyApiConfig.d.ts +25 -0
  12. package/dist/ClockifyApiConfig.d.ts.map +1 -0
  13. package/dist/ClockifyApiConfig.js +16 -0
  14. package/dist/ClockifyApiConfig.js.map +1 -0
  15. package/dist/ClockifyApiError.d.ts +11 -0
  16. package/dist/ClockifyApiError.d.ts.map +1 -0
  17. package/dist/ClockifyApiError.js +14 -0
  18. package/dist/ClockifyApiError.js.map +1 -0
  19. package/dist/OpenApiFetchClient.d.ts +55 -0
  20. package/dist/OpenApiFetchClient.d.ts.map +1 -0
  21. package/dist/OpenApiFetchClient.js +63 -0
  22. package/dist/OpenApiFetchClient.js.map +1 -0
  23. package/dist/generated/index.d.ts +2 -0
  24. package/dist/generated/index.d.ts.map +1 -0
  25. package/dist/generated/index.js +2 -0
  26. package/dist/generated/index.js.map +1 -0
  27. package/dist/generated/schema.d.ts +16204 -0
  28. package/dist/index.d.ts +12 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +10 -0
  31. package/dist/index.js.map +1 -0
  32. package/package.json +73 -0
  33. package/scripts/regenerate.ts +138 -0
  34. package/src/ClockifyApiClient.ts +278 -0
  35. package/src/ClockifyApiConfig.ts +25 -0
  36. package/src/ClockifyApiError.ts +17 -0
  37. package/src/OpenApiFetchClient.ts +92 -0
  38. package/src/generated/index.ts +1 -0
  39. package/src/generated/schema.d.ts +16204 -0
  40. package/src/index.ts +35 -0
  41. package/tsconfig.json +11 -0
  42. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,48 @@
1
+ {
2
+ "$comment": "Patches for clockify-v1.json — the official spec has incomplete DTOs. Applied by scripts/regenerate.ts before generating types.",
3
+ "schemas": {
4
+ "UserDto": {
5
+ "addProperties": {
6
+ "activeWorkspace": { "type": "string" },
7
+ "defaultWorkspace": { "type": "string" },
8
+ "profilePicture": { "type": "string" },
9
+ "status": { "type": "string" }
10
+ },
11
+ "required": ["id", "name", "email", "activeWorkspace", "defaultWorkspace", "status"]
12
+ },
13
+ "TimeEntryDto": {
14
+ "renameProperties": {
15
+ "get_id": "id"
16
+ },
17
+ "addProperties": {
18
+ "workspaceId": { "type": "string" },
19
+ "type": { "type": "string" },
20
+ "isLocked": { "type": "boolean" },
21
+ "tagIds": { "type": "array", "items": { "type": "string" } }
22
+ },
23
+ "required": ["id", "description", "billable", "userId", "workspaceId", "timeInterval"]
24
+ },
25
+ "TimeIntervalDto": {
26
+ "required": ["start"],
27
+ "patchProperties": {
28
+ "end": { "type": "string", "format": "date-time", "nullable": true },
29
+ "duration": { "type": "string", "nullable": true }
30
+ }
31
+ },
32
+ "TagDto": {
33
+ "required": ["id", "name", "workspaceId", "archived"]
34
+ },
35
+ "ProjectDtoV1": {
36
+ "addProperties": {
37
+ "name": { "type": "string" },
38
+ "note": { "type": "string" },
39
+ "workspaceId": { "type": "string" },
40
+ "public": { "type": "boolean" }
41
+ },
42
+ "required": ["id", "name", "archived", "billable", "color", "workspaceId"]
43
+ },
44
+ "CreateTimeEntryRequest": {
45
+ "required": ["description", "start"]
46
+ }
47
+ }
48
+ }
package/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # @knpkv/clockify-api-client
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#61](https://github.com/knpkv/npm/pull/61) [`fc7be8f`](https://github.com/knpkv/npm/commit/fc7be8ffaf5b6b094c7f81551e8ace6f2a8f2c4c) Thanks @konopkov! - feat: add jira-api-client and atlassian-common packages
8
+ - New @knpkv/atlassian-common: shared AST types, serializers, auth, and config
9
+ - New @knpkv/jira-api-client: Effect-based Jira REST API client (openapi-gen)
10
+ - Updated @knpkv/confluence-api-client: regenerated with openapi-gen
11
+ - Updated @knpkv/confluence-to-markdown: use new generated API client
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 knpkv
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,121 @@
1
+ # @knpkv/clockify-api-client
2
+
3
+ Effect-based Clockify REST API client. Type-safe paths and responses generated from a patched OpenAPI spec via `openapi-typescript` + `openapi-fetch`.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @knpkv/clockify-api-client
9
+ ```
10
+
11
+ Peer dependencies: `effect`
12
+
13
+ ## Usage
14
+
15
+ ### Method-based API
16
+
17
+ Convenience methods for common operations:
18
+
19
+ ```typescript
20
+ import { Effect, Layer } from "effect"
21
+ import * as Redacted from "effect/Redacted"
22
+ import { ClockifyApiClient, ClockifyApiConfig } from "@knpkv/clockify-api-client"
23
+
24
+ const program = Effect.gen(function* () {
25
+ const clockify = yield* ClockifyApiClient
26
+
27
+ const user = yield* clockify.getUser()
28
+ const projects = yield* clockify.getProjects("workspace-id")
29
+
30
+ // Start a timer
31
+ const entry = yield* clockify.createTimeEntry("workspace-id", {
32
+ start: new Date().toISOString(),
33
+ description: "Working on PROJ-123"
34
+ })
35
+
36
+ // Stop timer
37
+ yield* clockify.stopTimer("workspace-id", user.id, {
38
+ end: new Date().toISOString()
39
+ })
40
+
41
+ // Get running timer
42
+ const running = yield* clockify.getRunningTimer("workspace-id", user.id)
43
+ })
44
+
45
+ const configLayer = Layer.succeed(ClockifyApiConfig, {
46
+ apiKey: Redacted.make("your-clockify-api-key"),
47
+ workspaceId: "workspace-id",
48
+ userId: "user-id",
49
+ baseUrl: "https://api.clockify.me/api"
50
+ })
51
+
52
+ Effect.runPromise(program.pipe(Effect.provide(ClockifyApiClient.layer), Effect.provide(configLayer)))
53
+ ```
54
+
55
+ ### Raw API
56
+
57
+ Direct openapi-fetch access for any endpoint:
58
+
59
+ ```typescript
60
+ import { toEffect } from "@knpkv/clockify-api-client"
61
+
62
+ const program = Effect.gen(function* () {
63
+ const clockify = yield* ClockifyApiClient
64
+
65
+ const user = yield* toEffect(clockify.api.client.GET("/v1/user"))
66
+
67
+ const entries = yield* toEffect(
68
+ clockify.api.client.GET("/v1/workspaces/{workspaceId}/user/{userId}/time-entries", {
69
+ params: { path: { workspaceId: "ws-id", userId: "u-id" } }
70
+ })
71
+ )
72
+ })
73
+ ```
74
+
75
+ ## Available Methods
76
+
77
+ | Method | Description |
78
+ | ---------------------------------------- | ----------------------------- |
79
+ | `getUser()` | Current authenticated user |
80
+ | `getWorkspaces()` | List workspaces |
81
+ | `getProjects(wsId)` | List projects in workspace |
82
+ | `getProjectByName(wsId, name)` | Find project by name |
83
+ | `createTimeEntry(wsId, params)` | Start or create entry |
84
+ | `stopTimer(wsId, userId, params)` | Stop running timer |
85
+ | `getTimeEntries(wsId, userId, params?)` | List time entries |
86
+ | `getRunningTimer(wsId, userId)` | Get in-progress entry or null |
87
+ | `getTimeEntry(wsId, entryId)` | Get single entry |
88
+ | `updateTimeEntry(wsId, entryId, params)` | Update entry |
89
+ | `deleteTimeEntry(wsId, entryId)` | Delete entry |
90
+ | `getTags(wsId)` | List tags |
91
+ | `createTag(wsId, name)` | Create tag |
92
+ | `findOrCreateTag(wsId, name)` | Find or create tag |
93
+
94
+ ## Errors
95
+
96
+ `FetchClientError` — tagged error with `status` and `message`:
97
+
98
+ ```typescript
99
+ import { FetchClientError } from "@knpkv/clockify-api-client"
100
+
101
+ program.pipe(Effect.catchTag("FetchClientError", (e) => Console.log(`HTTP ${e.status}: ${e.message}`)))
102
+ ```
103
+
104
+ ## Notes
105
+
106
+ - The OpenAPI spec is patched for accurate types (the official spec has gaps).
107
+ - API key is stored as `Redacted<string>` to prevent accidental logging.
108
+
109
+ ## Subpath Exports
110
+
111
+ | Export | Contents |
112
+ | ---------------------------------------------- | ------------------------------- |
113
+ | `@knpkv/clockify-api-client` | Client, config, toEffect, types |
114
+ | `@knpkv/clockify-api-client/generated` | Generated OpenAPI types |
115
+ | `@knpkv/clockify-api-client/ClockifyApiClient` | Client service only |
116
+ | `@knpkv/clockify-api-client/ClockifyApiConfig` | Config service only |
117
+ | `@knpkv/clockify-api-client/ClockifyApiError` | Error types |
118
+
119
+ ## License
120
+
121
+ MIT
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Effect Layer wrapping openapi-fetch Clockify v1 client with auth and base URL.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **Auth via X-Api-Key**: The layer reads {@link ClockifyApiConfig} to build the
7
+ * `X-Api-Key` header and derive the base URL.
8
+ * - **openapi-fetch wrapper**: Uses {@link OpenApiFetchClient} for type-safe HTTP calls.
9
+ * - **Method-based interface**: Exposes convenience methods (getUser, createTimeEntry, etc.)
10
+ * for backwards compatibility with jira-clockify consumers.
11
+ * - **Raw API access**: `.api` exposes the raw `OpenApiFetchClient<paths>` for direct access.
12
+ *
13
+ * **Common tasks**
14
+ *
15
+ * - Use the client: `const clockify = yield* ClockifyApiClient`
16
+ * - Call a method: `clockify.getProjects(workspaceId)`
17
+ * - Raw access: `toEffect(clockify.api.client.GET("/v1/user"))`
18
+ * - Provide the layer: `Effect.provide(ClockifyApiClient.layer)`
19
+ *
20
+ * @module
21
+ */
22
+ import * as Context from "effect/Context";
23
+ import * as Effect from "effect/Effect";
24
+ import * as Layer from "effect/Layer";
25
+ import { ClockifyApiConfig } from "./ClockifyApiConfig.js";
26
+ import type { components, paths } from "./generated/schema.js";
27
+ import { type FetchClientError, type OpenApiFetchClient } from "./OpenApiFetchClient.js";
28
+ export type User = components["schemas"]["UserDto"];
29
+ export type Workspace = components["schemas"]["WorkspaceDtoV1"];
30
+ export type Project = components["schemas"]["ProjectDtoV1"];
31
+ export type Tag = components["schemas"]["TagDto"];
32
+ export type TimeInterval = components["schemas"]["TimeIntervalDto"];
33
+ export type TimeEntry = components["schemas"]["TimeEntryDto"];
34
+ export type CreateTimeEntryParams = components["schemas"]["CreateTimeEntryRequest"];
35
+ export type StopTimeEntryParams = components["schemas"]["StopTimeEntryRequest"];
36
+ export type UpdateTimeEntryParams = components["schemas"]["UpdateTimeEntryRequest"];
37
+ export interface GetTimeEntriesParams {
38
+ readonly start?: string | undefined;
39
+ readonly end?: string | undefined;
40
+ readonly page?: number | undefined;
41
+ readonly pageSize?: number | undefined;
42
+ }
43
+ export interface ClockifyApiClientShape {
44
+ /** Raw openapi-fetch client for direct type-safe API access. */
45
+ readonly api: OpenApiFetchClient<paths>;
46
+ readonly getUser: () => Effect.Effect<User, FetchClientError>;
47
+ readonly getWorkspaces: () => Effect.Effect<ReadonlyArray<Workspace>, FetchClientError>;
48
+ readonly getProjects: (workspaceId: string) => Effect.Effect<ReadonlyArray<Project>, FetchClientError>;
49
+ readonly getProjectByName: (workspaceId: string, name: string) => Effect.Effect<Project | null, FetchClientError>;
50
+ readonly createTimeEntry: (workspaceId: string, params: CreateTimeEntryParams) => Effect.Effect<TimeEntry, FetchClientError>;
51
+ readonly stopTimer: (workspaceId: string, userId: string, params: StopTimeEntryParams) => Effect.Effect<TimeEntry, FetchClientError>;
52
+ readonly getTimeEntries: (workspaceId: string, userId: string, params?: GetTimeEntriesParams) => Effect.Effect<ReadonlyArray<TimeEntry>, FetchClientError>;
53
+ readonly getRunningTimer: (workspaceId: string, userId: string) => Effect.Effect<TimeEntry | null, FetchClientError>;
54
+ readonly getTags: (workspaceId: string) => Effect.Effect<ReadonlyArray<Tag>, FetchClientError>;
55
+ readonly createTag: (workspaceId: string, name: string) => Effect.Effect<Tag, FetchClientError>;
56
+ readonly findOrCreateTag: (workspaceId: string, name: string) => Effect.Effect<Tag, FetchClientError>;
57
+ readonly getTimeEntry: (workspaceId: string, timeEntryId: string) => Effect.Effect<TimeEntry, FetchClientError>;
58
+ readonly deleteTimeEntry: (workspaceId: string, timeEntryId: string) => Effect.Effect<void, FetchClientError>;
59
+ readonly updateTimeEntry: (workspaceId: string, timeEntryId: string, params: UpdateTimeEntryParams) => Effect.Effect<TimeEntry, FetchClientError>;
60
+ }
61
+ declare const ClockifyApiClient_base: Context.TagClass<ClockifyApiClient, "@knpkv/clockify-api-client/ClockifyApiClient", ClockifyApiClientShape>;
62
+ export declare class ClockifyApiClient extends ClockifyApiClient_base {
63
+ static readonly layer: Layer.Layer<ClockifyApiClient, never, ClockifyApiConfig>;
64
+ }
65
+ export declare const layer: Layer.Layer<ClockifyApiClient, never, ClockifyApiConfig>;
66
+ export {};
67
+ //# sourceMappingURL=ClockifyApiClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClockifyApiClient.d.ts","sourceRoot":"","sources":["../src/ClockifyApiClient.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Effect Layer wrapping openapi-fetch Clockify v1 client with auth and base URL.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **Auth via X-Api-Key**: The layer reads {@link ClockifyApiConfig} to build the
7
+ * `X-Api-Key` header and derive the base URL.
8
+ * - **openapi-fetch wrapper**: Uses {@link OpenApiFetchClient} for type-safe HTTP calls.
9
+ * - **Method-based interface**: Exposes convenience methods (getUser, createTimeEntry, etc.)
10
+ * for backwards compatibility with jira-clockify consumers.
11
+ * - **Raw API access**: `.api` exposes the raw `OpenApiFetchClient<paths>` for direct access.
12
+ *
13
+ * **Common tasks**
14
+ *
15
+ * - Use the client: `const clockify = yield* ClockifyApiClient`
16
+ * - Call a method: `clockify.getProjects(workspaceId)`
17
+ * - Raw access: `toEffect(clockify.api.client.GET("/v1/user"))`
18
+ * - Provide the layer: `Effect.provide(ClockifyApiClient.layer)`
19
+ *
20
+ * @module
21
+ */
22
+ import * as Context from "effect/Context";
23
+ import * as Effect from "effect/Effect";
24
+ import * as Layer from "effect/Layer";
25
+ import * as Redacted from "effect/Redacted";
26
+ import { ClockifyApiConfig } from "./ClockifyApiConfig.js";
27
+ import { makeOpenApiFetchClient, toEffect } from "./OpenApiFetchClient.js";
28
+ // ---------------------------------------------------------------------------
29
+ // Service tag + layer
30
+ // ---------------------------------------------------------------------------
31
+ export class ClockifyApiClient extends Context.Tag("@knpkv/clockify-api-client/ClockifyApiClient")() {
32
+ static layer = Layer.effect(ClockifyApiClient, Effect.gen(function* () {
33
+ const config = yield* ClockifyApiConfig;
34
+ const headers = {
35
+ "X-Api-Key": Redacted.value(config.apiKey),
36
+ "Content-Type": "application/json"
37
+ };
38
+ const api = makeOpenApiFetchClient(config.baseUrl, headers);
39
+ const { client } = api;
40
+ return {
41
+ api,
42
+ getUser: () => toEffect(client.GET("/v1/user")),
43
+ getWorkspaces: () => toEffect(client.GET("/v1/workspaces")),
44
+ getProjects: (workspaceId) => toEffect(client.GET("/v1/workspaces/{workspaceId}/projects", {
45
+ params: {
46
+ path: { workspaceId },
47
+ query: { archived: false, "page-size": 500 }
48
+ }
49
+ })),
50
+ getProjectByName: (workspaceId, name) => Effect.gen(function* () {
51
+ const projects = yield* toEffect(client.GET("/v1/workspaces/{workspaceId}/projects", {
52
+ params: {
53
+ path: { workspaceId },
54
+ query: { name, archived: false }
55
+ }
56
+ }));
57
+ return projects.find((p) => p.name.toLowerCase() === name.toLowerCase()) ?? null;
58
+ }),
59
+ createTimeEntry: (workspaceId, params) => toEffect(client.POST("/v1/workspaces/{workspaceId}/time-entries", {
60
+ params: { path: { workspaceId } },
61
+ body: {
62
+ description: params.description,
63
+ start: params.start,
64
+ ...(params.end ? { end: params.end } : {}),
65
+ ...(params.projectId ? { projectId: params.projectId } : {}),
66
+ ...(params.taskId ? { taskId: params.taskId } : {}),
67
+ ...(params.billable !== undefined ? { billable: params.billable } : {}),
68
+ ...(params.tagIds !== undefined ? { tagIds: [...params.tagIds] } : {})
69
+ }
70
+ })),
71
+ stopTimer: (workspaceId, userId, params) => toEffect(client.PATCH("/v1/workspaces/{workspaceId}/user/{userId}/time-entries", {
72
+ params: { path: { workspaceId, userId } },
73
+ body: { end: params.end }
74
+ })),
75
+ getTimeEntries: (workspaceId, userId, params) => toEffect(client.GET("/v1/workspaces/{workspaceId}/user/{userId}/time-entries", {
76
+ params: {
77
+ path: { workspaceId, userId },
78
+ query: {
79
+ ...(params?.start !== undefined ? { start: params.start } : {}),
80
+ ...(params?.end !== undefined ? { end: params.end } : {}),
81
+ ...(params?.page !== undefined ? { page: params.page } : {}),
82
+ ...(params?.pageSize !== undefined ? { "page-size": params.pageSize } : {})
83
+ }
84
+ }
85
+ })),
86
+ getRunningTimer: (workspaceId, userId) => Effect.gen(function* () {
87
+ const entries = yield* toEffect(client.GET("/v1/workspaces/{workspaceId}/user/{userId}/time-entries", {
88
+ params: {
89
+ path: { workspaceId, userId },
90
+ query: { "in-progress": true, "page-size": 1 }
91
+ }
92
+ }));
93
+ return entries.length > 0 ? entries[0] : null;
94
+ }),
95
+ getTimeEntry: (workspaceId, timeEntryId) => toEffect(client.GET("/v1/workspaces/{workspaceId}/time-entries/{id}", {
96
+ params: { path: { workspaceId, id: timeEntryId } }
97
+ })),
98
+ updateTimeEntry: (workspaceId, timeEntryId, params) => toEffect(client.PUT("/v1/workspaces/{workspaceId}/time-entries/{id}", {
99
+ params: { path: { workspaceId, id: timeEntryId } },
100
+ body: {
101
+ start: params.start,
102
+ ...(params.end !== undefined ? { end: params.end } : {}),
103
+ ...(params.description !== undefined ? { description: params.description } : {}),
104
+ ...(params.projectId !== undefined ? { projectId: params.projectId } : {}),
105
+ ...(params.billable !== undefined ? { billable: params.billable } : {}),
106
+ ...(params.tagIds !== undefined ? { tagIds: [...params.tagIds] } : {})
107
+ }
108
+ })),
109
+ getTags: (workspaceId) => toEffect(client.GET("/v1/workspaces/{workspaceId}/tags", {
110
+ params: {
111
+ path: { workspaceId },
112
+ query: { archived: false, "page-size": 200 }
113
+ }
114
+ })),
115
+ createTag: (workspaceId, name) => toEffect(client.POST("/v1/workspaces/{workspaceId}/tags", {
116
+ params: { path: { workspaceId } },
117
+ body: { name }
118
+ })),
119
+ findOrCreateTag: (workspaceId, name) => Effect.gen(function* () {
120
+ const tags = yield* toEffect(client.GET("/v1/workspaces/{workspaceId}/tags", {
121
+ params: {
122
+ path: { workspaceId },
123
+ query: { name, archived: false }
124
+ }
125
+ }));
126
+ const existing = tags.find((t) => t.name.toLowerCase() === name.toLowerCase());
127
+ if (existing)
128
+ return existing;
129
+ return yield* toEffect(client.POST("/v1/workspaces/{workspaceId}/tags", {
130
+ params: { path: { workspaceId } },
131
+ body: { name }
132
+ }));
133
+ }),
134
+ deleteTimeEntry: (workspaceId, timeEntryId) => toEffect(client.DELETE("/v1/workspaces/{workspaceId}/time-entries/{id}", {
135
+ params: { path: { workspaceId, id: timeEntryId } }
136
+ }))
137
+ };
138
+ }));
139
+ }
140
+ export const layer = ClockifyApiClient.layer;
141
+ //# sourceMappingURL=ClockifyApiClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClockifyApiClient.js","sourceRoot":"","sources":["../src/ClockifyApiClient.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Configuration service tag for the Clockify API client.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **Context.Tag for DI**: {@link ClockifyApiConfig} carries API key (as `Redacted`),
7
+ * workspace ID, user ID, and base URL. Provided via `Layer.succeed` by the consumer.
8
+ * - **Redacted API key**: The `apiKey` field uses Effect's `Redacted` type to prevent
9
+ * accidental logging of credentials.
10
+ *
11
+ * @module
12
+ */
13
+ import * as Context from "effect/Context";
14
+ import type * as Redacted from "effect/Redacted";
15
+ export interface ClockifyApiConfigShape {
16
+ readonly apiKey: Redacted.Redacted<string>;
17
+ readonly workspaceId: string;
18
+ readonly userId: string;
19
+ readonly baseUrl: string;
20
+ }
21
+ declare const ClockifyApiConfig_base: Context.TagClass<ClockifyApiConfig, "@knpkv/clockify-api-client/ClockifyApiConfig", ClockifyApiConfigShape>;
22
+ export declare class ClockifyApiConfig extends ClockifyApiConfig_base {
23
+ }
24
+ export {};
25
+ //# sourceMappingURL=ClockifyApiConfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClockifyApiConfig.d.ts","sourceRoot":"","sources":["../src/ClockifyApiConfig.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAA;AACzC,OAAO,KAAK,KAAK,QAAQ,MAAM,iBAAiB,CAAA;AAEhD,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;IAC1C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB;;AAED,qBAAa,iBAAkB,SAAQ,sBAEO;CAAG"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Configuration service tag for the Clockify API client.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **Context.Tag for DI**: {@link ClockifyApiConfig} carries API key (as `Redacted`),
7
+ * workspace ID, user ID, and base URL. Provided via `Layer.succeed` by the consumer.
8
+ * - **Redacted API key**: The `apiKey` field uses Effect's `Redacted` type to prevent
9
+ * accidental logging of credentials.
10
+ *
11
+ * @module
12
+ */
13
+ import * as Context from "effect/Context";
14
+ export class ClockifyApiConfig extends Context.Tag("@knpkv/clockify-api-client/ClockifyApiConfig")() {
15
+ }
16
+ //# sourceMappingURL=ClockifyApiConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClockifyApiConfig.js","sourceRoot":"","sources":["../src/ClockifyApiConfig.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAA;AAUzC,MAAM,OAAO,iBAAkB,SAAQ,OAAO,CAAC,GAAG,CAChD,8CAA8C,CAC/C,EAA6C;CAAG"}
@@ -0,0 +1,11 @@
1
+ declare const ClockifyApiError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
2
+ readonly _tag: "ClockifyApiError";
3
+ } & Readonly<A>;
4
+ export declare class ClockifyApiError extends ClockifyApiError_base<{
5
+ readonly status: number;
6
+ readonly message: string;
7
+ readonly cause?: unknown;
8
+ }> {
9
+ }
10
+ export {};
11
+ //# sourceMappingURL=ClockifyApiError.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClockifyApiError.d.ts","sourceRoot":"","sources":["../src/ClockifyApiError.ts"],"names":[],"mappings":";;;AAYA,qBAAa,gBAAiB,SAAQ,sBAAqC;IACzE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CACzB,CAAC;CAAG"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Tagged error for Clockify API failures.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **Catch by tag**: `Effect.catchTag("ClockifyApiError", ...)` handles all API errors.
7
+ * The `status` field distinguishes HTTP errors (>0) from network errors (0).
8
+ *
9
+ * @module
10
+ */
11
+ import * as Data from "effect/Data";
12
+ export class ClockifyApiError extends Data.TaggedError("ClockifyApiError") {
13
+ }
14
+ //# sourceMappingURL=ClockifyApiError.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClockifyApiError.js","sourceRoot":"","sources":["../src/ClockifyApiError.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,IAAI,MAAM,aAAa,CAAA;AAEnC,MAAM,OAAO,gBAAiB,SAAQ,IAAI,CAAC,WAAW,CAAC,kBAAkB,CAIvE;CAAG"}
@@ -0,0 +1,55 @@
1
+ import * as Effect from "effect/Effect";
2
+ import { type Client, type FetchResponse } from "openapi-fetch";
3
+ import type { MediaType } from "openapi-typescript-helpers";
4
+ declare const FetchClientError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
5
+ readonly _tag: "FetchClientError";
6
+ } & Readonly<A>;
7
+ /**
8
+ * Error from openapi-fetch operations.
9
+ *
10
+ * @category Errors
11
+ */
12
+ export declare class FetchClientError extends FetchClientError_base<{
13
+ readonly error: unknown;
14
+ readonly status: number;
15
+ readonly message: string;
16
+ }> {
17
+ }
18
+ /** Extract success `data` from a FetchResponse discriminated union. */
19
+ export type SuccessData<T> = T extends {
20
+ data: infer D;
21
+ error?: undefined;
22
+ } ? D : T extends {
23
+ data?: infer D;
24
+ } ? NonNullable<D> : never;
25
+ /**
26
+ * Wrap an openapi-fetch `Promise<FetchResponse>` in Effect.
27
+ *
28
+ * Extracts `data` on success, maps errors to `FetchClientError`.
29
+ * Fully type-safe — path/body constraints come from the `Client<Paths>` call site.
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * const page = yield* toEffect(client.GET("/pages/{id}", { params: { path: { id: 123 } } }))
34
+ * ```
35
+ *
36
+ * @category Utilities
37
+ */
38
+ export declare const toEffect: <T extends Record<string | number, any>, O, M extends MediaType>(promise: Promise<FetchResponse<T, O, M>>) => Effect.Effect<SuccessData<FetchResponse<T, O, M>>, FetchClientError>;
39
+ /**
40
+ * openapi-fetch client paired with the `toEffect` helper.
41
+ *
42
+ * @category Client
43
+ */
44
+ export interface OpenApiFetchClient<Paths extends {}> {
45
+ /** Type-safe openapi-fetch client. Use with `toEffect()` to get Effect. */
46
+ readonly client: Client<Paths, "application/json">;
47
+ }
48
+ /**
49
+ * Create an openapi-fetch client with auth headers pre-configured.
50
+ *
51
+ * @category Constructors
52
+ */
53
+ export declare const makeOpenApiFetchClient: <Paths extends {}>(baseUrl: string, headers: Record<string, string>) => OpenApiFetchClient<Paths>;
54
+ export {};
55
+ //# sourceMappingURL=OpenApiFetchClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OpenApiFetchClient.d.ts","sourceRoot":"","sources":["../src/OpenApiFetchClient.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAqB,EAAE,KAAK,MAAM,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAA;AAC7E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;;;;AAE3D;;;;GAIG;AACH,qBAAa,gBAAiB,SAAQ,sBAAqC;IACzE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB,CAAC;CAAG;AAEL,uEAAuE;AACvE,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAA;CAAE,GAAG,CAAC,GAC3E,CAAC,SAAS;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,WAAW,CAAC,CAAC,CAAC,GAC7C,KAAK,CAAA;AAET;;;;;;;;;;;;GAYG;AAEH,eAAO,MAAM,QAAQ,GAAI,CAAC,SAAS,MAAM,CAAC,MAAM,GAAG,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,SAAS,SAAS,EACrF,SAAS,OAAO,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,KACvC,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,gBAAgB,CAiBlE,CAAA;AAEJ;;;;GAIG;AACH,MAAM,WAAW,kBAAkB,CAAC,KAAK,SAAS,EAAE;IAClD,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,EAAE,kBAAkB,CAAC,CAAA;CACnD;AAED;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GAAI,KAAK,SAAS,EAAE,EACrD,SAAS,MAAM,EACf,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAC9B,kBAAkB,CAAC,KAAK,CAEzB,CAAA"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Generic Effect wrapper for openapi-fetch clients.
3
+ *
4
+ * Exposes a type-safe `Client<Paths>` and a `toEffect` helper that wraps
5
+ * any `Promise<FetchResponse>` in Effect with error mapping.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * const page = yield* toEffect(client.GET("/pages/{id}", {
10
+ * params: { path: { id: 123 } }
11
+ * }))
12
+ * ```
13
+ *
14
+ * @module
15
+ */
16
+ import * as Data from "effect/Data";
17
+ import * as Effect from "effect/Effect";
18
+ import createClient, {} from "openapi-fetch";
19
+ /**
20
+ * Error from openapi-fetch operations.
21
+ *
22
+ * @category Errors
23
+ */
24
+ export class FetchClientError extends Data.TaggedError("FetchClientError") {
25
+ }
26
+ /**
27
+ * Wrap an openapi-fetch `Promise<FetchResponse>` in Effect.
28
+ *
29
+ * Extracts `data` on success, maps errors to `FetchClientError`.
30
+ * Fully type-safe — path/body constraints come from the `Client<Paths>` call site.
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * const page = yield* toEffect(client.GET("/pages/{id}", { params: { path: { id: 123 } } }))
35
+ * ```
36
+ *
37
+ * @category Utilities
38
+ */
39
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- matches FetchResponse generic constraint
40
+ export const toEffect = (promise) => Effect.tryPromise({
41
+ try: () => promise.then(({ data, error, response }) => {
42
+ if (error !== undefined || !response.ok) {
43
+ throw { error, status: response.status };
44
+ }
45
+ return data;
46
+ }),
47
+ catch: (e) => new FetchClientError({
48
+ error: e.error ?? e,
49
+ status: e.status ?? 0,
50
+ message: typeof e.error === "string"
51
+ ? e.error
52
+ : JSON.stringify(e.error ?? e)
53
+ })
54
+ });
55
+ /**
56
+ * Create an openapi-fetch client with auth headers pre-configured.
57
+ *
58
+ * @category Constructors
59
+ */
60
+ export const makeOpenApiFetchClient = (baseUrl, headers) => ({
61
+ client: createClient({ baseUrl, headers })
62
+ });
63
+ //# sourceMappingURL=OpenApiFetchClient.js.map