@elisym/sdk 0.18.1 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -41,10 +41,14 @@ var RELAYS = [
41
41
  "wss://relay.snort.social"
42
42
  ];
43
43
  var KIND_APP_HANDLER = 31990;
44
+ var KIND_LONG_FORM_ARTICLE = 30023;
44
45
  var KIND_JOB_REQUEST_BASE = 5e3;
45
46
  var KIND_JOB_RESULT_BASE = 6e3;
46
47
  var KIND_JOB_FEEDBACK = 7e3;
47
48
  var DEFAULT_KIND_OFFSET = 100;
49
+ var POLICY_T_TAG = "elisym-policy";
50
+ var POLICY_D_TAG_PREFIX = "elisym-policy-";
51
+ var POLICY_TYPE_REGEX = /^[a-z0-9](?:[a-z0-9-]{0,30}[a-z0-9])?$/;
48
52
  var KIND_JOB_REQUEST = KIND_JOB_REQUEST_BASE + DEFAULT_KIND_OFFSET;
49
53
  var KIND_JOB_RESULT = KIND_JOB_RESULT_BASE + DEFAULT_KIND_OFFSET;
50
54
  function jobRequestKind(offset) {
@@ -97,7 +101,13 @@ var LIMITS = {
97
101
  MAX_CAPABILITIES: 20,
98
102
  MAX_DESCRIPTION_LENGTH: 500,
99
103
  MAX_AGENT_NAME_LENGTH: 64,
100
- MAX_CAPABILITY_LENGTH: 64
104
+ MAX_CAPABILITY_LENGTH: 64,
105
+ MAX_POLICY_CONTENT_LENGTH: 5e4,
106
+ MAX_POLICIES_PER_AGENT: 12,
107
+ MAX_POLICY_TYPE_LENGTH: 32,
108
+ MAX_POLICY_TITLE_LENGTH: 120,
109
+ MAX_POLICY_SUMMARY_LENGTH: 280,
110
+ MAX_POLICY_VERSION_LENGTH: 32
101
111
  };
102
112
  function getConfigDecoder() {
103
113
  return kit.getStructDecoder([
@@ -2725,6 +2735,184 @@ var PingService = class _PingService {
2725
2735
  await this.pool.publishAll(pongEvent);
2726
2736
  }
2727
2737
  };
2738
+ function dTagFor(type) {
2739
+ return `${POLICY_D_TAG_PREFIX}${type}`;
2740
+ }
2741
+ function validatePolicyInput(input) {
2742
+ if (!POLICY_TYPE_REGEX.test(input.type)) {
2743
+ throw new Error(
2744
+ `Invalid policy type "${input.type}". Must be lowercase ASCII + hyphen, 1-${LIMITS.MAX_POLICY_TYPE_LENGTH} chars, no leading/trailing hyphen.`
2745
+ );
2746
+ }
2747
+ if (input.version.length === 0 || input.version.length > LIMITS.MAX_POLICY_VERSION_LENGTH) {
2748
+ throw new Error(
2749
+ `Policy version must be 1-${LIMITS.MAX_POLICY_VERSION_LENGTH} chars (got ${input.version.length}).`
2750
+ );
2751
+ }
2752
+ if (input.title.length === 0 || input.title.length > LIMITS.MAX_POLICY_TITLE_LENGTH) {
2753
+ throw new Error(
2754
+ `Policy title must be 1-${LIMITS.MAX_POLICY_TITLE_LENGTH} chars (got ${input.title.length}).`
2755
+ );
2756
+ }
2757
+ if (input.summary !== void 0 && input.summary.length > LIMITS.MAX_POLICY_SUMMARY_LENGTH) {
2758
+ throw new Error(
2759
+ `Policy summary too long: ${input.summary.length} chars (max ${LIMITS.MAX_POLICY_SUMMARY_LENGTH}).`
2760
+ );
2761
+ }
2762
+ if (input.content.length === 0) {
2763
+ throw new Error("Policy content cannot be empty.");
2764
+ }
2765
+ if (input.content.length > LIMITS.MAX_POLICY_CONTENT_LENGTH) {
2766
+ throw new Error(
2767
+ `Policy content too long: ${input.content.length} chars (max ${LIMITS.MAX_POLICY_CONTENT_LENGTH}).`
2768
+ );
2769
+ }
2770
+ }
2771
+ function parsePolicyEvent(event) {
2772
+ if (!nostrTools.verifyEvent(event)) {
2773
+ return null;
2774
+ }
2775
+ if (event.kind !== KIND_LONG_FORM_ARTICLE) {
2776
+ return null;
2777
+ }
2778
+ if (!event.content || event.content.length > LIMITS.MAX_POLICY_CONTENT_LENGTH) {
2779
+ return null;
2780
+ }
2781
+ const dTag = event.tags.find((tag) => tag[0] === "d")?.[1];
2782
+ if (!dTag || !dTag.startsWith(POLICY_D_TAG_PREFIX)) {
2783
+ return null;
2784
+ }
2785
+ const type = event.tags.find((tag) => tag[0] === "policy_type")?.[1];
2786
+ if (!type || !POLICY_TYPE_REGEX.test(type)) {
2787
+ return null;
2788
+ }
2789
+ const version = event.tags.find((tag) => tag[0] === "policy_version")?.[1];
2790
+ if (!version || version.length > LIMITS.MAX_POLICY_VERSION_LENGTH) {
2791
+ return null;
2792
+ }
2793
+ const title = event.tags.find((tag) => tag[0] === "title")?.[1];
2794
+ if (!title || title.length > LIMITS.MAX_POLICY_TITLE_LENGTH) {
2795
+ return null;
2796
+ }
2797
+ const summaryTag = event.tags.find((tag) => tag[0] === "summary")?.[1];
2798
+ const summary = summaryTag !== void 0 && summaryTag.length <= LIMITS.MAX_POLICY_SUMMARY_LENGTH ? summaryTag : void 0;
2799
+ const naddr = nostrTools.nip19.naddrEncode({
2800
+ kind: KIND_LONG_FORM_ARTICLE,
2801
+ pubkey: event.pubkey,
2802
+ identifier: dTag,
2803
+ relays: []
2804
+ });
2805
+ return {
2806
+ type,
2807
+ version,
2808
+ title,
2809
+ summary,
2810
+ content: event.content,
2811
+ naddr,
2812
+ dTag,
2813
+ publishedAt: event.created_at,
2814
+ eventId: event.id,
2815
+ authorPubkey: event.pubkey
2816
+ };
2817
+ }
2818
+ var PoliciesService = class {
2819
+ constructor(pool) {
2820
+ this.pool = pool;
2821
+ }
2822
+ /**
2823
+ * Fetch all elisym policies published by `pubkey`. Verifies signatures,
2824
+ * dedupes by d-tag (latest `created_at` wins), and returns sorted by `type`
2825
+ * for deterministic UI rendering.
2826
+ *
2827
+ * Returns `[]` on empty result. Network errors propagate to the caller.
2828
+ */
2829
+ async fetchPolicies(pubkey) {
2830
+ const filter = {
2831
+ kinds: [KIND_LONG_FORM_ARTICLE],
2832
+ authors: [pubkey],
2833
+ "#t": [POLICY_T_TAG]
2834
+ };
2835
+ const events = await this.pool.querySync(filter);
2836
+ const latestByDTag = /* @__PURE__ */ new Map();
2837
+ for (const event of events) {
2838
+ const dTag = event.tags.find((tag) => tag[0] === "d")?.[1] ?? "";
2839
+ const prev = latestByDTag.get(dTag);
2840
+ if (!prev || event.created_at > prev.created_at) {
2841
+ latestByDTag.set(dTag, event);
2842
+ }
2843
+ }
2844
+ const policies = [];
2845
+ for (const event of latestByDTag.values()) {
2846
+ const parsed = parsePolicyEvent(event);
2847
+ if (parsed) {
2848
+ policies.push(parsed);
2849
+ }
2850
+ }
2851
+ policies.sort((a, b) => a.type.localeCompare(b.type));
2852
+ return policies;
2853
+ }
2854
+ /**
2855
+ * Publish a policy as a NIP-23 long-form article (kind 30023). Replaces any
2856
+ * existing policy of the same `type` for this pubkey via the addressable
2857
+ * `(kind, pubkey, d-tag)` slot.
2858
+ */
2859
+ async publishPolicy(identity, input) {
2860
+ validatePolicyInput(input);
2861
+ const dTag = dTagFor(input.type);
2862
+ const tags = [
2863
+ ["d", dTag],
2864
+ ["t", POLICY_T_TAG],
2865
+ ["title", input.title],
2866
+ ["policy_type", input.type],
2867
+ ["policy_version", input.version]
2868
+ ];
2869
+ if (input.summary) {
2870
+ tags.push(["summary", input.summary]);
2871
+ }
2872
+ const event = nostrTools.finalizeEvent(
2873
+ {
2874
+ kind: KIND_LONG_FORM_ARTICLE,
2875
+ created_at: Math.floor(Date.now() / 1e3),
2876
+ tags,
2877
+ content: input.content
2878
+ },
2879
+ identity.secretKey
2880
+ );
2881
+ await this.pool.publishAll(event);
2882
+ const naddr = nostrTools.nip19.naddrEncode({
2883
+ kind: KIND_LONG_FORM_ARTICLE,
2884
+ pubkey: event.pubkey,
2885
+ identifier: dTag,
2886
+ relays: []
2887
+ });
2888
+ return { eventId: event.id, naddr };
2889
+ }
2890
+ /**
2891
+ * Tombstone a policy by publishing an empty replacement under the same
2892
+ * `(kind, pubkey, d-tag)` slot. Readers skip events with empty content.
2893
+ */
2894
+ async deletePolicy(identity, type) {
2895
+ if (!POLICY_TYPE_REGEX.test(type)) {
2896
+ throw new Error(`Invalid policy type "${type}".`);
2897
+ }
2898
+ const dTag = dTagFor(type);
2899
+ const event = nostrTools.finalizeEvent(
2900
+ {
2901
+ kind: KIND_LONG_FORM_ARTICLE,
2902
+ created_at: Math.floor(Date.now() / 1e3),
2903
+ tags: [
2904
+ ["d", dTag],
2905
+ ["t", POLICY_T_TAG],
2906
+ ["policy_type", type]
2907
+ ],
2908
+ content: ""
2909
+ },
2910
+ identity.secretKey
2911
+ );
2912
+ await this.pool.publishAll(event);
2913
+ return event.id;
2914
+ }
2915
+ };
2728
2916
 
2729
2917
  // src/primitives/bounded-set.ts
2730
2918
  var BoundedSet = class {
@@ -3033,6 +3221,7 @@ var ElisymClient = class {
3033
3221
  marketplace;
3034
3222
  ping;
3035
3223
  media;
3224
+ policies;
3036
3225
  payment;
3037
3226
  constructor(config = {}) {
3038
3227
  this.pool = new NostrPool(config.relays ?? RELAYS);
@@ -3040,6 +3229,7 @@ var ElisymClient = class {
3040
3229
  this.marketplace = new MarketplaceService(this.pool);
3041
3230
  this.ping = new PingService(this.pool);
3042
3231
  this.media = new MediaService(config.uploadUrl);
3232
+ this.policies = new PoliciesService(this.pool);
3043
3233
  this.payment = config.payment ?? new SolanaPaymentStrategy();
3044
3234
  }
3045
3235
  close() {
@@ -3613,6 +3803,7 @@ exports.KIND_JOB_REQUEST = KIND_JOB_REQUEST;
3613
3803
  exports.KIND_JOB_REQUEST_BASE = KIND_JOB_REQUEST_BASE;
3614
3804
  exports.KIND_JOB_RESULT = KIND_JOB_RESULT;
3615
3805
  exports.KIND_JOB_RESULT_BASE = KIND_JOB_RESULT_BASE;
3806
+ exports.KIND_LONG_FORM_ARTICLE = KIND_LONG_FORM_ARTICLE;
3616
3807
  exports.KIND_PING = KIND_PING;
3617
3808
  exports.KIND_PONG = KIND_PONG;
3618
3809
  exports.KNOWN_ASSETS = KNOWN_ASSETS;
@@ -3622,9 +3813,13 @@ exports.MarketplaceService = MarketplaceService;
3622
3813
  exports.MediaService = MediaService;
3623
3814
  exports.NATIVE_SOL = NATIVE_SOL;
3624
3815
  exports.NostrPool = NostrPool;
3816
+ exports.POLICY_D_TAG_PREFIX = POLICY_D_TAG_PREFIX;
3817
+ exports.POLICY_TYPE_REGEX = POLICY_TYPE_REGEX;
3818
+ exports.POLICY_T_TAG = POLICY_T_TAG;
3625
3819
  exports.PROTOCOL_PROGRAM_ID_DEVNET = PROTOCOL_PROGRAM_ID_DEVNET;
3626
3820
  exports.PaymentRequestSchema = PaymentRequestSchema;
3627
3821
  exports.PingService = PingService;
3822
+ exports.PoliciesService = PoliciesService;
3628
3823
  exports.RELAYS = RELAYS;
3629
3824
  exports.SECRET_REDACT_PATHS = SECRET_REDACT_PATHS;
3630
3825
  exports.SessionSpendLimitEntrySchema = SessionSpendLimitEntrySchema;