@siteping/adapter-memory 0.4.3
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 +86 -0
- package/dist/index.cjs +139 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +40 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +116 -0
- package/dist/index.js.map +1 -0
- package/dist/schema.d.ts +198 -0
- package/dist/siteping-core.d.ts +4 -0
- package/dist/types.d.ts +307 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
[](https://www.npmjs.com/package/@siteping/adapter-memory)
|
|
2
|
+
[](https://www.typescriptlang.org/)
|
|
3
|
+
|
|
4
|
+
# @siteping/adapter-memory
|
|
5
|
+
|
|
6
|
+
In-memory adapter for [Siteping](https://github.com/NeosiaNexus/SitePing) — zero dependencies, works everywhere.
|
|
7
|
+
|
|
8
|
+
Part of the [@siteping](https://github.com/NeosiaNexus/SitePing) monorepo.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @siteping/adapter-memory
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
### With the HTTP handler (server-side)
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { createSitepingHandler } from '@siteping/adapter-prisma'
|
|
22
|
+
import { MemoryStore } from '@siteping/adapter-memory'
|
|
23
|
+
|
|
24
|
+
const store = new MemoryStore()
|
|
25
|
+
|
|
26
|
+
export const { GET, POST, PATCH, DELETE, OPTIONS } = createSitepingHandler({ store })
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### With the widget directly (client-side, no server)
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { initSiteping } from '@siteping/widget'
|
|
33
|
+
import { MemoryStore } from '@siteping/adapter-memory'
|
|
34
|
+
|
|
35
|
+
const store = new MemoryStore()
|
|
36
|
+
|
|
37
|
+
initSiteping({
|
|
38
|
+
store,
|
|
39
|
+
projectName: 'my-project',
|
|
40
|
+
})
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## API
|
|
44
|
+
|
|
45
|
+
### `new MemoryStore()`
|
|
46
|
+
|
|
47
|
+
Creates a new in-memory store. Data lives in a plain array — lost on process restart.
|
|
48
|
+
|
|
49
|
+
### `store.clear()`
|
|
50
|
+
|
|
51
|
+
Remove all data and reset the ID counter.
|
|
52
|
+
|
|
53
|
+
## Use Cases
|
|
54
|
+
|
|
55
|
+
- **Testing** — fast, isolated store for unit and integration tests
|
|
56
|
+
- **Demos** — lightweight store that needs no database or localStorage
|
|
57
|
+
- **Prototyping** — get started without any infrastructure
|
|
58
|
+
- **Reference implementation** — simplest possible adapter for contributors
|
|
59
|
+
|
|
60
|
+
## Creating Your Own Adapter
|
|
61
|
+
|
|
62
|
+
`MemoryStore` is the simplest reference implementation of the `SitepingStore` interface. To create a new adapter (e.g. Drizzle, Supabase):
|
|
63
|
+
|
|
64
|
+
1. Implement the `SitepingStore` interface (6 methods)
|
|
65
|
+
2. Throw `StoreNotFoundError` on missing records in `updateFeedback` / `deleteFeedback`
|
|
66
|
+
3. Validate with the conformance test suite:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { testSitepingStore } from '@siteping/core/testing'
|
|
70
|
+
import { MyStore } from '../src/index.js'
|
|
71
|
+
|
|
72
|
+
testSitepingStore(() => new MyStore())
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Related Packages
|
|
76
|
+
|
|
77
|
+
| Package | Description |
|
|
78
|
+
|---------|-------------|
|
|
79
|
+
| [`@siteping/widget`](https://www.npmjs.com/package/@siteping/widget) | Browser feedback widget |
|
|
80
|
+
| [`@siteping/adapter-prisma`](https://www.npmjs.com/package/@siteping/adapter-prisma) | Server-side Prisma adapter |
|
|
81
|
+
| [`@siteping/adapter-localstorage`](https://www.npmjs.com/package/@siteping/adapter-localstorage) | Client-side localStorage adapter |
|
|
82
|
+
| [`@siteping/cli`](https://www.npmjs.com/package/@siteping/cli) | CLI for project setup |
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
[MIT](https://github.com/NeosiaNexus/SitePing/blob/main/LICENSE)
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
MemoryStore: () => MemoryStore,
|
|
24
|
+
StoreDuplicateError: () => StoreDuplicateError,
|
|
25
|
+
StoreNotFoundError: () => StoreNotFoundError
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(index_exports);
|
|
28
|
+
|
|
29
|
+
// ../core/src/types.ts
|
|
30
|
+
var StoreNotFoundError = class extends Error {
|
|
31
|
+
code = "STORE_NOT_FOUND";
|
|
32
|
+
constructor(message = "Record not found") {
|
|
33
|
+
super(message);
|
|
34
|
+
this.name = "StoreNotFoundError";
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var StoreDuplicateError = class extends Error {
|
|
38
|
+
code = "STORE_DUPLICATE";
|
|
39
|
+
constructor(message = "Duplicate record") {
|
|
40
|
+
super(message);
|
|
41
|
+
this.name = "StoreDuplicateError";
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// src/index.ts
|
|
46
|
+
var MemoryStore = class {
|
|
47
|
+
feedbacks = [];
|
|
48
|
+
idCounter = 1;
|
|
49
|
+
generateId() {
|
|
50
|
+
return `mem-${this.idCounter++}-${Date.now().toString(36)}`;
|
|
51
|
+
}
|
|
52
|
+
async createFeedback(data) {
|
|
53
|
+
const existing = this.feedbacks.find((f) => f.clientId === data.clientId);
|
|
54
|
+
if (existing) return existing;
|
|
55
|
+
const now = /* @__PURE__ */ new Date();
|
|
56
|
+
const feedbackId = this.generateId();
|
|
57
|
+
const annotations = data.annotations.map((ann) => ({
|
|
58
|
+
id: this.generateId(),
|
|
59
|
+
feedbackId,
|
|
60
|
+
cssSelector: ann.cssSelector,
|
|
61
|
+
xpath: ann.xpath,
|
|
62
|
+
textSnippet: ann.textSnippet,
|
|
63
|
+
elementTag: ann.elementTag,
|
|
64
|
+
elementId: ann.elementId ?? null,
|
|
65
|
+
textPrefix: ann.textPrefix,
|
|
66
|
+
textSuffix: ann.textSuffix,
|
|
67
|
+
fingerprint: ann.fingerprint,
|
|
68
|
+
neighborText: ann.neighborText,
|
|
69
|
+
xPct: ann.xPct,
|
|
70
|
+
yPct: ann.yPct,
|
|
71
|
+
wPct: ann.wPct,
|
|
72
|
+
hPct: ann.hPct,
|
|
73
|
+
scrollX: ann.scrollX,
|
|
74
|
+
scrollY: ann.scrollY,
|
|
75
|
+
viewportW: ann.viewportW,
|
|
76
|
+
viewportH: ann.viewportH,
|
|
77
|
+
devicePixelRatio: ann.devicePixelRatio,
|
|
78
|
+
createdAt: now
|
|
79
|
+
}));
|
|
80
|
+
const record = {
|
|
81
|
+
id: feedbackId,
|
|
82
|
+
type: data.type,
|
|
83
|
+
message: data.message,
|
|
84
|
+
status: data.status,
|
|
85
|
+
projectName: data.projectName,
|
|
86
|
+
url: data.url,
|
|
87
|
+
authorName: data.authorName,
|
|
88
|
+
authorEmail: data.authorEmail,
|
|
89
|
+
viewport: data.viewport,
|
|
90
|
+
userAgent: data.userAgent,
|
|
91
|
+
clientId: data.clientId,
|
|
92
|
+
resolvedAt: null,
|
|
93
|
+
createdAt: now,
|
|
94
|
+
updatedAt: now,
|
|
95
|
+
annotations
|
|
96
|
+
};
|
|
97
|
+
this.feedbacks.unshift(record);
|
|
98
|
+
return record;
|
|
99
|
+
}
|
|
100
|
+
async getFeedbacks(query) {
|
|
101
|
+
let results = this.feedbacks.filter((f) => f.projectName === query.projectName);
|
|
102
|
+
if (query.type) results = results.filter((f) => f.type === query.type);
|
|
103
|
+
if (query.status) results = results.filter((f) => f.status === query.status);
|
|
104
|
+
if (query.search) {
|
|
105
|
+
const s = query.search.toLowerCase();
|
|
106
|
+
results = results.filter((f) => f.message.toLowerCase().includes(s));
|
|
107
|
+
}
|
|
108
|
+
const total = results.length;
|
|
109
|
+
const page = query.page ?? 1;
|
|
110
|
+
const limit = Math.min(query.limit ?? 50, 100);
|
|
111
|
+
const start = (page - 1) * limit;
|
|
112
|
+
return { feedbacks: results.slice(start, start + limit), total };
|
|
113
|
+
}
|
|
114
|
+
async findByClientId(clientId) {
|
|
115
|
+
return this.feedbacks.find((f) => f.clientId === clientId) ?? null;
|
|
116
|
+
}
|
|
117
|
+
async updateFeedback(id, data) {
|
|
118
|
+
const fb = this.feedbacks.find((f) => f.id === id);
|
|
119
|
+
if (!fb) throw new StoreNotFoundError();
|
|
120
|
+
fb.status = data.status;
|
|
121
|
+
fb.resolvedAt = data.resolvedAt;
|
|
122
|
+
fb.updatedAt = /* @__PURE__ */ new Date();
|
|
123
|
+
return fb;
|
|
124
|
+
}
|
|
125
|
+
async deleteFeedback(id) {
|
|
126
|
+
const idx = this.feedbacks.findIndex((f) => f.id === id);
|
|
127
|
+
if (idx === -1) throw new StoreNotFoundError();
|
|
128
|
+
this.feedbacks.splice(idx, 1);
|
|
129
|
+
}
|
|
130
|
+
async deleteAllFeedbacks(projectName) {
|
|
131
|
+
this.feedbacks = this.feedbacks.filter((f) => f.projectName !== projectName);
|
|
132
|
+
}
|
|
133
|
+
/** Remove all data from this store instance. */
|
|
134
|
+
clear() {
|
|
135
|
+
this.feedbacks = [];
|
|
136
|
+
this.idCounter = 1;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../../core/src/types.ts"],"sourcesContent":["import {\n type AnnotationCreateInput,\n type AnnotationRecord,\n type FeedbackCreateInput,\n type FeedbackQuery,\n type FeedbackRecord,\n type FeedbackUpdateInput,\n type SitepingStore,\n StoreNotFoundError,\n} from \"@siteping/core\";\n\nexport type { SitepingStore } from \"@siteping/core\";\nexport { StoreDuplicateError, StoreNotFoundError } from \"@siteping/core\";\n\n/**\n * In-memory `SitepingStore` implementation.\n *\n * Zero dependencies, works in any JS environment (Node, Bun, Deno, browser,\n * Cloudflare Workers). Data lives in a plain array — lost on process restart.\n *\n * Use cases:\n * - **Testing** — fast, isolated store for unit/integration tests\n * - **Demos** — lightweight store that needs no database or localStorage\n * - **Reference** — simplest possible adapter for contributors to study\n *\n * @example\n * ```ts\n * import { MemoryStore } from '@siteping/adapter-memory'\n *\n * const store = new MemoryStore()\n * // Pass to createSitepingHandler({ store }) or initSiteping({ store })\n * ```\n */\nexport class MemoryStore implements SitepingStore {\n private feedbacks: FeedbackRecord[] = [];\n private idCounter = 1;\n\n private generateId(): string {\n return `mem-${this.idCounter++}-${Date.now().toString(36)}`;\n }\n\n async createFeedback(data: FeedbackCreateInput): Promise<FeedbackRecord> {\n // ClientId dedup — idempotent\n const existing = this.feedbacks.find((f) => f.clientId === data.clientId);\n if (existing) return existing;\n\n const now = new Date();\n const feedbackId = this.generateId();\n\n const annotations: AnnotationRecord[] = data.annotations.map((ann: AnnotationCreateInput) => ({\n id: this.generateId(),\n feedbackId,\n cssSelector: ann.cssSelector,\n xpath: ann.xpath,\n textSnippet: ann.textSnippet,\n elementTag: ann.elementTag,\n elementId: ann.elementId ?? null,\n textPrefix: ann.textPrefix,\n textSuffix: ann.textSuffix,\n fingerprint: ann.fingerprint,\n neighborText: ann.neighborText,\n xPct: ann.xPct,\n yPct: ann.yPct,\n wPct: ann.wPct,\n hPct: ann.hPct,\n scrollX: ann.scrollX,\n scrollY: ann.scrollY,\n viewportW: ann.viewportW,\n viewportH: ann.viewportH,\n devicePixelRatio: ann.devicePixelRatio,\n createdAt: now,\n }));\n\n const record: FeedbackRecord = {\n id: feedbackId,\n type: data.type,\n message: data.message,\n status: data.status,\n projectName: data.projectName,\n url: data.url,\n authorName: data.authorName,\n authorEmail: data.authorEmail,\n viewport: data.viewport,\n userAgent: data.userAgent,\n clientId: data.clientId,\n resolvedAt: null,\n createdAt: now,\n updatedAt: now,\n annotations,\n };\n\n this.feedbacks.unshift(record);\n return record;\n }\n\n async getFeedbacks(query: FeedbackQuery): Promise<{ feedbacks: FeedbackRecord[]; total: number }> {\n let results = this.feedbacks.filter((f) => f.projectName === query.projectName);\n\n if (query.type) results = results.filter((f) => f.type === query.type);\n if (query.status) results = results.filter((f) => f.status === query.status);\n if (query.search) {\n const s = query.search.toLowerCase();\n results = results.filter((f) => f.message.toLowerCase().includes(s));\n }\n\n const total = results.length;\n const page = query.page ?? 1;\n const limit = Math.min(query.limit ?? 50, 100);\n const start = (page - 1) * limit;\n\n return { feedbacks: results.slice(start, start + limit), total };\n }\n\n async findByClientId(clientId: string): Promise<FeedbackRecord | null> {\n return this.feedbacks.find((f) => f.clientId === clientId) ?? null;\n }\n\n async updateFeedback(id: string, data: FeedbackUpdateInput): Promise<FeedbackRecord> {\n const fb = this.feedbacks.find((f) => f.id === id);\n if (!fb) throw new StoreNotFoundError();\n\n fb.status = data.status;\n fb.resolvedAt = data.resolvedAt;\n fb.updatedAt = new Date();\n return fb;\n }\n\n async deleteFeedback(id: string): Promise<void> {\n const idx = this.feedbacks.findIndex((f) => f.id === id);\n if (idx === -1) throw new StoreNotFoundError();\n this.feedbacks.splice(idx, 1);\n }\n\n async deleteAllFeedbacks(projectName: string): Promise<void> {\n this.feedbacks = this.feedbacks.filter((f) => f.projectName !== projectName);\n }\n\n /** Remove all data from this store instance. */\n clear(): void {\n this.feedbacks = [];\n this.idCounter = 1;\n }\n}\n","// ---------------------------------------------------------------------------\n// Config\n// ---------------------------------------------------------------------------\n\n/** Configuration options for the Siteping widget. */\nexport interface SitepingConfig {\n /** HTTP endpoint that receives feedbacks (e.g. '/api/siteping'). Required unless `store` is provided. */\n endpoint?: string | undefined;\n /** Required — project identifier used to scope feedbacks */\n projectName: string;\n /** Direct store for client-side mode. When set, bypasses HTTP and uses the store directly in the browser. */\n store?: SitepingStore | undefined;\n /** FAB position — defaults to 'bottom-right' */\n position?: \"bottom-right\" | \"bottom-left\";\n /** Accent color for the widget UI — defaults to '#0066ff' */\n accentColor?: string;\n /** Show the widget even in production — defaults to false */\n forceShow?: boolean;\n /** Enable debug logging of lifecycle events — defaults to false */\n debug?: boolean;\n /** Color theme — defaults to 'light' */\n theme?: \"light\" | \"dark\" | \"auto\";\n /** UI locale — defaults to 'en' */\n locale?: \"fr\" | \"en\" | (string & {}) | undefined;\n /** Called when the widget is skipped (production mode, mobile viewport) */\n onSkip?: (reason: \"production\" | \"mobile\") => void;\n\n // Events\n /** Called when the feedback panel is opened. */\n onOpen?: () => void;\n /** Called when the feedback panel is closed. */\n onClose?: () => void;\n onFeedbackSent?: (feedback: FeedbackResponse) => void;\n onError?: (error: Error) => void;\n /** Called when the user starts drawing an annotation. */\n onAnnotationStart?: () => void;\n /** Called when the user finishes drawing an annotation. */\n onAnnotationEnd?: () => void;\n}\n\n/** Instance returned by initSiteping() with lifecycle methods. */\nexport interface SitepingInstance {\n /** Remove the widget from the DOM and clean up all listeners. */\n destroy: () => void;\n /** Open the panel programmatically */\n open: () => void;\n /** Close the panel */\n close: () => void;\n /** Reload feedbacks from server */\n refresh: () => void;\n /** Subscribe to a public widget event */\n on: <K extends keyof SitepingPublicEvents>(\n event: K,\n listener: (...args: SitepingPublicEvents[K]) => void,\n ) => () => void;\n /** Unsubscribe from a public widget event */\n off: <K extends keyof SitepingPublicEvents>(event: K, listener: (...args: SitepingPublicEvents[K]) => void) => void;\n}\n\n/** Events exposed to consumers via SitepingInstance.on / .off */\nexport interface SitepingPublicEvents {\n \"feedback:sent\": [FeedbackResponse];\n \"feedback:deleted\": [string];\n \"panel:open\": [];\n \"panel:close\": [];\n}\n\n// ---------------------------------------------------------------------------\n// Feedback\n// ---------------------------------------------------------------------------\n\n/** Single source of truth for feedback types — used by both TS types and Zod schemas. */\nexport const FEEDBACK_TYPES = [\"question\", \"change\", \"bug\", \"other\"] as const;\nexport type FeedbackType = (typeof FEEDBACK_TYPES)[number];\n\n/** Single source of truth for feedback statuses. */\nexport const FEEDBACK_STATUSES = [\"open\", \"resolved\"] as const;\nexport type FeedbackStatus = (typeof FEEDBACK_STATUSES)[number];\n\n// ---------------------------------------------------------------------------\n// Abstract Store — adapter pattern\n// ---------------------------------------------------------------------------\n\n/** Input for creating a feedback record in the store. */\nexport interface FeedbackCreateInput {\n projectName: string;\n type: FeedbackType;\n message: string;\n status: FeedbackStatus;\n url: string;\n viewport: string;\n userAgent: string;\n authorName: string;\n authorEmail: string;\n clientId: string;\n annotations: AnnotationCreateInput[];\n}\n\n/** Input for a single annotation when creating a feedback. */\nexport interface AnnotationCreateInput {\n cssSelector: string;\n xpath: string;\n textSnippet: string;\n elementTag: string;\n elementId?: string | undefined;\n textPrefix: string;\n textSuffix: string;\n fingerprint: string;\n neighborText: string;\n xPct: number;\n yPct: number;\n wPct: number;\n hPct: number;\n scrollX: number;\n scrollY: number;\n viewportW: number;\n viewportH: number;\n devicePixelRatio: number;\n}\n\n/** Query parameters for fetching feedbacks. */\nexport interface FeedbackQuery {\n projectName: string;\n type?: FeedbackType | undefined;\n status?: FeedbackStatus | undefined;\n search?: string | undefined;\n page?: number | undefined;\n limit?: number | undefined;\n}\n\n/** Update payload for patching a feedback. */\nexport interface FeedbackUpdateInput {\n status: FeedbackStatus;\n resolvedAt: Date | null;\n}\n\n/** A persisted feedback record returned by the store. */\nexport interface FeedbackRecord {\n id: string;\n type: FeedbackType;\n message: string;\n status: FeedbackStatus;\n projectName: string;\n url: string;\n authorName: string;\n authorEmail: string;\n viewport: string;\n userAgent: string;\n clientId: string;\n resolvedAt: Date | null;\n createdAt: Date;\n updatedAt: Date;\n annotations: AnnotationRecord[];\n}\n\n/** A persisted annotation record returned by the store. */\nexport interface AnnotationRecord {\n id: string;\n feedbackId: string;\n cssSelector: string;\n xpath: string;\n textSnippet: string;\n elementTag: string;\n elementId: string | null;\n textPrefix: string;\n textSuffix: string;\n fingerprint: string;\n neighborText: string;\n xPct: number;\n yPct: number;\n wPct: number;\n hPct: number;\n scrollX: number;\n scrollY: number;\n viewportW: number;\n viewportH: number;\n devicePixelRatio: number;\n createdAt: Date;\n}\n\n// ---------------------------------------------------------------------------\n// Store errors — throw these from adapter implementations\n// ---------------------------------------------------------------------------\n\n/**\n * Thrown when a record is not found during update or delete.\n *\n * Handlers translate this to HTTP 404. Adapters MUST throw this (not\n * ORM-specific errors) so the handler layer remains ORM-agnostic.\n */\nexport class StoreNotFoundError extends Error {\n readonly code = \"STORE_NOT_FOUND\" as const;\n constructor(message = \"Record not found\") {\n super(message);\n this.name = \"StoreNotFoundError\";\n }\n}\n\n/**\n * Thrown when a unique constraint is violated (e.g. duplicate `clientId`).\n *\n * Handlers use this to return the existing record instead of failing.\n */\nexport class StoreDuplicateError extends Error {\n readonly code = \"STORE_DUPLICATE\" as const;\n constructor(message = \"Duplicate record\") {\n super(message);\n this.name = \"StoreDuplicateError\";\n }\n}\n\n/** Type guard — works for `StoreNotFoundError` and ORM-specific equivalents (e.g. Prisma P2025). */\nexport function isStoreNotFound(error: unknown): boolean {\n if (error instanceof StoreNotFoundError) return true;\n // Backwards compat: Prisma's P2025\n return typeof error === \"object\" && error !== null && \"code\" in error && (error as { code: string }).code === \"P2025\";\n}\n\n/** Type guard — works for `StoreDuplicateError` and ORM-specific equivalents (e.g. Prisma P2002). */\nexport function isStoreDuplicate(error: unknown): boolean {\n if (error instanceof StoreDuplicateError) return true;\n // Backwards compat: Prisma's P2002\n return typeof error === \"object\" && error !== null && \"code\" in error && (error as { code: string }).code === \"P2002\";\n}\n\n// ---------------------------------------------------------------------------\n// Store helpers — shared conversion logic for adapters\n// ---------------------------------------------------------------------------\n\n/** Flatten a widget `AnnotationPayload` (nested anchor + rect) into a flat `AnnotationCreateInput`. */\nexport function flattenAnnotation(ann: AnnotationPayload): AnnotationCreateInput {\n return {\n cssSelector: ann.anchor.cssSelector,\n xpath: ann.anchor.xpath,\n textSnippet: ann.anchor.textSnippet,\n elementTag: ann.anchor.elementTag,\n elementId: ann.anchor.elementId,\n textPrefix: ann.anchor.textPrefix,\n textSuffix: ann.anchor.textSuffix,\n fingerprint: ann.anchor.fingerprint,\n neighborText: ann.anchor.neighborText,\n xPct: ann.rect.xPct,\n yPct: ann.rect.yPct,\n wPct: ann.rect.wPct,\n hPct: ann.rect.hPct,\n scrollX: ann.scrollX,\n scrollY: ann.scrollY,\n viewportW: ann.viewportW,\n viewportH: ann.viewportH,\n devicePixelRatio: ann.devicePixelRatio,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Abstract Store — adapter pattern\n// ---------------------------------------------------------------------------\n\n/**\n * Abstract storage interface for Siteping.\n *\n * Any adapter (Prisma, Drizzle, raw SQL, localStorage, etc.) implements this\n * interface. The HTTP handler and widget `StoreClient` operate against\n * `SitepingStore`, decoupled from the storage backend.\n *\n * ## Error contract\n *\n * - **`updateFeedback` / `deleteFeedback`**: throw `StoreNotFoundError` when\n * the record does not exist.\n * - **`createFeedback`**: either return the existing record on duplicate\n * `clientId` (idempotent) or throw `StoreDuplicateError`. The handler\n * handles both patterns.\n * - Other methods should not throw on empty results — return empty arrays or `null`.\n */\nexport interface SitepingStore {\n /** Create a feedback with its annotations. Idempotent on `clientId` — return existing record on duplicate, or throw `StoreDuplicateError`. */\n createFeedback(data: FeedbackCreateInput): Promise<FeedbackRecord>;\n /** Paginated query with optional filters. Returns empty array (not error) when no results. */\n getFeedbacks(query: FeedbackQuery): Promise<{ feedbacks: FeedbackRecord[]; total: number }>;\n /** Lookup by client-generated UUID. Returns `null` (not error) when not found. */\n findByClientId(clientId: string): Promise<FeedbackRecord | null>;\n /** Update status/resolvedAt. Throws `StoreNotFoundError` if `id` does not exist. */\n updateFeedback(id: string, data: FeedbackUpdateInput): Promise<FeedbackRecord>;\n /** Delete a single record. Throws `StoreNotFoundError` if `id` does not exist. */\n deleteFeedback(id: string): Promise<void>;\n /** Bulk delete all feedbacks for a project. No-op (not error) if none exist. */\n deleteAllFeedbacks(projectName: string): Promise<void>;\n}\n\n/** Payload sent from the widget to the server when submitting feedback. */\nexport interface FeedbackPayload {\n projectName: string;\n type: FeedbackType;\n message: string;\n url: string;\n viewport: string;\n userAgent: string;\n authorName: string;\n authorEmail: string;\n annotations: AnnotationPayload[];\n /** Client-generated UUID for deduplication */\n clientId: string;\n}\n\n// ---------------------------------------------------------------------------\n// Annotation — multi-selector anchoring (Hypothesis / W3C Web Annotation)\n// ---------------------------------------------------------------------------\n\n/** DOM anchoring data for re-attaching annotations to page elements. */\nexport interface AnchorData {\n /** CSS selector generated by @medv/finder — primary anchor */\n cssSelector: string;\n /** XPath — fallback 1 */\n xpath: string;\n /** First ~120 chars of element innerText — empty string if none */\n textSnippet: string;\n /** Tag name for validation (e.g. \"DIV\", \"SECTION\") */\n elementTag: string;\n /** Element id attribute if available — most stable */\n elementId?: string | undefined;\n /** ~32 chars of text before this element in document flow (disambiguation) */\n textPrefix: string;\n /** ~32 chars of text after this element in document flow (disambiguation) */\n textSuffix: string;\n /** Structural fingerprint: \"childCount:siblingIdx:attrHash\" */\n fingerprint: string;\n /** Text content of adjacent sibling elements (context) */\n neighborText: string;\n}\n\n/** Drawn rectangle coordinates as percentages relative to the anchor element. */\nexport interface RectData {\n /** X offset as fraction of anchor element width — must be in range [0, 1] */\n xPct: number;\n /** Y offset as fraction of anchor element height — must be in range [0, 1] */\n yPct: number;\n /** Width as fraction of anchor element width — must be in range [0, 1] */\n wPct: number;\n /** Height as fraction of anchor element height — must be in range [0, 1] */\n hPct: number;\n}\n\n/** Annotation data sent as part of a feedback submission. */\nexport interface AnnotationPayload {\n anchor: AnchorData;\n rect: RectData;\n scrollX: number;\n scrollY: number;\n viewportW: number;\n viewportH: number;\n devicePixelRatio: number;\n}\n\n// ---------------------------------------------------------------------------\n// API responses\n// ---------------------------------------------------------------------------\n\n/** Feedback record as returned by the API (dates serialized as strings). */\nexport interface FeedbackResponse {\n id: string;\n projectName: string;\n type: FeedbackType;\n message: string;\n status: FeedbackStatus;\n url: string;\n viewport: string;\n userAgent: string;\n authorName: string;\n authorEmail: string;\n resolvedAt: string | null;\n createdAt: string;\n updatedAt: string;\n annotations: AnnotationResponse[];\n}\n\n/** Annotation record as returned by the API. */\nexport interface AnnotationResponse {\n id: string;\n feedbackId: string;\n cssSelector: string;\n xpath: string;\n textSnippet: string;\n elementTag: string;\n elementId: string | null;\n textPrefix: string;\n textSuffix: string;\n fingerprint: string;\n neighborText: string;\n xPct: number;\n yPct: number;\n wPct: number;\n hPct: number;\n scrollX: number;\n scrollY: number;\n viewportW: number;\n viewportH: number;\n devicePixelRatio: number;\n createdAt: string;\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { SitepingStore, FeedbackCreateInput, FeedbackRecord, FeedbackQuery, FeedbackUpdateInput } from './siteping-core.js';
|
|
2
|
+
export { SitepingStore, StoreDuplicateError, StoreNotFoundError } from './siteping-core.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* In-memory `SitepingStore` implementation.
|
|
6
|
+
*
|
|
7
|
+
* Zero dependencies, works in any JS environment (Node, Bun, Deno, browser,
|
|
8
|
+
* Cloudflare Workers). Data lives in a plain array — lost on process restart.
|
|
9
|
+
*
|
|
10
|
+
* Use cases:
|
|
11
|
+
* - **Testing** — fast, isolated store for unit/integration tests
|
|
12
|
+
* - **Demos** — lightweight store that needs no database or localStorage
|
|
13
|
+
* - **Reference** — simplest possible adapter for contributors to study
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { MemoryStore } from '@siteping/adapter-memory'
|
|
18
|
+
*
|
|
19
|
+
* const store = new MemoryStore()
|
|
20
|
+
* // Pass to createSitepingHandler({ store }) or initSiteping({ store })
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
declare class MemoryStore implements SitepingStore {
|
|
24
|
+
private feedbacks;
|
|
25
|
+
private idCounter;
|
|
26
|
+
private generateId;
|
|
27
|
+
createFeedback(data: FeedbackCreateInput): Promise<FeedbackRecord>;
|
|
28
|
+
getFeedbacks(query: FeedbackQuery): Promise<{
|
|
29
|
+
feedbacks: FeedbackRecord[];
|
|
30
|
+
total: number;
|
|
31
|
+
}>;
|
|
32
|
+
findByClientId(clientId: string): Promise<FeedbackRecord | null>;
|
|
33
|
+
updateFeedback(id: string, data: FeedbackUpdateInput): Promise<FeedbackRecord>;
|
|
34
|
+
deleteFeedback(id: string): Promise<void>;
|
|
35
|
+
deleteAllFeedbacks(projectName: string): Promise<void>;
|
|
36
|
+
/** Remove all data from this store instance. */
|
|
37
|
+
clear(): void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { MemoryStore };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { SitepingStore, FeedbackCreateInput, FeedbackRecord, FeedbackQuery, FeedbackUpdateInput } from './siteping-core.js';
|
|
2
|
+
export { SitepingStore, StoreDuplicateError, StoreNotFoundError } from './siteping-core.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* In-memory `SitepingStore` implementation.
|
|
6
|
+
*
|
|
7
|
+
* Zero dependencies, works in any JS environment (Node, Bun, Deno, browser,
|
|
8
|
+
* Cloudflare Workers). Data lives in a plain array — lost on process restart.
|
|
9
|
+
*
|
|
10
|
+
* Use cases:
|
|
11
|
+
* - **Testing** — fast, isolated store for unit/integration tests
|
|
12
|
+
* - **Demos** — lightweight store that needs no database or localStorage
|
|
13
|
+
* - **Reference** — simplest possible adapter for contributors to study
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { MemoryStore } from '@siteping/adapter-memory'
|
|
18
|
+
*
|
|
19
|
+
* const store = new MemoryStore()
|
|
20
|
+
* // Pass to createSitepingHandler({ store }) or initSiteping({ store })
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
declare class MemoryStore implements SitepingStore {
|
|
24
|
+
private feedbacks;
|
|
25
|
+
private idCounter;
|
|
26
|
+
private generateId;
|
|
27
|
+
createFeedback(data: FeedbackCreateInput): Promise<FeedbackRecord>;
|
|
28
|
+
getFeedbacks(query: FeedbackQuery): Promise<{
|
|
29
|
+
feedbacks: FeedbackRecord[];
|
|
30
|
+
total: number;
|
|
31
|
+
}>;
|
|
32
|
+
findByClientId(clientId: string): Promise<FeedbackRecord | null>;
|
|
33
|
+
updateFeedback(id: string, data: FeedbackUpdateInput): Promise<FeedbackRecord>;
|
|
34
|
+
deleteFeedback(id: string): Promise<void>;
|
|
35
|
+
deleteAllFeedbacks(projectName: string): Promise<void>;
|
|
36
|
+
/** Remove all data from this store instance. */
|
|
37
|
+
clear(): void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { MemoryStore };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// ../core/src/types.ts
|
|
2
|
+
var StoreNotFoundError = class extends Error {
|
|
3
|
+
code = "STORE_NOT_FOUND";
|
|
4
|
+
constructor(message = "Record not found") {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "StoreNotFoundError";
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
var StoreDuplicateError = class extends Error {
|
|
10
|
+
code = "STORE_DUPLICATE";
|
|
11
|
+
constructor(message = "Duplicate record") {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "StoreDuplicateError";
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// src/index.ts
|
|
18
|
+
var MemoryStore = class {
|
|
19
|
+
feedbacks = [];
|
|
20
|
+
idCounter = 1;
|
|
21
|
+
generateId() {
|
|
22
|
+
return `mem-${this.idCounter++}-${Date.now().toString(36)}`;
|
|
23
|
+
}
|
|
24
|
+
async createFeedback(data) {
|
|
25
|
+
const existing = this.feedbacks.find((f) => f.clientId === data.clientId);
|
|
26
|
+
if (existing) return existing;
|
|
27
|
+
const now = /* @__PURE__ */ new Date();
|
|
28
|
+
const feedbackId = this.generateId();
|
|
29
|
+
const annotations = data.annotations.map((ann) => ({
|
|
30
|
+
id: this.generateId(),
|
|
31
|
+
feedbackId,
|
|
32
|
+
cssSelector: ann.cssSelector,
|
|
33
|
+
xpath: ann.xpath,
|
|
34
|
+
textSnippet: ann.textSnippet,
|
|
35
|
+
elementTag: ann.elementTag,
|
|
36
|
+
elementId: ann.elementId ?? null,
|
|
37
|
+
textPrefix: ann.textPrefix,
|
|
38
|
+
textSuffix: ann.textSuffix,
|
|
39
|
+
fingerprint: ann.fingerprint,
|
|
40
|
+
neighborText: ann.neighborText,
|
|
41
|
+
xPct: ann.xPct,
|
|
42
|
+
yPct: ann.yPct,
|
|
43
|
+
wPct: ann.wPct,
|
|
44
|
+
hPct: ann.hPct,
|
|
45
|
+
scrollX: ann.scrollX,
|
|
46
|
+
scrollY: ann.scrollY,
|
|
47
|
+
viewportW: ann.viewportW,
|
|
48
|
+
viewportH: ann.viewportH,
|
|
49
|
+
devicePixelRatio: ann.devicePixelRatio,
|
|
50
|
+
createdAt: now
|
|
51
|
+
}));
|
|
52
|
+
const record = {
|
|
53
|
+
id: feedbackId,
|
|
54
|
+
type: data.type,
|
|
55
|
+
message: data.message,
|
|
56
|
+
status: data.status,
|
|
57
|
+
projectName: data.projectName,
|
|
58
|
+
url: data.url,
|
|
59
|
+
authorName: data.authorName,
|
|
60
|
+
authorEmail: data.authorEmail,
|
|
61
|
+
viewport: data.viewport,
|
|
62
|
+
userAgent: data.userAgent,
|
|
63
|
+
clientId: data.clientId,
|
|
64
|
+
resolvedAt: null,
|
|
65
|
+
createdAt: now,
|
|
66
|
+
updatedAt: now,
|
|
67
|
+
annotations
|
|
68
|
+
};
|
|
69
|
+
this.feedbacks.unshift(record);
|
|
70
|
+
return record;
|
|
71
|
+
}
|
|
72
|
+
async getFeedbacks(query) {
|
|
73
|
+
let results = this.feedbacks.filter((f) => f.projectName === query.projectName);
|
|
74
|
+
if (query.type) results = results.filter((f) => f.type === query.type);
|
|
75
|
+
if (query.status) results = results.filter((f) => f.status === query.status);
|
|
76
|
+
if (query.search) {
|
|
77
|
+
const s = query.search.toLowerCase();
|
|
78
|
+
results = results.filter((f) => f.message.toLowerCase().includes(s));
|
|
79
|
+
}
|
|
80
|
+
const total = results.length;
|
|
81
|
+
const page = query.page ?? 1;
|
|
82
|
+
const limit = Math.min(query.limit ?? 50, 100);
|
|
83
|
+
const start = (page - 1) * limit;
|
|
84
|
+
return { feedbacks: results.slice(start, start + limit), total };
|
|
85
|
+
}
|
|
86
|
+
async findByClientId(clientId) {
|
|
87
|
+
return this.feedbacks.find((f) => f.clientId === clientId) ?? null;
|
|
88
|
+
}
|
|
89
|
+
async updateFeedback(id, data) {
|
|
90
|
+
const fb = this.feedbacks.find((f) => f.id === id);
|
|
91
|
+
if (!fb) throw new StoreNotFoundError();
|
|
92
|
+
fb.status = data.status;
|
|
93
|
+
fb.resolvedAt = data.resolvedAt;
|
|
94
|
+
fb.updatedAt = /* @__PURE__ */ new Date();
|
|
95
|
+
return fb;
|
|
96
|
+
}
|
|
97
|
+
async deleteFeedback(id) {
|
|
98
|
+
const idx = this.feedbacks.findIndex((f) => f.id === id);
|
|
99
|
+
if (idx === -1) throw new StoreNotFoundError();
|
|
100
|
+
this.feedbacks.splice(idx, 1);
|
|
101
|
+
}
|
|
102
|
+
async deleteAllFeedbacks(projectName) {
|
|
103
|
+
this.feedbacks = this.feedbacks.filter((f) => f.projectName !== projectName);
|
|
104
|
+
}
|
|
105
|
+
/** Remove all data from this store instance. */
|
|
106
|
+
clear() {
|
|
107
|
+
this.feedbacks = [];
|
|
108
|
+
this.idCounter = 1;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
export {
|
|
112
|
+
MemoryStore,
|
|
113
|
+
StoreDuplicateError,
|
|
114
|
+
StoreNotFoundError
|
|
115
|
+
};
|
|
116
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../core/src/types.ts","../src/index.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// Config\n// ---------------------------------------------------------------------------\n\n/** Configuration options for the Siteping widget. */\nexport interface SitepingConfig {\n /** HTTP endpoint that receives feedbacks (e.g. '/api/siteping'). Required unless `store` is provided. */\n endpoint?: string | undefined;\n /** Required — project identifier used to scope feedbacks */\n projectName: string;\n /** Direct store for client-side mode. When set, bypasses HTTP and uses the store directly in the browser. */\n store?: SitepingStore | undefined;\n /** FAB position — defaults to 'bottom-right' */\n position?: \"bottom-right\" | \"bottom-left\";\n /** Accent color for the widget UI — defaults to '#0066ff' */\n accentColor?: string;\n /** Show the widget even in production — defaults to false */\n forceShow?: boolean;\n /** Enable debug logging of lifecycle events — defaults to false */\n debug?: boolean;\n /** Color theme — defaults to 'light' */\n theme?: \"light\" | \"dark\" | \"auto\";\n /** UI locale — defaults to 'en' */\n locale?: \"fr\" | \"en\" | (string & {}) | undefined;\n /** Called when the widget is skipped (production mode, mobile viewport) */\n onSkip?: (reason: \"production\" | \"mobile\") => void;\n\n // Events\n /** Called when the feedback panel is opened. */\n onOpen?: () => void;\n /** Called when the feedback panel is closed. */\n onClose?: () => void;\n onFeedbackSent?: (feedback: FeedbackResponse) => void;\n onError?: (error: Error) => void;\n /** Called when the user starts drawing an annotation. */\n onAnnotationStart?: () => void;\n /** Called when the user finishes drawing an annotation. */\n onAnnotationEnd?: () => void;\n}\n\n/** Instance returned by initSiteping() with lifecycle methods. */\nexport interface SitepingInstance {\n /** Remove the widget from the DOM and clean up all listeners. */\n destroy: () => void;\n /** Open the panel programmatically */\n open: () => void;\n /** Close the panel */\n close: () => void;\n /** Reload feedbacks from server */\n refresh: () => void;\n /** Subscribe to a public widget event */\n on: <K extends keyof SitepingPublicEvents>(\n event: K,\n listener: (...args: SitepingPublicEvents[K]) => void,\n ) => () => void;\n /** Unsubscribe from a public widget event */\n off: <K extends keyof SitepingPublicEvents>(event: K, listener: (...args: SitepingPublicEvents[K]) => void) => void;\n}\n\n/** Events exposed to consumers via SitepingInstance.on / .off */\nexport interface SitepingPublicEvents {\n \"feedback:sent\": [FeedbackResponse];\n \"feedback:deleted\": [string];\n \"panel:open\": [];\n \"panel:close\": [];\n}\n\n// ---------------------------------------------------------------------------\n// Feedback\n// ---------------------------------------------------------------------------\n\n/** Single source of truth for feedback types — used by both TS types and Zod schemas. */\nexport const FEEDBACK_TYPES = [\"question\", \"change\", \"bug\", \"other\"] as const;\nexport type FeedbackType = (typeof FEEDBACK_TYPES)[number];\n\n/** Single source of truth for feedback statuses. */\nexport const FEEDBACK_STATUSES = [\"open\", \"resolved\"] as const;\nexport type FeedbackStatus = (typeof FEEDBACK_STATUSES)[number];\n\n// ---------------------------------------------------------------------------\n// Abstract Store — adapter pattern\n// ---------------------------------------------------------------------------\n\n/** Input for creating a feedback record in the store. */\nexport interface FeedbackCreateInput {\n projectName: string;\n type: FeedbackType;\n message: string;\n status: FeedbackStatus;\n url: string;\n viewport: string;\n userAgent: string;\n authorName: string;\n authorEmail: string;\n clientId: string;\n annotations: AnnotationCreateInput[];\n}\n\n/** Input for a single annotation when creating a feedback. */\nexport interface AnnotationCreateInput {\n cssSelector: string;\n xpath: string;\n textSnippet: string;\n elementTag: string;\n elementId?: string | undefined;\n textPrefix: string;\n textSuffix: string;\n fingerprint: string;\n neighborText: string;\n xPct: number;\n yPct: number;\n wPct: number;\n hPct: number;\n scrollX: number;\n scrollY: number;\n viewportW: number;\n viewportH: number;\n devicePixelRatio: number;\n}\n\n/** Query parameters for fetching feedbacks. */\nexport interface FeedbackQuery {\n projectName: string;\n type?: FeedbackType | undefined;\n status?: FeedbackStatus | undefined;\n search?: string | undefined;\n page?: number | undefined;\n limit?: number | undefined;\n}\n\n/** Update payload for patching a feedback. */\nexport interface FeedbackUpdateInput {\n status: FeedbackStatus;\n resolvedAt: Date | null;\n}\n\n/** A persisted feedback record returned by the store. */\nexport interface FeedbackRecord {\n id: string;\n type: FeedbackType;\n message: string;\n status: FeedbackStatus;\n projectName: string;\n url: string;\n authorName: string;\n authorEmail: string;\n viewport: string;\n userAgent: string;\n clientId: string;\n resolvedAt: Date | null;\n createdAt: Date;\n updatedAt: Date;\n annotations: AnnotationRecord[];\n}\n\n/** A persisted annotation record returned by the store. */\nexport interface AnnotationRecord {\n id: string;\n feedbackId: string;\n cssSelector: string;\n xpath: string;\n textSnippet: string;\n elementTag: string;\n elementId: string | null;\n textPrefix: string;\n textSuffix: string;\n fingerprint: string;\n neighborText: string;\n xPct: number;\n yPct: number;\n wPct: number;\n hPct: number;\n scrollX: number;\n scrollY: number;\n viewportW: number;\n viewportH: number;\n devicePixelRatio: number;\n createdAt: Date;\n}\n\n// ---------------------------------------------------------------------------\n// Store errors — throw these from adapter implementations\n// ---------------------------------------------------------------------------\n\n/**\n * Thrown when a record is not found during update or delete.\n *\n * Handlers translate this to HTTP 404. Adapters MUST throw this (not\n * ORM-specific errors) so the handler layer remains ORM-agnostic.\n */\nexport class StoreNotFoundError extends Error {\n readonly code = \"STORE_NOT_FOUND\" as const;\n constructor(message = \"Record not found\") {\n super(message);\n this.name = \"StoreNotFoundError\";\n }\n}\n\n/**\n * Thrown when a unique constraint is violated (e.g. duplicate `clientId`).\n *\n * Handlers use this to return the existing record instead of failing.\n */\nexport class StoreDuplicateError extends Error {\n readonly code = \"STORE_DUPLICATE\" as const;\n constructor(message = \"Duplicate record\") {\n super(message);\n this.name = \"StoreDuplicateError\";\n }\n}\n\n/** Type guard — works for `StoreNotFoundError` and ORM-specific equivalents (e.g. Prisma P2025). */\nexport function isStoreNotFound(error: unknown): boolean {\n if (error instanceof StoreNotFoundError) return true;\n // Backwards compat: Prisma's P2025\n return typeof error === \"object\" && error !== null && \"code\" in error && (error as { code: string }).code === \"P2025\";\n}\n\n/** Type guard — works for `StoreDuplicateError` and ORM-specific equivalents (e.g. Prisma P2002). */\nexport function isStoreDuplicate(error: unknown): boolean {\n if (error instanceof StoreDuplicateError) return true;\n // Backwards compat: Prisma's P2002\n return typeof error === \"object\" && error !== null && \"code\" in error && (error as { code: string }).code === \"P2002\";\n}\n\n// ---------------------------------------------------------------------------\n// Store helpers — shared conversion logic for adapters\n// ---------------------------------------------------------------------------\n\n/** Flatten a widget `AnnotationPayload` (nested anchor + rect) into a flat `AnnotationCreateInput`. */\nexport function flattenAnnotation(ann: AnnotationPayload): AnnotationCreateInput {\n return {\n cssSelector: ann.anchor.cssSelector,\n xpath: ann.anchor.xpath,\n textSnippet: ann.anchor.textSnippet,\n elementTag: ann.anchor.elementTag,\n elementId: ann.anchor.elementId,\n textPrefix: ann.anchor.textPrefix,\n textSuffix: ann.anchor.textSuffix,\n fingerprint: ann.anchor.fingerprint,\n neighborText: ann.anchor.neighborText,\n xPct: ann.rect.xPct,\n yPct: ann.rect.yPct,\n wPct: ann.rect.wPct,\n hPct: ann.rect.hPct,\n scrollX: ann.scrollX,\n scrollY: ann.scrollY,\n viewportW: ann.viewportW,\n viewportH: ann.viewportH,\n devicePixelRatio: ann.devicePixelRatio,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Abstract Store — adapter pattern\n// ---------------------------------------------------------------------------\n\n/**\n * Abstract storage interface for Siteping.\n *\n * Any adapter (Prisma, Drizzle, raw SQL, localStorage, etc.) implements this\n * interface. The HTTP handler and widget `StoreClient` operate against\n * `SitepingStore`, decoupled from the storage backend.\n *\n * ## Error contract\n *\n * - **`updateFeedback` / `deleteFeedback`**: throw `StoreNotFoundError` when\n * the record does not exist.\n * - **`createFeedback`**: either return the existing record on duplicate\n * `clientId` (idempotent) or throw `StoreDuplicateError`. The handler\n * handles both patterns.\n * - Other methods should not throw on empty results — return empty arrays or `null`.\n */\nexport interface SitepingStore {\n /** Create a feedback with its annotations. Idempotent on `clientId` — return existing record on duplicate, or throw `StoreDuplicateError`. */\n createFeedback(data: FeedbackCreateInput): Promise<FeedbackRecord>;\n /** Paginated query with optional filters. Returns empty array (not error) when no results. */\n getFeedbacks(query: FeedbackQuery): Promise<{ feedbacks: FeedbackRecord[]; total: number }>;\n /** Lookup by client-generated UUID. Returns `null` (not error) when not found. */\n findByClientId(clientId: string): Promise<FeedbackRecord | null>;\n /** Update status/resolvedAt. Throws `StoreNotFoundError` if `id` does not exist. */\n updateFeedback(id: string, data: FeedbackUpdateInput): Promise<FeedbackRecord>;\n /** Delete a single record. Throws `StoreNotFoundError` if `id` does not exist. */\n deleteFeedback(id: string): Promise<void>;\n /** Bulk delete all feedbacks for a project. No-op (not error) if none exist. */\n deleteAllFeedbacks(projectName: string): Promise<void>;\n}\n\n/** Payload sent from the widget to the server when submitting feedback. */\nexport interface FeedbackPayload {\n projectName: string;\n type: FeedbackType;\n message: string;\n url: string;\n viewport: string;\n userAgent: string;\n authorName: string;\n authorEmail: string;\n annotations: AnnotationPayload[];\n /** Client-generated UUID for deduplication */\n clientId: string;\n}\n\n// ---------------------------------------------------------------------------\n// Annotation — multi-selector anchoring (Hypothesis / W3C Web Annotation)\n// ---------------------------------------------------------------------------\n\n/** DOM anchoring data for re-attaching annotations to page elements. */\nexport interface AnchorData {\n /** CSS selector generated by @medv/finder — primary anchor */\n cssSelector: string;\n /** XPath — fallback 1 */\n xpath: string;\n /** First ~120 chars of element innerText — empty string if none */\n textSnippet: string;\n /** Tag name for validation (e.g. \"DIV\", \"SECTION\") */\n elementTag: string;\n /** Element id attribute if available — most stable */\n elementId?: string | undefined;\n /** ~32 chars of text before this element in document flow (disambiguation) */\n textPrefix: string;\n /** ~32 chars of text after this element in document flow (disambiguation) */\n textSuffix: string;\n /** Structural fingerprint: \"childCount:siblingIdx:attrHash\" */\n fingerprint: string;\n /** Text content of adjacent sibling elements (context) */\n neighborText: string;\n}\n\n/** Drawn rectangle coordinates as percentages relative to the anchor element. */\nexport interface RectData {\n /** X offset as fraction of anchor element width — must be in range [0, 1] */\n xPct: number;\n /** Y offset as fraction of anchor element height — must be in range [0, 1] */\n yPct: number;\n /** Width as fraction of anchor element width — must be in range [0, 1] */\n wPct: number;\n /** Height as fraction of anchor element height — must be in range [0, 1] */\n hPct: number;\n}\n\n/** Annotation data sent as part of a feedback submission. */\nexport interface AnnotationPayload {\n anchor: AnchorData;\n rect: RectData;\n scrollX: number;\n scrollY: number;\n viewportW: number;\n viewportH: number;\n devicePixelRatio: number;\n}\n\n// ---------------------------------------------------------------------------\n// API responses\n// ---------------------------------------------------------------------------\n\n/** Feedback record as returned by the API (dates serialized as strings). */\nexport interface FeedbackResponse {\n id: string;\n projectName: string;\n type: FeedbackType;\n message: string;\n status: FeedbackStatus;\n url: string;\n viewport: string;\n userAgent: string;\n authorName: string;\n authorEmail: string;\n resolvedAt: string | null;\n createdAt: string;\n updatedAt: string;\n annotations: AnnotationResponse[];\n}\n\n/** Annotation record as returned by the API. */\nexport interface AnnotationResponse {\n id: string;\n feedbackId: string;\n cssSelector: string;\n xpath: string;\n textSnippet: string;\n elementTag: string;\n elementId: string | null;\n textPrefix: string;\n textSuffix: string;\n fingerprint: string;\n neighborText: string;\n xPct: number;\n yPct: number;\n wPct: number;\n hPct: number;\n scrollX: number;\n scrollY: number;\n viewportW: number;\n viewportH: number;\n devicePixelRatio: number;\n createdAt: string;\n}\n","import {\n type AnnotationCreateInput,\n type AnnotationRecord,\n type FeedbackCreateInput,\n type FeedbackQuery,\n type FeedbackRecord,\n type FeedbackUpdateInput,\n type SitepingStore,\n StoreNotFoundError,\n} from \"@siteping/core\";\n\nexport type { SitepingStore } from \"@siteping/core\";\nexport { StoreDuplicateError, StoreNotFoundError } from \"@siteping/core\";\n\n/**\n * In-memory `SitepingStore` implementation.\n *\n * Zero dependencies, works in any JS environment (Node, Bun, Deno, browser,\n * Cloudflare Workers). Data lives in a plain array — lost on process restart.\n *\n * Use cases:\n * - **Testing** — fast, isolated store for unit/integration tests\n * - **Demos** — lightweight store that needs no database or localStorage\n * - **Reference** — simplest possible adapter for contributors to study\n *\n * @example\n * ```ts\n * import { MemoryStore } from '@siteping/adapter-memory'\n *\n * const store = new MemoryStore()\n * // Pass to createSitepingHandler({ store }) or initSiteping({ store })\n * ```\n */\nexport class MemoryStore implements SitepingStore {\n private feedbacks: FeedbackRecord[] = [];\n private idCounter = 1;\n\n private generateId(): string {\n return `mem-${this.idCounter++}-${Date.now().toString(36)}`;\n }\n\n async createFeedback(data: FeedbackCreateInput): Promise<FeedbackRecord> {\n // ClientId dedup — idempotent\n const existing = this.feedbacks.find((f) => f.clientId === data.clientId);\n if (existing) return existing;\n\n const now = new Date();\n const feedbackId = this.generateId();\n\n const annotations: AnnotationRecord[] = data.annotations.map((ann: AnnotationCreateInput) => ({\n id: this.generateId(),\n feedbackId,\n cssSelector: ann.cssSelector,\n xpath: ann.xpath,\n textSnippet: ann.textSnippet,\n elementTag: ann.elementTag,\n elementId: ann.elementId ?? null,\n textPrefix: ann.textPrefix,\n textSuffix: ann.textSuffix,\n fingerprint: ann.fingerprint,\n neighborText: ann.neighborText,\n xPct: ann.xPct,\n yPct: ann.yPct,\n wPct: ann.wPct,\n hPct: ann.hPct,\n scrollX: ann.scrollX,\n scrollY: ann.scrollY,\n viewportW: ann.viewportW,\n viewportH: ann.viewportH,\n devicePixelRatio: ann.devicePixelRatio,\n createdAt: now,\n }));\n\n const record: FeedbackRecord = {\n id: feedbackId,\n type: data.type,\n message: data.message,\n status: data.status,\n projectName: data.projectName,\n url: data.url,\n authorName: data.authorName,\n authorEmail: data.authorEmail,\n viewport: data.viewport,\n userAgent: data.userAgent,\n clientId: data.clientId,\n resolvedAt: null,\n createdAt: now,\n updatedAt: now,\n annotations,\n };\n\n this.feedbacks.unshift(record);\n return record;\n }\n\n async getFeedbacks(query: FeedbackQuery): Promise<{ feedbacks: FeedbackRecord[]; total: number }> {\n let results = this.feedbacks.filter((f) => f.projectName === query.projectName);\n\n if (query.type) results = results.filter((f) => f.type === query.type);\n if (query.status) results = results.filter((f) => f.status === query.status);\n if (query.search) {\n const s = query.search.toLowerCase();\n results = results.filter((f) => f.message.toLowerCase().includes(s));\n }\n\n const total = results.length;\n const page = query.page ?? 1;\n const limit = Math.min(query.limit ?? 50, 100);\n const start = (page - 1) * limit;\n\n return { feedbacks: results.slice(start, start + limit), total };\n }\n\n async findByClientId(clientId: string): Promise<FeedbackRecord | null> {\n return this.feedbacks.find((f) => f.clientId === clientId) ?? null;\n }\n\n async updateFeedback(id: string, data: FeedbackUpdateInput): Promise<FeedbackRecord> {\n const fb = this.feedbacks.find((f) => f.id === id);\n if (!fb) throw new StoreNotFoundError();\n\n fb.status = data.status;\n fb.resolvedAt = data.resolvedAt;\n fb.updatedAt = new Date();\n return fb;\n }\n\n async deleteFeedback(id: string): Promise<void> {\n const idx = this.feedbacks.findIndex((f) => f.id === id);\n if (idx === -1) throw new StoreNotFoundError();\n this.feedbacks.splice(idx, 1);\n }\n\n async deleteAllFeedbacks(projectName: string): Promise<void> {\n this.feedbacks = this.feedbacks.filter((f) => f.projectName !== projectName);\n }\n\n /** Remove all data from this store instance. */\n clear(): void {\n this.feedbacks = [];\n this.idCounter = 1;\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Siteping database models — single source of truth.
|
|
3
|
+
*
|
|
4
|
+
* Used by:
|
|
5
|
+
* - CLI to generate Prisma schema (via prisma-ast)
|
|
6
|
+
* - Adapter for Zod validation
|
|
7
|
+
* - Type exports
|
|
8
|
+
*
|
|
9
|
+
* This is a TS representation, NOT a .prisma file.
|
|
10
|
+
* The CLI generates the actual Prisma schema from this definition.
|
|
11
|
+
*/
|
|
12
|
+
/** Definition of a single field in a Siteping database model. */
|
|
13
|
+
export interface FieldDef {
|
|
14
|
+
type: string;
|
|
15
|
+
default?: string;
|
|
16
|
+
optional?: boolean;
|
|
17
|
+
relation?: {
|
|
18
|
+
kind: "1-to-many" | "many-to-1";
|
|
19
|
+
model: string;
|
|
20
|
+
fields?: string[];
|
|
21
|
+
references?: string[];
|
|
22
|
+
onDelete?: string;
|
|
23
|
+
};
|
|
24
|
+
isId?: boolean;
|
|
25
|
+
isUnique?: boolean;
|
|
26
|
+
/** Prisma native type attribute (e.g. "Text" for @db.Text) — used for MySQL compatibility on long strings */
|
|
27
|
+
nativeType?: string;
|
|
28
|
+
/** Prisma @updatedAt attribute */
|
|
29
|
+
isUpdatedAt?: boolean;
|
|
30
|
+
}
|
|
31
|
+
/** Definition of a composite index on a Siteping database model. */
|
|
32
|
+
export interface IndexDef {
|
|
33
|
+
fields: string[];
|
|
34
|
+
}
|
|
35
|
+
/** Definition of a single Siteping database model (fields + indexes). */
|
|
36
|
+
export interface ModelDef {
|
|
37
|
+
fields: Record<string, FieldDef>;
|
|
38
|
+
indexes?: IndexDef[];
|
|
39
|
+
}
|
|
40
|
+
export declare const SITEPING_MODELS: Readonly<{
|
|
41
|
+
readonly SitepingFeedback: {
|
|
42
|
+
readonly fields: {
|
|
43
|
+
readonly id: {
|
|
44
|
+
readonly type: "String";
|
|
45
|
+
readonly isId: true;
|
|
46
|
+
readonly default: "cuid()";
|
|
47
|
+
};
|
|
48
|
+
readonly projectName: {
|
|
49
|
+
readonly type: "String";
|
|
50
|
+
};
|
|
51
|
+
readonly type: {
|
|
52
|
+
readonly type: "String";
|
|
53
|
+
};
|
|
54
|
+
readonly message: {
|
|
55
|
+
readonly type: "String";
|
|
56
|
+
readonly nativeType: "Text";
|
|
57
|
+
};
|
|
58
|
+
readonly status: {
|
|
59
|
+
readonly type: "String";
|
|
60
|
+
readonly default: "\"open\"";
|
|
61
|
+
};
|
|
62
|
+
readonly url: {
|
|
63
|
+
readonly type: "String";
|
|
64
|
+
};
|
|
65
|
+
readonly viewport: {
|
|
66
|
+
readonly type: "String";
|
|
67
|
+
};
|
|
68
|
+
readonly userAgent: {
|
|
69
|
+
readonly type: "String";
|
|
70
|
+
};
|
|
71
|
+
readonly authorName: {
|
|
72
|
+
readonly type: "String";
|
|
73
|
+
};
|
|
74
|
+
readonly authorEmail: {
|
|
75
|
+
readonly type: "String";
|
|
76
|
+
};
|
|
77
|
+
readonly clientId: {
|
|
78
|
+
readonly type: "String";
|
|
79
|
+
readonly isUnique: true;
|
|
80
|
+
};
|
|
81
|
+
readonly resolvedAt: {
|
|
82
|
+
readonly type: "DateTime";
|
|
83
|
+
readonly optional: true;
|
|
84
|
+
};
|
|
85
|
+
readonly createdAt: {
|
|
86
|
+
readonly type: "DateTime";
|
|
87
|
+
readonly default: "now()";
|
|
88
|
+
};
|
|
89
|
+
readonly updatedAt: {
|
|
90
|
+
readonly type: "DateTime";
|
|
91
|
+
readonly isUpdatedAt: true;
|
|
92
|
+
};
|
|
93
|
+
readonly annotations: {
|
|
94
|
+
readonly type: "SitepingAnnotation";
|
|
95
|
+
readonly relation: {
|
|
96
|
+
readonly kind: "1-to-many";
|
|
97
|
+
readonly model: "SitepingAnnotation";
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
readonly indexes: [{
|
|
102
|
+
readonly fields: ["projectName"];
|
|
103
|
+
}, {
|
|
104
|
+
readonly fields: ["projectName", "status", "createdAt"];
|
|
105
|
+
}];
|
|
106
|
+
};
|
|
107
|
+
readonly SitepingAnnotation: {
|
|
108
|
+
readonly fields: {
|
|
109
|
+
readonly id: {
|
|
110
|
+
readonly type: "String";
|
|
111
|
+
readonly isId: true;
|
|
112
|
+
readonly default: "cuid()";
|
|
113
|
+
};
|
|
114
|
+
readonly feedbackId: {
|
|
115
|
+
readonly type: "String";
|
|
116
|
+
};
|
|
117
|
+
readonly feedback: {
|
|
118
|
+
readonly type: "SitepingFeedback";
|
|
119
|
+
readonly relation: {
|
|
120
|
+
readonly kind: "many-to-1";
|
|
121
|
+
readonly model: "SitepingFeedback";
|
|
122
|
+
readonly fields: ["feedbackId"];
|
|
123
|
+
readonly references: ["id"];
|
|
124
|
+
readonly onDelete: "Cascade";
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
readonly cssSelector: {
|
|
128
|
+
readonly type: "String";
|
|
129
|
+
readonly nativeType: "Text";
|
|
130
|
+
};
|
|
131
|
+
readonly xpath: {
|
|
132
|
+
readonly type: "String";
|
|
133
|
+
readonly nativeType: "Text";
|
|
134
|
+
};
|
|
135
|
+
readonly textSnippet: {
|
|
136
|
+
readonly type: "String";
|
|
137
|
+
readonly nativeType: "Text";
|
|
138
|
+
};
|
|
139
|
+
readonly elementTag: {
|
|
140
|
+
readonly type: "String";
|
|
141
|
+
};
|
|
142
|
+
readonly elementId: {
|
|
143
|
+
readonly type: "String";
|
|
144
|
+
readonly optional: true;
|
|
145
|
+
};
|
|
146
|
+
readonly textPrefix: {
|
|
147
|
+
readonly type: "String";
|
|
148
|
+
readonly nativeType: "Text";
|
|
149
|
+
};
|
|
150
|
+
readonly textSuffix: {
|
|
151
|
+
readonly type: "String";
|
|
152
|
+
readonly nativeType: "Text";
|
|
153
|
+
};
|
|
154
|
+
readonly fingerprint: {
|
|
155
|
+
readonly type: "String";
|
|
156
|
+
};
|
|
157
|
+
readonly neighborText: {
|
|
158
|
+
readonly type: "String";
|
|
159
|
+
readonly nativeType: "Text";
|
|
160
|
+
};
|
|
161
|
+
readonly xPct: {
|
|
162
|
+
readonly type: "Float";
|
|
163
|
+
};
|
|
164
|
+
readonly yPct: {
|
|
165
|
+
readonly type: "Float";
|
|
166
|
+
};
|
|
167
|
+
readonly wPct: {
|
|
168
|
+
readonly type: "Float";
|
|
169
|
+
};
|
|
170
|
+
readonly hPct: {
|
|
171
|
+
readonly type: "Float";
|
|
172
|
+
};
|
|
173
|
+
readonly scrollX: {
|
|
174
|
+
readonly type: "Float";
|
|
175
|
+
};
|
|
176
|
+
readonly scrollY: {
|
|
177
|
+
readonly type: "Float";
|
|
178
|
+
};
|
|
179
|
+
readonly viewportW: {
|
|
180
|
+
readonly type: "Int";
|
|
181
|
+
};
|
|
182
|
+
readonly viewportH: {
|
|
183
|
+
readonly type: "Int";
|
|
184
|
+
};
|
|
185
|
+
readonly devicePixelRatio: {
|
|
186
|
+
readonly type: "Float";
|
|
187
|
+
readonly default: "1";
|
|
188
|
+
};
|
|
189
|
+
readonly createdAt: {
|
|
190
|
+
readonly type: "DateTime";
|
|
191
|
+
readonly default: "now()";
|
|
192
|
+
};
|
|
193
|
+
};
|
|
194
|
+
readonly indexes: [{
|
|
195
|
+
readonly fields: ["feedbackId"];
|
|
196
|
+
}];
|
|
197
|
+
};
|
|
198
|
+
}>;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { FieldDef, IndexDef, ModelDef } from "./schema.js";
|
|
2
|
+
export { SITEPING_MODELS } from "./schema.js";
|
|
3
|
+
export type { AnchorData, AnnotationCreateInput, AnnotationPayload, AnnotationRecord, AnnotationResponse, FeedbackCreateInput, FeedbackPayload, FeedbackQuery, FeedbackRecord, FeedbackResponse, FeedbackStatus, FeedbackType, FeedbackUpdateInput, RectData, SitepingConfig, SitepingInstance, SitepingPublicEvents, SitepingStore, } from "./types.js";
|
|
4
|
+
export { FEEDBACK_STATUSES, FEEDBACK_TYPES, flattenAnnotation, isStoreDuplicate, isStoreNotFound, StoreDuplicateError, StoreNotFoundError, } from "./types.js";
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/** Configuration options for the Siteping widget. */
|
|
2
|
+
export interface SitepingConfig {
|
|
3
|
+
/** HTTP endpoint that receives feedbacks (e.g. '/api/siteping'). Required unless `store` is provided. */
|
|
4
|
+
endpoint?: string | undefined;
|
|
5
|
+
/** Required — project identifier used to scope feedbacks */
|
|
6
|
+
projectName: string;
|
|
7
|
+
/** Direct store for client-side mode. When set, bypasses HTTP and uses the store directly in the browser. */
|
|
8
|
+
store?: SitepingStore | undefined;
|
|
9
|
+
/** FAB position — defaults to 'bottom-right' */
|
|
10
|
+
position?: "bottom-right" | "bottom-left";
|
|
11
|
+
/** Accent color for the widget UI — defaults to '#0066ff' */
|
|
12
|
+
accentColor?: string;
|
|
13
|
+
/** Show the widget even in production — defaults to false */
|
|
14
|
+
forceShow?: boolean;
|
|
15
|
+
/** Enable debug logging of lifecycle events — defaults to false */
|
|
16
|
+
debug?: boolean;
|
|
17
|
+
/** Color theme — defaults to 'light' */
|
|
18
|
+
theme?: "light" | "dark" | "auto";
|
|
19
|
+
/** UI locale — defaults to 'en' */
|
|
20
|
+
locale?: "fr" | "en" | (string & {}) | undefined;
|
|
21
|
+
/** Called when the widget is skipped (production mode, mobile viewport) */
|
|
22
|
+
onSkip?: (reason: "production" | "mobile") => void;
|
|
23
|
+
/** Called when the feedback panel is opened. */
|
|
24
|
+
onOpen?: () => void;
|
|
25
|
+
/** Called when the feedback panel is closed. */
|
|
26
|
+
onClose?: () => void;
|
|
27
|
+
onFeedbackSent?: (feedback: FeedbackResponse) => void;
|
|
28
|
+
onError?: (error: Error) => void;
|
|
29
|
+
/** Called when the user starts drawing an annotation. */
|
|
30
|
+
onAnnotationStart?: () => void;
|
|
31
|
+
/** Called when the user finishes drawing an annotation. */
|
|
32
|
+
onAnnotationEnd?: () => void;
|
|
33
|
+
}
|
|
34
|
+
/** Instance returned by initSiteping() with lifecycle methods. */
|
|
35
|
+
export interface SitepingInstance {
|
|
36
|
+
/** Remove the widget from the DOM and clean up all listeners. */
|
|
37
|
+
destroy: () => void;
|
|
38
|
+
/** Open the panel programmatically */
|
|
39
|
+
open: () => void;
|
|
40
|
+
/** Close the panel */
|
|
41
|
+
close: () => void;
|
|
42
|
+
/** Reload feedbacks from server */
|
|
43
|
+
refresh: () => void;
|
|
44
|
+
/** Subscribe to a public widget event */
|
|
45
|
+
on: <K extends keyof SitepingPublicEvents>(event: K, listener: (...args: SitepingPublicEvents[K]) => void) => () => void;
|
|
46
|
+
/** Unsubscribe from a public widget event */
|
|
47
|
+
off: <K extends keyof SitepingPublicEvents>(event: K, listener: (...args: SitepingPublicEvents[K]) => void) => void;
|
|
48
|
+
}
|
|
49
|
+
/** Events exposed to consumers via SitepingInstance.on / .off */
|
|
50
|
+
export interface SitepingPublicEvents {
|
|
51
|
+
"feedback:sent": [FeedbackResponse];
|
|
52
|
+
"feedback:deleted": [string];
|
|
53
|
+
"panel:open": [];
|
|
54
|
+
"panel:close": [];
|
|
55
|
+
}
|
|
56
|
+
/** Single source of truth for feedback types — used by both TS types and Zod schemas. */
|
|
57
|
+
export declare const FEEDBACK_TYPES: readonly ["question", "change", "bug", "other"];
|
|
58
|
+
export type FeedbackType = (typeof FEEDBACK_TYPES)[number];
|
|
59
|
+
/** Single source of truth for feedback statuses. */
|
|
60
|
+
export declare const FEEDBACK_STATUSES: readonly ["open", "resolved"];
|
|
61
|
+
export type FeedbackStatus = (typeof FEEDBACK_STATUSES)[number];
|
|
62
|
+
/** Input for creating a feedback record in the store. */
|
|
63
|
+
export interface FeedbackCreateInput {
|
|
64
|
+
projectName: string;
|
|
65
|
+
type: FeedbackType;
|
|
66
|
+
message: string;
|
|
67
|
+
status: FeedbackStatus;
|
|
68
|
+
url: string;
|
|
69
|
+
viewport: string;
|
|
70
|
+
userAgent: string;
|
|
71
|
+
authorName: string;
|
|
72
|
+
authorEmail: string;
|
|
73
|
+
clientId: string;
|
|
74
|
+
annotations: AnnotationCreateInput[];
|
|
75
|
+
}
|
|
76
|
+
/** Input for a single annotation when creating a feedback. */
|
|
77
|
+
export interface AnnotationCreateInput {
|
|
78
|
+
cssSelector: string;
|
|
79
|
+
xpath: string;
|
|
80
|
+
textSnippet: string;
|
|
81
|
+
elementTag: string;
|
|
82
|
+
elementId?: string | undefined;
|
|
83
|
+
textPrefix: string;
|
|
84
|
+
textSuffix: string;
|
|
85
|
+
fingerprint: string;
|
|
86
|
+
neighborText: string;
|
|
87
|
+
xPct: number;
|
|
88
|
+
yPct: number;
|
|
89
|
+
wPct: number;
|
|
90
|
+
hPct: number;
|
|
91
|
+
scrollX: number;
|
|
92
|
+
scrollY: number;
|
|
93
|
+
viewportW: number;
|
|
94
|
+
viewportH: number;
|
|
95
|
+
devicePixelRatio: number;
|
|
96
|
+
}
|
|
97
|
+
/** Query parameters for fetching feedbacks. */
|
|
98
|
+
export interface FeedbackQuery {
|
|
99
|
+
projectName: string;
|
|
100
|
+
type?: FeedbackType | undefined;
|
|
101
|
+
status?: FeedbackStatus | undefined;
|
|
102
|
+
search?: string | undefined;
|
|
103
|
+
page?: number | undefined;
|
|
104
|
+
limit?: number | undefined;
|
|
105
|
+
}
|
|
106
|
+
/** Update payload for patching a feedback. */
|
|
107
|
+
export interface FeedbackUpdateInput {
|
|
108
|
+
status: FeedbackStatus;
|
|
109
|
+
resolvedAt: Date | null;
|
|
110
|
+
}
|
|
111
|
+
/** A persisted feedback record returned by the store. */
|
|
112
|
+
export interface FeedbackRecord {
|
|
113
|
+
id: string;
|
|
114
|
+
type: FeedbackType;
|
|
115
|
+
message: string;
|
|
116
|
+
status: FeedbackStatus;
|
|
117
|
+
projectName: string;
|
|
118
|
+
url: string;
|
|
119
|
+
authorName: string;
|
|
120
|
+
authorEmail: string;
|
|
121
|
+
viewport: string;
|
|
122
|
+
userAgent: string;
|
|
123
|
+
clientId: string;
|
|
124
|
+
resolvedAt: Date | null;
|
|
125
|
+
createdAt: Date;
|
|
126
|
+
updatedAt: Date;
|
|
127
|
+
annotations: AnnotationRecord[];
|
|
128
|
+
}
|
|
129
|
+
/** A persisted annotation record returned by the store. */
|
|
130
|
+
export interface AnnotationRecord {
|
|
131
|
+
id: string;
|
|
132
|
+
feedbackId: string;
|
|
133
|
+
cssSelector: string;
|
|
134
|
+
xpath: string;
|
|
135
|
+
textSnippet: string;
|
|
136
|
+
elementTag: string;
|
|
137
|
+
elementId: string | null;
|
|
138
|
+
textPrefix: string;
|
|
139
|
+
textSuffix: string;
|
|
140
|
+
fingerprint: string;
|
|
141
|
+
neighborText: string;
|
|
142
|
+
xPct: number;
|
|
143
|
+
yPct: number;
|
|
144
|
+
wPct: number;
|
|
145
|
+
hPct: number;
|
|
146
|
+
scrollX: number;
|
|
147
|
+
scrollY: number;
|
|
148
|
+
viewportW: number;
|
|
149
|
+
viewportH: number;
|
|
150
|
+
devicePixelRatio: number;
|
|
151
|
+
createdAt: Date;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Thrown when a record is not found during update or delete.
|
|
155
|
+
*
|
|
156
|
+
* Handlers translate this to HTTP 404. Adapters MUST throw this (not
|
|
157
|
+
* ORM-specific errors) so the handler layer remains ORM-agnostic.
|
|
158
|
+
*/
|
|
159
|
+
export declare class StoreNotFoundError extends Error {
|
|
160
|
+
readonly code: "STORE_NOT_FOUND";
|
|
161
|
+
constructor(message?: string);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Thrown when a unique constraint is violated (e.g. duplicate `clientId`).
|
|
165
|
+
*
|
|
166
|
+
* Handlers use this to return the existing record instead of failing.
|
|
167
|
+
*/
|
|
168
|
+
export declare class StoreDuplicateError extends Error {
|
|
169
|
+
readonly code: "STORE_DUPLICATE";
|
|
170
|
+
constructor(message?: string);
|
|
171
|
+
}
|
|
172
|
+
/** Type guard — works for `StoreNotFoundError` and ORM-specific equivalents (e.g. Prisma P2025). */
|
|
173
|
+
export declare function isStoreNotFound(error: unknown): boolean;
|
|
174
|
+
/** Type guard — works for `StoreDuplicateError` and ORM-specific equivalents (e.g. Prisma P2002). */
|
|
175
|
+
export declare function isStoreDuplicate(error: unknown): boolean;
|
|
176
|
+
/** Flatten a widget `AnnotationPayload` (nested anchor + rect) into a flat `AnnotationCreateInput`. */
|
|
177
|
+
export declare function flattenAnnotation(ann: AnnotationPayload): AnnotationCreateInput;
|
|
178
|
+
/**
|
|
179
|
+
* Abstract storage interface for Siteping.
|
|
180
|
+
*
|
|
181
|
+
* Any adapter (Prisma, Drizzle, raw SQL, localStorage, etc.) implements this
|
|
182
|
+
* interface. The HTTP handler and widget `StoreClient` operate against
|
|
183
|
+
* `SitepingStore`, decoupled from the storage backend.
|
|
184
|
+
*
|
|
185
|
+
* ## Error contract
|
|
186
|
+
*
|
|
187
|
+
* - **`updateFeedback` / `deleteFeedback`**: throw `StoreNotFoundError` when
|
|
188
|
+
* the record does not exist.
|
|
189
|
+
* - **`createFeedback`**: either return the existing record on duplicate
|
|
190
|
+
* `clientId` (idempotent) or throw `StoreDuplicateError`. The handler
|
|
191
|
+
* handles both patterns.
|
|
192
|
+
* - Other methods should not throw on empty results — return empty arrays or `null`.
|
|
193
|
+
*/
|
|
194
|
+
export interface SitepingStore {
|
|
195
|
+
/** Create a feedback with its annotations. Idempotent on `clientId` — return existing record on duplicate, or throw `StoreDuplicateError`. */
|
|
196
|
+
createFeedback(data: FeedbackCreateInput): Promise<FeedbackRecord>;
|
|
197
|
+
/** Paginated query with optional filters. Returns empty array (not error) when no results. */
|
|
198
|
+
getFeedbacks(query: FeedbackQuery): Promise<{
|
|
199
|
+
feedbacks: FeedbackRecord[];
|
|
200
|
+
total: number;
|
|
201
|
+
}>;
|
|
202
|
+
/** Lookup by client-generated UUID. Returns `null` (not error) when not found. */
|
|
203
|
+
findByClientId(clientId: string): Promise<FeedbackRecord | null>;
|
|
204
|
+
/** Update status/resolvedAt. Throws `StoreNotFoundError` if `id` does not exist. */
|
|
205
|
+
updateFeedback(id: string, data: FeedbackUpdateInput): Promise<FeedbackRecord>;
|
|
206
|
+
/** Delete a single record. Throws `StoreNotFoundError` if `id` does not exist. */
|
|
207
|
+
deleteFeedback(id: string): Promise<void>;
|
|
208
|
+
/** Bulk delete all feedbacks for a project. No-op (not error) if none exist. */
|
|
209
|
+
deleteAllFeedbacks(projectName: string): Promise<void>;
|
|
210
|
+
}
|
|
211
|
+
/** Payload sent from the widget to the server when submitting feedback. */
|
|
212
|
+
export interface FeedbackPayload {
|
|
213
|
+
projectName: string;
|
|
214
|
+
type: FeedbackType;
|
|
215
|
+
message: string;
|
|
216
|
+
url: string;
|
|
217
|
+
viewport: string;
|
|
218
|
+
userAgent: string;
|
|
219
|
+
authorName: string;
|
|
220
|
+
authorEmail: string;
|
|
221
|
+
annotations: AnnotationPayload[];
|
|
222
|
+
/** Client-generated UUID for deduplication */
|
|
223
|
+
clientId: string;
|
|
224
|
+
}
|
|
225
|
+
/** DOM anchoring data for re-attaching annotations to page elements. */
|
|
226
|
+
export interface AnchorData {
|
|
227
|
+
/** CSS selector generated by @medv/finder — primary anchor */
|
|
228
|
+
cssSelector: string;
|
|
229
|
+
/** XPath — fallback 1 */
|
|
230
|
+
xpath: string;
|
|
231
|
+
/** First ~120 chars of element innerText — empty string if none */
|
|
232
|
+
textSnippet: string;
|
|
233
|
+
/** Tag name for validation (e.g. "DIV", "SECTION") */
|
|
234
|
+
elementTag: string;
|
|
235
|
+
/** Element id attribute if available — most stable */
|
|
236
|
+
elementId?: string | undefined;
|
|
237
|
+
/** ~32 chars of text before this element in document flow (disambiguation) */
|
|
238
|
+
textPrefix: string;
|
|
239
|
+
/** ~32 chars of text after this element in document flow (disambiguation) */
|
|
240
|
+
textSuffix: string;
|
|
241
|
+
/** Structural fingerprint: "childCount:siblingIdx:attrHash" */
|
|
242
|
+
fingerprint: string;
|
|
243
|
+
/** Text content of adjacent sibling elements (context) */
|
|
244
|
+
neighborText: string;
|
|
245
|
+
}
|
|
246
|
+
/** Drawn rectangle coordinates as percentages relative to the anchor element. */
|
|
247
|
+
export interface RectData {
|
|
248
|
+
/** X offset as fraction of anchor element width — must be in range [0, 1] */
|
|
249
|
+
xPct: number;
|
|
250
|
+
/** Y offset as fraction of anchor element height — must be in range [0, 1] */
|
|
251
|
+
yPct: number;
|
|
252
|
+
/** Width as fraction of anchor element width — must be in range [0, 1] */
|
|
253
|
+
wPct: number;
|
|
254
|
+
/** Height as fraction of anchor element height — must be in range [0, 1] */
|
|
255
|
+
hPct: number;
|
|
256
|
+
}
|
|
257
|
+
/** Annotation data sent as part of a feedback submission. */
|
|
258
|
+
export interface AnnotationPayload {
|
|
259
|
+
anchor: AnchorData;
|
|
260
|
+
rect: RectData;
|
|
261
|
+
scrollX: number;
|
|
262
|
+
scrollY: number;
|
|
263
|
+
viewportW: number;
|
|
264
|
+
viewportH: number;
|
|
265
|
+
devicePixelRatio: number;
|
|
266
|
+
}
|
|
267
|
+
/** Feedback record as returned by the API (dates serialized as strings). */
|
|
268
|
+
export interface FeedbackResponse {
|
|
269
|
+
id: string;
|
|
270
|
+
projectName: string;
|
|
271
|
+
type: FeedbackType;
|
|
272
|
+
message: string;
|
|
273
|
+
status: FeedbackStatus;
|
|
274
|
+
url: string;
|
|
275
|
+
viewport: string;
|
|
276
|
+
userAgent: string;
|
|
277
|
+
authorName: string;
|
|
278
|
+
authorEmail: string;
|
|
279
|
+
resolvedAt: string | null;
|
|
280
|
+
createdAt: string;
|
|
281
|
+
updatedAt: string;
|
|
282
|
+
annotations: AnnotationResponse[];
|
|
283
|
+
}
|
|
284
|
+
/** Annotation record as returned by the API. */
|
|
285
|
+
export interface AnnotationResponse {
|
|
286
|
+
id: string;
|
|
287
|
+
feedbackId: string;
|
|
288
|
+
cssSelector: string;
|
|
289
|
+
xpath: string;
|
|
290
|
+
textSnippet: string;
|
|
291
|
+
elementTag: string;
|
|
292
|
+
elementId: string | null;
|
|
293
|
+
textPrefix: string;
|
|
294
|
+
textSuffix: string;
|
|
295
|
+
fingerprint: string;
|
|
296
|
+
neighborText: string;
|
|
297
|
+
xPct: number;
|
|
298
|
+
yPct: number;
|
|
299
|
+
wPct: number;
|
|
300
|
+
hPct: number;
|
|
301
|
+
scrollX: number;
|
|
302
|
+
scrollY: number;
|
|
303
|
+
viewportW: number;
|
|
304
|
+
viewportH: number;
|
|
305
|
+
devicePixelRatio: number;
|
|
306
|
+
createdAt: string;
|
|
307
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@siteping/adapter-memory",
|
|
3
|
+
"version": "0.4.3",
|
|
4
|
+
"description": "In-memory adapter for Siteping — zero dependencies, works everywhere",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"require": "./dist/index.cjs"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"main": "./dist/index.cjs",
|
|
15
|
+
"module": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup && node ../../scripts/fix-dts.mjs dist",
|
|
22
|
+
"check": "tsc --noEmit",
|
|
23
|
+
"clean": "rm -rf dist"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"siteping",
|
|
27
|
+
"memory",
|
|
28
|
+
"adapter",
|
|
29
|
+
"feedback",
|
|
30
|
+
"testing",
|
|
31
|
+
"demo",
|
|
32
|
+
"typescript"
|
|
33
|
+
],
|
|
34
|
+
"author": "neosianexus",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"homepage": "https://siteping.dev",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+https://github.com/NeosiaNexus/SitePing.git",
|
|
40
|
+
"directory": "packages/adapter-memory"
|
|
41
|
+
},
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/NeosiaNexus/SitePing/issues"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@siteping/core": "workspace:*"
|
|
50
|
+
}
|
|
51
|
+
}
|