@nakafa/aksara-contracts 0.27.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 (269) hide show
  1. package/LICENSE +185 -0
  2. package/README.md +73 -0
  3. package/dist/artifact/integrity.d.ts +29 -0
  4. package/dist/artifact/integrity.js +54 -0
  5. package/dist/artifact/limits.d.ts +26 -0
  6. package/dist/artifact/limits.js +46 -0
  7. package/dist/artifact/source.d.ts +21 -0
  8. package/dist/artifact/source.js +24 -0
  9. package/dist/artifact/spec.d.ts +148 -0
  10. package/dist/artifact/spec.js +92 -0
  11. package/dist/artifact/verify.d.ts +26 -0
  12. package/dist/artifact/verify.js +18 -0
  13. package/dist/cache/content.d.ts +52 -0
  14. package/dist/cache/content.js +80 -0
  15. package/dist/content.d.ts +168 -0
  16. package/dist/content.js +107 -0
  17. package/dist/country.d.ts +4 -0
  18. package/dist/country.js +8 -0
  19. package/dist/date.d.ts +24 -0
  20. package/dist/date.js +44 -0
  21. package/dist/decode.d.ts +4 -0
  22. package/dist/decode.js +8 -0
  23. package/dist/delivery.d.ts +4 -0
  24. package/dist/delivery.js +7 -0
  25. package/dist/errors.d.ts +10 -0
  26. package/dist/errors.js +8 -0
  27. package/dist/graph/family.d.ts +45 -0
  28. package/dist/graph/family.js +85 -0
  29. package/dist/graph/identity.d.ts +19 -0
  30. package/dist/graph/identity.js +39 -0
  31. package/dist/graph/spec.d.ts +20 -0
  32. package/dist/graph/spec.js +26 -0
  33. package/dist/hash/text.d.ts +10 -0
  34. package/dist/hash/text.js +20 -0
  35. package/dist/ids.d.ts +27 -0
  36. package/dist/ids.js +63 -0
  37. package/dist/limits.d.ts +10 -0
  38. package/dist/limits.js +10 -0
  39. package/dist/locale.d.ts +37 -0
  40. package/dist/locale.js +53 -0
  41. package/dist/material/domain.d.ts +4 -0
  42. package/dist/material/domain.js +8 -0
  43. package/dist/preview/artifact.d.ts +198 -0
  44. package/dist/preview/artifact.js +20 -0
  45. package/dist/preview/auth.d.ts +71 -0
  46. package/dist/preview/auth.js +49 -0
  47. package/dist/preview/document.d.ts +692 -0
  48. package/dist/preview/document.js +90 -0
  49. package/dist/preview/spec.d.ts +2517 -0
  50. package/dist/preview/spec.js +142 -0
  51. package/dist/preview/target.d.ts +172 -0
  52. package/dist/preview/target.js +70 -0
  53. package/dist/program/curriculum.d.ts +69 -0
  54. package/dist/program/curriculum.js +181 -0
  55. package/dist/program/snapshot/digest.d.ts +26 -0
  56. package/dist/program/snapshot/digest.js +220 -0
  57. package/dist/program/snapshot/hash.d.ts +31 -0
  58. package/dist/program/snapshot/hash.js +40 -0
  59. package/dist/program/snapshot/row-hash.d.ts +155 -0
  60. package/dist/program/snapshot/row-hash.js +34 -0
  61. package/dist/program/snapshot/row.d.ts +139 -0
  62. package/dist/program/snapshot/row.js +28 -0
  63. package/dist/program/snapshot/spec.d.ts +36 -0
  64. package/dist/program/snapshot/spec.js +39 -0
  65. package/dist/program/spec.d.ts +85 -0
  66. package/dist/program/spec.js +197 -0
  67. package/dist/projection/article.d.ts +145 -0
  68. package/dist/projection/article.js +150 -0
  69. package/dist/projection/digest.d.ts +261 -0
  70. package/dist/projection/digest.js +59 -0
  71. package/dist/projection/hash.d.ts +3 -0
  72. package/dist/projection/hash.js +10 -0
  73. package/dist/projection/material.d.ts +109 -0
  74. package/dist/projection/material.js +160 -0
  75. package/dist/projection/page.d.ts +112 -0
  76. package/dist/projection/page.js +106 -0
  77. package/dist/projection/question.d.ts +520 -0
  78. package/dist/projection/question.js +109 -0
  79. package/dist/projection/spec.d.ts +503 -0
  80. package/dist/projection/spec.js +56 -0
  81. package/dist/projection/verify.d.ts +281 -0
  82. package/dist/projection/verify.js +86 -0
  83. package/dist/question/identity.d.ts +175 -0
  84. package/dist/question/identity.js +205 -0
  85. package/dist/question/item.d.ts +453 -0
  86. package/dist/question/item.js +146 -0
  87. package/dist/question/response.d.ts +175 -0
  88. package/dist/question/response.js +150 -0
  89. package/dist/quran/attribution.d.ts +48 -0
  90. package/dist/quran/attribution.js +44 -0
  91. package/dist/quran/identity.d.ts +105 -0
  92. package/dist/quran/identity.js +100 -0
  93. package/dist/quran/notes.d.ts +56 -0
  94. package/dist/quran/notes.js +142 -0
  95. package/dist/quran/provenance.d.ts +257 -0
  96. package/dist/quran/provenance.js +144 -0
  97. package/dist/quran/snapshot/digest.d.ts +33 -0
  98. package/dist/quran/snapshot/digest.js +232 -0
  99. package/dist/quran/snapshot/hash.d.ts +51 -0
  100. package/dist/quran/snapshot/hash.js +50 -0
  101. package/dist/quran/snapshot/row/hash.d.ts +748 -0
  102. package/dist/quran/snapshot/row/hash.js +115 -0
  103. package/dist/quran/snapshot/row.d.ts +354 -0
  104. package/dist/quran/snapshot/row.js +91 -0
  105. package/dist/quran/snapshot/spec.d.ts +50 -0
  106. package/dist/quran/snapshot/spec.js +66 -0
  107. package/dist/quran/source.d.ts +206 -0
  108. package/dist/quran/source.js +153 -0
  109. package/dist/quran/spec.d.ts +47 -0
  110. package/dist/quran/spec.js +41 -0
  111. package/dist/quran/text.d.ts +7 -0
  112. package/dist/quran/text.js +11 -0
  113. package/dist/release/canonical.d.ts +23 -0
  114. package/dist/release/canonical.js +29 -0
  115. package/dist/release/current/evidence.d.ts +424 -0
  116. package/dist/release/current/evidence.js +41 -0
  117. package/dist/release/current/runtime.d.ts +4 -0
  118. package/dist/release/current/runtime.js +15 -0
  119. package/dist/release/current/state.d.ts +567 -0
  120. package/dist/release/current/state.js +91 -0
  121. package/dist/release/digest.d.ts +54 -0
  122. package/dist/release/digest.js +71 -0
  123. package/dist/release/hash.d.ts +72 -0
  124. package/dist/release/hash.js +14 -0
  125. package/dist/release/head.d.ts +206 -0
  126. package/dist/release/head.js +131 -0
  127. package/dist/release/items.d.ts +98 -0
  128. package/dist/release/items.js +114 -0
  129. package/dist/release/lifecycle.d.ts +297 -0
  130. package/dist/release/lifecycle.js +97 -0
  131. package/dist/release/origin.d.ts +30 -0
  132. package/dist/release/origin.js +24 -0
  133. package/dist/release/policy.d.ts +45 -0
  134. package/dist/release/policy.js +124 -0
  135. package/dist/release/result/digest.d.ts +127 -0
  136. package/dist/release/result/digest.js +140 -0
  137. package/dist/release/result/spec.d.ts +4 -0
  138. package/dist/release/result/spec.js +5 -0
  139. package/dist/release/rollback/digest.d.ts +126 -0
  140. package/dist/release/rollback/digest.js +92 -0
  141. package/dist/release/rollback/spec.d.ts +1729 -0
  142. package/dist/release/rollback/spec.js +156 -0
  143. package/dist/release/route/digest.d.ts +45 -0
  144. package/dist/release/route/digest.js +59 -0
  145. package/dist/release/route/page.d.ts +56 -0
  146. package/dist/release/route/page.js +46 -0
  147. package/dist/release/route/spec.d.ts +49 -0
  148. package/dist/release/route/spec.js +40 -0
  149. package/dist/release/route/verify.d.ts +66 -0
  150. package/dist/release/route/verify.js +67 -0
  151. package/dist/release/signing.d.ts +6 -0
  152. package/dist/release/signing.js +38 -0
  153. package/dist/release/snapshot/data.d.ts +475 -0
  154. package/dist/release/snapshot/data.js +74 -0
  155. package/dist/release/snapshot/evidence-requirement.d.ts +22 -0
  156. package/dist/release/snapshot/evidence-requirement.js +21 -0
  157. package/dist/release/snapshot/evidence.d.ts +58 -0
  158. package/dist/release/snapshot/evidence.js +14 -0
  159. package/dist/release/snapshot/program-evidence.d.ts +17 -0
  160. package/dist/release/snapshot/program-evidence.js +39 -0
  161. package/dist/release/snapshot/quran-evidence.d.ts +27 -0
  162. package/dist/release/snapshot/quran-evidence.js +43 -0
  163. package/dist/release/snapshot/scope.d.ts +60 -0
  164. package/dist/release/snapshot/scope.js +102 -0
  165. package/dist/release/snapshot/spec.d.ts +193 -0
  166. package/dist/release/snapshot/spec.js +139 -0
  167. package/dist/release/snapshot/tryout-evidence.d.ts +22 -0
  168. package/dist/release/snapshot/tryout-evidence.js +86 -0
  169. package/dist/release/snapshot/verify.d.ts +710 -0
  170. package/dist/release/snapshot/verify.js +147 -0
  171. package/dist/release/spec.d.ts +491 -0
  172. package/dist/release/spec.js +200 -0
  173. package/dist/release/verify.d.ts +98 -0
  174. package/dist/release/verify.js +70 -0
  175. package/dist/renderer/compatibility.d.ts +35 -0
  176. package/dist/renderer/compatibility.js +48 -0
  177. package/dist/renderer/component.d.ts +99 -0
  178. package/dist/renderer/component.js +102 -0
  179. package/dist/renderer/contract.d.ts +144 -0
  180. package/dist/renderer/contract.js +122 -0
  181. package/dist/renderer/domain.d.ts +6 -0
  182. package/dist/renderer/domain.js +18 -0
  183. package/dist/renderer/manifest.d.ts +86 -0
  184. package/dist/renderer/manifest.js +49 -0
  185. package/dist/renderer/selection.d.ts +39 -0
  186. package/dist/renderer/selection.js +87 -0
  187. package/dist/runtime/error.d.ts +8 -0
  188. package/dist/runtime/error.js +20 -0
  189. package/dist/runtime/protected/limits.d.ts +10 -0
  190. package/dist/runtime/protected/limits.js +15 -0
  191. package/dist/runtime/protected/spec.d.ts +328 -0
  192. package/dist/runtime/protected/spec.js +79 -0
  193. package/dist/runtime/protected/verify.d.ts +97 -0
  194. package/dist/runtime/protected/verify.js +72 -0
  195. package/dist/runtime/result.d.ts +12 -0
  196. package/dist/runtime/result.js +17 -0
  197. package/dist/runtime/spec.d.ts +705 -0
  198. package/dist/runtime/spec.js +61 -0
  199. package/dist/runtime/verify.d.ts +479 -0
  200. package/dist/runtime/verify.js +123 -0
  201. package/dist/signature/spec.d.ts +53 -0
  202. package/dist/signature/spec.js +29 -0
  203. package/dist/signature/trusted.d.ts +19 -0
  204. package/dist/signature/trusted.js +32 -0
  205. package/dist/signature/verify.d.ts +10 -0
  206. package/dist/signature/verify.js +82 -0
  207. package/dist/text/order.d.ts +2 -0
  208. package/dist/text/order.js +10 -0
  209. package/dist/text/syntax.d.ts +6 -0
  210. package/dist/text/syntax.js +15 -0
  211. package/dist/transport/batch.d.ts +971 -0
  212. package/dist/transport/batch.js +101 -0
  213. package/dist/transport/failure.d.ts +168 -0
  214. package/dist/transport/failure.js +194 -0
  215. package/dist/transport/group.d.ts +2946 -0
  216. package/dist/transport/group.js +47 -0
  217. package/dist/transport/limits.d.ts +30 -0
  218. package/dist/transport/limits.js +30 -0
  219. package/dist/transport/request.d.ts +9333 -0
  220. package/dist/transport/request.js +121 -0
  221. package/dist/transport/response.d.ts +6247 -0
  222. package/dist/transport/response.js +181 -0
  223. package/dist/transport/runtime.d.ts +77 -0
  224. package/dist/transport/runtime.js +33 -0
  225. package/dist/transport/snapshot.d.ts +978 -0
  226. package/dist/transport/snapshot.js +70 -0
  227. package/dist/tryout/canonical.d.ts +2 -0
  228. package/dist/tryout/canonical.js +11 -0
  229. package/dist/tryout/catalog-hash.d.ts +134 -0
  230. package/dist/tryout/catalog-hash.js +203 -0
  231. package/dist/tryout/catalog.d.ts +376 -0
  232. package/dist/tryout/catalog.js +147 -0
  233. package/dist/tryout/content-hash.d.ts +89 -0
  234. package/dist/tryout/content-hash.js +72 -0
  235. package/dist/tryout/identity.d.ts +12 -0
  236. package/dist/tryout/identity.js +46 -0
  237. package/dist/tryout/key.d.ts +4 -0
  238. package/dist/tryout/key.js +8 -0
  239. package/dist/tryout/language.d.ts +24 -0
  240. package/dist/tryout/language.js +32 -0
  241. package/dist/tryout/locale-closure.d.ts +20 -0
  242. package/dist/tryout/locale-closure.js +164 -0
  243. package/dist/tryout/placement-closure.d.ts +5 -0
  244. package/dist/tryout/placement-closure.js +30 -0
  245. package/dist/tryout/placement-hash.d.ts +11 -0
  246. package/dist/tryout/placement-hash.js +55 -0
  247. package/dist/tryout/placement.d.ts +280 -0
  248. package/dist/tryout/placement.js +82 -0
  249. package/dist/tryout/row-hash.d.ts +28 -0
  250. package/dist/tryout/row-hash.js +50 -0
  251. package/dist/tryout/runtime/canonical.d.ts +8 -0
  252. package/dist/tryout/runtime/canonical.js +33 -0
  253. package/dist/tryout/runtime/hash.d.ts +32 -0
  254. package/dist/tryout/runtime/hash.js +11 -0
  255. package/dist/tryout/runtime/source.d.ts +42 -0
  256. package/dist/tryout/runtime/source.js +47 -0
  257. package/dist/tryout/runtime/spec.d.ts +58 -0
  258. package/dist/tryout/runtime/spec.js +21 -0
  259. package/dist/tryout/runtime/verify.d.ts +61 -0
  260. package/dist/tryout/runtime/verify.js +69 -0
  261. package/dist/tryout/snapshot/canonical.d.ts +7 -0
  262. package/dist/tryout/snapshot/canonical.js +17 -0
  263. package/dist/tryout/snapshot/hash.d.ts +8 -0
  264. package/dist/tryout/snapshot/hash.js +19 -0
  265. package/dist/tryout/snapshot/spec.d.ts +46 -0
  266. package/dist/tryout/snapshot/spec.js +30 -0
  267. package/dist/tryout/spec.d.ts +13 -0
  268. package/dist/tryout/spec.js +15 -0
  269. package/package.json +506 -0
