edsger-feedback 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/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # edsger-feedback
2
+
3
+ Submit user feedback to [Edsger](https://edsger.ai) products programmatically.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install edsger-feedback
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```ts
14
+ import { submitFeedback } from 'edsger-feedback'
15
+
16
+ const result = await submitFeedback({
17
+ slug: 'spoke-translator',
18
+ title: 'Add dark mode',
19
+ description: 'It would be great to have a dark mode option.',
20
+ category: 'feature_request',
21
+ reporterEmail: 'user@example.com',
22
+ })
23
+
24
+ console.log('Feedback submitted:', result.id)
25
+ ```
26
+
27
+ ## API
28
+
29
+ ### `submitFeedback(options)`
30
+
31
+ Submit feedback for a product. Returns `Promise<FeedbackResult>`.
32
+
33
+ | Option | Type | Required | Description |
34
+ |--------|------|----------|-------------|
35
+ | `slug` | `string` | Yes | Product slug (e.g. `"spoke-translator"`) |
36
+ | `title` | `string` | Yes | Feedback title (max 200 characters) |
37
+ | `description` | `string` | Yes | Feedback description (max 5000 characters) |
38
+ | `category` | `FeedbackCategory` | No | One of `bug`, `feature_request`, `improvement`, `question`, `other`. Defaults to `other` |
39
+ | `reporterName` | `string` | No | Reporter's name |
40
+ | `reporterEmail` | `string` | No | Reporter's email |
41
+
42
+ **Returns:**
43
+
44
+ ```ts
45
+ { id: string; createdAt: string }
46
+ ```
47
+
48
+ ### `getFeedbackConfig(slug)`
49
+
50
+ Get the feedback page configuration for a product. Returns `Promise<FeedbackConfig>`.
51
+
52
+ **Returns:**
53
+
54
+ ```ts
55
+ {
56
+ productId: string
57
+ productName: string
58
+ customTitle: string | null
59
+ welcomeMessage: string | null
60
+ slug: string
61
+ }
62
+ ```
63
+
64
+ ## Error Handling
65
+
66
+ All errors are thrown as `FeedbackError` with a `statusCode` property.
67
+
68
+ ```ts
69
+ import { submitFeedback, FeedbackError } from 'edsger-feedback'
70
+
71
+ try {
72
+ await submitFeedback({ ... })
73
+ } catch (err) {
74
+ if (err instanceof FeedbackError) {
75
+ console.error(err.message, err.statusCode)
76
+ // e.g. "Too many submissions..." 429
77
+ }
78
+ }
79
+ ```
80
+
81
+ ## Requirements
82
+
83
+ - Node.js >= 18 (uses native `fetch`)
84
+
85
+ ## License
86
+
87
+ ISC
@@ -0,0 +1,25 @@
1
+ import type { SubmitFeedbackOptions, FeedbackResult, FeedbackConfig } from './types.js';
2
+ /**
3
+ * Get the feedback page config for a product slug.
4
+ * Useful for checking if feedback collection is enabled.
5
+ */
6
+ export declare function getFeedbackConfig(slug: string): Promise<FeedbackConfig>;
7
+ /**
8
+ * Submit feedback for a product.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * import { submitFeedback } from 'edsger-feedback'
13
+ *
14
+ * const result = await submitFeedback({
15
+ * slug: 'spoke-translator',
16
+ * title: 'Add dark mode',
17
+ * description: 'It would be great to have a dark mode option.',
18
+ * category: 'feature_request',
19
+ * reporterEmail: 'user@example.com',
20
+ * })
21
+ *
22
+ * console.log('Feedback submitted:', result.id)
23
+ * ```
24
+ */
25
+ export declare function submitFeedback(options: SubmitFeedbackOptions): Promise<FeedbackResult>;
package/dist/client.js ADDED
@@ -0,0 +1,94 @@
1
+ import { FeedbackError } from './types.js';
2
+ const ENDPOINT = 'https://ktkogvogdaffjmvrewiu.supabase.co/functions/v1/public-feedback';
3
+ const VALID_CATEGORIES = [
4
+ 'bug',
5
+ 'feature_request',
6
+ 'improvement',
7
+ 'question',
8
+ 'other',
9
+ ];
10
+ /**
11
+ * Get the feedback page config for a product slug.
12
+ * Useful for checking if feedback collection is enabled.
13
+ */
14
+ export async function getFeedbackConfig(slug) {
15
+ const res = await fetch(`${ENDPOINT}?slug=${encodeURIComponent(slug)}`);
16
+ const data = await res.json();
17
+ if (!res.ok) {
18
+ throw new FeedbackError(data.error || 'Failed to load feedback config', res.status);
19
+ }
20
+ const config = data.config;
21
+ return {
22
+ productId: config.product_id,
23
+ productName: config.product_name,
24
+ customTitle: config.custom_title,
25
+ welcomeMessage: config.welcome_message,
26
+ slug: config.slug,
27
+ };
28
+ }
29
+ /**
30
+ * Submit feedback for a product.
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * import { submitFeedback } from 'edsger-feedback'
35
+ *
36
+ * const result = await submitFeedback({
37
+ * slug: 'spoke-translator',
38
+ * title: 'Add dark mode',
39
+ * description: 'It would be great to have a dark mode option.',
40
+ * category: 'feature_request',
41
+ * reporterEmail: 'user@example.com',
42
+ * })
43
+ *
44
+ * console.log('Feedback submitted:', result.id)
45
+ * ```
46
+ */
47
+ export async function submitFeedback(options) {
48
+ validate(options);
49
+ const res = await fetch(ENDPOINT, {
50
+ method: 'POST',
51
+ headers: { 'Content-Type': 'application/json' },
52
+ body: JSON.stringify({
53
+ slug: options.slug,
54
+ category: options.category || 'other',
55
+ title: options.title.trim(),
56
+ description: options.description.trim(),
57
+ reporter_name: options.reporterName?.trim() || undefined,
58
+ reporter_email: options.reporterEmail?.trim() || undefined,
59
+ }),
60
+ });
61
+ const data = await res.json();
62
+ if (!res.ok) {
63
+ throw new FeedbackError(data.error || 'Failed to submit feedback', res.status);
64
+ }
65
+ return {
66
+ id: data.feedback.id,
67
+ createdAt: data.feedback.created_at,
68
+ };
69
+ }
70
+ function validate(options) {
71
+ if (!options.slug) {
72
+ throw new FeedbackError('slug is required', 400);
73
+ }
74
+ if (!options.title?.trim()) {
75
+ throw new FeedbackError('title is required', 400);
76
+ }
77
+ if (options.title.length > 200) {
78
+ throw new FeedbackError('title must be 200 characters or less', 400);
79
+ }
80
+ if (!options.description?.trim()) {
81
+ throw new FeedbackError('description is required', 400);
82
+ }
83
+ if (options.description.length > 5000) {
84
+ throw new FeedbackError('description must be 5000 characters or less', 400);
85
+ }
86
+ if (options.category &&
87
+ !VALID_CATEGORIES.includes(options.category)) {
88
+ throw new FeedbackError(`Invalid category. Must be one of: ${VALID_CATEGORIES.join(', ')}`, 400);
89
+ }
90
+ if (options.reporterEmail &&
91
+ !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(options.reporterEmail)) {
92
+ throw new FeedbackError('Invalid email format', 400);
93
+ }
94
+ }
@@ -0,0 +1,2 @@
1
+ export { submitFeedback, getFeedbackConfig } from './client.js';
2
+ export { FeedbackError, type SubmitFeedbackOptions, type FeedbackResult, type FeedbackConfig, type FeedbackCategory, } from './types.js';
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { submitFeedback, getFeedbackConfig } from './client.js';
2
+ export { FeedbackError, } from './types.js';
@@ -0,0 +1,30 @@
1
+ export type FeedbackCategory = 'bug' | 'feature_request' | 'improvement' | 'question' | 'other';
2
+ export interface SubmitFeedbackOptions {
3
+ /** Product slug, e.g. "spoke-translator" */
4
+ slug: string;
5
+ /** Feedback title (required, max 200 characters) */
6
+ title: string;
7
+ /** Feedback description (required, max 5000 characters) */
8
+ description: string;
9
+ /** Feedback category (defaults to "other") */
10
+ category?: FeedbackCategory;
11
+ /** Reporter name (optional) */
12
+ reporterName?: string;
13
+ /** Reporter email (optional, used for rate limiting) */
14
+ reporterEmail?: string;
15
+ }
16
+ export interface FeedbackResult {
17
+ id: string;
18
+ createdAt: string;
19
+ }
20
+ export interface FeedbackConfig {
21
+ productId: string;
22
+ productName: string;
23
+ customTitle: string | null;
24
+ welcomeMessage: string | null;
25
+ slug: string;
26
+ }
27
+ export declare class FeedbackError extends Error {
28
+ readonly statusCode: number;
29
+ constructor(message: string, statusCode: number);
30
+ }
package/dist/types.js ADDED
@@ -0,0 +1,8 @@
1
+ export class FeedbackError extends Error {
2
+ statusCode;
3
+ constructor(message, statusCode) {
4
+ super(message);
5
+ this.statusCode = statusCode;
6
+ this.name = 'FeedbackError';
7
+ }
8
+ }
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "edsger-feedback",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "dev": "tsc --watch",
19
+ "prepublishOnly": "npm run build"
20
+ },
21
+ "keywords": [
22
+ "edsger",
23
+ "feedback",
24
+ "user-feedback",
25
+ "product-feedback"
26
+ ],
27
+ "author": "",
28
+ "license": "ISC",
29
+ "description": "Submit user feedback to Edsger products programmatically",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/stevenzg/edsger.git",
33
+ "directory": "packages/feedback"
34
+ },
35
+ "engines": {
36
+ "node": ">=18.0.0"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "^20.0.0",
40
+ "typescript": "^5.0.0"
41
+ }
42
+ }