@knpkv/atlassian-common 0.2.0 → 0.3.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 (57) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/Brand.d.ts +15 -15
  3. package/dist/Brand.d.ts.map +1 -1
  4. package/dist/Brand.js +5 -5
  5. package/dist/Brand.js.map +1 -1
  6. package/dist/Hash.d.ts +12 -14
  7. package/dist/Hash.d.ts.map +1 -1
  8. package/dist/Hash.js +18 -24
  9. package/dist/Hash.js.map +1 -1
  10. package/dist/ast/BlockNode.d.ts +120 -177
  11. package/dist/ast/BlockNode.d.ts.map +1 -1
  12. package/dist/ast/BlockNode.js +28 -8
  13. package/dist/ast/BlockNode.js.map +1 -1
  14. package/dist/ast/Document.d.ts +114 -156
  15. package/dist/ast/Document.d.ts.map +1 -1
  16. package/dist/ast/Document.js +1 -1
  17. package/dist/ast/Document.js.map +1 -1
  18. package/dist/ast/InlineNode.d.ts +59 -247
  19. package/dist/ast/InlineNode.d.ts.map +1 -1
  20. package/dist/ast/InlineNode.js +28 -3
  21. package/dist/ast/InlineNode.js.map +1 -1
  22. package/dist/ast/MacroNode.d.ts +62 -102
  23. package/dist/ast/MacroNode.d.ts.map +1 -1
  24. package/dist/ast/MacroNode.js +12 -5
  25. package/dist/ast/MacroNode.js.map +1 -1
  26. package/dist/auth/OAuthOperations.d.ts +1 -1
  27. package/dist/auth/OAuthOperations.d.ts.map +1 -1
  28. package/dist/auth/OAuthOperations.js +5 -6
  29. package/dist/auth/OAuthOperations.js.map +1 -1
  30. package/dist/auth/OAuthResponseSchemas.d.ts +13 -13
  31. package/dist/config/ConfigPaths.d.ts +6 -26
  32. package/dist/config/ConfigPaths.d.ts.map +1 -1
  33. package/dist/config/ConfigPaths.js +23 -3
  34. package/dist/config/ConfigPaths.js.map +1 -1
  35. package/dist/config/OAuthSchemas.d.ts +24 -24
  36. package/dist/config/TokenStorage.d.ts +5 -24
  37. package/dist/config/TokenStorage.d.ts.map +1 -1
  38. package/dist/config/TokenStorage.js +35 -22
  39. package/dist/config/TokenStorage.js.map +1 -1
  40. package/dist/index.d.ts +1 -1
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +1 -1
  43. package/dist/index.js.map +1 -1
  44. package/package.json +7 -9
  45. package/src/Brand.ts +5 -17
  46. package/src/Hash.ts +27 -27
  47. package/src/ast/BlockNode.ts +13 -13
  48. package/src/ast/Document.ts +1 -1
  49. package/src/ast/InlineNode.ts +5 -5
  50. package/src/ast/MacroNode.ts +7 -6
  51. package/src/auth/OAuthOperations.ts +5 -6
  52. package/src/config/ConfigPaths.ts +7 -7
  53. package/src/config/TokenStorage.ts +23 -27
  54. package/src/index.ts +1 -1
  55. package/test/Hash.test.ts +52 -0
  56. package/test/OAuthOperations.test.ts +2 -3
  57. package/tsconfig.tsbuildinfo +1 -1
@@ -10,6 +10,7 @@
10
10
  *
11
11
  * @module
12
12
  */
13
+ import * as Effect from "effect/Effect"
13
14
  import * as Schema from "effect/Schema"
14
15
  import { RawSource, SchemaVersion, SimpleBlockNode } from "./BlockNode.js"
15
16
 
@@ -25,7 +26,7 @@ export const PanelTypes = ["info", "warning", "note", "tip", "error", "panel"] a
25
26
  *
26
27
  * @category MacroNode
27
28
  */
28
- export const PanelType = Schema.Literal(...PanelTypes)
29
+ export const PanelType = Schema.Literals(PanelTypes)
29
30
 