@@ -0,0 +1,145 @@
1
+ import { Schema } from "effect";
2
+ /** Stable source-owned category segment used below the article route family. */
3
+ export declare const ArticleCategorySchema: Schema.brand<Schema.String, "@NakafaAI/AksaraArticleCategory">;
4
+ export type ArticleCategory = typeof ArticleCategorySchema.Type;
5
+ /** Localized display title owned by one reviewed article category source. */
6
+ export declare const ArticleCategoryTitleSchema: Schema.Trimmed;
7
+ export type ArticleCategoryTitle = typeof ArticleCategoryTitleSchema.Type;
8
+ /** Stable source-owned article segment used below its category route. */
9
+ export declare const ArticleSlugSchema: Schema.brand<Schema.String, "@NakafaAI/AksaraArticleSlug">;
10
+ export type ArticleSlug = typeof ArticleSlugSchema.Type;
11
+ /** Locale-owned public route segment for one article category or body. */
12
+ export declare const ArticleRouteSlugSchema: Schema.brand<Schema.String, "@NakafaAI/AksaraArticleRouteSlug">;
13
+ export type ArticleRouteSlug = typeof ArticleRouteSlugSchema.Type;
14
+ /** Exact reviewed citation fields consumed by Nakafa article pages. */
15
+ export declare const ArticleReferenceSchema: Schema.Struct<{
16
+ readonly authors: Schema.String;
17
+ readonly citation: Schema.optional<Schema.String>;
18
+ readonly details: Schema.optional<Schema.String>;
19
+ readonly publication: Schema.optional<Schema.String>;
20
+ readonly title: Schema.String;
21
+ readonly url: Schema.optional<Schema.String>;
22
+ readonly year: Schema.Finite;
23
+ }>;
24
+ export type ArticleReference = typeof ArticleReferenceSchema.Type;
25
+ /** Exact metadata contract required from newly authored article sources. */
26
+ export declare const ArticleMetadataSchema: Schema.Struct<{
27
+ readonly dateModified: Schema.optionalKey<Schema.String>;
28
+ readonly datePublished: Schema.String;
29
+ readonly authors: Schema.$Array<Schema.Struct<{
30
+ readonly name: Schema.String;
31
+ }>>;
32
+ readonly description: Schema.optional<Schema.String>;
33
+ readonly title: Schema.String;
34
+ }>;
35
+ export type ArticleMetadata = typeof ArticleMetadataSchema.Type;
36
+ /** Source-owned identity and public route for one localized article body. */
37
+ export declare const ArticleRouteSchema: Schema.Struct<{
38
+ appLocale: Schema.brand<Schema.Literals<readonly ["en", "id", "de"]>, "@NakafaAI/AksaraAppLocale">;
39
+ articleRouteSlug: Schema.brand<Schema.String, "@NakafaAI/AksaraArticleRouteSlug">;
40
+ articleSlug: Schema.brand<Schema.String, "@NakafaAI/AksaraArticleSlug">;
41
+ artifactLocale: Schema.brand<Schema.Literals<readonly ["en", "id", "de"]>, "@NakafaAI/AksaraArtifactLocale">;
42
+ category: Schema.brand<Schema.String, "@NakafaAI/AksaraArticleCategory">;
43
+ categoryRouteSlug: Schema.brand<Schema.String, "@NakafaAI/AksaraArticleRouteSlug">;
44
+ contentKey: Schema.brand<Schema.String, "@NakafaAI/AksaraContentKey">;
45
+ graph: Schema.Struct<{
46
+ readonly alignmentId: Schema.String;
47
+ readonly assetId: Schema.String;
48
+ readonly conceptId: Schema.String;
49
+ readonly learningObjectId: Schema.String;
50
+ readonly lensId: Schema.String;
51
+ }>;
52
+ publicPath: Schema.brand<Schema.String, "@NakafaAI/AksaraPublicPath">;
53
+ }>;
54
+ export type ArticleRoute = typeof ArticleRouteSchema.Type;
55
+ /** Canonical published read model for one reviewed article body. */
56
+ export declare const ArticleProjectionSchema: Schema.Struct<{
57
+ readonly appLocale: Schema.brand<Schema.Literals<readonly ["en", "id", "de"]>, "@NakafaAI/AksaraAppLocale">;
58
+ readonly articleRouteSlug: Schema.brand<Schema.String, "@NakafaAI/AksaraArticleRouteSlug">;
59
+ readonly articleSlug: Schema.brand<Schema.String, "@NakafaAI/AksaraArticleSlug">;
60
+ readonly artifactLocale: Schema.brand<Schema.Literals<readonly ["en", "id", "de"]>, "@NakafaAI/AksaraArtifactLocale">;
61
+ readonly category: Schema.brand<Schema.String, "@NakafaAI/AksaraArticleCategory">;
62
+ readonly categoryRouteSlug: Schema.brand<Schema.String, "@NakafaAI/AksaraArticleRouteSlug">;
63
+ readonly contentKey: Schema.brand<Schema.String, "@NakafaAI/AksaraContentKey">;
64
+ readonly graph: Schema.Struct<{
65
+ readonly alignmentId: Schema.String;
66
+ readonly assetId: Schema.String;
67
+ readonly conceptId: Schema.String;
68
+ readonly learningObjectId: Schema.String;
69
+ readonly lensId: Schema.String;
70
+ }>;
71
+ readonly publicPath: Schema.brand<Schema.String, "@NakafaAI/AksaraPublicPath">;
72
+ readonly categoryTitle: Schema.Trimmed;
73
+ readonly kind: Schema.Literal<"article">;
74
+ readonly metadata: Schema.Struct<{
75
+ readonly dateModified: Schema.optionalKey<Schema.String>;
76
+ readonly datePublished: Schema.String;
77
+ readonly authors: Schema.$Array<Schema.Struct<{
78
+ readonly name: Schema.String;
79
+ }>>;
80
+ readonly description: Schema.optional<Schema.String>;
81
+ readonly title: Schema.String;
82
+ }>;
83
+ readonly official: Schema.Boolean;
84
+ readonly parentPath: Schema.brand<Schema.String, "@NakafaAI/AksaraPublicPath">;
85
+ readonly references: Schema.$Array<Schema.Struct<{
86
+ readonly authors: Schema.String;
87
+ readonly citation: Schema.optional<Schema.String>;
88
+ readonly details: Schema.optional<Schema.String>;
89
+ readonly publication: Schema.optional<Schema.String>;
90
+ readonly title: Schema.String;
91
+ readonly url: Schema.optional<Schema.String>;
92
+ readonly year: Schema.Finite;
93
+ }>>;
94
+ readonly sitemap: Schema.Literal<true>;
95
+ }>;
96
+ export type ArticleProjection = typeof ArticleProjectionSchema.Type;
97
+ /** Combines source-owned routing with decoded metadata and references. */
98
+ export declare function makeArticleProjection(input: {
99
+ readonly categoryTitle: ArticleCategoryTitle;
100
+ readonly metadata: ArticleMetadata;
101
+ readonly official: boolean;
102
+ readonly references: readonly ArticleReference[];
103
+ readonly route: ArticleRoute;
104
+ }): {
105
+ readonly appLocale: ("de" | "en" | "id") & import("effect/Brand").Brand<"@NakafaAI/AksaraAppLocale">;
106
+ readonly articleRouteSlug: string & import("effect/Brand").Brand<"@NakafaAI/AksaraArticleRouteSlug">;
107
+ readonly articleSlug: string & import("effect/Brand").Brand<"@NakafaAI/AksaraArticleSlug">;
108
+ readonly artifactLocale: ("de" | "en" | "id") & import("effect/Brand").Brand<"@NakafaAI/AksaraArtifactLocale">;
109
+ readonly category: string & import("effect/Brand").Brand<"@NakafaAI/AksaraArticleCategory">;
110
+ readonly categoryRouteSlug: string & import("effect/Brand").Brand<"@NakafaAI/AksaraArticleRouteSlug">;
111
+ readonly contentKey: string & import("effect/Brand").Brand<"@NakafaAI/AksaraContentKey">;
112
+ readonly graph: {
113
+ readonly alignmentId: string;
114
+ readonly assetId: string;
115
+ readonly conceptId: string;
116
+ readonly learningObjectId: string;
117
+ readonly lensId: string;
118
+ };
119
+ readonly publicPath: string & import("effect/Brand").Brand<"@NakafaAI/AksaraPublicPath">;
120
+ readonly categoryTitle: string;
121
+ readonly kind: "article";
122
+ readonly metadata: {
123
+ readonly dateModified?: string;
124
+ readonly datePublished: string;
125
+ readonly authors: readonly {
126
+ readonly name: string;
127
+ }[];
128
+ readonly description?: string | undefined;
129
+ readonly title: string;
130
+ };
131
+ readonly official: boolean;
132
+ readonly parentPath: string & import("effect/Brand").Brand<"@NakafaAI/AksaraPublicPath">;
133
+ readonly references: readonly {
134
+ readonly authors: string;
135
+ readonly citation?: string | undefined;
136
+ readonly details?: string | undefined;
137
+ readonly publication?: string | undefined;
138
+ readonly title: string;
139
+ readonly url?: string | undefined;
140
+ readonly year: number;
141
+ }[];
142
+ readonly sitemap: true;
143
+ };
144
+ /** Serializes one article projection with stable signed field order. */
145
+ export declare function canonicalizeArticleProjection(projection: ArticleProjection): string;
@@ -0,0 +1,150 @@
1
+ import { Schema } from "effect";
2
+ import { ContentAuthorSchema } from "#contracts/content";
3
+ import { withPublicationDates } from "#contracts/date";
4
+ import { canonicalizeLearningGraphIdentity, LearningGraphIdentitySchema, } from "#contracts/graph/spec";
5
+ import { ContentKeySchema, PublicPathSchema } from "#contracts/ids";
6
+ import { AppLocaleSchema, ArtifactLocaleSchema } from "#contracts/locale";
7
+ import { isLowerKebab } from "#contracts/text/syntax";
8
+ /** Stable source-owned category segment used below the article route family. */
9
+ export const ArticleCategorySchema = Schema.String.pipe(Schema.check(Schema.makeFilter(isLowerKebab)), Schema.brand("@NakafaAI/AksaraArticleCategory"));
10
+ /** Localized display title owned by one reviewed article category source. */
11
+ export const ArticleCategoryTitleSchema = Schema.Trimmed.check(Schema.isNonEmpty());
12
+ /** Stable source-owned article segment used below its category route. */
13
+ export const ArticleSlugSchema = Schema.String.pipe(Schema.check(Schema.makeFilter(isLowerKebab)), Schema.brand("@NakafaAI/AksaraArticleSlug"));
14
+ /** Locale-owned public route segment for one article category or body. */
15
+ export const ArticleRouteSlugSchema = Schema.String.pipe(Schema.check(Schema.makeFilter(isLowerKebab)), Schema.brand("@NakafaAI/AksaraArticleRouteSlug"));
16
+ /** Exact reviewed citation fields consumed by Nakafa article pages. */
17
+ export const ArticleReferenceSchema = Schema.Struct({
18
+ authors: Schema.String,
19
+ citation: Schema.optional(Schema.String),
20
+ details: Schema.optional(Schema.String),
21
+ publication: Schema.optional(Schema.String),
22
+ title: Schema.String,
23
+ url: Schema.optional(Schema.String),
24
+ year: Schema.Finite,
25
+ });
26
+ const ArticleMetadataFields = {
27
+ authors: Schema.Array(ContentAuthorSchema),
28
+ description: Schema.optional(Schema.String),
29
+ title: Schema.String,
30
+ };
31
+ /** Exact metadata contract required from newly authored article sources. */
32
+ export const ArticleMetadataSchema = withPublicationDates(ArticleMetadataFields);
33
+ const ArticleProjectionMetadataSchema = withPublicationDates(ArticleMetadataFields);
34
+ const ArticleRouteFields = {
35
+ appLocale: AppLocaleSchema,
36
+ articleRouteSlug: ArticleRouteSlugSchema,
37
+ articleSlug: ArticleSlugSchema,
38
+ artifactLocale: ArtifactLocaleSchema,
39
+ category: ArticleCategorySchema,
40
+ categoryRouteSlug: ArticleRouteSlugSchema,
41
+ contentKey: ContentKeySchema,
42
+ graph: LearningGraphIdentitySchema,
43
+ publicPath: PublicPathSchema,
44
+ };
45
+ /** Checks graph identities against the source-owned article category and slug. */
46
+ function hasCoherentArticleGraph(input) {
47
+ const lens = `article:${input.category}`;
48
+ const object = `article:${input.category}:${input.articleSlug}`;
49
+ return (input.graph.alignmentId === `alignment:${lens}:${object}` &&
50
+ input.graph.assetId === `asset:${input.appLocale}:${lens}:${object}` &&
51
+ input.graph.conceptId === `concept:${lens}` &&
52
+ input.graph.learningObjectId === `lo:${object}` &&
53
+ input.graph.lensId === `lens:${lens}`);
54
+ }
55
+ /** Checks stable content identity separately from locale-owned public routing. */
56
+ function hasCoherentArticleRoute(input) {
57
+ return (input.contentKey === `articles/${input.category}/${input.articleSlug}` &&
58
+ input.publicPath ===
59
+ `articles/${input.categoryRouteSlug}/${input.articleRouteSlug}`);
60
+ }
61
+ /** Public article bodies use the same locale for routes and artifacts. */
62
+ function hasCoherentArticleLocales(input) {
63
+ return input.appLocale === input.artifactLocale;
64
+ }
65
+ /** Source-owned identity and public route for one localized article body. */
66
+ export const ArticleRouteSchema = Schema.Struct(ArticleRouteFields).pipe(Schema.check(Schema.makeFilter(hasCoherentArticleLocales, {
67
+ message: "Expected public article route and artifact locales to match.",
68
+ })), Schema.check(Schema.makeFilter(hasCoherentArticleRoute, {
69
+ message: "Expected stable article identity and locale-owned public route to be coherent.",
70
+ })), Schema.check(Schema.makeFilter(hasCoherentArticleGraph, {
71
+ message: "Expected article graph identities to match its stable source keys.",
72
+ })));
73
+ /** Canonical published read model for one reviewed article body. */
74
+ export const ArticleProjectionSchema = Schema.Struct({
75
+ ...ArticleRouteFields,
76
+ categoryTitle: ArticleCategoryTitleSchema,
77
+ kind: Schema.Literal("article"),
78
+ metadata: ArticleProjectionMetadataSchema,
79
+ official: Schema.Boolean,
80
+ parentPath: PublicPathSchema,
81
+ references: Schema.Array(ArticleReferenceSchema),
82
+ sitemap: Schema.Literal(true),
83
+ }).pipe(Schema.check(Schema.makeFilter(hasCoherentArticleLocales, {
84
+ message: "Expected public article route and artifact locales to match.",
85
+ })), Schema.check(Schema.makeFilter(hasCoherentArticleRoute, {
86
+ message: "Expected stable article identity and locale-owned public route to be coherent.",
87
+ })), Schema.check(Schema.makeFilter((projection) => projection.parentPath === `articles/${projection.categoryRouteSlug}`, {
88
+ message: "Expected the article parent path to match its category route.",
89
+ })), Schema.check(Schema.makeFilter(hasCoherentArticleGraph, {
90
+ message: "Expected article graph identities to match its stable source keys.",
91
+ })));
92
+ /** Combines source-owned routing with decoded metadata and references. */
93
+ export function makeArticleProjection(input) {
94
+ return ArticleProjectionSchema.make({
95
+ ...input.route,
96
+ categoryTitle: input.categoryTitle,
97
+ kind: "article",
98
+ metadata: input.metadata,
99
+ official: input.official,
100
+ parentPath: PublicPathSchema.make(`articles/${input.route.categoryRouteSlug}`),
101
+ references: [...input.references],
102
+ sitemap: true,
103
+ });
104
+ }
105
+ /** Serializes one article projection with stable signed field order. */
106
+ export function canonicalizeArticleProjection(projection) {
107
+ const metadata = {
108
+ authors: projection.metadata.authors.map(({ name }) => ({ name })),
109
+ ...(projection.metadata.dateModified === undefined
110
+ ? {}
111
+ : { dateModified: projection.metadata.dateModified }),
112
+ datePublished: projection.metadata.datePublished,
113
+ ...(projection.metadata.description === undefined
114
+ ? {}
115
+ : { description: projection.metadata.description }),
116
+ title: projection.metadata.title,
117
+ };
118
+ return JSON.stringify({
119
+ appLocale: projection.appLocale,
120
+ articleRouteSlug: projection.articleRouteSlug,
121
+ articleSlug: projection.articleSlug,
122
+ artifactLocale: projection.artifactLocale,
123
+ category: projection.category,
124
+ categoryRouteSlug: projection.categoryRouteSlug,
125
+ categoryTitle: projection.categoryTitle,
126
+ contentKey: projection.contentKey,
127
+ graph: canonicalizeLearningGraphIdentity(projection.graph),
128
+ kind: projection.kind,
129
+ metadata,
130
+ official: projection.official,
131
+ parentPath: projection.parentPath,
132
+ publicPath: projection.publicPath,
133
+ references: projection.references.map((reference) => ({
134
+ authors: reference.authors,
135
+ ...(reference.citation === undefined
136
+ ? {}
137
+ : { citation: reference.citation }),
138
+ ...(reference.details === undefined
139
+ ? {}
140
+ : { details: reference.details }),
141
+ ...(reference.publication === undefined
142
+ ? {}
143
+ : { publication: reference.publication }),
144
+ title: reference.title,
145
+ ...(reference.url === undefined ? {} : { url: reference.url }),
146
+ year: reference.year,
147
+ })),
148
+ sitemap: projection.sitemap,
149
+ });
150
+ }
@@ -0,0 +1,261 @@
1
+ import { Effect, Schema, Stream } from "effect";
2
+ import { ReleaseIdSchema } from "#contracts/ids";
3
+ import { type ContentProjection } from "#contracts/projection/spec";
4
+ declare const ProjectionHashError_base: Schema.Class<ProjectionHashError, Schema.TaggedStruct<"ProjectionHashError", {
5
+ readonly releaseId: Schema.brand<Schema.String, "@NakafaAI/AksaraReleaseId">;
6
+ }>, import("effect/Cause").YieldableError>;
7
+ /** SHA-256 computation failed before projection integrity was established. */
8
+ export declare class ProjectionHashError extends ProjectionHashError_base {
9
+ }
10
+ /** Keeps Node hash state private while exposing only canonical counters. */
11
+ declare class ProjectionDigestState {
12
+ #private;
13
+ count: number;
14
+ /** Initializes a domain-separated incremental projection hash. */
15
+ constructor();
16
+ /** Adds one canonical projection to this invocation-owned digest. */
17
+ update(projection: ContentProjection): void;
18
+ /** Consumes the hash and returns its branded immutable identity. */
19
+ digest(): string & import("effect/Brand").Brand<"@NakafaAI/AksaraSha256Hash">;
20
+ }
21
+ /** Creates a fresh domain-separated projection digest state. */
22
+ export declare function createProjectionDigest(releaseId: typeof ReleaseIdSchema.Type): Effect.Effect<ProjectionDigestState, ProjectionHashError, never>;
23
+ /** Adds one canonical content projection to an incremental digest. */
24
+ export declare function updateProjectionDigest(releaseId: typeof ReleaseIdSchema.Type, state: ProjectionDigestState, projection: ContentProjection): Effect.Effect<ProjectionDigestState, ProjectionHashError, never>;
25
+ /** Finalizes one incremental projection digest with typed hash failures. */
26
+ export declare function finalizeProjectionDigest(releaseId: typeof ReleaseIdSchema.Type, state: ProjectionDigestState): Effect.Effect<string & import("effect/Brand").Brand<"@NakafaAI/AksaraSha256Hash">, ProjectionHashError, never>;
27
+ /** Digests a projection stream without retaining its content bodies. */
28
+ export declare const digestProjections: <E, R>(releaseId: string & import("effect/Brand").Brand<"@NakafaAI/AksaraReleaseId">, projections: Stream.Stream<{
29
+ readonly appLocale: ("de" | "en" | "id") & import("effect/Brand").Brand<"@NakafaAI/AksaraAppLocale">;
30
+ readonly artifactLocale: ("de" | "en" | "id") & import("effect/Brand").Brand<"@NakafaAI/AksaraArtifactLocale">;
31
+ readonly contentKey: string & import("effect/Brand").Brand<"@NakafaAI/AksaraContentKey">;
32
+ readonly graph: {
33
+ readonly alignmentId: string;
34
+ readonly assetId: string;
35
+ readonly conceptId: string;
36
+ readonly learningObjectId: string;
37
+ readonly lensId: string;
38
+ };
39
+ readonly materialKey: string & import("effect/Brand").Brand<"@NakafaAI/AksaraMaterialKey">;
40
+ readonly order: number;
41
+ readonly publicPath: string & import("effect/Brand").Brand<"@NakafaAI/AksaraPublicPath">;
42
+ readonly sectionKey: string & import("effect/Brand").Brand<"@NakafaAI/AksaraMaterialSection">;
43
+ readonly kind: "subject-lesson";
44
+ readonly metadata: {
45
+ readonly dateModified?: string;
46
+ readonly datePublished: string;
47
+ readonly authors: readonly {
48
+ readonly name: string;
49
+ }[];
50
+ readonly description?: string | undefined;
51
+ readonly subject?: string | undefined;
52
+ readonly title: string;
53
+ };
54
+ readonly parentPath: string & import("effect/Brand").Brand<"@NakafaAI/AksaraPublicPath">;
55
+ readonly sitemap: true;
56
+ readonly topicTitle: string;
57
+ } | {
58
+ readonly appLocale: ("de" | "en" | "id") & import("effect/Brand").Brand<"@NakafaAI/AksaraAppLocale">;
59
+ readonly articleRouteSlug: string & import("effect/Brand").Brand<"@NakafaAI/AksaraArticleRouteSlug">;
60
+ readonly articleSlug: string & import("effect/Brand").Brand<"@NakafaAI/AksaraArticleSlug">;
61
+ readonly artifactLocale: ("de" | "en" | "id") & import("effect/Brand").Brand<"@NakafaAI/AksaraArtifactLocale">;
62
+ readonly category: string & import("effect/Brand").Brand<"@NakafaAI/AksaraArticleCategory">;
63
+ readonly categoryRouteSlug: string & import("effect/Brand").Brand<"@NakafaAI/AksaraArticleRouteSlug">;
64
+ readonly contentKey: string & import("effect/Brand").Brand<"@NakafaAI/AksaraContentKey">;
65
+ readonly graph: {
66
+ readonly alignmentId: string;
67
+ readonly assetId: string;
68
+ readonly conceptId: string;
69
+ readonly learningObjectId: string;
70
+ readonly lensId: string;
71
+ };
72
+ readonly publicPath: string & import("effect/Brand").Brand<"@NakafaAI/AksaraPublicPath">;
73
+ readonly categoryTitle: string;
74
+ readonly kind: "article";
75
+ readonly metadata: {
76
+ readonly dateModified?: string;
77
+ readonly datePublished: string;
78
+ readonly authors: readonly {
79
+ readonly name: string;
80
+ }[];
81
+ readonly description?: string | undefined;
82
+ readonly title: string;
83
+ };
84
+ readonly official: boolean;
85
+ readonly parentPath: string & import("effect/Brand").Brand<"@NakafaAI/AksaraPublicPath">;
86
+ readonly references: readonly {
87
+ readonly authors: string;
88
+ readonly citation?: string | undefined;
89
+ readonly details?: string | undefined;
90
+ readonly publication?: string | undefined;
91
+ readonly title: string;
92
+ readonly url?: string | undefined;
93
+ readonly year: number;
94
+ }[];
95
+ readonly sitemap: true;
96
+ } | {
97
+ readonly appLocale: ("de" | "en" | "id") & import("effect/Brand").Brand<"@NakafaAI/AksaraAppLocale">;
98
+ readonly artifactLocale: ("de" | "en" | "id") & import("effect/Brand").Brand<"@NakafaAI/AksaraArtifactLocale">;
99
+ readonly contentKey: string & import("effect/Brand").Brand<"@NakafaAI/AksaraContentKey">;
100
+ readonly pageKey: string & import("effect/Brand").Brand<"@NakafaAI/AksaraPageKey">;
101
+ readonly publicPath: string & import("effect/Brand").Brand<"@NakafaAI/AksaraPublicPath">;
102
+ readonly kind: "public-page";
103
+ readonly metadata: {
104
+ readonly dateModified?: string;
105
+ readonly datePublished: string;
106
+ readonly description: string;
107
+ readonly title: string;
108
+ };
109
+ readonly sitemap: true;
110
+ readonly sourcePath: string & import("effect/Brand").Brand<"@NakafaAI/AksaraCorpusSourcePath">;
111
+ } | {
112
+ readonly artifactLocale: ("de" | "en" | "id") & import("effect/Brand").Brand<"@NakafaAI/AksaraArtifactLocale">;
113
+ readonly contentKey: string & import("effect/Brand").Brand<"@NakafaAI/AksaraContentKey">;
114
+ readonly peerContentKey: string & import("effect/Brand").Brand<"@NakafaAI/AksaraContentKey">;
115
+ readonly questionKey: string & import("effect/Brand").Brand<"@NakafaAI/AksaraQuestionKey">;
116
+ readonly questionNumber: number;
117
+ readonly setKey: string & import("effect/Brand").Brand<"@NakafaAI/AksaraQuestionSetKey">;
118
+ readonly bodyKind: "question";
119
+ readonly blueprint?: {
120
+ readonly cognitiveLevel: string;
121
+ readonly contentDomain: string;
122
+ readonly topic: string;
123
+ };
124
+ readonly kind: "question-body";
125
+ readonly metadata: {
126
+ readonly dateModified?: string;
127
+ readonly datePublished: string;
128
+ readonly authors: readonly {
129
+ readonly name: string;
130
+ }[];
131
+ readonly title: string;
132
+ };
133
+ readonly stimulusKey?: string;
134
+ readonly response: {
135
+ readonly kind: "single-choice";
136
+ readonly options: readonly {
137
+ readonly isCorrect: boolean;
138
+ readonly label: readonly [{
139
+ readonly display: "block" | "inline";
140
+ readonly kind: "math";
141
+ readonly math: string;
142
+ } | {
143
+ readonly kind: "text";
144
+ readonly text: string;
145
+ }, ...({
146
+ readonly display: "block" | "inline";
147
+ readonly kind: "math";
148
+ readonly math: string;
149
+ } | {
150
+ readonly kind: "text";
151
+ readonly text: string;
152
+ })[]];
153
+ readonly optionKey: string;
154
+ readonly order: number;
155
+ }[];
156
+ } | {
157
+ readonly kind: "multiple-choice";
158
+ readonly options: readonly {
159
+ readonly isCorrect: boolean;
160
+ readonly label: readonly [{
161
+ readonly display: "block" | "inline";
162
+ readonly kind: "math";
163
+ readonly math: string;
164
+ } | {
165
+ readonly kind: "text";
166
+ readonly text: string;
167
+ }, ...({
168
+ readonly display: "block" | "inline";
169
+ readonly kind: "math";
170
+ readonly math: string;
171
+ } | {
172
+ readonly kind: "text";
173
+ readonly text: string;
174
+ })[]];
175
+ readonly optionKey: string;
176
+ readonly order: number;
177
+ }[];
178
+ } | {
179
+ readonly categories: readonly {
180
+ readonly categoryKey: string;
181
+ readonly label: readonly [{
182
+ readonly display: "block" | "inline";
183
+ readonly kind: "math";
184
+ readonly math: string;
185
+ } | {
186
+ readonly kind: "text";
187
+ readonly text: string;
188
+ }, ...({
189
+ readonly display: "block" | "inline";
190
+ readonly kind: "math";
191
+ readonly math: string;
192
+ } | {
193
+ readonly kind: "text";
194
+ readonly text: string;
195
+ })[]];
196
+ readonly order: number;
197
+ }[];
198
+ readonly kind: "category";
199
+ readonly statements: readonly {
200
+ readonly correctCategoryKey: string;
201
+ readonly label: readonly [{
202
+ readonly display: "block" | "inline";
203
+ readonly kind: "math";
204
+ readonly math: string;
205
+ } | {
206
+ readonly kind: "text";
207
+ readonly text: string;
208
+ }, ...({
209
+ readonly display: "block" | "inline";
210
+ readonly kind: "math";
211
+ readonly math: string;
212
+ } | {
213
+ readonly kind: "text";
214
+ readonly text: string;
215
+ })[]];
216
+ readonly order: number;
217
+ readonly statementKey: string;
218
+ }[];
219
+ };
220
+ } | {
221
+ readonly artifactLocale: ("de" | "en" | "id") & import("effect/Brand").Brand<"@NakafaAI/AksaraArtifactLocale">;
222
+ readonly contentKey: string & import("effect/Brand").Brand<"@NakafaAI/AksaraContentKey">;
223
+ readonly peerContentKey: string & import("effect/Brand").Brand<"@NakafaAI/AksaraContentKey">;
224
+ readonly questionKey: string & import("effect/Brand").Brand<"@NakafaAI/AksaraQuestionKey">;
225
+ readonly questionNumber: number;
226
+ readonly setKey: string & import("effect/Brand").Brand<"@NakafaAI/AksaraQuestionSetKey">;
227
+ readonly bodyKind: "answer";
228
+ readonly blueprint?: {
229
+ readonly cognitiveLevel: string;
230
+ readonly contentDomain: string;
231
+ readonly topic: string;
232
+ };
233
+ readonly kind: "question-body";
234
+ readonly metadata: {
235
+ readonly dateModified?: string;
236
+ readonly datePublished: string;
237
+ readonly authors: readonly {
238
+ readonly name: string;
239
+ }[];
240
+ readonly title: string;
241
+ };
242
+ readonly stimulusKey?: string;
243
+ } | {
244
+ readonly appLocale: ("de" | "en" | "id") & import("effect/Brand").Brand<"@NakafaAI/AksaraAppLocale">;
245
+ readonly artifactLocale: ("de" | "en" | "id") & import("effect/Brand").Brand<"@NakafaAI/AksaraArtifactLocale">;
246
+ readonly contentKey: string & import("effect/Brand").Brand<"@NakafaAI/AksaraContentKey">;
247
+ readonly pageKey: string & import("effect/Brand").Brand<"@NakafaAI/AksaraPageKey">;
248
+ readonly publicPath: string & import("effect/Brand").Brand<"@NakafaAI/AksaraPublicPath">;
249
+ readonly kind: "public-page";
250
+ readonly metadata: {
251
+ readonly description: string;
252
+ readonly lastModified: string;
253
+ readonly title: string;
254
+ };
255
+ readonly sitemap: true;
256
+ readonly sourcePath: string & import("effect/Brand").Brand<"@NakafaAI/AksaraCorpusSourcePath">;
257
+ }, E, R>) => Effect.Effect<{
258
+ count: number;
259
+ digest: string & import("effect/Brand").Brand<"@NakafaAI/AksaraSha256Hash">;
260
+ }, E | ProjectionHashError, R>;
261
+ export {};
@@ -0,0 +1,59 @@
1
+ import { createHash } from "node:crypto";
2
+ import { Effect, Schema, Stream } from "effect";
3
+ import { ReleaseIdSchema, Sha256HashSchema, } from "#contracts/ids";
4
+ import { canonicalizeContentProjection, } from "#contracts/projection/spec";
5
+ const CONTENT_PROJECTION_DIGEST_DOMAIN = "nakafa.aksara.content-projections";
6
+ /** SHA-256 computation failed before projection integrity was established. */
7
+ export class ProjectionHashError extends Schema.TaggedError()("ProjectionHashError", { releaseId: ReleaseIdSchema }) {
8
+ }
9
+ /** Keeps Node hash state private while exposing only canonical counters. */
10
+ class ProjectionDigestState {
11
+ #hash = createHash("sha256");
12
+ count = 0;
13
+ /** Initializes a domain-separated incremental projection hash. */
14
+ constructor() {
15
+ this.#hash.update(CONTENT_PROJECTION_DIGEST_DOMAIN);
16
+ this.#hash.update("\n");
17
+ }
18
+ /** Adds one canonical projection to this invocation-owned digest. */
19
+ update(projection) {
20
+ this.#hash.update(canonicalizeContentProjection(projection));
21
+ this.#hash.update("\n");
22
+ this.count += 1;
23
+ }
24
+ /** Consumes the hash and returns its branded immutable identity. */
25
+ digest() {
26
+ return Sha256HashSchema.make(`sha256:${this.#hash.digest("hex")}`);
27
+ }
28
+ }
29
+ /** Creates a fresh domain-separated projection digest state. */
30
+ export function createProjectionDigest(releaseId) {
31
+ return Effect.try({
32
+ catch: () => new ProjectionHashError({ releaseId }),
33
+ try: () => new ProjectionDigestState(),
34
+ });
35
+ }
36
+ /** Adds one canonical content projection to an incremental digest. */
37
+ export function updateProjectionDigest(releaseId, state, projection) {
38
+ return Effect.try({
39
+ catch: () => new ProjectionHashError({ releaseId }),
40
+ try: () => {
41
+ state.update(projection);
42
+ return state;
43
+ },
44
+ });
45
+ }
46
+ /** Finalizes one incremental projection digest with typed hash failures. */
47
+ export function finalizeProjectionDigest(releaseId, state) {
48
+ return Effect.try({
49
+ catch: () => new ProjectionHashError({ releaseId }),
50
+ try: () => state.digest(),
51
+ });
52
+ }
53
+ /** Digests a projection stream without retaining its content bodies. */
54
+ export const digestProjections = Effect.fn("AksaraContracts.digestProjections")(function* (releaseId, projections) {
55
+ const initial = yield* createProjectionDigest(releaseId);
56
+ const state = yield* projections.pipe(Stream.runFoldEffect(() => initial, (current, projection) => updateProjectionDigest(releaseId, current, projection)));
57
+ const digest = yield* finalizeProjectionDigest(releaseId, state);
58
+ return { count: state.count, digest };
59
+ });
@@ -0,0 +1,3 @@
1
+ import { type ContentProjection } from "#contracts/projection/spec";
2
+ /** Hashes one canonical projection for authoritative content-head diffing. */
3
+ export declare function hashContentProjection(projection: ContentProjection): string & import("effect/Brand").Brand<"@NakafaAI/AksaraSha256Hash">;
@@ -0,0 +1,10 @@
1
+ import { createHash } from "node:crypto";
2
+ import { Sha256HashSchema } from "#contracts/ids";
3
+ import { canonicalizeContentProjection, } from "#contracts/projection/spec";
4
+ /** Hashes one canonical projection for authoritative content-head diffing. */
5
+ export function hashContentProjection(projection) {
6
+ const digest = createHash("sha256")
7
+ .update(canonicalizeContentProjection(projection))
8
+ .digest("hex");
9
+ return Sha256HashSchema.make(`sha256:${digest}`);
10
+ }