@rocapine/community-core 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.
- package/LICENSE +21 -0
- package/README.md +173 -0
- package/lib/config.d.ts +74 -0
- package/lib/config.js +51 -0
- package/lib/events.d.ts +20 -0
- package/lib/events.js +33 -0
- package/lib/hooks.d.ts +122 -0
- package/lib/hooks.js +494 -0
- package/lib/identity.d.ts +23 -0
- package/lib/identity.js +103 -0
- package/lib/inbox-hooks.d.ts +23 -0
- package/lib/inbox-hooks.js +57 -0
- package/lib/inbox-service.d.ts +26 -0
- package/lib/inbox-service.js +94 -0
- package/lib/index.d.ts +17 -0
- package/lib/index.js +89 -0
- package/lib/models.d.ts +187 -0
- package/lib/models.js +178 -0
- package/lib/provider.d.ts +7 -0
- package/lib/provider.js +23 -0
- package/lib/schema-check.d.ts +19 -0
- package/lib/schema-check.js +52 -0
- package/lib/service.d.ts +147 -0
- package/lib/service.js +454 -0
- package/lib/time.d.ts +21 -0
- package/lib/time.js +32 -0
- package/package.json +32 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ReactElement, type ReactNode } from "react";
|
|
2
|
+
import { type CommunityConfig, type ResolvedCommunityConfig } from "./config";
|
|
3
|
+
export declare function CommunityProvider(props: {
|
|
4
|
+
config: CommunityConfig;
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
}): ReactElement;
|
|
7
|
+
export declare function useCommunityConfig(): ResolvedCommunityConfig;
|
package/lib/provider.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CommunityProvider = CommunityProvider;
|
|
4
|
+
exports.useCommunityConfig = useCommunityConfig;
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const config_1 = require("./config");
|
|
7
|
+
const schema_check_1 = require("./schema-check");
|
|
8
|
+
const CommunityConfigContext = (0, react_1.createContext)(null);
|
|
9
|
+
function CommunityProvider(props) {
|
|
10
|
+
const { config, children } = props;
|
|
11
|
+
const resolved = (0, react_1.useMemo)(() => (0, config_1.resolveConfig)(config), [config]);
|
|
12
|
+
(0, react_1.useEffect)(() => {
|
|
13
|
+
void (0, schema_check_1.checkSchemaVersion)(resolved);
|
|
14
|
+
}, [resolved]);
|
|
15
|
+
return (<CommunityConfigContext.Provider value={resolved}>{children}</CommunityConfigContext.Provider>);
|
|
16
|
+
}
|
|
17
|
+
function useCommunityConfig() {
|
|
18
|
+
const resolved = (0, react_1.useContext)(CommunityConfigContext);
|
|
19
|
+
if (!resolved) {
|
|
20
|
+
throw new Error("CommunityProvider is missing");
|
|
21
|
+
}
|
|
22
|
+
return resolved;
|
|
23
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ResolvedCommunityConfig } from "./config";
|
|
2
|
+
/** Bump whenever the SDK's expected `community_meta` shape changes. */
|
|
3
|
+
export declare const REQUIRED_SCHEMA_VERSION = 1;
|
|
4
|
+
/**
|
|
5
|
+
* Read path, exported separately from the `__DEV__` gate so tests — where
|
|
6
|
+
* `__DEV__` is `undefined` under vitest/Node, not `false` — can exercise the
|
|
7
|
+
* four behaviors (mismatch warns, match is silent, query error is silent,
|
|
8
|
+
* degraded mode is silent) directly. `checkSchemaVersion` below is the only
|
|
9
|
+
* symbol wired into `CommunityProvider`; this one stays package-internal
|
|
10
|
+
* (not re-exported from `index.ts`).
|
|
11
|
+
*/
|
|
12
|
+
export declare function checkSchemaVersionUnguarded(cfg: ResolvedCommunityConfig): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Dev-only entry point, called once from `CommunityProvider`'s mount effect.
|
|
15
|
+
* No-ops outside dev (production RN bundles define `__DEV__ === false`; a
|
|
16
|
+
* plain Node/test environment leaves it `undefined`, which also no-ops here —
|
|
17
|
+
* see `checkSchemaVersionUnguarded` for how tests reach the read path).
|
|
18
|
+
*/
|
|
19
|
+
export declare function checkSchemaVersion(cfg: ResolvedCommunityConfig): Promise<void>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Dev-only schema drift check. Reads the single `community_meta.schema_version`
|
|
3
|
+
// row and warns when it doesn't match what this SDK version expects, so a host
|
|
4
|
+
// developer notices a stale backend migration during development. This is a
|
|
5
|
+
// diagnostic aid, not a runtime gate — it must never throw and must never run
|
|
6
|
+
// in production (RN's global `__DEV__` gate, same convention every host app
|
|
7
|
+
// already relies on for dev-only logging).
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.REQUIRED_SCHEMA_VERSION = void 0;
|
|
10
|
+
exports.checkSchemaVersionUnguarded = checkSchemaVersionUnguarded;
|
|
11
|
+
exports.checkSchemaVersion = checkSchemaVersion;
|
|
12
|
+
/** Bump whenever the SDK's expected `community_meta` shape changes. */
|
|
13
|
+
exports.REQUIRED_SCHEMA_VERSION = 1;
|
|
14
|
+
/**
|
|
15
|
+
* Read path, exported separately from the `__DEV__` gate so tests — where
|
|
16
|
+
* `__DEV__` is `undefined` under vitest/Node, not `false` — can exercise the
|
|
17
|
+
* four behaviors (mismatch warns, match is silent, query error is silent,
|
|
18
|
+
* degraded mode is silent) directly. `checkSchemaVersion` below is the only
|
|
19
|
+
* symbol wired into `CommunityProvider`; this one stays package-internal
|
|
20
|
+
* (not re-exported from `index.ts`).
|
|
21
|
+
*/
|
|
22
|
+
async function checkSchemaVersionUnguarded(cfg) {
|
|
23
|
+
const client = cfg.supabase;
|
|
24
|
+
if (!client)
|
|
25
|
+
return; // degraded mode (no backend configured) — nothing to check
|
|
26
|
+
try {
|
|
27
|
+
const { data, error } = await client
|
|
28
|
+
.from("community_meta")
|
|
29
|
+
.select("schema_version")
|
|
30
|
+
.maybeSingle();
|
|
31
|
+
if (error)
|
|
32
|
+
return; // e.g. table doesn't exist yet on this backend — silent
|
|
33
|
+
const version = data?.schema_version;
|
|
34
|
+
if (version !== undefined && version !== exports.REQUIRED_SCHEMA_VERSION) {
|
|
35
|
+
console.warn(`[@rocapine/community-core] community_meta.schema_version is ${version}, this SDK expects ${exports.REQUIRED_SCHEMA_VERSION}. Run the latest backend migrations.`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// network error or unexpected client shape — never throw from a dev diagnostic
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Dev-only entry point, called once from `CommunityProvider`'s mount effect.
|
|
44
|
+
* No-ops outside dev (production RN bundles define `__DEV__ === false`; a
|
|
45
|
+
* plain Node/test environment leaves it `undefined`, which also no-ops here —
|
|
46
|
+
* see `checkSchemaVersionUnguarded` for how tests reach the read path).
|
|
47
|
+
*/
|
|
48
|
+
async function checkSchemaVersion(cfg) {
|
|
49
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
50
|
+
await checkSchemaVersionUnguarded(cfg);
|
|
51
|
+
}
|
|
52
|
+
}
|
package/lib/service.d.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { ResolvedCommunityConfig } from "./config";
|
|
2
|
+
import { type CommunityProfile, type FeedPost, type ReactionData, type ThreadComment } from "./models";
|
|
3
|
+
/**
|
|
4
|
+
* Appends `extraPostColumns` (if any) to the posts feed select. Pure and
|
|
5
|
+
* exported so it's unit-testable without a Supabase client. Invalid column
|
|
6
|
+
* names are dropped with a `console.warn` rather than failing the query.
|
|
7
|
+
*/
|
|
8
|
+
export declare function buildFeedSelect(extraPostColumns?: readonly string[]): string;
|
|
9
|
+
/**
|
|
10
|
+
* Batched reaction state for one page: count + last reactor's display name + my-reaction
|
|
11
|
+
* flag, all from the single security-definer `post_reaction_summary` RPC (the backend
|
|
12
|
+
* already resolves "did I react" server-side, so unlike the mold's prayer batching this
|
|
13
|
+
* needs no second query). Gated on `cfg.modules.reaction`; a missing/erroring RPC (e.g.
|
|
14
|
+
* the reaction module's migration not installed yet) degrades to no reaction data instead
|
|
15
|
+
* of taking the whole feed down.
|
|
16
|
+
*/
|
|
17
|
+
export declare function fetchReactionSummaries(cfg: ResolvedCommunityConfig, postIds: string[]): Promise<ReactionData>;
|
|
18
|
+
/**
|
|
19
|
+
* One feed page, newest first. RLS already excludes other users' hidden and
|
|
20
|
+
* blocked-author content; we only exclude our own soft-deleted posts.
|
|
21
|
+
*/
|
|
22
|
+
export declare function fetchFeedPage(cfg: ResolvedCommunityConfig, opts?: {
|
|
23
|
+
topic?: string;
|
|
24
|
+
cursor?: number;
|
|
25
|
+
}): Promise<FeedPost[]>;
|
|
26
|
+
/**
|
|
27
|
+
* Count of visible posts newer than `sinceIso`, excluding my own, for the
|
|
28
|
+
* given topic. RLS already excludes blocked authors and hidden/deleted rows.
|
|
29
|
+
* `topic` is beyond the brief's literal signature but kept (both source apps
|
|
30
|
+
* take it) so a topic-filtered feed's "N new posts" pill stays accurate.
|
|
31
|
+
*/
|
|
32
|
+
export declare function countNewPosts(cfg: ResolvedCommunityConfig, sinceIso: string, topic?: string): Promise<number>;
|
|
33
|
+
/** A single profile by id, e.g. for the profile page header. */
|
|
34
|
+
export declare function fetchProfile(cfg: ResolvedCommunityConfig, userId: string): Promise<CommunityProfile>;
|
|
35
|
+
/**
|
|
36
|
+
* One user's posts, newest first (no pinned ordering — a profile page is
|
|
37
|
+
* strictly chronological). Same visibility rules as the main feed, same
|
|
38
|
+
* page-index cursor as `fetchFeedPage` (product decision, reversing the
|
|
39
|
+
* earlier single-page scope cut).
|
|
40
|
+
*/
|
|
41
|
+
export declare function fetchUserPosts(cfg: ResolvedCommunityConfig, userId: string, opts?: {
|
|
42
|
+
cursor?: number;
|
|
43
|
+
}): Promise<FeedPost[]>;
|
|
44
|
+
/**
|
|
45
|
+
* Full-text-ish search over visible post content (newest first), same
|
|
46
|
+
* page-index cursor as `fetchFeedPage` (product decision, reversing the
|
|
47
|
+
* earlier single-page scope cut).
|
|
48
|
+
*/
|
|
49
|
+
export declare function searchPosts(cfg: ResolvedCommunityConfig, query: string, opts?: {
|
|
50
|
+
cursor?: number;
|
|
51
|
+
}): Promise<FeedPost[]>;
|
|
52
|
+
/**
|
|
53
|
+
* Note: `cfg.feed.extraPostColumns`/`transformPost` do NOT apply here — this
|
|
54
|
+
* queries `comments`, not `posts` (the thread's parent post is passed in by
|
|
55
|
+
* the caller from an already-fetched, already-transformed `FeedPost`, not
|
|
56
|
+
* re-fetched).
|
|
57
|
+
*/
|
|
58
|
+
export declare function fetchThread(cfg: ResolvedCommunityConfig, postId: string): Promise<ThreadComment[]>;
|
|
59
|
+
/** Verdict returned by the synchronous moderate-one Edge Function. */
|
|
60
|
+
export type ModerationVerdict = {
|
|
61
|
+
status: "published" | "rejected" | "pending";
|
|
62
|
+
reason?: string;
|
|
63
|
+
};
|
|
64
|
+
export type CreatePostInput = {
|
|
65
|
+
topic: string;
|
|
66
|
+
body: string;
|
|
67
|
+
pollOptions?: string[];
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Insert a post as 'pending' (invisible to others until moderated). Returns id.
|
|
71
|
+
* With pollOptions, the post and its options are created transactionally by
|
|
72
|
+
* the create_poll_post RPC (security invoker, same RLS as a plain insert);
|
|
73
|
+
* that branch is gated on `cfg.modules.polls`.
|
|
74
|
+
*/
|
|
75
|
+
export declare function createPost(cfg: ResolvedCommunityConfig, input: CreatePostInput): Promise<string>;
|
|
76
|
+
/**
|
|
77
|
+
* Cast or change my vote: one row per (post, user), upsert swaps the option.
|
|
78
|
+
* `optionId` is the poll option's row id (matching `FeedPoll.myOptionId` /
|
|
79
|
+
* `applyPollVote`'s optimistic-update contract from Task 3), not a numeric
|
|
80
|
+
* index — see the task report for this naming call against the brief.
|
|
81
|
+
* Gated on `cfg.modules.polls` for consistent DX with the reaction module.
|
|
82
|
+
*/
|
|
83
|
+
export declare function votePoll(cfg: ResolvedCommunityConfig, postId: string, optionId: string): Promise<void>;
|
|
84
|
+
/** Insert a comment as 'pending'. Returns id. */
|
|
85
|
+
export declare function createComment(cfg: ResolvedCommunityConfig, postId: string, body: string): Promise<string>;
|
|
86
|
+
/** Run synchronous moderation on a just-created pending post/comment. */
|
|
87
|
+
export declare function moderateOne(cfg: ResolvedCommunityConfig, input: {
|
|
88
|
+
kind: "post" | "comment";
|
|
89
|
+
id: string;
|
|
90
|
+
}): Promise<ModerationVerdict>;
|
|
91
|
+
export declare function setLike(cfg: ResolvedCommunityConfig, postId: string, on: boolean): Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* `setPrayed` generalized to `setReaction`: insert my private, non-retractable
|
|
94
|
+
* reaction row (rows are never deleted — see spec §6). Gated on `cfg.modules.reaction`.
|
|
95
|
+
*/
|
|
96
|
+
export declare function setReaction(cfg: ResolvedCommunityConfig, postId: string): Promise<void>;
|
|
97
|
+
export type ReportReason = "spam" | "harassment" | "hate" | "inappropriate" | "other";
|
|
98
|
+
export interface ReportInput {
|
|
99
|
+
reportedUserId: string;
|
|
100
|
+
postId?: string;
|
|
101
|
+
commentId?: string;
|
|
102
|
+
reason: ReportReason;
|
|
103
|
+
details?: string;
|
|
104
|
+
}
|
|
105
|
+
export declare function reportContent(cfg: ResolvedCommunityConfig, input: ReportInput): Promise<void>;
|
|
106
|
+
export declare function blockUser(cfg: ResolvedCommunityConfig, blockedId: string): Promise<void>;
|
|
107
|
+
export declare function deleteOwnPost(cfg: ResolvedCommunityConfig, postId: string): Promise<void>;
|
|
108
|
+
export declare function deleteOwnComment(cfg: ResolvedCommunityConfig, commentId: string): Promise<void>;
|
|
109
|
+
/**
|
|
110
|
+
* `avatarUrl` here is actually the Storage *path* returned by `uploadAvatar`
|
|
111
|
+
* (mold field name `avatarPath`) — kept as `avatarUrl` per the brief's produced
|
|
112
|
+
* signature and translated to the Edge Function's expected `avatarPath` body
|
|
113
|
+
* key at the call site below.
|
|
114
|
+
*/
|
|
115
|
+
export type UpdateProfileInput = {
|
|
116
|
+
handle?: string;
|
|
117
|
+
bio?: string;
|
|
118
|
+
avatarUrl?: string;
|
|
119
|
+
};
|
|
120
|
+
export type UpdateProfileResult = {
|
|
121
|
+
status: "ok";
|
|
122
|
+
handle: string | null;
|
|
123
|
+
bio: string | null;
|
|
124
|
+
avatarUrl: string | null;
|
|
125
|
+
} | {
|
|
126
|
+
status: "rejected";
|
|
127
|
+
field: "handle" | "bio" | "avatar";
|
|
128
|
+
reason?: string;
|
|
129
|
+
} | {
|
|
130
|
+
status: "error";
|
|
131
|
+
code?: "handle_taken";
|
|
132
|
+
error?: string;
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* The only write path for handle/bio/avatar — moderated server-side by the
|
|
136
|
+
* `update-profile` Edge Function. `invoke` treats any non-2xx as `error`; a
|
|
137
|
+
* `FunctionsHttpError` carries the typed JSON body (e.g. `handle_taken`) on
|
|
138
|
+
* its `context` response, other error kinds mean the request never landed.
|
|
139
|
+
*/
|
|
140
|
+
export declare function updateProfile(cfg: ResolvedCommunityConfig, input: UpdateProfileInput): Promise<UpdateProfileResult>;
|
|
141
|
+
/**
|
|
142
|
+
* Upload a JPEG avatar to the `avatars` bucket from a local file URI. Returns
|
|
143
|
+
* the storage path. Takes a `fileUri` (not a base64 string, unlike the mold)
|
|
144
|
+
* so the core has no Hermes/`atob` dependency — `fetch` on a `file://` URI is
|
|
145
|
+
* the portable way to read local bytes in React Native.
|
|
146
|
+
*/
|
|
147
|
+
export declare function uploadAvatar(cfg: ResolvedCommunityConfig, fileUri: string): Promise<string>;
|