@thomasfosterau/pulpit 0.0.0-stage → 0.1.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 (143) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1272 -2
  3. package/dist/blobs.d.ts +249 -0
  4. package/dist/blobs.d.ts.map +1 -0
  5. package/dist/blobs.js +193 -0
  6. package/dist/blobs.js.map +1 -0
  7. package/dist/budgets.d.ts +255 -0
  8. package/dist/budgets.d.ts.map +1 -0
  9. package/dist/budgets.js +211 -0
  10. package/dist/budgets.js.map +1 -0
  11. package/dist/cache.d.ts +154 -0
  12. package/dist/cache.d.ts.map +1 -0
  13. package/dist/cache.js +118 -0
  14. package/dist/cache.js.map +1 -0
  15. package/dist/collections.d.ts +242 -0
  16. package/dist/collections.d.ts.map +1 -0
  17. package/dist/collections.js +215 -0
  18. package/dist/collections.js.map +1 -0
  19. package/dist/comments.d.ts +212 -0
  20. package/dist/comments.d.ts.map +1 -0
  21. package/dist/comments.js +192 -0
  22. package/dist/comments.js.map +1 -0
  23. package/dist/content.d.ts +231 -0
  24. package/dist/content.d.ts.map +1 -0
  25. package/dist/content.js +204 -0
  26. package/dist/content.js.map +1 -0
  27. package/dist/documents.d.ts +184 -0
  28. package/dist/documents.d.ts.map +1 -0
  29. package/dist/documents.js +190 -0
  30. package/dist/documents.js.map +1 -0
  31. package/dist/edges.d.ts +645 -0
  32. package/dist/edges.d.ts.map +1 -0
  33. package/dist/edges.js +558 -0
  34. package/dist/edges.js.map +1 -0
  35. package/dist/filter.d.ts +461 -0
  36. package/dist/filter.d.ts.map +1 -0
  37. package/dist/filter.js +387 -0
  38. package/dist/filter.js.map +1 -0
  39. package/dist/index.d.ts +133 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +133 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/internal/filter-columns.d.ts +8 -0
  44. package/dist/internal/filter-columns.d.ts.map +1 -0
  45. package/dist/internal/filter-columns.js +43 -0
  46. package/dist/internal/filter-columns.js.map +1 -0
  47. package/dist/internal/list-order.d.ts +26 -0
  48. package/dist/internal/list-order.d.ts.map +1 -0
  49. package/dist/internal/list-order.js +92 -0
  50. package/dist/internal/list-order.js.map +1 -0
  51. package/dist/internal/owned-columns.d.ts +93 -0
  52. package/dist/internal/owned-columns.d.ts.map +1 -0
  53. package/dist/internal/owned-columns.js +98 -0
  54. package/dist/internal/owned-columns.js.map +1 -0
  55. package/dist/internal/plain-rows.d.ts +32 -0
  56. package/dist/internal/plain-rows.d.ts.map +1 -0
  57. package/dist/internal/plain-rows.js +32 -0
  58. package/dist/internal/plain-rows.js.map +1 -0
  59. package/dist/internal/reference-columns.d.ts +80 -0
  60. package/dist/internal/reference-columns.d.ts.map +1 -0
  61. package/dist/internal/reference-columns.js +102 -0
  62. package/dist/internal/reference-columns.js.map +1 -0
  63. package/dist/moderation.d.ts +392 -0
  64. package/dist/moderation.d.ts.map +1 -0
  65. package/dist/moderation.js +375 -0
  66. package/dist/moderation.js.map +1 -0
  67. package/dist/paths.d.ts +204 -0
  68. package/dist/paths.d.ts.map +1 -0
  69. package/dist/paths.js +165 -0
  70. package/dist/paths.js.map +1 -0
  71. package/dist/proposals.d.ts +523 -0
  72. package/dist/proposals.d.ts.map +1 -0
  73. package/dist/proposals.js +505 -0
  74. package/dist/proposals.js.map +1 -0
  75. package/dist/publication.d.ts +148 -0
  76. package/dist/publication.d.ts.map +1 -0
  77. package/dist/publication.js +183 -0
  78. package/dist/publication.js.map +1 -0
  79. package/dist/purge.d.ts +224 -0
  80. package/dist/purge.d.ts.map +1 -0
  81. package/dist/purge.js +151 -0
  82. package/dist/purge.js.map +1 -0
  83. package/dist/reactions.d.ts +246 -0
  84. package/dist/reactions.d.ts.map +1 -0
  85. package/dist/reactions.js +183 -0
  86. package/dist/reactions.js.map +1 -0
  87. package/dist/registry.d.ts +316 -0
  88. package/dist/registry.d.ts.map +1 -0
  89. package/dist/registry.js +305 -0
  90. package/dist/registry.js.map +1 -0
  91. package/dist/search.d.ts +366 -0
  92. package/dist/search.d.ts.map +1 -0
  93. package/dist/search.js +273 -0
  94. package/dist/search.js.map +1 -0
  95. package/dist/social.d.ts +311 -0
  96. package/dist/social.d.ts.map +1 -0
  97. package/dist/social.js +205 -0
  98. package/dist/social.js.map +1 -0
  99. package/dist/subtype.d.ts +1613 -0
  100. package/dist/subtype.d.ts.map +1 -0
  101. package/dist/subtype.js +1300 -0
  102. package/dist/subtype.js.map +1 -0
  103. package/dist/testing.d.ts +675 -0
  104. package/dist/testing.d.ts.map +1 -0
  105. package/dist/testing.js +1022 -0
  106. package/dist/testing.js.map +1 -0
  107. package/dist/version-chain.d.ts +702 -0
  108. package/dist/version-chain.d.ts.map +1 -0
  109. package/dist/version-chain.js +489 -0
  110. package/dist/version-chain.js.map +1 -0
  111. package/dist/working-copy.d.ts +116 -0
  112. package/dist/working-copy.d.ts.map +1 -0
  113. package/dist/working-copy.js +95 -0
  114. package/dist/working-copy.js.map +1 -0
  115. package/package.json +145 -3
  116. package/src/blobs.ts +384 -0
  117. package/src/budgets.ts +338 -0
  118. package/src/cache.ts +218 -0
  119. package/src/collections.ts +427 -0
  120. package/src/comments.ts +392 -0
  121. package/src/content.ts +238 -0
  122. package/src/documents.ts +284 -0
  123. package/src/edges.ts +1185 -0
  124. package/src/filter.ts +523 -0
  125. package/src/index.ts +153 -0
  126. package/src/internal/filter-columns.ts +50 -0
  127. package/src/internal/list-order.ts +117 -0
  128. package/src/internal/owned-columns.ts +140 -0
  129. package/src/internal/plain-rows.ts +54 -0
  130. package/src/internal/reference-columns.ts +156 -0
  131. package/src/moderation.ts +728 -0
  132. package/src/paths.ts +267 -0
  133. package/src/proposals.ts +1014 -0
  134. package/src/publication.ts +225 -0
  135. package/src/purge.ts +313 -0
  136. package/src/reactions.ts +347 -0
  137. package/src/registry.ts +524 -0
  138. package/src/search.ts +540 -0
  139. package/src/social.ts +469 -0
  140. package/src/subtype.ts +3095 -0
  141. package/src/testing.ts +1873 -0
  142. package/src/version-chain.ts +1093 -0
  143. package/src/working-copy.ts +192 -0
