pubky-app-specs 0.5.0 → 0.5.1-rc1

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/README.md CHANGED
@@ -1,254 +1,409 @@
1
- # Pubky.app Data Model Specification
1
+ # Pubky App Specs · `pubky-app-specs`
2
2
 
3
- _Version 0.4.4_
3
+ [![npm version](https://img.shields.io/npm/v/pubky-app-specs)](https://www.npmjs.com/package/pubky-app-specs)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
5
 
5
- > ⚠️ **Warning: Rapid Development Phase**
6
- > This specification is in an **early development phase** and is evolving quickly. Expect frequent changes and updates as the system matures. Consider this a **v0 draft**.
7
- >
8
- > When we reach the first stable, long-term support version of the schemas, paths will adopt the format: `pubky.app/v1/` to indicate compatibility and stability.
6
+ A WASM library for building and validating structured JSON models compatible with Pubky.App social powered by [`@synonymdev/pubky`](https://www.npmjs.com/package/@synonymdev/pubky). It handles domain objects like **Users**, **Posts**, **Feeds**, **Bookmarks**, **Tags**, and more. Each object is:
9
7
 
10
- ### JS package
8
+ - **Sanitized** and **Validated** via Rust logic.
9
+ - **Auto-ID’ed** and **Auto-Pathed** according to your domain rules.
10
+ - **Exported** to JavaScript/TypeScript with minimal overhead.
11
11
 
12
- The package is available as an npm module [pubky-app-specs](https://www.npmjs.com/package/pubky-app-specs). Alternatively, you can build from source using the provided build scripts:
12
+ ## 🤔 Why Use This Crate Instead of [Manual JSONs](https://github.com/pubky/pubky-app-specs?tab=readme-ov-file#data-models)?
13
13
 
14
- ```bash
15
- cd pkg
16
- npm run build
17
- ```
14
+ - **Validation Consistency**: Ensures your app uses the same sanitization and validation rules as [Pubky indexers](https://github.com/pubky/pubky-nexus), avoiding errors.
15
+ - **Schema Versioning**: Automatically stay up-to-date with schema changes, reducing maintenance overhead.
16
+ - **Auto IDs & Paths**: Generates unique IDs, paths, and URLs according to Pubky standards.
17
+ - **Rust-to-JavaScript Compatibility**: Type-safe models that work seamlessly across Rust and JavaScript/TypeScript.
18
+ - **Future-Proof**: Easily adapt to new Pubky object types without rewriting JSON manually.
18
19
 
19
- Test with:
20
-
21
- ```bash
22
- cd pkg
23
- npm run install
24
- npm run test
25
- ```
20
+ ---
26
21
 
27
- Examples with:
22
+ ## ⚙️ Installation
28
23
 
29
24
  ```bash
30
- cd pkg
31
- npm run example
25
+ npm install pubky-app-specs
26
+ # or
27
+ yarn add pubky-app-specs
32
28
  ```
33
29
 
30
+ > **Note**: This package uses WASM with embedded bytes for automatic initialization. No manual WASM loading required - just import and use!
31
+
34
32
  ---
35
33
 
36
- ## Table of Contents
37
-
38
- - [Pubky.app Data Model Specification](#pubkyapp-data-model-specification)
39
- - [JS package](#js-package)
40
- - [Table of Contents](#table-of-contents)
41
- - [Introduction](#introduction)
42
- - [Quick Start](#quick-start)
43
- - [Concepts:](#concepts)
44
- - [Data Models](#data-models)
45
- - [PubkyAppUser](#pubkyappuser)
46
- - [PubkyAppFile](#pubkyappfile)
47
- - [PubkyAppPost](#pubkyapppost)
48
- - [PubkyAppTag](#pubkyapptag)
49
- - [PubkyAppBookmark](#pubkyappbookmark)
50
- - [PubkyAppFollow](#pubkyappfollow)
51
- - [PubkyAppFeed](#pubkyappfeed)
52
- - [Validation Rules](#validation-rules)
53
- - [Common Rules](#common-rules)
54
- - [License](#license)
34
+ ## 🚀 Quick Start
55
35
 
56
- ---
36
+ 1. **Import** the library.
37
+ 2. **Construct** a `PubkySpecsBuilder(pubkyId)` object.
38
+ 3. **Create** validated domain objects (User, Post, Tag, etc.).
39
+ 4. **Store** them on the [PubKy homeserver](https://github.com/synonymdev/pubky) or any distributed storage solution you prefer.
40
+
41
+ ### Import & Usage
57
42
 
58
- ## Introduction
43
+ ```js
44
+ // ES Modules
45
+ import { PubkySpecsBuilder } from "pubky-app-specs";
59
46
 
60
- This document specifies the data models and validation rules for the **Pubky.app** clients interactions. It defines the structure of data entities, their properties, and the validation rules to ensure data integrity and consistency. This is intended for developers building compatible libraries or clients.
47
+ // OR CommonJS
48
+ const { PubkySpecsBuilder } = require("pubky-app-specs");
61
49
 
62
- This document intents to be a faithful representation of our [Rust pubky.app models](https://github.com/pubky/pubky-app-specs/tree/main/src). If you intend to develop in Rust, use them directly. In case of disagreement between this document and the Rust implementation, the Rust implementation prevails.
50
+ function loadSpecs(pubkyId) {
51
+ // Create a specs builder instance - WASM is already initialized
52
+ const specs = new PubkySpecsBuilder(pubkyId);
53
+ return specs;
54
+ }
55
+ ```
63
56
 
64
57
  ---
65
58
 
66
- ## Quick Start
59
+ ## 🎨 Example Usage
67
60
 
68
- Pubky.app models are designed for decentralized content sharing. The system uses a combination of timestamp-based IDs and Blake3-hashed IDs encoded in Crockford Base32 to ensure unique identifiers for each entity.
61
+ Below are **succinct** examples to illustrate how to create or update data using **`pubky-app-specs`** and then **store** it with [`@synonymdev/pubky`](https://www.npmjs.com/package/@synonymdev/pubky).
69
62
 
70
- ### Concepts:
63
+ ### 1) Creating a New User
71
64
 
72
- - **Timestamp IDs** for sequential objects like posts and files.
73
- - **Hash IDs** for content-based uniqueness (e.g., tags and bookmarks).
74
- - **Validation Rules** ensure consistent and interoperable data formats.
65
+ ```js
66
+ import { Client, PublicKey } from "@synonymdev/pubky";
67
+ import { PubkySpecsBuilder } from "pubky-app-specs";
75
68
 
76
- ---
69
+ async function createUser(pubkyId) {
70
+ const client = new Client();
71
+ const specs = new PubkySpecsBuilder(pubkyId);
72
+
73
+ // Create user object with minimal fields
74
+ const { user, meta } = specs.createUser(
75
+ "Alice", // Name
76
+ "Hello from WASM", // Bio
77
+ null, // Image URL or File
78
+ null, // Links
79
+ "active", // Status
80
+ );
81
+
82
+ // meta contains { id, path, url }.
83
+ // user is the Rust "PubkyAppUser" object.
77
84
 
78
- ## Data Models
85
+ // We bring the Rust object to JS using the .toJson() method.
86
+ const userJson = user.toJson();
79
87
 
80
- ### PubkyAppUser
88
+ // Store in homeserver via pubky
89
+ const response = await client.fetch(meta.url, {
90
+ method: "PUT",
91
+ body: JSON.stringify(userJson),
92
+ credentials: "include",
93
+ });
81
94
 
82
- **Description:** Represents a user's profile information.
95
+ if (!response.ok) {
96
+ throw new Error(`Failed to store user: ${response.statusText}`);
97
+ }
83
98
 
84
- **URI:** `/pub/pubky.app/profile.json`
99
+ console.log("User stored at:", meta.url);
100
+ return { user, meta };
101
+ }
102
+ ```
85
103
 
86
- | **Field** | **Type** | **Description** | **Validation Rules** |
87
- | --------- | -------- | --------------------------------------- | -------------------------------------------------------------------------------------------- |
88
- | `name` | String | User's name. | Required. Length: 3–50 characters. Cannot be `"[DELETED]"`. |
89
- | `bio` | String | Short biography. | Optional. Maximum length: 160 characters. |
90
- | `image` | String | URL to the user's profile image. | Optional. Valid URL. Maximum length: 300 characters. |
91
- | `links` | Array | List of associated links (title + URL). | Optional. Maximum of 5 links, each with title (100 chars max) and valid URL (300 chars max). |
92
- | `status` | String | User's current status. | Optional. Maximum length: 50 characters. |
104
+ ### 2) Creating a Post
105
+
106
+ ```js
107
+ import { Client } from "@synonymdev/pubky";
108
+ import { PubkySpecsBuilder, PubkyAppPostKind } from "pubky-app-specs";
109
+
110
+ async function createPost(pubkyId, content) {
111
+ const client = new Client();
112
+ const specs = new PubkySpecsBuilder(pubkyId);
113
+
114
+ // Create the Post object
115
+ const { post, meta } = specs.createPost(
116
+ content,
117
+ PubkyAppPostKind.Short,
118
+ null, // parent post URI (for replies)
119
+ null, // embed object (for reposts)
120
+ null, // attachments (array of file URLs, max 3)
121
+ );
122
+
123
+ // Store the post
124
+ const postJson = post.toJson();
125
+ await client.fetch(meta.url, {
126
+ method: "PUT",
127
+ body: JSON.stringify(postJson),
128
+ });
129
+
130
+ console.log("Post stored at:", meta.url);
131
+ return { post, meta };
132
+ }
133
+ ```
93
134
 
94
- **Validation Notes:**
135
+ ### 3) Creating a Post with Attachments
95
136
 
96
- - Reserved keyword `[DELETED]` cannot be used for `name`.
97
- - Each `UserLink` in `links` must have a valid title and URL.
137
+ ```js
138
+ import { Client } from "@synonymdev/pubky";
139
+ import { PubkySpecsBuilder, PubkyAppPostKind } from "pubky-app-specs";
98
140
 
99
- **Example: Valid User**
141
+ async function createPostWithAttachments(pubkyId, content, fileUrls) {
142
+ const client = new Client();
143
+ const specs = new PubkySpecsBuilder(pubkyId);
100
144
 
101
- ```json
102
- {
103
- "name": "Alice",
104
- "bio": "Toxic maximalist.",
105
- "image": "pubky://user_id/pub/pubky.app/files/0000000000000",
106
- "links": [
107
- {
108
- "title": "GitHub",
109
- "url": "https://github.com/alice"
110
- }
111
- ],
112
- "status": "Exploring decentralized tech."
145
+ // Create post with attachments (max 3 allowed)
146
+ const { post, meta } = specs.createPost(
147
+ content,
148
+ PubkyAppPostKind.Image,
149
+ null, // parent
150
+ null, // embed
151
+ fileUrls, // e.g. ["pubky://user/pub/pubky.app/files/abc123"]
152
+ );
153
+
154
+ const postJson = post.toJson();
155
+ console.log("Attachments:", postJson.attachments);
156
+
157
+ await client.fetch(meta.url, {
158
+ method: "PUT",
159
+ body: JSON.stringify(postJson),
160
+ });
161
+
162
+ console.log("Post with attachments stored at:", meta.url);
163
+ return { post, meta };
113
164
  }
114
165
  ```
115
166
 
116
- ---
117
-
118
- ### PubkyAppFile
167
+ ### 4) Following a User
119
168
 
120
- **Description:** Represents a file uploaded by the user, containing its metadata, including a reference to the actual blob of the file in `src` property.
169
+ ```js
170
+ import { Client } from "@synonymdev/pubky";
171
+ import { PubkySpecsBuilder } from "pubky-app-specs";
121
172
 
122
- **URI:** `/pub/pubky.app/files/:file_id`
173
+ async function followUser(myPubkyId, userToFollow) {
174
+ const client = new Client();
175
+ const specs = new PubkySpecsBuilder(myPubkyId);
123
176
 
124
- | **Field** | **Type** | **Description** | **Validation Rules** |
125
- | -------------- | -------- | --------------------------- | ---------------------------------------------- |
126
- | `name` | String | Name of the file. | Required. Must be 1-255 characters |
127
- | `created_at` | Integer | Unix timestamp of creation. | Required. |
128
- | `src` | String | File blob URL | Required. must be a valid URL. Max length 1024 |
129
- | `content_type` | String | MIME type of the file. | Required. Valid IANA mime types |
130
- | `size` | Integer | Size of the file in bytes. | Required. Positive integer. Max size is 10Mb |
177
+ const { follow, meta } = specs.createFollow(userToFollow);
131
178
 
132
- **Validation Notes:**
179
+ // We only need to store the JSON in the homeserver
180
+ await client.fetch(meta.url, {
181
+ method: "PUT",
182
+ body: JSON.stringify(follow.toJson()),
183
+ });
133
184
 
134
- - The `file_id` in the URI must be a valid **Timestamp ID**.
185
+ console.log(`Successfully followed: ${userToFollow}`);
186
+ }
187
+ ```
135
188
 
136
189
  ---
137
190
 
138
- ### PubkyAppPost
191
+ ## 📁 Additional Models
139
192
 
140
- **Description:** Represents a user's post.
193
+ This library supports many more domain objects beyond `User` and `Post`. Here are a few more you can explore:
141
194
 
142
- **URI:** `/pub/pubky.app/posts/:post_id`
195
+ - **Feeds**: `createFeed(...)`
196
+ - **Bookmarks**: `createBookmark(...)`
197
+ - **Tags**: `createTag(...)`
198
+ - **Mutes**: `createMute(...)`
199
+ - **Follows**: `createFollow(...)`
200
+ - **LastRead**: `createLastRead(...)`
201
+ - **Blobs**: `createBlob(...)`
202
+ - **Files**: `createFile(...)`
143
203
 
144
- | **Field** | **Type** | **Description** | **Validation Rules** |
145
- | ------------- | -------- | ------------------------------------ | -------------------------------------------------------------------------- |
146
- | `content` | String | Content of the post. | Required. Max length: 2000 (short), 50000 (long). Cannot be `"[DELETED]"`. |
147
- | `kind` | String | Type of post. | Required. Must be a valid `PubkyAppPostKind` value. |
148
- | `parent` | String | URI of the parent post (if a reply). | Optional. Must be a valid URI if present. |
149
- | `embed` | Object | Reposted content (type + URI). | Optional. URI must be valid if present. |
150
- | `attachments` | Array | List of attachment URIs. | Optional. Each must be a valid URI. |
204
+ Each has a `meta` field for storing relevant IDs/paths and a typed data object.
151
205
 
152
- **Post Kinds:**
206
+ ### Creating a File with Blob
153
207
 
154
- - `short`
155
- - `long`
156
- - `image`
157
- - `video`
158
- - `link`
159
- - `file`
208
+ ```js
209
+ import { Client } from "@synonymdev/pubky";
210
+ import { PubkySpecsBuilder, getValidMimeTypes } from "pubky-app-specs";
160
211
 
161
- **Example: Valid Post**
212
+ async function uploadFile(pubkyId, fileData, fileName, contentType, fileSize) {
213
+ const client = new Client();
214
+ const specs = new PubkySpecsBuilder(pubkyId);
162
215
 
163
- ```json
164
- {
165
- "content": "Hello world! This is my first post.",
166
- "kind": "short",
167
- "parent": null,
168
- "embed": {
169
- "kind": "short",
170
- "uri": "pubky://user_id/pub/pubky.app/posts/0000000000000"
171
- },
172
- "attachments": ["pubky://user_id/pub/pubky.app/files/0000000000000"]
216
+ // First, create and store the blob (raw binary data)
217
+ const { blob, meta: blobMeta } = specs.createBlob(fileData);
218
+
219
+ await client.fetch(blobMeta.url, {
220
+ method: "PUT",
221
+ body: JSON.stringify(blob.toJson()),
222
+ });
223
+
224
+ // Then create the file metadata pointing to the blob
225
+ const { file, meta: fileMeta } = specs.createFile(
226
+ fileName, // e.g. "vacation-photo.jpg"
227
+ blobMeta.url, // Reference to the blob
228
+ contentType, // e.g. "image/jpeg"
229
+ fileSize, // Size in bytes
230
+ );
231
+
232
+ await client.fetch(fileMeta.url, {
233
+ method: "PUT",
234
+ body: JSON.stringify(file.toJson()),
235
+ });
236
+
237
+ console.log("File stored at:", fileMeta.url);
238
+ return { file, meta: fileMeta };
173
239
  }
174
240
  ```
175
241
 
176
242
  ---
177
243
 
178
- ### PubkyAppTag
244
+ ## ✅ Validating File MIME Types
245
+
246
+ Use `getValidMimeTypes()` to get the list of allowed MIME types for file attachments. This helps validate files before upload without duplicating the validation list.
179
247
 
180
- **Description:** Represents a tag applied to a URI.
248
+ ```js
249
+ import { getValidMimeTypes } from "pubky-app-specs";
181
250
 
182
- **URI:** `/pub/pubky.app/tags/:tag_id`
251
+ // Get the list of valid MIME types
252
+ const validMimeTypes = getValidMimeTypes();
253
+ // Returns: ["application/javascript", "application/json", "application/pdf", "image/png", ...]
183
254
 
184
- | **Field** | **Type** | **Description** | **Validation Rules** |
185
- | ------------ | -------- | --------------------------- | -------------------------------------------------------- |
186
- | `uri` | String | URI of the tagged object. | Required. Must be a valid URI. |
187
- | `label` | String | Label for the tag. | Required. Trimmed, lowercase. Max length: 20 characters. |
188
- | `created_at` | Integer | Unix timestamp of creation. | Required. |
255
+ // Validate a file before upload
256
+ function isValidFileType(mimeType) {
257
+ return validMimeTypes.includes(mimeType);
258
+ }
189
259
 
190
- **Validation Notes:**
260
+ // Example usage
261
+ if (isValidFileType(file.type)) {
262
+ // Proceed with upload
263
+ } else {
264
+ console.error(`Invalid file type: ${file.type}`);
265
+ }
266
+ ```
191
267
 
192
- - The `tag_id` is a **Hash ID** derived from the `uri` and `label`.
268
+ ## 🔗 URI Builder Utilities
269
+
270
+ These helper functions construct properly formatted Pubky URIs:
271
+
272
+ ```js
273
+ import {
274
+ userUriBuilder,
275
+ postUriBuilder,
276
+ bookmarkUriBuilder,
277
+ followUriBuilder,
278
+ tagUriBuilder,
279
+ muteUriBuilder,
280
+ lastReadUriBuilder,
281
+ blobUriBuilder,
282
+ fileUriBuilder,
283
+ feedUriBuilder,
284
+ } from "pubky-app-specs";
285
+
286
+ const userId = "8kkppkmiubfq4pxn6f73nqrhhhgkb5xyfprntc9si3np9ydbotto";
287
+ const targetUserId = "dzswkfy7ek3bqnoc89jxuqqfbzhjrj6mi8qthgbxxcqkdugm3rio";
288
+
289
+ // Build URIs for different resources
290
+ userUriBuilder(userId); // pubky://{userId}/pub/pubky.app/profile.json
291
+ postUriBuilder(userId, "0033SSE3B1FQ0"); // pubky://{userId}/pub/pubky.app/posts/{postId}
292
+ bookmarkUriBuilder(userId, "ABC123"); // pubky://{userId}/pub/pubky.app/bookmarks/{bookmarkId}
293
+ followUriBuilder(userId, targetUserId); // pubky://{userId}/pub/pubky.app/follows/{targetUserId}
294
+ tagUriBuilder(userId, "XYZ789"); // pubky://{userId}/pub/pubky.app/tags/{tagId}
295
+ muteUriBuilder(userId, targetUserId); // pubky://{userId}/pub/pubky.app/mutes/{targetUserId}
296
+ lastReadUriBuilder(userId); // pubky://{userId}/pub/pubky.app/last_read
297
+ blobUriBuilder(userId, "BLOB123"); // pubky://{userId}/pub/pubky.app/blobs/{blobId}
298
+ fileUriBuilder(userId, "FILE456"); // pubky://{userId}/pub/pubky.app/files/{fileId}
299
+ feedUriBuilder(userId, "FEED789"); // pubky://{userId}/pub/pubky.app/feeds/{feedId}
300
+ ```
193
301
 
194
302
  ---
195
303
 
196
- ### PubkyAppBookmark
304
+ ## 📌 Parsing a Pubky URI
305
+
306
+ The `parse_uri()` function converts a Pubky URI string into a strongly typed object.
197
307
 
198
- **Description:** Represents a bookmark to a URI.
308
+ **Usage:**
199
309
 
200
- **URI:** `/pub/pubky.app/bookmarks/:bookmark_id`
310
+ ```js
311
+ import { parse_uri } from "pubky-app-specs";
312
+
313
+ try {
314
+ const result = parse_uri("pubky://userID/pub/pubky.app/posts/postID");
315
+ console.log(result.user_id); // "userID"
316
+ console.log(result.resource); // e.g. "posts"
317
+ console.log(result.resource_id); // "postID" or null
318
+ } catch (error) {
319
+ console.error("URI parse error:", error);
320
+ }
321
+ ```
201
322
 
202
- | **Field** | **Type** | **Description** | **Validation Rules** |
203
- | ------------ | -------- | ---------------------- | ------------------------------ |
204
- | `uri` | String | URI of the bookmark. | Required. Must be a valid URI. |
205
- | `created_at` | Integer | Timestamp of creation. | Required. |
323
+ **Returns:**
206
324
 
207
- **Validation Notes:**
325
+ A `ParsedUriResult` object with:
208
326
 
209
- - The `bookmark_id` is a **Hash ID** derived from the `uri`.
327
+ - **user_id:** The parsed user identifier.
328
+ - **resource:** A string indicating the resource type.
329
+ - **resource_id:** An optional resource identifier.
210
330
 
211
331
  ---
212
332
 
213
- ### PubkyAppFollow
333
+ ## Validation limits
214
334
 
215
- **Description:** Represents a follow relationship.
335
+ Validation limits are published as JSON so UIs and tests can reuse the
336
+ canonical rules without WASM, plus optional WASM accessors when needed.
216
337
 
217
- **URI:** `/pub/pubky.app/follows/:user_id`
338
+ ### ✅ Recommended (no WASM)
218
339
 
219
- | **Field** | **Type** | **Description** | **Validation Rules** |
220
- | ------------ | -------- | ---------------------- | -------------------- |
221
- | `created_at` | Integer | Timestamp of creation. | Required. |
340
+ **Named export from the package root:**
222
341
 
223
- ---
342
+ ```js
343
+ import { validationLimits, getValidationLimits } from "pubky-app-specs";
224
344
 
225
- ### PubkyAppFeed
345
+ console.log(validationLimits);
346
+ const copy = getValidationLimits();
347
+ ```
226
348
 
227
- **Description:** Represents a feed configuration.
349
+ **Direct subpath import (ESM):**
228
350
 
229
- **URI:** `/pub/pubky.app/feeds/:feed_id`
351
+ ```js
352
+ import limits from "pubky-app-specs/validationLimits";
353
+ // or
354
+ import limitsJson from "pubky-app-specs/validationLimits.json";
355
+ ```
230
356
 
231
- | **Field** | **Type** | **Description** | **Validation Rules** |
232
- | --------- | -------- | ----------------------------------------- | ---------------------------------- |
233
- | `tags` | Array | List of tags for filtering. | Optional. Strings must be trimmed. |
234
- | `reach` | String | Feed visibility (e.g., `all`, `friends`). | Required. Must be a valid reach. |
235
- | `layout` | String | Feed layout style (e.g., `columns`). | Required. Must be valid layout. |
236
- | `sort` | String | Sort order (e.g., `recent`). | Required. Must be valid sort. |
237
- | `content` | String | Type of content filtered. | Optional. |
238
- | `name` | String | Name of the feed. | Required. |
357
+ **Direct subpath import (CJS):**
239
358
 
240
- ---
359
+ ```js
360
+ const { validationLimits, getValidationLimits } = require("pubky-app-specs");
361
+ // or
362
+ const limits = require("pubky-app-specs/validationLimits");
363
+ ```
364
+
365
+ ### WASM accessors
241
366
 
242
- ## Validation Rules
367
+ ```js
368
+ import { PubkySpecsBuilder, getValidationLimits } from "pubky-app-specs";
243
369
 
244
- ### Common Rules
370
+ const limitsFromWasm = getValidationLimits();
245
371
 
246
- 1. **Timestamp IDs:** 13-character Crockford Base32 strings derived from timestamps (in microseconds).
247
- 2. **Hash IDs:** First half of the bytes from the resulting Blake3-hashed strings encoded in Crockford Base32.
248
- 3. **URLs:** All URLs must pass standard validation.
372
+ const builder = new PubkySpecsBuilder("pubky_id_here");
373
+ const limitsFromBuilder = builder.validationLimits;
374
+ ```
375
+
376
+ Example output shape:
377
+
378
+ ```json
379
+ {
380
+ "maxBlobSizeBytes": 104857600,
381
+ "maxFileSizeBytes": 104857600,
382
+ "tagLabelMinLength": 1,
383
+ "tagLabelMaxLength": 20,
384
+ "tagInvalidChars": [",", ":", " ", "\t", "\n", "\r"],
385
+ "userNameMinLength": 3,
386
+ "userNameMaxLength": 50,
387
+ "userBioMaxLength": 160,
388
+ "userImageUrlMaxLength": 300,
389
+ "userLinksMaxCount": 5,
390
+ "userLinkTitleMaxLength": 100,
391
+ "userLinkUrlMaxLength": 300,
392
+ "userStatusMaxLength": 50,
393
+ "postShortContentMaxLength": 2000,
394
+ "postLongContentMaxLength": 50000,
395
+ "postAttachmentsMaxCount": 4,
396
+ "postAttachmentUrlMaxLength": 200,
397
+ "postAllowedAttachmentProtocols": ["pubky", "http", "https"],
398
+ "fileNameMinLength": 1,
399
+ "fileNameMaxLength": 255,
400
+ "fileSrcMaxLength": 1024,
401
+ "feedTagsMaxCount": 5
402
+ }
403
+ ```
249
404
 
250
405
  ---
251
406
 
252
- ## License
407
+ ## 📄 License
253
408
 
254
- This specification is released under the MIT License.
409
+ MIT