@takosjp/yurucommu-core 3.0.0 → 3.0.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takosjp/yurucommu-core",
3
- "version": "3.0.0",
3
+ "version": "3.0.1",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -12,10 +12,29 @@
12
12
  "src/plugin",
13
13
  "src/runtime",
14
14
  "!src/backend/**/__tests__/**",
15
- "!packages/api/src/**/*.test.ts",
16
15
  "scripts/apply-takosumi-migrations.ts",
17
16
  "migrations",
18
- "packages/api/src",
17
+ "packages/api/src/index.ts",
18
+ "packages/api/src/social-server.ts",
19
+ "packages/api/src/types",
20
+ "packages/api/src/lib/api.ts",
21
+ "packages/api/src/lib/transport.ts",
22
+ "packages/api/src/lib/fetch-with-timeout.ts",
23
+ "packages/api/src/lib/api/account.ts",
24
+ "packages/api/src/lib/api/actors.ts",
25
+ "packages/api/src/lib/api/auth.ts",
26
+ "packages/api/src/lib/api/communities.ts",
27
+ "packages/api/src/lib/api/dm.ts",
28
+ "packages/api/src/lib/api/fetch.ts",
29
+ "packages/api/src/lib/api/follow.ts",
30
+ "packages/api/src/lib/api/media.ts",
31
+ "packages/api/src/lib/api/moderation.ts",
32
+ "packages/api/src/lib/api/normalize.ts",
33
+ "packages/api/src/lib/api/notifications.ts",
34
+ "packages/api/src/lib/api/posts.ts",
35
+ "packages/api/src/lib/api/recommendations.ts",
36
+ "packages/api/src/lib/api/search.ts",
37
+ "packages/api/src/lib/api/stories.ts",
19
38
  "packages/api/package.json",
20
39
  "packages/api/LICENSE"
21
40
  ],