30
31
  /**
31
32
  * Type for panel type.
@@ -118,8 +119,8 @@ export const CodeMacro = Schema.Struct({
118
119
  language: Schema.optional(Schema.String),
119
120
  title: Schema.optional(Schema.String),
120
121
  code: Schema.String,
121
- lineNumbers: Schema.optionalWith(Schema.Boolean, { default: () => false }),
122
- collapse: Schema.optionalWith(Schema.Boolean, { default: () => false }),
122
+ lineNumbers: Schema.Boolean.pipe(Schema.withConstructorDefault(Effect.succeed(false))),
123
+ collapse: Schema.Boolean.pipe(Schema.withConstructorDefault(Effect.succeed(false))),
123
124
  firstLine: Schema.optional(Schema.Number),
124
125
  rawSource: RawSource
125
126
  })
@@ -140,7 +141,7 @@ export const StatusMacro = Schema.Struct({
140
141
  _tag: Schema.Literal("StatusMacro"),
141
142
  version: SchemaVersion,
142
143
  text: Schema.String,
143
- color: Schema.Literal("Grey", "Red", "Yellow", "Green", "Blue"),
144
+ color: Schema.Literals(["Grey", "Red", "Yellow", "Green", "Blue"]),
144
145
  rawSource: RawSource
145
146
  })
146
147
 
@@ -156,13 +157,13 @@ export type StatusMacro = Schema.Schema.Type<typeof StatusMacro>
156
157
  *
157
158
  * @category MacroNode
158
159
  */
159
- export const MacroNode = Schema.Union(
160
+ export const MacroNode = Schema.Union([
160
161
  InfoPanel,
161
162
  ExpandMacro,
162
163
  TocMacro,
163
164
  CodeMacro,
164
165
  StatusMacro
165
- )
166
+ ])
166
167
 
167
168
  /**
168
169
  * Type for macro nodes.
@@ -18,10 +18,9 @@
18
18
  *
19
19
  * @module
20
20
  */
21
- import * as HttpClient from "@effect/platform/HttpClient"
22
- import * as HttpClientRequest from "@effect/platform/HttpClientRequest"
23
21
  import * as Effect from "effect/Effect"
24
22
  import * as Schema from "effect/Schema"
23
+ import { HttpClient, HttpClientRequest } from "effect/unstable/http"
25
24
  import { type OAuthConfig, type OAuthToken } from "../config/OAuthSchemas.js"
26
25
  import { ME_URL, RESOURCES_URL, REVOKE_URL, TOKEN_URL } from "./OAuthEndpoints.js"
27
26
  import { OAuthError } from "./OAuthErrors.js"
@@ -83,7 +82,7 @@ export const exchangeCodeForTokens = (
83
82
 
84
83
  const body = yield* response.json
85
84
 
86
- return yield* Schema.decodeUnknown(TokenResponseSchema)(body)
85
+ return yield* Schema.decodeUnknownEffect(TokenResponseSchema)(body)
87
86
  }).pipe(
88
87
  Effect.mapError((cause) => new OAuthError({ step: "token", cause }))
89
88
  )
@@ -117,7 +116,7 @@ export const getAccessibleResources = (
117
116
 
118
117
  const body = yield* response.json
119
118
 
120
- return yield* Schema.decodeUnknown(Schema.Array(AccessibleResourceSchema))(body)
119
+ return yield* Schema.decodeUnknownEffect(Schema.Array(AccessibleResourceSchema))(body)
121
120
  }).pipe(
122
121
  Effect.mapError((cause) => new OAuthError({ step: "resources", cause }))
123
122
  )
@@ -151,7 +150,7 @@ export const getUserInfo = (
151
150
 
152
151
  const body = yield* response.json
153
152
 
154
- return yield* Schema.decodeUnknown(UserInfoSchema)(body)
153
+ return yield* Schema.decodeUnknownEffect(UserInfoSchema)(body)
155
154
  }).pipe(
156
155
  Effect.mapError((cause) => new OAuthError({ step: "user-info", cause }))
157
156
  )
