pubky-app-specs 0.4.1 → 0.4.2
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 +150 -10
- package/example.js +230 -100
- package/index.cjs +26 -0
- package/index.js +28 -1
- package/package.json +16 -4
- package/pubky_app_specs.d.ts +19 -0
- package/pubky_app_specs_bg.wasm +0 -0
package/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# Pubky App Specs · `pubky-app-specs`
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/pubky-app-specs)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
3
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:
|
|
4
7
|
|
|
5
8
|
- **Sanitized** and **Validated** via Rust logic.
|
|
@@ -42,7 +45,7 @@ yarn add pubky-app-specs
|
|
|
42
45
|
import { PubkySpecsBuilder } from "pubky-app-specs";
|
|
43
46
|
|
|
44
47
|
// OR CommonJS
|
|
45
|
-
const { PubkySpecsBuilder } = require("pubky-app-specs
|
|
48
|
+
const { PubkySpecsBuilder } = require("pubky-app-specs");
|
|
46
49
|
|
|
47
50
|
function loadSpecs(pubkyId) {
|
|
48
51
|
// Create a specs builder instance - WASM is already initialized
|
|
@@ -83,7 +86,7 @@ async function createUser(pubkyId) {
|
|
|
83
86
|
const userJson = user.toJson();
|
|
84
87
|
|
|
85
88
|
// Store in homeserver via pubky
|
|
86
|
-
const response = await client.fetch(
|
|
89
|
+
const response = await client.fetch(meta.url, {
|
|
87
90
|
method: "PUT",
|
|
88
91
|
body: JSON.stringify(userJson),
|
|
89
92
|
credentials: "include",
|
|
@@ -93,8 +96,8 @@ async function createUser(pubkyId) {
|
|
|
93
96
|
throw new Error(`Failed to store user: ${response.statusText}`);
|
|
94
97
|
}
|
|
95
98
|
|
|
96
|
-
console.log("User stored at:",
|
|
97
|
-
return
|
|
99
|
+
console.log("User stored at:", meta.url);
|
|
100
|
+
return { user, meta };
|
|
98
101
|
}
|
|
99
102
|
```
|
|
100
103
|
|
|
@@ -105,17 +108,16 @@ import { Client } from "@synonymdev/pubky";
|
|
|
105
108
|
import { PubkySpecsBuilder, PubkyAppPostKind } from "pubky-app-specs";
|
|
106
109
|
|
|
107
110
|
async function createPost(pubkyId, content) {
|
|
108
|
-
// fileData can be a File (browser) or a raw Blob/Buffer (Node).
|
|
109
111
|
const client = new Client();
|
|
110
112
|
const specs = new PubkySpecsBuilder(pubkyId);
|
|
111
113
|
|
|
112
|
-
// Create the Post object
|
|
114
|
+
// Create the Post object
|
|
113
115
|
const {post, meta} = specs.createPost(
|
|
114
116
|
content,
|
|
115
117
|
PubkyAppPostKind.Short,
|
|
116
|
-
null, // parent post
|
|
117
|
-
null, // embed
|
|
118
|
-
null
|
|
118
|
+
null, // parent post URI (for replies)
|
|
119
|
+
null, // embed object (for reposts)
|
|
120
|
+
null // attachments (array of file URLs, max 3)
|
|
119
121
|
);
|
|
120
122
|
|
|
121
123
|
// Store the post
|
|
@@ -130,7 +132,39 @@ async function createPost(pubkyId, content) {
|
|
|
130
132
|
}
|
|
131
133
|
```
|
|
132
134
|
|
|
133
|
-
### 3)
|
|
135
|
+
### 3) Creating a Post with Attachments
|
|
136
|
+
|
|
137
|
+
```js
|
|
138
|
+
import { Client } from "@synonymdev/pubky";
|
|
139
|
+
import { PubkySpecsBuilder, PubkyAppPostKind } from "pubky-app-specs";
|
|
140
|
+
|
|
141
|
+
async function createPostWithAttachments(pubkyId, content, fileUrls) {
|
|
142
|
+
const client = new Client();
|
|
143
|
+
const specs = new PubkySpecsBuilder(pubkyId);
|
|
144
|
+
|
|
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};
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### 4) Following a User
|
|
134
168
|
|
|
135
169
|
```js
|
|
136
170
|
import { Client } from "@synonymdev/pubky";
|
|
@@ -164,9 +198,109 @@ This library supports many more domain objects beyond `User` and `Post`. Here ar
|
|
|
164
198
|
- **Mutes**: `createMute(...)`
|
|
165
199
|
- **Follows**: `createFollow(...)`
|
|
166
200
|
- **LastRead**: `createLastRead(...)`
|
|
201
|
+
- **Blobs**: `createBlob(...)`
|
|
202
|
+
- **Files**: `createFile(...)`
|
|
167
203
|
|
|
168
204
|
Each has a `meta` field for storing relevant IDs/paths and a typed data object.
|
|
169
205
|
|
|
206
|
+
### Creating a File with Blob
|
|
207
|
+
|
|
208
|
+
```js
|
|
209
|
+
import { Client } from "@synonymdev/pubky";
|
|
210
|
+
import { PubkySpecsBuilder, getValidMimeTypes } from "pubky-app-specs";
|
|
211
|
+
|
|
212
|
+
async function uploadFile(pubkyId, fileData, fileName, contentType, fileSize) {
|
|
213
|
+
const client = new Client();
|
|
214
|
+
const specs = new PubkySpecsBuilder(pubkyId);
|
|
215
|
+
|
|
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 };
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
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.
|
|
247
|
+
|
|
248
|
+
```js
|
|
249
|
+
import { getValidMimeTypes } from "pubky-app-specs";
|
|
250
|
+
|
|
251
|
+
// Get the list of valid MIME types
|
|
252
|
+
const validMimeTypes = getValidMimeTypes();
|
|
253
|
+
// Returns: ["application/javascript", "application/json", "application/pdf", "image/png", ...]
|
|
254
|
+
|
|
255
|
+
// Validate a file before upload
|
|
256
|
+
function isValidFileType(mimeType) {
|
|
257
|
+
return validMimeTypes.includes(mimeType);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Example usage
|
|
261
|
+
if (isValidFileType(file.type)) {
|
|
262
|
+
// Proceed with upload
|
|
263
|
+
} else {
|
|
264
|
+
console.error(`Invalid file type: ${file.type}`);
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
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
|
+
```
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
170
304
|
## 📌 Parsing a Pubky URI
|
|
171
305
|
|
|
172
306
|
The `parse_uri()` function converts a Pubky URI string into a strongly typed object.
|
|
@@ -193,3 +327,9 @@ A `ParsedUriResult` object with:
|
|
|
193
327
|
- **user_id:** The parsed user identifier.
|
|
194
328
|
- **resource:** A string indicating the resource type.
|
|
195
329
|
- **resource_id:** An optional resource identifier.
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## 📄 License
|
|
334
|
+
|
|
335
|
+
MIT
|
package/example.js
CHANGED
|
@@ -12,14 +12,55 @@ import {
|
|
|
12
12
|
blobUriBuilder,
|
|
13
13
|
fileUriBuilder,
|
|
14
14
|
feedUriBuilder,
|
|
15
|
+
getValidMimeTypes,
|
|
15
16
|
} from "./index.js";
|
|
16
17
|
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// ANSI color helpers for pretty output
|
|
20
|
+
// =============================================================================
|
|
21
|
+
const c = {
|
|
22
|
+
reset: "\x1b[0m",
|
|
23
|
+
bright: "\x1b[1m",
|
|
24
|
+
dim: "\x1b[2m",
|
|
25
|
+
cyan: "\x1b[36m",
|
|
26
|
+
green: "\x1b[32m",
|
|
27
|
+
yellow: "\x1b[33m",
|
|
28
|
+
blue: "\x1b[34m",
|
|
29
|
+
magenta: "\x1b[35m",
|
|
30
|
+
gray: "\x1b[90m",
|
|
31
|
+
white: "\x1b[37m",
|
|
32
|
+
bgBlue: "\x1b[44m",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const divider = () => console.log(c.gray + "─".repeat(70) + c.reset);
|
|
36
|
+
const header = (title) => {
|
|
37
|
+
console.log();
|
|
38
|
+
console.log(`${c.bright}${c.blue}${title}${c.reset}`);
|
|
39
|
+
divider();
|
|
40
|
+
};
|
|
41
|
+
const field = (label, value) => {
|
|
42
|
+
console.log(` ${c.dim}${label.padEnd(12)}${c.reset} ${c.white}${value}${c.reset}`);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// =============================================================================
|
|
46
|
+
// Setup
|
|
47
|
+
// =============================================================================
|
|
17
48
|
const OTTO = "8kkppkmiubfq4pxn6f73nqrhhhgkb5xyfprntc9si3np9ydbotto";
|
|
18
49
|
const RIO = "dzswkfy7ek3bqnoc89jxuqqfbzhjrj6mi8qthgbxxcqkdugm3rio";
|
|
19
50
|
|
|
20
|
-
|
|
21
|
-
console.log(
|
|
51
|
+
console.log();
|
|
52
|
+
console.log(`${c.bgBlue}${c.white}${c.bright} ${c.reset}`);
|
|
53
|
+
console.log(`${c.bgBlue}${c.white}${c.bright} PUBKY APP SPECS - EXAMPLES ${c.reset}`);
|
|
54
|
+
console.log(`${c.bgBlue}${c.white}${c.bright} ${c.reset}`);
|
|
55
|
+
console.log();
|
|
56
|
+
|
|
22
57
|
const specsBuilder = new PubkySpecsBuilder(OTTO);
|
|
58
|
+
console.log(`${c.dim}Using PubkyId: ${c.reset}${c.cyan}${OTTO}${c.reset}`);
|
|
59
|
+
|
|
60
|
+
// =============================================================================
|
|
61
|
+
// 1. User Profile
|
|
62
|
+
// =============================================================================
|
|
63
|
+
header("USER PROFILE");
|
|
23
64
|
const { user, meta: userMeta } = specsBuilder.createUser(
|
|
24
65
|
"Alice Smith",
|
|
25
66
|
"Software Developer",
|
|
@@ -27,12 +68,18 @@ const { user, meta: userMeta } = specsBuilder.createUser(
|
|
|
27
68
|
null,
|
|
28
69
|
"active"
|
|
29
70
|
);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
71
|
+
field("URL", userMeta.url);
|
|
72
|
+
field("Name", user.toJson().name);
|
|
73
|
+
field("Bio", user.toJson().bio);
|
|
74
|
+
field("Status", user.toJson().status);
|
|
33
75
|
|
|
34
|
-
//
|
|
35
|
-
|
|
76
|
+
// =============================================================================
|
|
77
|
+
// 2. Posts
|
|
78
|
+
// =============================================================================
|
|
79
|
+
header("POSTS");
|
|
80
|
+
|
|
81
|
+
// Simple post
|
|
82
|
+
console.log(` ${c.yellow}▸ Simple Post${c.reset}`);
|
|
36
83
|
const { post, meta } = specsBuilder.createPost(
|
|
37
84
|
"Hello, Pubky world! This is my first post.",
|
|
38
85
|
PubkyAppPostKind.Short,
|
|
@@ -40,12 +87,13 @@ const { post, meta } = specsBuilder.createPost(
|
|
|
40
87
|
null,
|
|
41
88
|
null
|
|
42
89
|
);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
console.log(
|
|
90
|
+
field("ID", meta.id);
|
|
91
|
+
field("URL", meta.url);
|
|
92
|
+
field("Content", post.toJson().content);
|
|
93
|
+
console.log();
|
|
47
94
|
|
|
48
|
-
|
|
95
|
+
// Reply post
|
|
96
|
+
console.log(` ${c.yellow}▸ Reply Post${c.reset}`);
|
|
49
97
|
const { post: replyPost, meta: replyMeta } = specsBuilder.createPost(
|
|
50
98
|
"This is a reply to the first post!",
|
|
51
99
|
PubkyAppPostKind.Short,
|
|
@@ -53,113 +101,195 @@ const { post: replyPost, meta: replyMeta } = specsBuilder.createPost(
|
|
|
53
101
|
null,
|
|
54
102
|
null
|
|
55
103
|
);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
console.log(
|
|
59
|
-
console.log("-".repeat(60));
|
|
104
|
+
field("ID", replyMeta.id);
|
|
105
|
+
field("Parent", replyPost.toJson().parent);
|
|
106
|
+
console.log();
|
|
60
107
|
|
|
61
|
-
|
|
62
|
-
|
|
108
|
+
// Repost with embed
|
|
109
|
+
console.log(` ${c.yellow}▸ Repost with Embed${c.reset}`);
|
|
110
|
+
const embed = new PubkyAppPostEmbed(
|
|
63
111
|
`pubky://${RIO}/pub/pubky.app/posts/0033SREKPC4N0`,
|
|
64
112
|
PubkyAppPostKind.Video
|
|
65
113
|
);
|
|
66
114
|
const { post: repost, meta: repostMeta } = specsBuilder.createPost(
|
|
67
|
-
"
|
|
115
|
+
"Check out this awesome video!",
|
|
68
116
|
PubkyAppPostKind.Short,
|
|
69
117
|
null,
|
|
70
|
-
|
|
118
|
+
embed,
|
|
71
119
|
null
|
|
72
120
|
);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
console.log(
|
|
121
|
+
field("ID", repostMeta.id);
|
|
122
|
+
field("Embed URI", repost.toJson().embed.uri);
|
|
123
|
+
field("Embed Kind", repost.toJson().embed.kind);
|
|
124
|
+
console.log();
|
|
77
125
|
|
|
78
|
-
|
|
79
|
-
|
|
126
|
+
// Post with attachments
|
|
127
|
+
console.log(` ${c.yellow}▸ Post with Attachments${c.reset}`);
|
|
128
|
+
const { post: postWithAttachments, meta: postWithAttachmentsMeta } = specsBuilder.createPost(
|
|
129
|
+
"Check out these photos from my trip!",
|
|
130
|
+
PubkyAppPostKind.Image,
|
|
131
|
+
null,
|
|
132
|
+
null,
|
|
133
|
+
[
|
|
134
|
+
`pubky://${OTTO}/pub/pubky.app/files/0034A0X7NJ52G`,
|
|
135
|
+
`pubky://${OTTO}/pub/pubky.app/files/0034A0X7NJ53H`,
|
|
136
|
+
]
|
|
137
|
+
);
|
|
138
|
+
field("ID", postWithAttachmentsMeta.id);
|
|
139
|
+
field("Attachments", `${postWithAttachments.toJson().attachments.length} files`);
|
|
140
|
+
|
|
141
|
+
// =============================================================================
|
|
142
|
+
// 3. Social Actions
|
|
143
|
+
// =============================================================================
|
|
144
|
+
header("SOCIAL ACTIONS");
|
|
145
|
+
|
|
146
|
+
// Bookmark
|
|
147
|
+
console.log(` ${c.yellow}▸ Bookmark${c.reset}`);
|
|
148
|
+
const { bookmark, meta: bookmarkMeta } = specsBuilder.createBookmark(
|
|
80
149
|
`pubky://${RIO}/pub/pubky.app/posts/0033SREKPC4N0`
|
|
81
150
|
);
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
console.log(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
console.log(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
console.log(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
console.log(
|
|
95
|
-
|
|
151
|
+
field("ID", bookmarkMeta.id);
|
|
152
|
+
field("URI", bookmark.toJson().uri);
|
|
153
|
+
console.log();
|
|
154
|
+
|
|
155
|
+
// Follow
|
|
156
|
+
console.log(` ${c.yellow}▸ Follow${c.reset}`);
|
|
157
|
+
const { follow, meta: followMeta } = specsBuilder.createFollow(RIO);
|
|
158
|
+
field("ID", followMeta.id);
|
|
159
|
+
field("URL", followMeta.url);
|
|
160
|
+
console.log();
|
|
161
|
+
|
|
162
|
+
// Tag
|
|
163
|
+
console.log(` ${c.yellow}▸ Tag${c.reset}`);
|
|
164
|
+
const { tag, meta: tagMeta } = specsBuilder.createTag(
|
|
96
165
|
`pubky://${OTTO}/pub/pubky.app/profile.json`,
|
|
97
|
-
"
|
|
98
|
-
);
|
|
99
|
-
console.log("Tag ID:", tagMeta.id);
|
|
100
|
-
console.log("Tag URL:", tagMeta.url);
|
|
101
|
-
console.log("Tag Data:", JSON.stringify(tag.toJson(), null, 2));
|
|
102
|
-
console.log("-".repeat(60));
|
|
103
|
-
|
|
104
|
-
console.log("🔇 Creating Mute...");
|
|
105
|
-
let { mute, meta: muteMeta } = specsBuilder.createMute(RIO);
|
|
106
|
-
console.log("Mute ID:", muteMeta.id);
|
|
107
|
-
console.log("Mute URL:", muteMeta.url);
|
|
108
|
-
console.log("Mute Data:", JSON.stringify(mute.toJson(), null, 2));
|
|
109
|
-
console.log("-".repeat(60));
|
|
110
|
-
|
|
111
|
-
console.log("📖 Creating Last Read...");
|
|
112
|
-
let { last_read, meta: lastReadMeta } = specsBuilder.createLastRead(RIO);
|
|
113
|
-
console.log("LastRead Timestamp:", lastReadMeta.url);
|
|
114
|
-
console.log("LastRead Data:", JSON.stringify(last_read.toJson(), null, 2));
|
|
115
|
-
console.log("-".repeat(60));
|
|
116
|
-
|
|
117
|
-
console.log("💾 Creating Blob...");
|
|
118
|
-
let { blob, meta: blobMeta } = specsBuilder.createBlob(
|
|
119
|
-
Array.from({ length: 8 }, () => Math.floor(Math.random() * 256))
|
|
166
|
+
"developer"
|
|
120
167
|
);
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
console.log(
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
168
|
+
field("ID", tagMeta.id);
|
|
169
|
+
field("Label", tag.toJson().label);
|
|
170
|
+
field("URI", tag.toJson().uri);
|
|
171
|
+
console.log();
|
|
172
|
+
|
|
173
|
+
// Mute
|
|
174
|
+
console.log(` ${c.yellow}▸ Mute${c.reset}`);
|
|
175
|
+
const { mute, meta: muteMeta } = specsBuilder.createMute(RIO);
|
|
176
|
+
field("ID", muteMeta.id);
|
|
177
|
+
field("URL", muteMeta.url);
|
|
178
|
+
|
|
179
|
+
// =============================================================================
|
|
180
|
+
// 4. Files & Blobs
|
|
181
|
+
// =============================================================================
|
|
182
|
+
header("FILES & BLOBS");
|
|
183
|
+
|
|
184
|
+
// Blob
|
|
185
|
+
console.log(` ${c.yellow}▸ Blob (raw data)${c.reset}`);
|
|
186
|
+
const blobData = Array.from({ length: 8 }, () => Math.floor(Math.random() * 256));
|
|
187
|
+
const { blob, meta: blobMeta } = specsBuilder.createBlob(blobData);
|
|
188
|
+
field("ID", blobMeta.id);
|
|
189
|
+
field("URL", blobMeta.url);
|
|
190
|
+
field("Size", `${blobData.length} bytes`);
|
|
191
|
+
console.log();
|
|
192
|
+
|
|
193
|
+
// File
|
|
194
|
+
console.log(` ${c.yellow}▸ File (metadata)${c.reset}`);
|
|
195
|
+
const { file, meta: fileMeta } = specsBuilder.createFile(
|
|
196
|
+
"vacation-photos.pdf",
|
|
129
197
|
blobMeta.url,
|
|
130
198
|
"application/pdf",
|
|
131
|
-
|
|
199
|
+
1024
|
|
132
200
|
);
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
201
|
+
field("ID", fileMeta.id);
|
|
202
|
+
field("Name", file.toJson().name);
|
|
203
|
+
field("Type", file.toJson().content_type);
|
|
204
|
+
field("Size", `${file.toJson().size} bytes`);
|
|
205
|
+
field("Source", file.toJson().src);
|
|
206
|
+
|
|
207
|
+
// =============================================================================
|
|
208
|
+
// 5. Feeds & LastRead
|
|
209
|
+
// =============================================================================
|
|
210
|
+
header("FEEDS & LAST READ");
|
|
211
|
+
|
|
212
|
+
// Feed
|
|
213
|
+
console.log(` ${c.yellow}▸ Custom Feed${c.reset}`);
|
|
214
|
+
const { feed, meta: feedMeta } = specsBuilder.createFeed(
|
|
215
|
+
["mountain", "hiking", "nature"],
|
|
141
216
|
"all",
|
|
142
217
|
"columns",
|
|
143
218
|
"recent",
|
|
144
219
|
"image",
|
|
145
|
-
"
|
|
220
|
+
"Outdoor Adventures"
|
|
146
221
|
);
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
console.log(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
console.log(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
222
|
+
field("ID", feedMeta.id);
|
|
223
|
+
field("Name", feed.toJson().name);
|
|
224
|
+
field("Tags", feed.toJson().feed.tags.join(", "));
|
|
225
|
+
field("Layout", feed.toJson().feed.layout);
|
|
226
|
+
field("Sort", feed.toJson().feed.sort);
|
|
227
|
+
console.log();
|
|
228
|
+
|
|
229
|
+
// LastRead
|
|
230
|
+
console.log(` ${c.yellow}▸ Last Read Marker${c.reset}`);
|
|
231
|
+
const { last_read, meta: lastReadMeta } = specsBuilder.createLastRead();
|
|
232
|
+
field("URL", lastReadMeta.url);
|
|
233
|
+
field("Timestamp", new Date(last_read.toJson().timestamp / 1000).toISOString());
|
|
234
|
+
|
|
235
|
+
// =============================================================================
|
|
236
|
+
// 6. URI Builders
|
|
237
|
+
// =============================================================================
|
|
238
|
+
header("URI BUILDERS");
|
|
239
|
+
const uris = [
|
|
240
|
+
["User", userUriBuilder(OTTO)],
|
|
241
|
+
["Post", postUriBuilder(OTTO, meta.id)],
|
|
242
|
+
["Bookmark", bookmarkUriBuilder(OTTO, bookmarkMeta.id)],
|
|
243
|
+
["Follow", followUriBuilder(OTTO, RIO)],
|
|
244
|
+
["Tag", tagUriBuilder(OTTO, tagMeta.id)],
|
|
245
|
+
["Mute", muteUriBuilder(OTTO, RIO)],
|
|
246
|
+
["LastRead", lastReadUriBuilder(OTTO)],
|
|
247
|
+
["Blob", blobUriBuilder(OTTO, blobMeta.id)],
|
|
248
|
+
["File", fileUriBuilder(OTTO, fileMeta.id)],
|
|
249
|
+
["Feed", feedUriBuilder(OTTO, feedMeta.id)],
|
|
250
|
+
];
|
|
251
|
+
uris.forEach(([name, uri]) => {
|
|
252
|
+
console.log(` ${c.dim}${name.padEnd(10)}${c.reset} ${c.cyan}${uri}${c.reset}`);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// =============================================================================
|
|
256
|
+
// 7. Valid MIME Types
|
|
257
|
+
// =============================================================================
|
|
258
|
+
header("VALID MIME TYPES");
|
|
259
|
+
const validMimeTypes = getValidMimeTypes();
|
|
260
|
+
console.log(` ${c.dim}Total types:${c.reset} ${c.bright}${validMimeTypes.length}${c.reset}`);
|
|
261
|
+
console.log();
|
|
262
|
+
|
|
263
|
+
// Group by category
|
|
264
|
+
const categories = {
|
|
265
|
+
"Images": validMimeTypes.filter(t => t.startsWith("image/")),
|
|
266
|
+
"Videos": validMimeTypes.filter(t => t.startsWith("video/")),
|
|
267
|
+
"Audio": validMimeTypes.filter(t => t.startsWith("audio/")),
|
|
268
|
+
"Documents": validMimeTypes.filter(t => t.startsWith("application/") || t.startsWith("text/")),
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
Object.entries(categories).forEach(([category, types]) => {
|
|
272
|
+
if (types.length > 0) {
|
|
273
|
+
console.log(` ${c.yellow}${category}:${c.reset}`);
|
|
274
|
+
types.forEach(type => console.log(` ${c.dim}-${c.reset} ${type}`));
|
|
275
|
+
console.log();
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// Validation example
|
|
280
|
+
console.log(` ${c.yellow}Validation Example:${c.reset}`);
|
|
281
|
+
const testTypes = ["image/png", "video/mp4", "application/x-executable"];
|
|
282
|
+
testTypes.forEach(type => {
|
|
283
|
+
const isValid = validMimeTypes.includes(type);
|
|
284
|
+
const icon = isValid ? `${c.green}[ok]${c.reset}` : `${c.magenta}[x]${c.reset}`;
|
|
285
|
+
console.log(` ${icon} ${type}`);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// =============================================================================
|
|
289
|
+
// Done!
|
|
290
|
+
// =============================================================================
|
|
291
|
+
console.log();
|
|
292
|
+
console.log(`${c.bgBlue}${c.white}${c.bright} ${c.reset}`);
|
|
293
|
+
console.log(`${c.bgBlue}${c.white}${c.bright} ALL EXAMPLES COMPLETED SUCCESSFULLY! ${c.reset}`);
|
|
294
|
+
console.log(`${c.bgBlue}${c.white}${c.bright} ${c.reset}`);
|
|
295
|
+
console.log();
|
package/index.cjs
CHANGED
|
@@ -446,6 +446,32 @@ module.exports.lastReadUriBuilder = function(author_id) {
|
|
|
446
446
|
}
|
|
447
447
|
};
|
|
448
448
|
|
|
449
|
+
/**
|
|
450
|
+
* Returns the list of valid MIME types for file attachments.
|
|
451
|
+
*
|
|
452
|
+
* This allows JavaScript consumers to validate file types before submission
|
|
453
|
+
* without having to duplicate the list.
|
|
454
|
+
*
|
|
455
|
+
* # Example (TypeScript)
|
|
456
|
+
*
|
|
457
|
+
* ```typescript
|
|
458
|
+
* import { get_valid_mime_types } from "pubky-app-specs";
|
|
459
|
+
*
|
|
460
|
+
* const validTypes = get_valid_mime_types();
|
|
461
|
+
* const fileType = "image/png";
|
|
462
|
+
* if (validTypes.includes(fileType)) {
|
|
463
|
+
* console.log("Valid file type!");
|
|
464
|
+
* }
|
|
465
|
+
* ```
|
|
466
|
+
* @returns {any[]}
|
|
467
|
+
*/
|
|
468
|
+
module.exports.getValidMimeTypes = function() {
|
|
469
|
+
const ret = wasm.getValidMimeTypes();
|
|
470
|
+
var v1 = getArrayJsValueFromWasm0(ret[0], ret[1]).slice();
|
|
471
|
+
wasm.__wbindgen_free(ret[0], ret[1] * 4, 4);
|
|
472
|
+
return v1;
|
|
473
|
+
};
|
|
474
|
+
|
|
449
475
|
/**
|
|
450
476
|
* Parses a Pubky URI and returns a strongly typed `ParsedUriResult`.
|
|
451
477
|
*
|