@@ -0,0 +1,311 @@
1
+ /**
2
+ * **The social graph** (pulpit#195) — follow, mute and block between
3
+ * accounts, as relationship facts with their own gates, the follower and
4
+ * following lists, a {@link FollowerSeam} built from the graph so
5
+ * `followers` rows work end to end, and an opt-in read filter over the
6
+ * accounts a viewer silenced.
7
+ *
8
+ * The behavioural spec is gardeneria's `social.follows` and its operations
9
+ * (`apps/api/src/operations/follows.ts`), with their tests.
10
+ *
11
+ * ## The model (owner decisions on pulpit#195)
12
+ *
13
+ * - **A relationship is a fact**, keyed `(kind, from, to)` for the kinds
14
+ * `follow`, `mute` and `block`: no history; asserting one twice is a
15
+ * no-op, removing it hard-deletes it. Only the actor writes their own
16
+ * (`from` is always the viewer).
17
+ * - **Following is immediate.** There are no follow requests (a later
18
+ * slice).
19
+ * - **A block suppresses interaction only.** Neither side may follow the
20
+ * other while either blocks the other ({@link InteractionBlocked}), a
21
+ * block removes both accounts' follows of each other, and other modules
22
+ * gate an interaction (a reaction, a reply, a mention) on
23
+ * {@link SocialOps.interactionAllowed}. It hides no content by itself:
24
+ * {@link SocialOps.silencedFilter} is the opt-in read filter that drops
25
+ * the authors a viewer muted or blocked from a feed or a thread.
26
+ * - **A mute is one-way and private**: it only feeds that read filter.
27
+ *
28
+ * Mute and block lists are their owner's alone; the follower and following
29
+ * lists are public, filtered through the application's account visibility
30
+ * ({@link AccountVisibility}).
31
+ *
32
+ * @copyright Thomas Foster
33
+ * @module pulpit/social
34
+ * @since 0.1.0
35
+ */
36
+ import * as Effect from "effect/Effect";
37
+ import * as HashSet from "effect/HashSet";
38
+ import * as Option from "effect/Option";
39
+ import * as Schema from "effect/Schema";
40
+ import { type ListFilter } from "./filter.ts";
41
+ import { type FollowerSeam, type Page, type Unauthorized, type Viewer } from "./subtype.ts";
42
+ /**
43
+ * The relationship kinds.
44
+ *
45
+ * @since 0.1.0
46
+ */
47
+ export declare const RelationshipKind: Schema.Literals<readonly ["follow", "mute", "block"]>;
48
+ /**
49
+ * The {@link RelationshipKind} literal union.
50
+ *
51
+ * @since 0.1.0
52
+ */
53
+ export type RelationshipKind = typeof RelationshipKind.Type;
54
+ /**
55
+ * One stored relationship: `from` follows, mutes or blocks `to`.
56
+ *
57
+ * @since 0.1.0
58
+ */
59
+ export interface Relationship {
60
+ readonly kind: RelationshipKind;
61
+ readonly from: string;
62
+ readonly to: string;
63
+ readonly at: Date;
64
+ }
65
+ declare const InteractionBlocked_base: Schema.Class<InteractionBlocked, Schema.TaggedStruct<"InteractionBlocked", {
66
+ readonly accountId: Schema.String;
67
+ }>, import("effect/Cause").YieldableError & {
68
+ readonly brand: unique symbol;
69
+ }>;
70
+ /**
71
+ * Either account blocks the other, so the interaction is refused: the
72
+ * wire's 403. It tells the actor a block exists between the two, as every
73
+ * social network's refusal does.
74
+ *
75
+ * @since 0.1.0
76
+ */
77
+ export declare class InteractionBlocked extends InteractionBlocked_base {
78
+ }
79
+ declare const SelfRelationship_base: Schema.Class<SelfRelationship, Schema.TaggedStruct<"SelfRelationship", {
80
+ readonly kind: Schema.Literals<readonly ["follow", "mute", "block"]>;
81
+ }>, import("effect/Cause").YieldableError & {
82
+ readonly brand: unique symbol;
83
+ }>;
84
+ /**
85
+ * A relationship with oneself: the wire's 422.
86
+ *
87
+ * @since 0.1.0
88
+ */
89
+ export declare class SelfRelationship extends SelfRelationship_base {
90
+ }
91
+ /**
92
+ * The relationship table an application supplies (gardeneria's
93
+ * `social.follows`, widened by a `kind` column), keyed
94
+ * `(kind, from_id, to_id)` with both account foreign keys cascading. A store
95
+ * seam: no policy, never opens a transaction, fails only with its `E`.
96
+ * `Following` is the follower seam's set (a subquery in SQL). See the
97
+ * package README → Seams.
98
+ *
99
+ * @since 0.1.0
100
+ */
101
+ export interface SocialStore<Following, E, R> {
102
+ /** Insert the relationship (`ON CONFLICT DO NOTHING`); whether a row was written. Used for a mute. */
103
+ readonly add: (relationship: Relationship) => Effect.Effect<boolean, E, R>;
104
+ /**
105
+ * Insert a follow unless a block stands between the two accounts, either
106
+ * way: `"added"`, `"present"` (already following) or `"blocked"` (nothing
107
+ * written). The check and the insert must be **one atomic step against
108
+ * {@link SocialStore.addBlock} on the same pair**, so a concurrent block
109
+ * can never leave a follow standing: take a lock on the unordered pair
110
+ * that `addBlock` also takes (`pg_advisory_xact_lock` on it, in the
111
+ * caller's transaction), then `INSERT … SELECT … WHERE NOT EXISTS (a
112
+ * block either way) ON CONFLICT DO NOTHING`.
113
+ */
114
+ readonly addFollow: (relationship: Relationship) => Effect.Effect<"added" | "present" | "blocked", E, R>;
115
+ /**
116
+ * Insert a block and delete both follows between the two accounts, as one
117
+ * atomic step against {@link SocialStore.addFollow} on the same pair (the
118
+ * same pair lock); whether the block row was written.
119
+ */
120
+ readonly addBlock: (relationship: Relationship) => Effect.Effect<boolean, E, R>;
121
+ /** Delete the relationship; whether a row went. */
122
+ readonly remove: (kind: RelationshipKind, from: string, to: string) => Effect.Effect<boolean, E, R>;
123
+ /** Whether the relationship is stored. */
124
+ readonly has: (kind: RelationshipKind, from: string, to: string) => Effect.Effect<boolean, E, R>;
125
+ /**
126
+ * One page of an account's relationships of `kind`, newest first
127
+ * (`at DESC`, then the other account's id): by `from` (whom it follows,
128
+ * mutes or blocks) or by `to` (who follows it), with the unpaged total.
129
+ */
130
+ readonly list: (query: {
131
+ readonly kind: RelationshipKind;
132
+ readonly side: "from" | "to";
133
+ readonly accountId: string;
134
+ readonly limit: number;
135
+ readonly offset: number;
136
+ }) => Effect.Effect<Page<{
137
+ readonly accountId: string;
138
+ readonly at: Date;
139
+ }>, E, R>;
140
+ /** Whether `follower` follows `followed`, as the follower seam asks it (never fails). */
141
+ readonly isFollowing: (follower: string, followed: string) => Effect.Effect<boolean, never, R>;
142
+ /** The accounts `follower` follows, as the list filter's `in` set (a subquery, never an array). */
143
+ readonly followingOf: (follower: string) => Following;
144
+ /** The accounts `viewer` muted or blocked, as the list filter's set (a subquery). */
145
+ readonly silencedBy: (viewer: string) => Following;
146
+ /** Delete every relationship from or to the account (account erasure, pulpit#207); how many went. */
147
+ readonly eraseAccount: (accountId: string) => Effect.Effect<number, E, R>;
148
+ }
149
+ /**
150
+ * Which accounts a viewer may see in a follower or following list: the
151
+ * application's own account visibility (a private profile, a suspended
152
+ * account). Given a page's account ids, the ones to show.
153
+ *
154
+ * @since 0.1.0
155
+ */
156
+ export interface AccountVisibility<R> {
157
+ readonly visible: (viewer: Viewer, accountIds: ReadonlyArray<string>) => Effect.Effect<HashSet.HashSet<string>, never, R>;
158
+ }
159
+ /**
160
+ * The follower seam a social store fills (`./subtype`'s
161
+ * {@link FollowerSeam}), so a `followers` row is readable by the accounts
162
+ * that follow its owner.
163
+ *
164
+ * @example
165
+ * ```ts
166
+ * import { strictEqual } from "node:assert";
167
+ * import * as Effect from "effect/Effect";
168
+ * import { socialFollowers } from "@thomasfosterau/pulpit/social";
169
+ * import { makeInMemorySocialStore } from "@thomasfosterau/pulpit/testing";
170
+ *
171
+ * const store = makeInMemorySocialStore();
172
+ * Effect.runSync(store.add({ kind: "follow", from: "u-1", to: "u-2", at: new Date(0) }));
173
+ * const followers = socialFollowers(store);
174
+ * strictEqual(Effect.runSync(followers.isFollower("u-1", "u-2")), true);
175
+ * strictEqual(Effect.runSync(followers.isFollower("u-2", "u-1")), false);
176
+ * ```
177
+ *
178
+ * @since 0.1.0
179
+ */
180
+ export declare const socialFollowers: <Following, E, R>(store: Pick<SocialStore<Following, E, R>, "isFollowing" | "followingOf">) => FollowerSeam<Following, R>;
181
+ /**
182
+ * The social graph's declaration.
183
+ *
184
+ * @since 0.1.0
185
+ */
186
+ export interface SocialDefinition<Following, E, R> {
187
+ readonly store: SocialStore<Following, E, R>;
188
+ /** The request's viewer. */
189
+ readonly viewer: Effect.Effect<Viewer, never, R>;
190
+ /** Which accounts the follower and following lists show; omitted, every one. */
191
+ readonly accounts?: AccountVisibility<R> | undefined;
192
+ }
193
+ /**
194
+ * A page request: clamped to `[1, MAX_PAGE_LIMIT]` and an offset `≥ 0`.
195
+ *
196
+ * @since 0.1.0
197
+ */
198
+ export interface SocialPageRequest {
199
+ readonly limit: number;
200
+ readonly offset: number;
201
+ }
202
+ /**
203
+ * One account on a list, and when the relationship began.
204
+ *
205
+ * @since 0.1.0
206
+ */
207
+ export interface Related {
208
+ readonly accountId: string;
209
+ readonly at: Date;
210
+ }
211
+ /**
212
+ * The operations {@link defineSocial} produces. Every write is the
213
+ * viewer's own and fails `Unauthorized` when anonymous; asserting a
214
+ * relationship again, or removing one that isn't there, changes nothing
215
+ * (`changed: false`).
216
+ *
217
+ * @since 0.1.0
218
+ */
219
+ export interface SocialOps<Following, E, R> {
220
+ /**
221
+ * Follow `accountId`. Fails {@link SelfRelationship} for oneself and
222
+ * {@link InteractionBlocked} when either account blocks the other.
223
+ */
224
+ readonly follow: (accountId: string) => Effect.Effect<{
225
+ readonly changed: boolean;
226
+ }, Unauthorized | SelfRelationship | InteractionBlocked | E, R>;
227
+ readonly unfollow: (accountId: string) => Effect.Effect<{
228
+ readonly changed: boolean;
229
+ }, Unauthorized | E, R>;
230
+ /** Mute `accountId` (one-way and private). Fails {@link SelfRelationship} for oneself. */
231
+ readonly mute: (accountId: string) => Effect.Effect<{
232
+ readonly changed: boolean;
233
+ }, Unauthorized | SelfRelationship | E, R>;
234
+ readonly unmute: (accountId: string) => Effect.Effect<{
235
+ readonly changed: boolean;
236
+ }, Unauthorized | E, R>;
237
+ /**
238
+ * Block `accountId`: also removes each account's follow of the other.
239
+ * Fails {@link SelfRelationship} for oneself.
240
+ */
241
+ readonly block: (accountId: string) => Effect.Effect<{
242
+ readonly changed: boolean;
243
+ }, Unauthorized | SelfRelationship | E, R>;
244
+ readonly unblock: (accountId: string) => Effect.Effect<{
245
+ readonly changed: boolean;
246
+ }, Unauthorized | E, R>;
247
+ /**
248
+ * The accounts following `accountId`, newest first, as the application's
249
+ * account visibility shows them. A subject the viewer can't see answers
250
+ * as an account with no followers.
251
+ */
252
+ readonly followers: (accountId: string, request: SocialPageRequest) => Effect.Effect<RelatedPage, E, R>;
253
+ /** The accounts `accountId` follows, as {@link SocialOps.followers} shows them. */
254
+ readonly following: (accountId: string, request: SocialPageRequest) => Effect.Effect<RelatedPage, E, R>;
255
+ /** The viewer's own mutes, newest first. Fails `Unauthorized` when anonymous. */
256
+ readonly muted: (request: SocialPageRequest) => Effect.Effect<Page<Related>, Unauthorized | E, R>;
257
+ /** The viewer's own blocks, newest first. Fails `Unauthorized` when anonymous. */
258
+ readonly blocked: (request: SocialPageRequest) => Effect.Effect<Page<Related>, Unauthorized | E, R>;
259
+ /** Whether `a` and `b` may interact: neither blocks the other. Always true for one account with itself. */
260
+ readonly interactionAllowed: (a: string, b: string) => Effect.Effect<boolean, E, R>;
261
+ /**
262
+ * The opt-in read filter that drops what the viewer silenced:
263
+ * `column` (an author column) not in the accounts the viewer muted or
264
+ * blocked, through `./filter`'s `notIn` (a `NULL` author is kept). The
265
+ * anonymous have silenced nobody: `always`.
266
+ */
267
+ readonly silencedFilter: (column: string) => Effect.Effect<ListFilter<Following>, never, R>;
268
+ }
269
+ /** A page request clamped as every list's is (`SEC-INPUT-003`). */
270
+ /**
271
+ * One page of a public follower or following list. `total` counts only
272
+ * accounts the viewer may see: it is the store's total when the
273
+ * application declares no {@link AccountVisibility}, and none when it does
274
+ * (the store's count would include accounts the seam hides), so a filtered
275
+ * page may also hold fewer than `limit` rows.
276
+ *
277
+ * @since 0.1.0
278
+ */
279
+ export interface RelatedPage {
280
+ readonly rows: ReadonlyArray<Related>;
281
+ readonly total: Option.Option<number>;
282
+ }
283
+ /**
284
+ * Bind the social graph's operations. Every operation is traced as
285
+ * `social.<operation>`, and the writes run in the caller's transaction.
286
+ *
287
+ * @example
288
+ * ```ts
289
+ * import { strictEqual } from "node:assert";
290
+ * import * as Effect from "effect/Effect";
291
+ * import * as Option from "effect/Option";
292
+ * import { defineSocial } from "@thomasfosterau/pulpit/social";
293
+ * import { makeInMemorySocialStore } from "@thomasfosterau/pulpit/testing";
294
+ *
295
+ * const social = defineSocial({
296
+ * store: makeInMemorySocialStore(),
297
+ * viewer: Effect.succeedSome({ id: "u-1", role: "user" }),
298
+ * });
299
+ * const program = Effect.gen(function* () {
300
+ * yield* social.follow("u-2");
301
+ * return yield* social.following("u-1", { limit: 10, offset: 0 });
302
+ * });
303
+ * // No AccountVisibility seam, so the total is the store's.
304
+ * strictEqual(Option.getOrElse(Effect.runSync(program).total, () => 0), 1);
305
+ * ```
306
+ *
307
+ * @since 0.1.0
308
+ */
309
+ export declare const defineSocial: <Following, E, R>(definition: SocialDefinition<Following, E, R>) => SocialOps<Following, E, R>;
310
+ export {};
311
+ //# sourceMappingURL=social.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"social.d.ts","sourceRoot":"","sources":["../src/social.ts"],"names":[],"mappings":"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"}
package/dist/social.js ADDED
@@ -0,0 +1,205 @@
1
+ /**
2
+ * **The social graph** (pulpit#195) — follow, mute and block between
3
+ * accounts, as relationship facts with their own gates, the follower and
4
+ * following lists, a {@link FollowerSeam} built from the graph so
5
+ * `followers` rows work end to end, and an opt-in read filter over the
6
+ * accounts a viewer silenced.
7
+ *
8
+ * The behavioural spec is gardeneria's `social.follows` and its operations
9
+ * (`apps/api/src/operations/follows.ts`), with their tests.
10
+ *
11
+ * ## The model (owner decisions on pulpit#195)
12
+ *
13
+ * - **A relationship is a fact**, keyed `(kind, from, to)` for the kinds
14
+ * `follow`, `mute` and `block`: no history; asserting one twice is a
15
+ * no-op, removing it hard-deletes it. Only the actor writes their own
16
+ * (`from` is always the viewer).
17
+ * - **Following is immediate.** There are no follow requests (a later
18
+ * slice).
19
+ * - **A block suppresses interaction only.** Neither side may follow the
20
+ * other while either blocks the other ({@link InteractionBlocked}), a
21
+ * block removes both accounts' follows of each other, and other modules
22
+ * gate an interaction (a reaction, a reply, a mention) on
23
+ * {@link SocialOps.interactionAllowed}. It hides no content by itself:
24
+ * {@link SocialOps.silencedFilter} is the opt-in read filter that drops
25
+ * the authors a viewer muted or blocked from a feed or a thread.
26
+ * - **A mute is one-way and private**: it only feeds that read filter.
27
+ *
28
+ * Mute and block lists are their owner's alone; the follower and following
29
+ * lists are public, filtered through the application's account visibility
30
+ * ({@link AccountVisibility}).
31
+ *
32
+ * @copyright Thomas Foster
33
+ * @module pulpit/social
34
+ * @since 0.1.0
35
+ */
36
+ import * as Arr from "effect/Array";
37
+ import * as Effect from "effect/Effect";
38
+ import * as HashSet from "effect/HashSet";
39
+ import * as Option from "effect/Option";
40
+ import * as Schema from "effect/Schema";
41
+ import { always, isNotIn } from "./filter.js";
42
+ import { nowDate, signedIn as signedInAs } from "./internal/plain-rows.js";
43
+ import { MAX_PAGE_LIMIT, } from "./subtype.js";
44
+ // ── Vocabulary ────────────────────────────────────────────────────────
45
+ /**
46
+ * The relationship kinds.
47
+ *
48
+ * @since 0.1.0
49
+ */
50
+ export const RelationshipKind = Schema.Literals(["follow", "mute", "block"]);
51
+ // ── Errors ────────────────────────────────────────────────────────────
52
+ /**
53
+ * Either account blocks the other, so the interaction is refused: the
54
+ * wire's 403. It tells the actor a block exists between the two, as every
55
+ * social network's refusal does.
56
+ *
57
+ * @since 0.1.0
58
+ */
59
+ export class InteractionBlocked extends Schema.TaggedError()("InteractionBlocked", { accountId: Schema.String }) {
60
+ }
61
+ /**
62
+ * A relationship with oneself: the wire's 422.
63
+ *
64
+ * @since 0.1.0
65
+ */
66
+ export class SelfRelationship extends Schema.TaggedError()("SelfRelationship", {
67
+ kind: RelationshipKind,
68
+ }) {
69
+ }
70
+ /**
71
+ * The follower seam a social store fills (`./subtype`'s
72
+ * {@link FollowerSeam}), so a `followers` row is readable by the accounts
73
+ * that follow its owner.
74
+ *
75
+ * @example
76
+ * ```ts
77
+ * import { strictEqual } from "node:assert";
78
+ * import * as Effect from "effect/Effect";
79
+ * import { socialFollowers } from "@thomasfosterau/pulpit/social";
80
+ * import { makeInMemorySocialStore } from "@thomasfosterau/pulpit/testing";
81
+ *
82
+ * const store = makeInMemorySocialStore();
83
+ * Effect.runSync(store.add({ kind: "follow", from: "u-1", to: "u-2", at: new Date(0) }));
84
+ * const followers = socialFollowers(store);
85
+ * strictEqual(Effect.runSync(followers.isFollower("u-1", "u-2")), true);
86
+ * strictEqual(Effect.runSync(followers.isFollower("u-2", "u-1")), false);
87
+ * ```
88
+ *
89
+ * @since 0.1.0
90
+ */
91
+ export const socialFollowers = (store) => ({
92
+ isFollower: store.isFollowing,
93
+ following: store.followingOf,
94
+ });
95
+ const window = (request) => ({
96
+ limit: Math.max(1, Math.min(Math.trunc(request.limit), MAX_PAGE_LIMIT)),
97
+ offset: Math.max(0, Math.trunc(request.offset)),
98
+ });
99
+ /**
100
+ * Bind the social graph's operations. Every operation is traced as
101
+ * `social.<operation>`, and the writes run in the caller's transaction.
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * import { strictEqual } from "node:assert";
106
+ * import * as Effect from "effect/Effect";
107
+ * import * as Option from "effect/Option";
108
+ * import { defineSocial } from "@thomasfosterau/pulpit/social";
109
+ * import { makeInMemorySocialStore } from "@thomasfosterau/pulpit/testing";
110
+ *
111
+ * const social = defineSocial({
112
+ * store: makeInMemorySocialStore(),
113
+ * viewer: Effect.succeedSome({ id: "u-1", role: "user" }),
114
+ * });
115
+ * const program = Effect.gen(function* () {
116
+ * yield* social.follow("u-2");
117
+ * return yield* social.following("u-1", { limit: 10, offset: 0 });
118
+ * });
119
+ * // No AccountVisibility seam, so the total is the store's.
120
+ * strictEqual(Option.getOrElse(Effect.runSync(program).total, () => 0), 1);
121
+ * ```
122
+ *
123
+ * @since 0.1.0
124
+ */
125
+ export const defineSocial = (definition) => {
126
+ const { store } = definition;
127
+ const signedIn = signedInAs(definition.viewer);
128
+ const assert = (kind, account, to) => Effect.flatMap(nowDate, (at) => Effect.map(store.add({ kind, from: account.id, to, at }), (changed) => ({ changed })));
129
+ const retract = (kind, operation) => Effect.fn(`social.${operation}`)(function* (to) {
130
+ const account = yield* signedIn;
131
+ return { changed: yield* store.remove(kind, account.id, to) };
132
+ });
133
+ const notSelf = (kind, account, to) => account.id === to ? Effect.fail(new SelfRelationship({ kind })) : Effect.void;
134
+ const interactionAllowed = Effect.fn("social.interactionAllowed")(function* (a, b) {
135
+ if (a === b)
136
+ return true;
137
+ return !(yield* store.has("block", a, b)) && !(yield* store.has("block", b, a));
138
+ });
139
+ const follow = Effect.fn("social.follow")(function* (to) {
140
+ const account = yield* signedIn;
141
+ yield* notSelf("follow", account, to);
142
+ // The block check is part of the store's insert, never a read before
143
+ // it, so a concurrent block can't land in between.
144
+ const at = yield* nowDate;
145
+ const outcome = yield* store.addFollow({ kind: "follow", from: account.id, to, at });
146
+ if (outcome === "blocked")
147
+ return yield* new InteractionBlocked({ accountId: to });
148
+ return { changed: outcome === "added" };
149
+ });
150
+ const mute = Effect.fn("social.mute")(function* (to) {
151
+ const account = yield* signedIn;
152
+ yield* notSelf("mute", account, to);
153
+ return yield* assert("mute", account, to);
154
+ });
155
+ const block = Effect.fn("social.block")(function* (to) {
156
+ const account = yield* signedIn;
157
+ yield* notSelf("block", account, to);
158
+ // A block ends both follows between the two accounts, in the same atomic step.
159
+ const at = yield* nowDate;
160
+ return { changed: yield* store.addBlock({ kind: "block", from: account.id, to, at }) };
161
+ });
162
+ const publicList = (side, operation) => Effect.fn(`social.${operation}`)(function* (accountId, request) {
163
+ const { accounts } = definition;
164
+ if (accounts === undefined) {
165
+ const page = yield* store.list({ kind: "follow", side, accountId, ...window(request) });
166
+ return { rows: page.rows, total: Option.some(page.total) };
167
+ }
168
+ const viewer = yield* definition.viewer;
169
+ // A subject the viewer can't see answers as one with no relationships.
170
+ if (!HashSet.has(yield* accounts.visible(viewer, [accountId]), accountId)) {
171
+ return { rows: [], total: Option.none() };
172
+ }
173
+ const page = yield* store.list({ kind: "follow", side, accountId, ...window(request) });
174
+ const shown = Arr.isReadonlyArrayNonEmpty(page.rows)
175
+ ? yield* accounts.visible(viewer, Arr.map(page.rows, (row) => row.accountId))
176
+ : HashSet.empty();
177
+ return {
178
+ rows: Arr.filter(page.rows, (row) => HashSet.has(shown, row.accountId)),
179
+ total: Option.none(),
180
+ };
181
+ });
182
+ const ownList = (kind, operation) => Effect.fn(`social.${operation}`)(function* (request) {
183
+ const account = yield* signedIn;
184
+ return yield* store.list({ kind, side: "from", accountId: account.id, ...window(request) });
185
+ });
186
+ const silencedFilter = (column) => Effect.map(definition.viewer, (viewer) => Option.match(viewer, {
187
+ onNone: () => always,
188
+ onSome: (account) => isNotIn(column, store.silencedBy(account.id)),
189
+ }));
190
+ return {
191
+ follow,
192
+ unfollow: retract("follow", "unfollow"),
193
+ mute,
194
+ unmute: retract("mute", "unmute"),
195
+ block,
196
+ unblock: retract("block", "unblock"),
197
+ followers: publicList("to", "followers"),
198
+ following: publicList("from", "following"),
199
+ muted: ownList("mute", "muted"),
200
+ blocked: ownList("block", "blocked"),
201
+ interactionAllowed,
202
+ silencedFilter,
203
+ };
204
+ };
205
+ //# sourceMappingURL=social.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"social.js","sourceRoot":"","sources":["../src/social.ts"],"names":[],"mappings":"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"}