@@ -197,7 +196,7 @@ export const refreshToken = (
197
196
  const body = yield* response.json.pipe(
198
197
  Effect.mapError((cause) => new OAuthError({ step: "refresh", cause }))
199
198
  )
200
- const tokenResponse = yield* Schema.decodeUnknown(TokenResponseSchema)(body).pipe(
199
+ const tokenResponse = yield* Schema.decodeUnknownEffect(TokenResponseSchema)(body).pipe(
201
200
  Effect.mapError((cause) => new OAuthError({ step: "refresh", cause }))
202
201
  )
203
202
 
@@ -18,15 +18,15 @@
18
18
  *
19
19
  * @module
20
20
  */
21
- import type * as Error from "@effect/platform/Error"
22
- import * as FileSystem from "@effect/platform/FileSystem"
23
- import * as Path from "@effect/platform/Path"
24
21
  import * as Config from "effect/Config"
25
22
  import * as Context from "effect/Context"
26
23
  import * as Data from "effect/Data"
27
24
  import * as Effect from "effect/Effect"
25
+ import * as FileSystem from "effect/FileSystem"
28
26
  import * as Layer from "effect/Layer"
29
27
  import * as Option from "effect/Option"
28
+ import * as Path from "effect/Path"
29
+ import type * as PlatformError from "effect/PlatformError"
30
30
 
31
31
  /**
32
32
  * Error when home directory cannot be determined.
@@ -56,10 +56,10 @@ export interface HomeDirectory {
56
56
  *
57
57
  * @category Services
58
58
  */
59
- export class HomeDirectoryTag extends Context.Tag("@knpkv/atlassian-common/HomeDirectory")<
59
+ export class HomeDirectoryTag extends Context.Service<
60
60
  HomeDirectoryTag,
61
61
  HomeDirectory
62
- >() {}
62
+ >()("@knpkv/atlassian-common/HomeDirectory") {}
63
63
 
64
64
  const HomeConfig = Config.option(Config.string("HOME")).pipe(
65
65
  Config.orElse(() => Config.option(Config.string("USERPROFILE")))
@@ -145,7 +145,7 @@ export const ensureConfigDir = (
145
145
  toolName: string
146
146
  ): Effect.Effect<
147
147
  string,
148
- HomeDirectoryError | Error.PlatformError,
148
+ HomeDirectoryError | PlatformError.PlatformError,
149
149
  FileSystem.FileSystem | Path.Path | HomeDirectoryTag
150
150
  > =>
151
151
  Effect.gen(function*() {
@@ -168,7 +168,7 @@ export const ensureConfigDir = (
168
168
  export const writeSecureFile = (
169
169
  filePath: string,
170
170
  content: string
171
- ): Effect.Effect<void, Error.PlatformError, FileSystem.FileSystem> =>
171
+ ): Effect.Effect<void, PlatformError.PlatformError, FileSystem.FileSystem> =>
172
172
  Effect.gen(function*() {
173
173
  const fs = yield* FileSystem.FileSystem
174
174
 
@@ -6,7 +6,7 @@
6
6
  * - **Load returns null, not error**: {@link loadToken} and {@link loadOAuthConfig} return
7
7
  * `null` for missing/corrupt files — callers decide whether absence is an error.
8
8
  * - **Save is atomic**: Writes go through {@link writeSecureFile} with `0o600` permissions.
9
- * - **Schema-gated reads**: JSON is parsed then validated via `Schema.decodeUnknown` —
9
+ * - **Schema-gated reads**: JSON is parsed then validated via `Schema.decodeUnknownEffect` —
10
10
  * corrupt data is treated as absent rather than crashing.
11
11
  *
12
12
  * **Common tasks**
@@ -17,16 +17,24 @@
17
17
  *
18
18
  * @module
19
19
  */
20
- import type * as Error from "@effect/platform/Error"
21
- import * as FileSystem from "@effect/platform/FileSystem"
22
- import type * as Path from "@effect/platform/Path"
23
20
  import * as Data from "effect/Data"
24
21
  import * as Effect from "effect/Effect"
22
+ import * as FileSystem from "effect/FileSystem"
23
+ import type * as Path from "effect/Path"
24
+ import type * as PlatformError from "effect/PlatformError"
25
25
  import * as Schema from "effect/Schema"
26
26
  import { type HomeDirectoryError, type HomeDirectoryTag } from "./ConfigPaths.js"
27
27
  import { ensureConfigDir, getAuthPath, getOAuthConfigPath, writeSecureFile } from "./ConfigPaths.js"
28
28
  import { type OAuthConfig, OAuthConfigSchema, type OAuthToken, OAuthTokenSchema } from "./OAuthSchemas.js"
29
29
 
30
+ const parseJsonOrNull = (content: string): unknown | null => {
31
+ try {
32
+ return JSON.parse(content) as unknown
33
+ } catch {
34
+ return null
35
+ }
36
+ }
37
+
30
38
  /**
31
39
  * Error during file system operations.
32
40
  *
@@ -62,7 +70,7 @@ export const loadToken = (
62
70
  const tokenPath = yield* getAuthPath(toolName)
63
71
 
64
72
  const exists = yield* fs.exists(tokenPath).pipe(
65
- Effect.catchAll(() => Effect.succeed(false))
73
+ Effect.catchCause(() => Effect.succeed(false))
66
74
  )
67
75
  if (!exists) {
68
76
  return null
@@ -72,20 +80,14 @@ export const loadToken = (
72
80
  Effect.mapError((cause) => new FileSystemError({ operation: "read", path: tokenPath, cause }))
73
81
  )
74
82
 
75
- const parsed = yield* Effect.sync(() => {
76
- try {
77
- return JSON.parse(content) as unknown
78
- } catch {
79
- return null
80
- }
81
- })
83
+ const parsed = yield* Effect.sync(() => parseJsonOrNull(content))
82
84
  if (parsed === null) {
83
85
  yield* Effect.logWarning(`Corrupted auth.json at ${tokenPath} — could not parse JSON`)
84
86
  return null
85
87
  }
86
88
 
87
- return yield* Schema.decodeUnknown(OAuthTokenSchema)(parsed).pipe(
88
- Effect.catchAll((e) =>
89
+ return yield* Schema.decodeUnknownEffect(OAuthTokenSchema)(parsed).pipe(
90
+ Effect.catchCause((e) =>
89
91
  Effect.logWarning(`Invalid token schema in ${tokenPath}: ${e}`).pipe(
90
92
  Effect.map(() => null)
91
93
  )
@@ -106,7 +108,7 @@ export const saveToken = (
106
108
  token: OAuthToken
107
109
  ): Effect.Effect<
108
110
  void,
109
- FileSystemError | HomeDirectoryError | Error.PlatformError,
111
+ FileSystemError | HomeDirectoryError | PlatformError.PlatformError,
110
112
  FileSystem.FileSystem | Path.Path | HomeDirectoryTag
111
113
  > =>
112
114
  Effect.gen(function*() {
@@ -134,7 +136,7 @@ export const deleteToken = (
134
136
  const tokenPath = yield* getAuthPath(toolName)
135
137
 
136
138
  yield* fs.remove(tokenPath).pipe(
137
- Effect.catchAll(() => Effect.void)
139
+ Effect.catchCause(() => Effect.void)
138
140
  )
139
141
  })
140
142
 
@@ -158,7 +160,7 @@ export const loadOAuthConfig = (
158
160
  const configPath = yield* getOAuthConfigPath(toolName)
159
161
 
160
162
  const exists = yield* fs.exists(configPath).pipe(
161
- Effect.catchAll(() => Effect.succeed(false))
163
+ Effect.catchCause(() => Effect.succeed(false))
162
164
  )
163
165
  if (!exists) {
164
166
  return null
@@ -168,20 +170,14 @@ export const loadOAuthConfig = (
168
170
  Effect.mapError((cause) => new FileSystemError({ operation: "read", path: configPath, cause }))
169
171
  )
170
172
 
171
- const parsed = yield* Effect.sync(() => {
172
- try {
173
- return JSON.parse(content) as unknown
174
- } catch {
175
- return null
176
- }
177
- })
173
+ const parsed = yield* Effect.sync(() => parseJsonOrNull(content))
178
174
  if (parsed === null) {
179
175
  yield* Effect.logWarning(`Corrupted oauth config at ${configPath} — could not parse JSON`)
180
176
  return null
181
177
  }
182
178
 
183
- return yield* Schema.decodeUnknown(OAuthConfigSchema)(parsed).pipe(
184
- Effect.catchAll((e) =>
179
+ return yield* Schema.decodeUnknownEffect(OAuthConfigSchema)(parsed).pipe(
180
+ Effect.catchCause((e) =>
185
181
  Effect.logWarning(`Invalid OAuth config schema in ${configPath}: ${e}`).pipe(
186
182
  Effect.map(() => null)
187
183
  )
@@ -202,7 +198,7 @@ export const saveOAuthConfig = (
202
198
  config: OAuthConfig
203
199
  ): Effect.Effect<
204
200
  void,
205
- FileSystemError | HomeDirectoryError | Error.PlatformError,
201
+ FileSystemError | HomeDirectoryError | PlatformError.PlatformError,
206
202
  FileSystem.FileSystem | Path.Path | HomeDirectoryTag
207
203
  > =>
208
204
  Effect.gen(function*() {
package/src/index.ts CHANGED
@@ -19,7 +19,7 @@ export {
19
19
  } from "./Brand.js"
20
20
 
21
21
  // Hash utilities
22
- export { hashBuffer, hashContent, hashContentSync, hashEquals } from "./Hash.js"
22
+ export { hashBuffer, hashContent, hashEquals } from "./Hash.js"
23
23
 
24
24
  // Error types
25
25
  export { ParseError, SerializeError } from "./SerializeError.js"
@@ -0,0 +1,52 @@
1
+ import { it as effectIt } from "@effect/vitest"
2
+ import * as Crypto from "effect/Crypto"
3
+ import * as Effect from "effect/Effect"
4
+ import { describe, expect, it } from "vitest"
5
+ import { ContentHash } from "../src/Brand.js"
6
+ import { hashBuffer, hashContent, hashEquals } from "../src/Hash.js"
7
+
8
+ const byteSequenceDigest = Uint8Array.from({ length: 32 }, (_, index) => index)
9
+ const byteSequenceHash = ContentHash("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f")
10
+
11
+ describe("Hash", () => {
12
+ effectIt.effect("delegates SHA-256 hashing to the provided Crypto service", () =>
13
+ Effect.gen(function*() {
14
+ const hash = yield* hashContent("hello")
15
+ expect(hash).toBe(byteSequenceHash)
16
+ }).pipe(
17
+ Effect.provideService(
18
+ Crypto.Crypto,
19
+ Crypto.make({
20
+ randomBytes: (size) => new Uint8Array(size),
21
+ digest: (algorithm, data) => {
22
+ expect(algorithm).toBe("SHA-256")
23
+ expect(Array.from(data)).toEqual([104, 101, 108, 108, 111])
24
+ return Effect.succeed(byteSequenceDigest)
25
+ }
26
+ })
27
+ )
28
+ ))
29
+
30
+ effectIt.effect("hashes binary content through the provided Crypto service", () =>
31
+ Effect.gen(function*() {
32
+ const hash = yield* hashBuffer(Uint8Array.of(1, 2, 3))
33
+ expect(hash).toBe(byteSequenceHash)
34
+ }).pipe(
35
+ Effect.provideService(
36
+ Crypto.Crypto,
37
+ Crypto.make({
38
+ randomBytes: (size) => new Uint8Array(size),
39
+ digest: (algorithm, data) => {
40
+ expect(algorithm).toBe("SHA-256")
41
+ expect(Array.from(data)).toEqual([1, 2, 3])
42
+ return Effect.succeed(byteSequenceDigest)
43
+ }
44
+ })
45
+ )
46
+ ))
47
+
48
+ it("compares content hashes by value", () => {
49
+ expect(hashEquals(byteSequenceHash, byteSequenceHash)).toBe(true)
50
+ expect(hashEquals(byteSequenceHash, ContentHash("f".repeat(64)))).toBe(false)
51
+ })
52
+ })
@@ -1,9 +1,8 @@
1
- import * as HttpClient from "@effect/platform/HttpClient"
2
- import type * as HttpClientRequest from "@effect/platform/HttpClientRequest"
3
- import * as HttpClientResponse from "@effect/platform/HttpClientResponse"
4
1
  import { describe, it } from "@effect/vitest"
5
2
  import * as Effect from "effect/Effect"
6
3
  import * as Layer from "effect/Layer"
4
+ import type { HttpClientRequest } from "effect/unstable/http"
5
+ import { HttpClient, HttpClientResponse } from "effect/unstable/http"
7
6
  import { expect } from "vitest"
8
7
  import { OAuthError } from "../src/auth/OAuthErrors.js"
9
8
  import {