@knpkv/atlassian-common 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 (121) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +107 -0
  4. package/dist/Brand.d.ts +118 -0
  5. package/dist/Brand.d.ts.map +1 -0
  6. package/dist/Brand.js +107 -0
  7. package/dist/Brand.js.map +1 -0
  8. package/dist/Hash.d.ts +53 -0
  9. package/dist/Hash.d.ts.map +1 -0
  10. package/dist/Hash.js +63 -0
  11. package/dist/Hash.js.map +1 -0
  12. package/dist/SerializeError.d.ts +48 -0
  13. package/dist/SerializeError.d.ts.map +1 -0
  14. package/dist/SerializeError.js +42 -0
  15. package/dist/SerializeError.js.map +1 -0
  16. package/dist/ast/BlockNode.d.ts +429 -0
  17. package/dist/ast/BlockNode.d.ts.map +1 -0
  18. package/dist/ast/BlockNode.js +279 -0
  19. package/dist/ast/BlockNode.js.map +1 -0
  20. package/dist/ast/Document.d.ts +255 -0
  21. package/dist/ast/Document.d.ts.map +1 -0
  22. package/dist/ast/Document.js +80 -0
  23. package/dist/ast/Document.js.map +1 -0
  24. package/dist/ast/InlineNode.d.ts +483 -0
  25. package/dist/ast/InlineNode.d.ts.map +1 -0
  26. package/dist/ast/InlineNode.js +268 -0
  27. package/dist/ast/InlineNode.js.map +1 -0
  28. package/dist/ast/MacroNode.d.ts +214 -0
  29. package/dist/ast/MacroNode.d.ts.map +1 -0
  30. package/dist/ast/MacroNode.js +110 -0
  31. package/dist/ast/MacroNode.js.map +1 -0
  32. package/dist/ast/index.d.ts +10 -0
  33. package/dist/ast/index.d.ts.map +1 -0
  34. package/dist/ast/index.js +14 -0
  35. package/dist/ast/index.js.map +1 -0
  36. package/dist/auth/OAuthEndpoints.d.ts +95 -0
  37. package/dist/auth/OAuthEndpoints.d.ts.map +1 -0
  38. package/dist/auth/OAuthEndpoints.js +112 -0
  39. package/dist/auth/OAuthEndpoints.js.map +1 -0
  40. package/dist/auth/OAuthErrors.d.ts +76 -0
  41. package/dist/auth/OAuthErrors.d.ts.map +1 -0
  42. package/dist/auth/OAuthErrors.js +85 -0
  43. package/dist/auth/OAuthErrors.js.map +1 -0
  44. package/dist/auth/OAuthOperations.d.ts +87 -0
  45. package/dist/auth/OAuthOperations.d.ts.map +1 -0
  46. package/dist/auth/OAuthOperations.js +169 -0
  47. package/dist/auth/OAuthOperations.js.map +1 -0
  48. package/dist/auth/OAuthResponseSchemas.d.ts +65 -0
  49. package/dist/auth/OAuthResponseSchemas.d.ts.map +1 -0
  50. package/dist/auth/OAuthResponseSchemas.js +47 -0
  51. package/dist/auth/OAuthResponseSchemas.js.map +1 -0
  52. package/dist/auth/index.d.ts +11 -0
  53. package/dist/auth/index.d.ts.map +1 -0
  54. package/dist/auth/index.js +16 -0
  55. package/dist/auth/index.js.map +1 -0
  56. package/dist/auth/uuid.d.ts +14 -0
  57. package/dist/auth/uuid.d.ts.map +1 -0
  58. package/dist/auth/uuid.js +14 -0
  59. package/dist/auth/uuid.js.map +1 -0
  60. package/dist/config/ConfigPaths.d.ts +95 -0
  61. package/dist/config/ConfigPaths.d.ts.map +1 -0
  62. package/dist/config/ConfigPaths.js +102 -0
  63. package/dist/config/ConfigPaths.js.map +1 -0
  64. package/dist/config/OAuthSchemas.d.ts +144 -0
  65. package/dist/config/OAuthSchemas.d.ts.map +1 -0
  66. package/dist/config/OAuthSchemas.js +107 -0
  67. package/dist/config/OAuthSchemas.js.map +1 -0
  68. package/dist/config/TokenStorage.d.ts +96 -0
  69. package/dist/config/TokenStorage.d.ts.map +1 -0
  70. package/dist/config/TokenStorage.js +128 -0
  71. package/dist/config/TokenStorage.js.map +1 -0
  72. package/dist/config/index.d.ts +9 -0
  73. package/dist/config/index.d.ts.map +1 -0
  74. package/dist/config/index.js +12 -0
  75. package/dist/config/index.js.map +1 -0
  76. package/dist/index.d.ts +13 -0
  77. package/dist/index.d.ts.map +1 -0
  78. package/dist/index.js +20 -0
  79. package/dist/index.js.map +1 -0
  80. package/dist/parsers/index.d.ts +7 -0
  81. package/dist/parsers/index.d.ts.map +1 -0
  82. package/dist/parsers/index.js +8 -0
  83. package/dist/parsers/index.js.map +1 -0
  84. package/dist/serializers/MarkdownSerializer.d.ts +63 -0
  85. package/dist/serializers/MarkdownSerializer.d.ts.map +1 -0
  86. package/dist/serializers/MarkdownSerializer.js +367 -0
  87. package/dist/serializers/MarkdownSerializer.js.map +1 -0
  88. package/dist/serializers/index.d.ts +7 -0
  89. package/dist/serializers/index.d.ts.map +1 -0
  90. package/dist/serializers/index.js +7 -0
  91. package/dist/serializers/index.js.map +1 -0
  92. package/package.json +86 -0
  93. package/src/Brand.ts +142 -0
  94. package/src/Hash.ts +68 -0
  95. package/src/SerializeError.ts +50 -0
  96. package/src/ast/BlockNode.ts +386 -0
  97. package/src/ast/Document.ts +101 -0
  98. package/src/ast/InlineNode.ts +328 -0
  99. package/src/ast/MacroNode.ts +172 -0
  100. package/src/ast/index.ts +87 -0
  101. package/src/auth/OAuthEndpoints.ts +142 -0
  102. package/src/auth/OAuthErrors.ts +101 -0
  103. package/src/auth/OAuthOperations.ts +276 -0
  104. package/src/auth/OAuthResponseSchemas.ts +70 -0
  105. package/src/auth/index.ts +47 -0
  106. package/src/auth/uuid.ts +14 -0
  107. package/src/config/ConfigPaths.ts +179 -0
  108. package/src/config/OAuthSchemas.ts +146 -0
  109. package/src/config/TokenStorage.ts +228 -0
  110. package/src/config/index.ts +43 -0
  111. package/src/index.ts +34 -0
  112. package/src/parsers/index.ts +8 -0
  113. package/src/serializers/MarkdownSerializer.ts +498 -0
  114. package/src/serializers/index.ts +7 -0
  115. package/test/Brand.test.ts +90 -0
  116. package/test/MarkdownSerializer.test.ts +82 -0
  117. package/test/OAuthEndpoints.test.ts +109 -0
  118. package/test/OAuthOperations.test.ts +315 -0
  119. package/tsconfig.json +11 -0
  120. package/tsconfig.tsbuildinfo +1 -0
  121. package/vitest.config.ts +12 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # @knpkv/atlassian-common
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,107 @@
1
+ # @knpkv/atlassian-common
2
+
3
+ Shared utilities for Atlassian tools: OAuth2 + PKCE auth, token storage, config paths, AST types, and markdown serialization.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @knpkv/atlassian-common
9
+ ```
10
+
11
+ ## Subpath Exports
12
+
13
+ | Export | Contents |
14
+ | ------------------------------------- | -------------------------------------------- |
15
+ | `@knpkv/atlassian-common` | Everything (AST, auth, config, serializers) |
16
+ | `@knpkv/atlassian-common/ast` | Inline, block, macro, and document AST nodes |
17
+ | `@knpkv/atlassian-common/auth` | OAuth2 endpoints, PKCE, token exchange |
18
+ | `@knpkv/atlassian-common/config` | Config paths, schemas, token storage |
19
+ | `@knpkv/atlassian-common/serializers` | Markdown serializer for AST nodes |
20
+
21
+ ## OAuth2 + PKCE
22
+
23
+ Uses Web Crypto API (`globalThis.crypto`) — no `node:crypto` dependency. PKCE code verifier/challenge use `effect/Encoding` for base64url.
24
+
25
+ ```typescript
26
+ import { Effect } from "effect"
27
+ import {
28
+ generateCodeVerifier,
29
+ computeCodeChallenge,
30
+ buildAuthUrl,
31
+ exchangeCodeForTokens,
32
+ JIRA_SCOPES
33
+ } from "@knpkv/atlassian-common/auth"
34
+
35
+ const program = Effect.gen(function* () {
36
+ // 1. Generate PKCE pair
37
+ const codeVerifier = generateCodeVerifier()
38
+ const codeChallenge = yield* computeCodeChallenge(codeVerifier)
39
+
40
+ // 2. Build authorization URL
41
+ const url = buildAuthUrl({
42
+ clientId: "your-client-id",
43
+ state: "random-csrf-token",
44
+ port: 8585,
45
+ scopes: JIRA_SCOPES,
46
+ codeChallenge
47
+ })
48
+ // → redirect user to `url`
49
+
50
+ // 3. Exchange code for tokens (after callback)
51
+ const tokens = yield* exchangeCodeForTokens({
52
+ clientId: "your-client-id",
53
+ clientSecret: "your-client-secret",
54
+ code: "auth-code-from-callback",
55
+ redirectUri: "http://localhost:8585/callback",
56
+ codeVerifier
57
+ })
58
+ // tokens.access_token, tokens.refresh_token, tokens.expires_in
59
+ })
60
+ ```
61
+
62
+ ## Token Storage
63
+
64
+ Stores OAuth config and tokens in `~/.config/atlassian/` with 0600 permissions.
65
+
66
+ ```typescript
67
+ import { Effect } from "effect"
68
+ import {
69
+ saveToken,
70
+ loadToken,
71
+ isTokenExpired,
72
+ saveOAuthConfig,
73
+ loadOAuthConfig,
74
+ HomeDirectoryLive
75
+ } from "@knpkv/atlassian-common/config"
76
+
77
+ const program = Effect.gen(function* () {
78
+ yield* saveOAuthConfig({ clientId: "...", clientSecret: "..." }, "jira")
79
+ const config = yield* loadOAuthConfig("jira")
80
+
81
+ yield* saveToken({ access_token: "...", refresh_token: "...", expires_in: 3600 }, "jira")
82
+ const token = yield* loadToken("jira")
83
+ const expired = isTokenExpired(token)
84
+ }).pipe(Effect.provide(HomeDirectoryLive))
85
+ ```
86
+
87
+ ## AST Types
88
+
89
+ Typed Atlassian Document Format nodes — used by `@knpkv/jira-cli` and `@knpkv/confluence-to-markdown`.
90
+
91
+ ```typescript
92
+ import { Document, Paragraph, Text, Heading, CodeBlock } from "@knpkv/atlassian-common/ast"
93
+ ```
94
+
95
+ ## Markdown Serializer
96
+
97
+ Converts AST nodes to markdown strings.
98
+
99
+ ```typescript
100
+ import { serializeToMarkdown } from "@knpkv/atlassian-common/serializers"
101
+
102
+ const markdown = serializeToMarkdown(document)
103
+ ```
104
+
105
+ ## License
106
+
107
+ MIT
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Branded string types for type-safe Atlassian domain values.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **Brand = runtime validation + compile-time narrowing**: Each brand combines
7
+ * an Effect `Brand.refined` (throws on invalid values) with an Effect `Schema`
8
+ * (composable validation). The `Type` property exposes the branded type for use
9
+ * in type annotations.
10
+ * - **Pattern-guarded strings**: `SpaceKey`, `IssueKey`, `ProjectKey` are regex-branded;
11
+ * `PageId` is non-empty branded; `ContentHash` is SHA256 hex branded.
12
+ *
13
+ * **Common tasks**
14
+ *
15
+ * - Create a custom brand: {@link makeBrandedString}, {@link makeBrandedNonEmptyString}
16
+ * - Validate a Jira ticket key: {@link IssueKey}
17
+ * - Validate a content hash: {@link ContentHash}
18
+ *
19
+ * **Gotchas**
20
+ *
21
+ * - Brand constructors throw synchronously on invalid input — wrap in `Effect.try` if
22
+ * the input is untrusted.
23
+ *
24
+ * @module
25
+ */
26
+ import * as Brand from "effect/Brand";
27
+ import * as Schema from "effect/Schema";
28
+ /**
29
+ * Create a branded string type with pattern validation.
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * import { makeBrandedString } from "@knpkv/atlassian-common"
34
+ *
35
+ * const IssueKey = makeBrandedString("IssueKey", /^[A-Z]+-\d+$/)
36
+ * type IssueKey = typeof IssueKey.Type
37
+ *
38
+ * const key = IssueKey("PROJ-123") // OK
39
+ * const bad = IssueKey("invalid") // Throws
40
+ * ```
41
+ *
42
+ * @category Brand
43
+ */
44
+ export declare const makeBrandedString: <B extends string>(name: B, pattern: RegExp) => Brand.Brand.Constructor<string & Brand.Brand<B>> & {
45
+ schema: Schema.brand<Schema.filter<typeof Schema.String>, B>;
46
+ Type: string & Brand.Brand<B>;
47
+ };
48
+ /**
49
+ * Create a branded non-empty string type.
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * import { makeBrandedNonEmptyString } from "@knpkv/atlassian-common"
54
+ *
55
+ * const PageId = makeBrandedNonEmptyString("PageId")
56
+ * type PageId = typeof PageId.Type
57
+ *
58
+ * const id = PageId("12345") // OK
59
+ * const bad = PageId("") // Throws
60
+ * ```
61
+ *
62
+ * @category Brand
63
+ */
64
+ export declare const makeBrandedNonEmptyString: <B extends string>(name: B) => Brand.Brand.Constructor<string & Brand.Brand<B>> & {
65
+ schema: Schema.brand<Schema.filter<typeof Schema.String>, B>;
66
+ Type: string & Brand.Brand<B>;
67
+ };
68
+ /**
69
+ * Confluence Page ID (non-empty string).
70
+ *
71
+ * @category Brand
72
+ */
73
+ export declare const PageId: Brand.Brand.Constructor<string & Brand.Brand<"PageId">> & {
74
+ schema: Schema.brand<Schema.filter<typeof Schema.String>, "PageId">;
75
+ Type: string & Brand.Brand<"PageId">;
76
+ };
77
+ export type PageId = typeof PageId.Type;
78
+ /**
79
+ * Confluence Space Key (uppercase alphanumeric).
80
+ *
81
+ * @category Brand
82
+ */
83
+ export declare const SpaceKey: Brand.Brand.Constructor<string & Brand.Brand<"SpaceKey">> & {
84
+ schema: Schema.brand<Schema.filter<typeof Schema.String>, "SpaceKey">;
85
+ Type: string & Brand.Brand<"SpaceKey">;
86
+ };
87
+ export type SpaceKey = typeof SpaceKey.Type;
88
+ /**
89
+ * Jira Issue Key (PROJECT-123 format).
90
+ *
91
+ * @category Brand
92
+ */
93
+ export declare const IssueKey: Brand.Brand.Constructor<string & Brand.Brand<"IssueKey">> & {
94
+ schema: Schema.brand<Schema.filter<typeof Schema.String>, "IssueKey">;
95
+ Type: string & Brand.Brand<"IssueKey">;
96
+ };
97
+ export type IssueKey = typeof IssueKey.Type;
98
+ /**
99
+ * Jira Project Key (uppercase alphanumeric).
100
+ *
101
+ * @category Brand
102
+ */
103
+ export declare const ProjectKey: Brand.Brand.Constructor<string & Brand.Brand<"ProjectKey">> & {
104
+ schema: Schema.brand<Schema.filter<typeof Schema.String>, "ProjectKey">;
105
+ Type: string & Brand.Brand<"ProjectKey">;
106
+ };
107
+ export type ProjectKey = typeof ProjectKey.Type;
108
+ /**
109
+ * Content hash (SHA256 hex string).
110
+ *
111
+ * @category Brand
112
+ */
113
+ export declare const ContentHash: Brand.Brand.Constructor<string & Brand.Brand<"ContentHash">> & {
114
+ schema: Schema.brand<Schema.filter<typeof Schema.String>, "ContentHash">;
115
+ Type: string & Brand.Brand<"ContentHash">;
116
+ };
117
+ export type ContentHash = typeof ContentHash.Type;
118
+ //# sourceMappingURL=Brand.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Brand.d.ts","sourceRoot":"","sources":["../src/Brand.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,iBAAiB,GAAI,CAAC,SAAS,MAAM,EAChD,MAAM,CAAC,EACP,SAAS,MAAM;;;CAkBhB,CAAA;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,yBAAyB,GAAI,CAAC,SAAS,MAAM,EAAE,MAAM,CAAC;;;CAiBlE,CAAA;AAID;;;;GAIG;AACH,eAAO,MAAM,MAAM;;;CAAsC,CAAA;AACzD,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,IAAI,CAAA;AAEvC;;;;GAIG;AACH,eAAO,MAAM,QAAQ;;;CAAoD,CAAA;AACzE,MAAM,MAAM,QAAQ,GAAG,OAAO,QAAQ,CAAC,IAAI,CAAA;AAE3C;;;;GAIG;AACH,eAAO,MAAM,QAAQ;;;CAAwD,CAAA;AAC7E,MAAM,MAAM,QAAQ,GAAG,OAAO,QAAQ,CAAC,IAAI,CAAA;AAE3C;;;;GAIG;AACH,eAAO,MAAM,UAAU;;;CAAsD,CAAA;AAC7E,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC,IAAI,CAAA;AAE/C;;;;GAIG;AACH,eAAO,MAAM,WAAW;;;CAAqD,CAAA;AAC7E,MAAM,MAAM,WAAW,GAAG,OAAO,WAAW,CAAC,IAAI,CAAA"}
package/dist/Brand.js ADDED
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Branded string types for type-safe Atlassian domain values.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **Brand = runtime validation + compile-time narrowing**: Each brand combines
7
+ * an Effect `Brand.refined` (throws on invalid values) with an Effect `Schema`
8
+ * (composable validation). The `Type` property exposes the branded type for use
9
+ * in type annotations.
10
+ * - **Pattern-guarded strings**: `SpaceKey`, `IssueKey`, `ProjectKey` are regex-branded;
11
+ * `PageId` is non-empty branded; `ContentHash` is SHA256 hex branded.
12
+ *
13
+ * **Common tasks**
14
+ *
15
+ * - Create a custom brand: {@link makeBrandedString}, {@link makeBrandedNonEmptyString}
16
+ * - Validate a Jira ticket key: {@link IssueKey}
17
+ * - Validate a content hash: {@link ContentHash}
18
+ *
19
+ * **Gotchas**
20
+ *
21
+ * - Brand constructors throw synchronously on invalid input — wrap in `Effect.try` if
22
+ * the input is untrusted.
23
+ *
24
+ * @module
25
+ */
26
+ import * as Brand from "effect/Brand";
27
+ import * as Schema from "effect/Schema";
28
+ /**
29
+ * Create a branded string type with pattern validation.
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * import { makeBrandedString } from "@knpkv/atlassian-common"
34
+ *
35
+ * const IssueKey = makeBrandedString("IssueKey", /^[A-Z]+-\d+$/)
36
+ * type IssueKey = typeof IssueKey.Type
37
+ *
38
+ * const key = IssueKey("PROJ-123") // OK
39
+ * const bad = IssueKey("invalid") // Throws
40
+ * ```
41
+ *
42
+ * @category Brand
43
+ */
44
+ export const makeBrandedString = (name, pattern) => {
45
+ const brand = Brand.refined((s) => pattern.test(s), (s) => Brand.error(`Invalid ${name}: ${s} does not match ${pattern}`));
46
+ const schema = Schema.String.pipe(Schema.pattern(pattern), Schema.brand(name));
47
+ return Object.assign(brand, {
48
+ schema,
49
+ Type: undefined
50
+ });
51
+ };
52
+ /**
53
+ * Create a branded non-empty string type.
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * import { makeBrandedNonEmptyString } from "@knpkv/atlassian-common"
58
+ *
59
+ * const PageId = makeBrandedNonEmptyString("PageId")
60
+ * type PageId = typeof PageId.Type
61
+ *
62
+ * const id = PageId("12345") // OK
63
+ * const bad = PageId("") // Throws
64
+ * ```
65
+ *
66
+ * @category Brand
67
+ */
68
+ export const makeBrandedNonEmptyString = (name) => {
69
+ const brand = Brand.refined((s) => s.length > 0, (_s) => Brand.error(`${name} cannot be empty`));
70
+ const schema = Schema.String.pipe(Schema.nonEmptyString(), Schema.brand(name));
71
+ return Object.assign(brand, {
72
+ schema,
73
+ Type: undefined
74
+ });
75
+ };
76
+ // Common branded types for Atlassian products
77
+ /**
78
+ * Confluence Page ID (non-empty string).
79
+ *
80
+ * @category Brand
81
+ */
82
+ export const PageId = makeBrandedNonEmptyString("PageId");
83
+ /**
84
+ * Confluence Space Key (uppercase alphanumeric).
85
+ *
86
+ * @category Brand
87
+ */
88
+ export const SpaceKey = makeBrandedString("SpaceKey", /^[A-Z][A-Z0-9]*$/);
89
+ /**
90
+ * Jira Issue Key (PROJECT-123 format).
91
+ *
92
+ * @category Brand
93
+ */
94
+ export const IssueKey = makeBrandedString("IssueKey", /^[A-Z][A-Z0-9]*-\d+$/);
95
+ /**
96
+ * Jira Project Key (uppercase alphanumeric).
97
+ *
98
+ * @category Brand
99
+ */
100
+ export const ProjectKey = makeBrandedString("ProjectKey", /^[A-Z][A-Z0-9]*$/);
101
+ /**
102
+ * Content hash (SHA256 hex string).
103
+ *
104
+ * @category Brand
105
+ */
106
+ export const ContentHash = makeBrandedString("ContentHash", /^[a-f0-9]{64}$/);
107
+ //# sourceMappingURL=Brand.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Brand.js","sourceRoot":"","sources":["../src/Brand.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,IAAO,EACP,OAAe,EACf,EAAE;IAGF,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CACzB,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EACtB,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,mBAAmB,OAAO,EAAE,CAAC,CACtE,CAAA;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAC/B,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EACvB,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CACnB,CAAA;IAED,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE;QAC1B,MAAM;QACN,IAAI,EAAE,SAAmC;KAC1C,CAAC,CAAA;AACJ,CAAC,CAAA;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAmB,IAAO,EAAE,EAAE;IAGrE,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CACzB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,EACnB,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,IAAI,kBAAkB,CAAC,CAC/C,CAAA;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAC/B,MAAM,CAAC,cAAc,EAAE,EACvB,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CACnB,CAAA;IAED,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE;QAC1B,MAAM;QACN,IAAI,EAAE,SAAmC;KAC1C,CAAC,CAAA;AACJ,CAAC,CAAA;AAED,8CAA8C;AAE9C;;;;GAIG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,yBAAyB,CAAC,QAAQ,CAAC,CAAA;AAGzD;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,iBAAiB,CAAC,UAAU,EAAE,kBAAkB,CAAC,CAAA;AAGzE;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,iBAAiB,CAAC,UAAU,EAAE,sBAAsB,CAAC,CAAA;AAG7E;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,iBAAiB,CAAC,YAAY,EAAE,kBAAkB,CAAC,CAAA;AAG7E;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,iBAAiB,CAAC,aAAa,EAAE,gBAAgB,CAAC,CAAA"}
package/dist/Hash.d.ts ADDED
@@ -0,0 +1,53 @@
1
+ /**
2
+ * SHA256 content hashing via Node.js crypto, returning branded {@link ContentHash} values.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **Effect-wrapped crypto**: {@link hashContent} and {@link hashBuffer} return
7
+ * `Effect<ContentHash>` so hashing composes into pipelines without try/catch.
8
+ * - **Sync escape hatch**: {@link hashContentSync} for contexts where Effect isn't needed.
9
+ *
10
+ * **Common tasks**
11
+ *
12
+ * - Hash a string: {@link hashContent}
13
+ * - Hash a buffer: {@link hashBuffer}
14
+ * - Compare hashes: {@link hashEquals}
15
+ *
16
+ * @module
17
+ */
18
+ import * as Effect from "effect/Effect";
19
+ import { ContentHash } from "./Brand.js";
20
+ /**
21
+ * Compute SHA256 hash of a string.
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * import { hashContent } from "@knpkv/atlassian-common"
26
+ * import * as Effect from "effect/Effect"
27
+ *
28
+ * const hash = Effect.runSync(hashContent("hello world"))
29
+ * // => "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
30
+ * ```
31
+ *
32
+ * @category Hash
33
+ */
34
+ export declare const hashContent: (content: string) => Effect.Effect<ContentHash>;
35
+ /**
36
+ * Compute SHA256 hash of a buffer.
37
+ *
38
+ * @category Hash
39
+ */
40
+ export declare const hashBuffer: (buffer: Buffer | Uint8Array) => Effect.Effect<ContentHash>;
41
+ /**
42
+ * Check if two hashes are equal.
43
+ *
44
+ * @category Hash
45
+ */
46
+ export declare const hashEquals: (a: ContentHash, b: ContentHash) => boolean;
47
+ /**
48
+ * Synchronous version of hashContent for simple use cases.
49
+ *
50
+ * @category Hash
51
+ */
52
+ export declare const hashContentSync: (content: string) => ContentHash;
53
+ //# sourceMappingURL=Hash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Hash.d.ts","sourceRoot":"","sources":["../src/Hash.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAExC;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,WAAW,GAAI,SAAS,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,WAAW,CAInE,CAAA;AAEJ;;;;GAIG;AACH,eAAO,MAAM,UAAU,GAAI,QAAQ,MAAM,GAAG,UAAU,KAAG,MAAM,CAAC,MAAM,CAAC,WAAW,CAI9E,CAAA;AAEJ;;;;GAIG;AACH,eAAO,MAAM,UAAU,GAAI,GAAG,WAAW,EAAE,GAAG,WAAW,KAAG,OAAkB,CAAA;AAE9E;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,SAAS,MAAM,KAAG,WAGjD,CAAA"}
package/dist/Hash.js ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * SHA256 content hashing via Node.js crypto, returning branded {@link ContentHash} values.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **Effect-wrapped crypto**: {@link hashContent} and {@link hashBuffer} return
7
+ * `Effect<ContentHash>` so hashing composes into pipelines without try/catch.
8
+ * - **Sync escape hatch**: {@link hashContentSync} for contexts where Effect isn't needed.
9
+ *
10
+ * **Common tasks**
11
+ *
12
+ * - Hash a string: {@link hashContent}
13
+ * - Hash a buffer: {@link hashBuffer}
14
+ * - Compare hashes: {@link hashEquals}
15
+ *
16
+ * @module
17
+ */
18
+ import * as Effect from "effect/Effect";
19
+ import * as crypto from "node:crypto";
20
+ import { ContentHash } from "./Brand.js";
21
+ /**
22
+ * Compute SHA256 hash of a string.
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * import { hashContent } from "@knpkv/atlassian-common"
27
+ * import * as Effect from "effect/Effect"
28
+ *
29
+ * const hash = Effect.runSync(hashContent("hello world"))
30
+ * // => "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
31
+ * ```
32
+ *
33
+ * @category Hash
34
+ */
35
+ export const hashContent = (content) => Effect.sync(() => {
36
+ const hash = crypto.createHash("sha256").update(content, "utf8").digest("hex");
37
+ return ContentHash(hash);
38
+ });
39
+ /**
40
+ * Compute SHA256 hash of a buffer.
41
+ *
42
+ * @category Hash
43
+ */
44
+ export const hashBuffer = (buffer) => Effect.sync(() => {
45
+ const hash = crypto.createHash("sha256").update(buffer).digest("hex");
46
+ return ContentHash(hash);
47
+ });
48
+ /**
49
+ * Check if two hashes are equal.
50
+ *
51
+ * @category Hash
52
+ */
53
+ export const hashEquals = (a, b) => a === b;
54
+ /**
55
+ * Synchronous version of hashContent for simple use cases.
56
+ *
57
+ * @category Hash
58
+ */
59
+ export const hashContentSync = (content) => {
60
+ const hash = crypto.createHash("sha256").update(content, "utf8").digest("hex");
61
+ return ContentHash(hash);
62
+ };
63
+ //# sourceMappingURL=Hash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Hash.js","sourceRoot":"","sources":["../src/Hash.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAExC;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,OAAe,EAA8B,EAAE,CACzE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;IACf,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IAC9E,OAAO,WAAW,CAAC,IAAI,CAAC,CAAA;AAC1B,CAAC,CAAC,CAAA;AAEJ;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,MAA2B,EAA8B,EAAE,CACpF,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;IACf,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IACrE,OAAO,WAAW,CAAC,IAAI,CAAC,CAAA;AAC1B,CAAC,CAAC,CAAA;AAEJ;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAc,EAAE,CAAc,EAAW,EAAE,CAAC,CAAC,KAAK,CAAC,CAAA;AAE9E;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,OAAe,EAAe,EAAE;IAC9D,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IAC9E,OAAO,WAAW,CAAC,IAAI,CAAC,CAAA;AAC1B,CAAC,CAAA"}
@@ -0,0 +1,48 @@
1
+ declare const SerializeError_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: "SerializeError";
3
+ } & Readonly<A>;
4
+ /**
5
+ * Error thrown when serializing AST fails.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { Effect } from "effect"
10
+ * import { SerializeError } from "@knpkv/atlassian-common"
11
+ *
12
+ * Effect.gen(function* () {
13
+ * // ... serialization operation
14
+ * }).pipe(
15
+ * Effect.catchTag("SerializeError", (error) =>
16
+ * Effect.sync(() => console.error(`Serialize error: ${error.message}`))
17
+ * )
18
+ * )
19
+ * ```
20
+ *
21
+ * @category Errors
22
+ */
23
+ export declare class SerializeError extends SerializeError_base<{
24
+ readonly target: "confluence" | "markdown" | "adf";
25
+ readonly nodeType: string;
26
+ readonly message: string;
27
+ }> {
28
+ }
29
+ declare const ParseError_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 & {
30
+ readonly _tag: "ParseError";
31
+ } & Readonly<A>;
32
+ /**
33
+ * Error thrown when parsing content fails.
34
+ *
35
+ * @category Errors
36
+ */
37
+ export declare class ParseError extends ParseError_base<{
38
+ readonly source: "confluence" | "markdown" | "adf";
39
+ readonly message: string;
40
+ readonly position?: {
41
+ readonly line: number;
42
+ readonly column: number;
43
+ };
44
+ readonly rawContent?: string;
45
+ }> {
46
+ }
47
+ export {};
48
+ //# sourceMappingURL=SerializeError.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SerializeError.d.ts","sourceRoot":"","sources":["../src/SerializeError.ts"],"names":[],"mappings":";;;AAcA;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,cAAe,SAAQ,oBAAmC;IACrE,QAAQ,CAAC,MAAM,EAAE,YAAY,GAAG,UAAU,GAAG,KAAK,CAAA;IAClD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB,CAAC;CAAG;;;;AAEL;;;;GAIG;AACH,qBAAa,UAAW,SAAQ,gBAA+B;IAC7D,QAAQ,CAAC,MAAM,EAAE,YAAY,GAAG,UAAU,GAAG,KAAK,CAAA;IAClD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IACtE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAC7B,CAAC;CAAG"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Tagged error types for AST serialization and parsing failures.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **Discriminated by `_tag`**: {@link SerializeError} and {@link ParseError} extend
7
+ * `Data.TaggedError`, enabling `Effect.catchTag` for selective recovery.
8
+ * - **Direction-aware**: Both carry a `target`/`source` field (`"confluence" | "markdown" | "adf"`)
9
+ * so handlers know which serialization pipeline failed.
10
+ *
11
+ * @module
12
+ */
13
+ import * as Data from "effect/Data";
14
+ /**
15
+ * Error thrown when serializing AST fails.
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * import { Effect } from "effect"
20
+ * import { SerializeError } from "@knpkv/atlassian-common"
21
+ *
22
+ * Effect.gen(function* () {
23
+ * // ... serialization operation
24
+ * }).pipe(
25
+ * Effect.catchTag("SerializeError", (error) =>
26
+ * Effect.sync(() => console.error(`Serialize error: ${error.message}`))
27
+ * )
28
+ * )
29
+ * ```
30
+ *
31
+ * @category Errors
32
+ */
33
+ export class SerializeError extends Data.TaggedError("SerializeError") {
34
+ }
35
+ /**
36
+ * Error thrown when parsing content fails.
37
+ *
38
+ * @category Errors
39
+ */
40
+ export class ParseError extends Data.TaggedError("ParseError") {
41
+ }
42
+ //# sourceMappingURL=SerializeError.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SerializeError.js","sourceRoot":"","sources":["../src/SerializeError.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,IAAI,MAAM,aAAa,CAAA;AAEnC;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,OAAO,cAAe,SAAQ,IAAI,CAAC,WAAW,CAAC,gBAAgB,CAInE;CAAG;AAEL;;;;GAIG;AACH,MAAM,OAAO,UAAW,SAAQ,IAAI,CAAC,WAAW,CAAC,YAAY,CAK3D;CAAG"}