@@ -1,67 +0,0 @@
1
- import { expect, test } from "bun:test";
2
- import { clearYurucommuApiTransport } from "../transport.ts";
3
- import type { DMMessage } from "../../types/index.ts";
4
- import { fetchUserDMMessages } from "./dm.ts";
5
-
6
- function makeMessage(id: string): DMMessage {
7
- return {
8
- id,
9
- sender: {
10
- ap_id: "https://example.com/ap/users/alice",
11
- username: "alice@example.com",
12
- preferred_username: "alice",
13
- name: "Alice",
14
- icon_url: null,
15
- },
16
- content: "hi",
17
- attachments: [],
18
- created_at: "2026-01-01T00:00:00.000Z",
19
- } as unknown as DMMessage;
20
- }
21
-
22
- async function withMockFetch<T>(
23
- responseBody: unknown,
24
- fn: () => Promise<T>,
25
- ): Promise<T> {
26
- const originalFetch = globalThis.fetch;
27
- clearYurucommuApiTransport();
28
- globalThis.fetch = ((_input: RequestInfo | URL) =>
29
- Promise.resolve(
30
- new Response(JSON.stringify(responseBody), {
31
- status: 200,
32
- headers: { "Content-Type": "application/json" },
33
- }),
34
- )) as typeof fetch;
35
- try {
36
- return await fn();
37
- } finally {
38
- globalThis.fetch = originalFetch;
39
- clearYurucommuApiTransport();
40
- }
41
- }
42
-
43
- // Regression: the DM thread's "load older" affordance depends on the client
44
- // surfacing the server's `has_more` flag (it was previously discarded).
45
- test("fetchUserDMMessages surfaces has_more as hasMore and maps messages", async () => {
46
- const result = await withMockFetch(
47
- {
48
- messages: [makeMessage("m1")],
49
- conversation_id: "conv-1",
50
- has_more: true,
51
- },
52
- () => fetchUserDMMessages("https://example.com/ap/users/alice"),
53
- );
54
-
55
- expect(result.messages.map((m) => m.id)).toEqual(["m1"]);
56
- expect(result.conversation_id).toBe("conv-1");
57
- expect(result.hasMore).toBe(true);
58
- });
59
-
60
- test("fetchUserDMMessages defaults hasMore to false when the server omits it", async () => {
61
- const result = await withMockFetch({ messages: [] }, () =>
62
- fetchUserDMMessages("https://example.com/ap/users/alice"),
63
- );
64
-
65
- expect(result.messages).toEqual([]);
66
- expect(result.hasMore).toBe(false);
67
- });
@@ -1,63 +0,0 @@
1
- import { expect, test } from "bun:test";
2
- import { clearYurucommuApiTransport } from "../transport.ts";
3
- import type { Notification } from "../../types/index.ts";
4
- import { fetchNotifications } from "./notifications.ts";
5
-
6
- function makeNotification(id: string): Notification {
7
- return {
8
- id,
9
- type: "like",
10
- actor: {
11
- ap_id: "https://example.com/ap/users/alice",
12
- username: "alice@example.com",
13
- preferred_username: "alice",
14
- name: "Alice",
15
- icon_url: null,
16
- },
17
- object_ap_id: null,
18
- read: false,
19
- created_at: "2026-01-01T00:00:00.000Z",
20
- } as unknown as Notification;
21
- }
22
-
23
- async function withMockFetch<T>(
24
- responseBody: unknown,
25
- fn: () => Promise<T>,
26
- ): Promise<T> {
27
- const originalFetch = globalThis.fetch;
28
- clearYurucommuApiTransport();
29
- globalThis.fetch = ((_input: RequestInfo | URL) =>
30
- Promise.resolve(
31
- new Response(JSON.stringify(responseBody), {
32
- status: 200,
33
- headers: { "Content-Type": "application/json" },
34
- }),
35
- )) as typeof fetch;
36
- try {
37
- return await fn();
38
- } finally {
39
- globalThis.fetch = originalFetch;
40
- clearYurucommuApiTransport();
41
- }
42
- }
43
-
44
- // Regression: the notifications "load older" affordance depends on the client
45
- // surfacing the server's `has_more` (it was previously discarded).
46
- test("fetchNotifications surfaces has_more as hasMore and maps notifications", async () => {
47
- const result = await withMockFetch(
48
- { notifications: [makeNotification("n1")], has_more: true },
49
- () => fetchNotifications({ limit: 20 }),
50
- );
51
-
52
- expect(result.notifications.map((n) => n.id)).toEqual(["n1"]);
53
- expect(result.hasMore).toBe(true);
54
- });
55
-
56
- test("fetchNotifications defaults hasMore to false when the server omits it", async () => {
57
- const result = await withMockFetch({ notifications: [] }, () =>
58
- fetchNotifications({ limit: 20 }),
59
- );
60
-
61
- expect(result.notifications).toEqual([]);
62
- expect(result.hasMore).toBe(false);
63
- });
@@ -1,110 +0,0 @@
1
- import { expect, test } from "bun:test";
2
- import { clearYurucommuApiTransport } from "../transport.ts";
3
- import type { Post } from "../../types/index.ts";
4
- import { createPost, fetchBookmarks, fetchTimeline } from "./posts.ts";
5
-
6
- function makePost(overrides: Partial<Post> = {}): Post {
7
- return {
8
- ap_id: "https://example.com/ap/objects/post-1",
9
- type: "Note",
10
- author: {
11
- ap_id: "https://example.com/ap/users/alice",
12
- username: "alice@example.com",
13
- preferred_username: "alice",
14
- name: "Alice",
15
- icon_url: null,
16
- },
17
- content: "hello",
18
- summary: null,
19
- attachments: [],
20
- in_reply_to: null,
21
- visibility: "public",
22
- community_ap_id: null,
23
- like_count: 0,
24
- reply_count: 0,
25
- announce_count: 0,
26
- published: "2026-01-01T00:00:00.000Z",
27
- edited_at: null,
28
- liked: false,
29
- bookmarked: false,
30
- reposted: false,
31
- ...overrides,
32
- };
33
- }
34
-
35
- async function withMockFetch<T>(
36
- responseBody: unknown,
37
- fn: () => Promise<T>,
38
- ): Promise<T> {
39
- const originalFetch = globalThis.fetch;
40
- clearYurucommuApiTransport();
41
- globalThis.fetch = ((_input: RequestInfo | URL, _init?: RequestInit) => {
42
- return Promise.resolve(
43
- new Response(JSON.stringify(responseBody), {
44
- status: 200,
45
- headers: { "Content-Type": "application/json" },
46
- }),
47
- );
48
- }) as typeof fetch;
49
-
50
- try {
51
- return await fn();
52
- } finally {
53
- globalThis.fetch = originalFetch;
54
- clearYurucommuApiTransport();
55
- }
56
- }
57
-
58
- test("createPost reads the current wrapped post response", async () => {
59
- const post = makePost();
60
-
61
- const result = await withMockFetch({ post }, () =>
62
- createPost({ content: "hello" }),
63
- );
64
-
65
- expect(result.ap_id).toBe(post.ap_id);
66
- });
67
-
68
- test("fetchBookmarks reads the posts + pagination fields", async () => {
69
- const post = makePost({ bookmarked: true });
70
-
71
- const result = await withMockFetch(
72
- { posts: [post], has_more: true, next_cursor: "c1" },
73
- () => fetchBookmarks(),
74
- );
75
-
76
- expect(result.posts.map((p) => p.ap_id)).toEqual([post.ap_id]);
77
- expect(result.hasMore).toBe(true);
78
- expect(result.nextCursor).toBe("c1");
79
- });
80
-
81
- // Regression: the timeline client must SURFACE the server's composite cursor (so
82
- // loadMore can echo it back as `before`). It was discarding `next_cursor` and
83
- // paginating with a post's ap_id instead, which the server decodes as a
84
- // published-only cursor whose string compare matches every row → the feed
85
- // re-serves page 1 forever and never advances.
86
- test("fetchTimeline surfaces the server next_cursor and has_more", async () => {
87
- const post = makePost();
88
- const result = await withMockFetch(
89
- {
90
- posts: [post],
91
- has_more: true,
92
- next_cursor: "2026-01-01T00:00:00.000Z\u0000" + post.ap_id,
93
- },
94
- () => fetchTimeline({ limit: 20 }),
95
- );
96
-
97
- expect(result.posts.map((p) => p.ap_id)).toEqual([post.ap_id]);
98
- expect(result.hasMore).toBe(true);
99
- expect(result.nextCursor).toBe("2026-01-01T00:00:00.000Z\u0000" + post.ap_id);
100
- });
101
-
102
- test("fetchTimeline defaults to no cursor / hasMore=false when the server omits them", async () => {
103
- const result = await withMockFetch({ posts: [] }, () =>
104
- fetchTimeline({ limit: 20 }),
105
- );
106
-
107
- expect(result.posts).toEqual([]);
108
- expect(result.hasMore).toBe(false);
109
- expect(result.nextCursor).toBe(null);
110
- });