@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.
- package/.specs/VERSION +1 -0
- package/.specs/clockify-v1.json +31670 -0
- package/.specs/clockify-v1.patch.json +48 -0
- package/CHANGELOG.md +11 -0
- package/LICENSE +21 -0
- package/README.md +121 -0
- package/dist/ClockifyApiClient.d.ts +67 -0
- package/dist/ClockifyApiClient.d.ts.map +1 -0
- package/dist/ClockifyApiClient.js +141 -0
- package/dist/ClockifyApiClient.js.map +1 -0
- package/dist/ClockifyApiConfig.d.ts +25 -0
- package/dist/ClockifyApiConfig.d.ts.map +1 -0
- package/dist/ClockifyApiConfig.js +16 -0
- package/dist/ClockifyApiConfig.js.map +1 -0
- package/dist/ClockifyApiError.d.ts +11 -0
- package/dist/ClockifyApiError.d.ts.map +1 -0
- package/dist/ClockifyApiError.js +14 -0
- package/dist/ClockifyApiError.js.map +1 -0
- package/dist/OpenApiFetchClient.d.ts +55 -0
- package/dist/OpenApiFetchClient.d.ts.map +1 -0
- package/dist/OpenApiFetchClient.js +63 -0
- package/dist/OpenApiFetchClient.js.map +1 -0
- package/dist/generated/index.d.ts +2 -0
- package/dist/generated/index.d.ts.map +1 -0
- package/dist/generated/index.js +2 -0
- package/dist/generated/index.js.map +1 -0
- package/dist/generated/schema.d.ts +16204 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/package.json +73 -0
- package/scripts/regenerate.ts +138 -0
- package/src/ClockifyApiClient.ts +278 -0
- package/src/ClockifyApiConfig.ts +25 -0
- package/src/ClockifyApiError.ts +17 -0
- package/src/OpenApiFetchClient.ts +92 -0
- package/src/generated/index.ts +1 -0
- package/src/generated/schema.d.ts +16204 -0
- package/src/index.ts +35 -0
- package/tsconfig.json +11 -0
- 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
|