@feedclip/activity 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Andrey Shedko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,141 @@
1
+ # Activity
2
+
3
+ Local-first activity history engine and React UI for business resources such as
4
+ invoices, customers, tickets, and orders.
5
+
6
+ Activity History is the product; the framework-independent Activity Engine is the
7
+ architecture underneath it.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install @feedclip/activity
13
+ ```
14
+
15
+ React 18 or 19 is required only when using the React entrypoint.
16
+
17
+ ## Quick start
18
+
19
+ ```ts
20
+ import {
21
+ createActivity,
22
+ createMemoryStorageAdapter,
23
+ } from "@feedclip/activity";
24
+
25
+ const activity = createActivity({
26
+ adapter: createMemoryStorageAdapter(),
27
+ });
28
+
29
+ await activity.track({
30
+ resource: {
31
+ type: "invoice",
32
+ id: "inv_123",
33
+ title: "Invoice INV-123",
34
+ },
35
+ actor: {
36
+ type: "user",
37
+ id: "user_1",
38
+ name: "John Smith",
39
+ },
40
+ action: "update",
41
+ changes: [
42
+ {
43
+ field: "status",
44
+ label: "Status",
45
+ before: "Draft",
46
+ after: "Approved",
47
+ valueType: "enum",
48
+ },
49
+ ],
50
+ });
51
+
52
+ const entries = await activity.query({
53
+ resource: { type: "invoice", id: "inv_123" },
54
+ });
55
+ ```
56
+
57
+ ## React
58
+
59
+ The Activity instance is passed explicitly. No provider is required.
60
+
61
+ ```tsx
62
+ import { ActivityPanel } from "@feedclip/activity/react";
63
+ import "@feedclip/activity/styles.css";
64
+
65
+ export function InvoiceActivity() {
66
+ return (
67
+ <ActivityPanel
68
+ activity={activity}
69
+ resource={{ type: "invoice", id: "inv_123" }}
70
+ />
71
+ );
72
+ }
73
+ ```
74
+
75
+ Use `messages` to replace UI copy and `locale` to format dates and time:
76
+
77
+ ```tsx
78
+ <ActivityPanel
79
+ activity={activity}
80
+ locale="cs-CZ"
81
+ messages={{ title: "Aktivita", searchPlaceholder: "Hledat..." }}
82
+ resource={{ type: "invoice", id: "inv_123" }}
83
+ />
84
+ ```
85
+
86
+ ## PostgreSQL
87
+
88
+ Apply the bundled migration, then create an adapter around any client exposing a
89
+ Promise-based `query(sql, params)` method:
90
+
91
+ ```ts
92
+ import { createActivity } from "@feedclip/activity";
93
+ import { postgresAdapter } from "@feedclip/activity/adapters/postgres";
94
+
95
+ const activity = createActivity({
96
+ adapter: postgresAdapter(pool),
97
+ });
98
+ ```
99
+
100
+ The migration is exported as `@feedclip/activity/migration.sql` and also lives
101
+ at [`migrations/001_activity_schema.sql`](./migrations/001_activity_schema.sql).
102
+
103
+ ## Public entrypoints
104
+
105
+ - `@feedclip/activity` — engine, types, and memory adapter
106
+ - `@feedclip/activity/react` — `ActivityPanel`
107
+ - `@feedclip/activity/adapters/memory`
108
+ - `@feedclip/activity/adapters/postgres`
109
+ - `@feedclip/activity/styles.css`
110
+ - `@feedclip/activity/migration.sql`
111
+
112
+ ## Development
113
+
114
+ ```bash
115
+ npm install
116
+ npm test
117
+ npm run build
118
+ npm run dev
119
+ ```
120
+
121
+ `npm test` runs the PostgreSQL integration suite when `DATABASE_URL` is set. The
122
+ GitHub Actions workflow provides PostgreSQL automatically; without a database the
123
+ integration test is reported as skipped while unit and React tests still run.
124
+
125
+ Architecture and product decisions are documented in [`spec/README.md`](./spec/README.md).
126
+ The [`examples/nextjs`](./examples/nextjs) app verifies the package in a Next.js
127
+ App Router production build.
128
+
129
+ Release preparation and publishing requirements are tracked in
130
+ [`RELEASE_CHECKLIST.md`](./RELEASE_CHECKLIST.md). Publishing is performed by the
131
+ GitHub Release workflow with npm provenance after the `npm` environment and
132
+ `NPM_TOKEN` secret are configured.
133
+
134
+ ## Status
135
+
136
+ Version `0.1.0` is an MVP. The public contracts are usable, but the package has not
137
+ yet reached a stable `1.0` compatibility guarantee.
138
+
139
+ ## License
140
+
141
+ MIT
@@ -0,0 +1,68 @@
1
+ import { type Action, type Activity, type ActivityRecord, type ResourceReference } from "./activity";
2
+ export type ActivityPanelMessages = typeof defaultMessages;
3
+ export type ActivityPanelProps = {
4
+ activity: Activity;
5
+ resource: ResourceReference;
6
+ entries?: ActivityRecord[];
7
+ search?: string;
8
+ filters?: ActivityFilters;
9
+ variant?: "default" | "compact" | "comfortable";
10
+ onEntryClick?: (entry: ActivityRecord) => void;
11
+ onError?: (error: Error) => void;
12
+ onQueryChange?: (query: ActivityPanelQuery) => void;
13
+ messages?: Partial<ActivityPanelMessages>;
14
+ locale?: string;
15
+ };
16
+ type ActivityFilters = {
17
+ actions?: readonly Action[];
18
+ actor?: string;
19
+ from?: Date;
20
+ to?: Date;
21
+ };
22
+ export type ActivityPanelQuery = {
23
+ resource: ResourceReference;
24
+ search?: string;
25
+ actions?: readonly Action[];
26
+ actor?: string;
27
+ from?: Date;
28
+ to?: Date;
29
+ };
30
+ declare const defaultMessages: {
31
+ readonly panelLabel: "Activity history";
32
+ readonly title: "Activity";
33
+ readonly developer: "Developer";
34
+ readonly searchLabel: "Search activity";
35
+ readonly searchPlaceholder: "Search...";
36
+ readonly filterLabel: "Filter by action family";
37
+ readonly filterAll: "All";
38
+ readonly filterUpdates: "Updates";
39
+ readonly filterContent: "Content";
40
+ readonly filterLifecycle: "Lifecycle";
41
+ readonly loadingLabel: "Loading activity";
42
+ readonly entryActionsLabel: "Entry actions";
43
+ readonly copyIdLabel: "Copy activity id";
44
+ readonly copyIdTitle: "Copy ID";
45
+ readonly changedBy: "Changed by";
46
+ readonly at: "At";
47
+ readonly source: "Source";
48
+ readonly version: "Version";
49
+ readonly application: "Application";
50
+ readonly unversioned: "Unversioned";
51
+ readonly noActivity: "No activity yet";
52
+ readonly noMatches: "No matching activity";
53
+ readonly noActivityHelp: "Tracked entries for this resource will appear here.";
54
+ readonly noMatchesHelp: "Try a broader search or a different action family.";
55
+ readonly loadError: "Activity could not load";
56
+ readonly loadErrorFallback: "The activity query returned an error.";
57
+ readonly none: "None";
58
+ readonly moreChanges: "more changes";
59
+ readonly updated: "Updated";
60
+ readonly updatedSuffix: "updated";
61
+ readonly comment: "Comment";
62
+ readonly attachment: "Attachment";
63
+ readonly reason: "Reason";
64
+ readonly createdSuffix: "created";
65
+ };
66
+ export declare function ActivityPanel({ activity, resource, entries: controlledEntries, filters, onEntryClick, onError, onQueryChange, search, variant, messages: messageOverrides, locale, }: ActivityPanelProps): import("react").JSX.Element;
67
+ export {};
68
+ //# sourceMappingURL=ActivityPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ActivityPanel.d.ts","sourceRoot":"","sources":["../src/ActivityPanel.tsx"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,MAAM,EACX,KAAK,QAAQ,EACb,KAAK,cAAc,EAInB,KAAK,iBAAiB,EACvB,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,qBAAqB,GAAG,OAAO,eAAe,CAAC;AAE3D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,EAAE,QAAQ,CAAC;IACnB,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,OAAO,CAAC,EAAE,cAAc,EAAE,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,aAAa,CAAC;IAChD,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IAC/C,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACpD,QAAQ,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,EAAE,CAAC,EAAE,IAAI,CAAC;CACX,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,EAAE,CAAC,EAAE,IAAI,CAAC;CACX,CAAC;AAgBF,QAAA,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmCX,CAAC;AAEX,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,QAAQ,EACR,OAAO,EAAE,iBAAiB,EAC1B,OAAO,EACP,YAAY,EACZ,OAAO,EACP,aAAa,EACb,MAAM,EACN,OAAmB,EACnB,QAAQ,EAAE,gBAAgB,EAC1B,MAAM,GACP,EAAE,kBAAkB,+BAwJpB"}
@@ -0,0 +1,118 @@
1
+ export type ActorType = "user" | "system" | "api" | "agent";
2
+ export type BuiltInAction = "create" | "update" | "delete" | "archive" | "restore" | "comment" | "attachment";
3
+ export type Action = BuiltInAction | (string & {});
4
+ export type ValueType = "string" | "number" | "boolean" | "date" | "currency" | "user" | "enum" | "json" | "custom";
5
+ export type Resource = {
6
+ type: string;
7
+ id: string;
8
+ title?: string;
9
+ };
10
+ export type ResourceReference = Pick<Resource, "type" | "id"> & {
11
+ title?: string;
12
+ };
13
+ export type Actor = {
14
+ type: ActorType;
15
+ id: string;
16
+ name: string;
17
+ avatarUrl?: string;
18
+ };
19
+ export type Change = {
20
+ field: string;
21
+ label: string;
22
+ before?: unknown;
23
+ after?: unknown;
24
+ valueType: ValueType;
25
+ };
26
+ export type CommentContent = {
27
+ type: "comment";
28
+ text: string;
29
+ };
30
+ export type AttachmentContent = {
31
+ type: "attachment";
32
+ fileName: string;
33
+ mimeType: string;
34
+ size: number;
35
+ url?: string;
36
+ };
37
+ export type CustomContent = {
38
+ type: "custom";
39
+ [key: string]: unknown;
40
+ };
41
+ export type Content = CommentContent | AttachmentContent | CustomContent;
42
+ export type Metadata = {
43
+ source?: string;
44
+ version?: string;
45
+ requestId?: string;
46
+ ipAddress?: string;
47
+ custom?: Record<string, unknown>;
48
+ [key: string]: unknown;
49
+ };
50
+ export type ActivityRecord = Readonly<{
51
+ id: string;
52
+ resource: Readonly<Resource>;
53
+ action: Action;
54
+ actor: Readonly<Actor>;
55
+ timestamp: Date;
56
+ changes?: readonly Readonly<Change>[];
57
+ content?: Readonly<Content>;
58
+ metadata?: Readonly<Metadata>;
59
+ }>;
60
+ export type TrackInput = {
61
+ resource: ResourceReference;
62
+ actor: Actor;
63
+ action: Action;
64
+ changes?: Change[];
65
+ content?: Content;
66
+ metadata?: Metadata;
67
+ timestamp?: Date;
68
+ };
69
+ export type QueryOptions = {
70
+ resource: ResourceReference;
71
+ search?: string;
72
+ actor?: string;
73
+ actorId?: string;
74
+ actions?: readonly Action[];
75
+ from?: Date;
76
+ to?: Date;
77
+ limit?: number;
78
+ offset?: number;
79
+ };
80
+ export type QueryResult = {
81
+ entries: ActivityRecord[];
82
+ total: number;
83
+ hasMore: boolean;
84
+ };
85
+ export type StorageAdapter = {
86
+ insert(entry: ActivityRecord): Promise<void>;
87
+ query(options: QueryOptions): Promise<QueryResult>;
88
+ };
89
+ export type Activity = {
90
+ track(input: TrackInput): Promise<ActivityRecord>;
91
+ query(options: QueryOptions): Promise<ActivityRecord[]>;
92
+ };
93
+ export type ActivityOptions = {
94
+ adapter: StorageAdapter;
95
+ clock?: () => Date;
96
+ idGenerator?: () => string;
97
+ };
98
+ export declare class ActivityError extends Error {
99
+ readonly code: string;
100
+ readonly field?: string;
101
+ constructor(code: string, message: string, field?: string);
102
+ }
103
+ export declare function createActivity({ adapter, clock, idGenerator, }: ActivityOptions): Activity;
104
+ export declare function createMemoryStorageAdapter(initialEntries?: ActivityRecord[], latency?: number): StorageAdapter;
105
+ type Queryable = {
106
+ query(sql: string, params?: readonly unknown[]): Promise<{
107
+ rows: unknown[];
108
+ }>;
109
+ };
110
+ type ReleasableQueryable = Queryable & {
111
+ release(): void;
112
+ };
113
+ type Connectable = Queryable & {
114
+ connect(): Promise<ReleasableQueryable>;
115
+ };
116
+ export declare function postgresAdapter(client: Queryable | Connectable): StorageAdapter;
117
+ export {};
118
+ //# sourceMappingURL=activity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activity.d.ts","sourceRoot":"","sources":["../src/activity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,152 @@
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 __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
21
+
22
+ // src/adapters/memory.ts
23
+ var memory_exports = {};
24
+ __export(memory_exports, {
25
+ createMemoryStorageAdapter: () => createMemoryStorageAdapter
26
+ });
27
+ module.exports = __toCommonJS(memory_exports);
28
+
29
+ // src/activity.ts
30
+ var ActivityError = class extends Error {
31
+ constructor(code, message, field) {
32
+ super(message);
33
+ __publicField(this, "code");
34
+ __publicField(this, "field");
35
+ this.name = "ActivityError";
36
+ this.code = code;
37
+ this.field = field;
38
+ }
39
+ };
40
+ function createMemoryStorageAdapter(initialEntries = [], latency = 0) {
41
+ let entries = initialEntries.map(freezeRecord);
42
+ return {
43
+ async insert(entry) {
44
+ await wait(latency);
45
+ entries = [freezeRecord(entry), ...entries];
46
+ },
47
+ async query(options) {
48
+ await wait(latency);
49
+ const normalized = normalizeQuery(options);
50
+ const filtered = entries.filter((entry) => entry.resource.type === normalized.resource.type).filter((entry) => entry.resource.id === normalized.resource.id).filter((entry) => matchesActions(entry, normalized.actions)).filter((entry) => matchesActor(entry, normalized.actorId ?? normalized.actor)).filter((entry) => matchesDates(entry, normalized.from, normalized.to)).filter((entry) => matchesSearch(entry, normalized.search)).sort(compareRecords);
51
+ const offset = normalized.offset ?? 0;
52
+ const limit = normalized.limit ?? 50;
53
+ const page = filtered.slice(offset, offset + limit);
54
+ return {
55
+ entries: page,
56
+ total: filtered.length,
57
+ hasMore: offset + page.length < filtered.length
58
+ };
59
+ }
60
+ };
61
+ }
62
+ function normalizeQuery(options) {
63
+ if (!options.resource?.type?.trim() || !options.resource?.id?.trim()) {
64
+ throw new ActivityError("INVALID_RESOURCE", "query.resource is required", "resource");
65
+ }
66
+ if (options.limit !== void 0 && (options.limit < 1 || options.limit > 500)) {
67
+ throw new ActivityError("INVALID_LIMIT", "limit must be between 1 and 500", "limit");
68
+ }
69
+ if (options.offset !== void 0 && options.offset < 0) {
70
+ throw new ActivityError("INVALID_OFFSET", "offset must not be negative", "offset");
71
+ }
72
+ if (options.from && options.to && options.from > options.to) {
73
+ throw new ActivityError("INVALID_DATE_RANGE", "from must be before to", "from");
74
+ }
75
+ return freezeObject({
76
+ ...options,
77
+ resource: freezeObject({
78
+ type: options.resource.type.trim(),
79
+ id: options.resource.id.trim(),
80
+ title: options.resource.title?.trim()
81
+ }),
82
+ search: options.search?.trim() || void 0,
83
+ actor: options.actor?.trim() || void 0,
84
+ actorId: options.actorId?.trim() || void 0,
85
+ actions: options.actions ? Object.freeze(
86
+ Array.from(
87
+ new Set(options.actions.map((action) => action.trim().toLowerCase()))
88
+ )
89
+ ) : void 0,
90
+ limit: options.limit ?? 50,
91
+ offset: options.offset ?? 0
92
+ });
93
+ }
94
+ function matchesActions(entry, actions) {
95
+ return !actions?.length || actions.includes(entry.action);
96
+ }
97
+ function matchesActor(entry, actorId) {
98
+ return !actorId || entry.actor.id === actorId;
99
+ }
100
+ function matchesDates(entry, from, to) {
101
+ return (!from || entry.timestamp >= from) && (!to || entry.timestamp <= to);
102
+ }
103
+ function matchesSearch(entry, search) {
104
+ if (!search) {
105
+ return true;
106
+ }
107
+ return searchableText(entry).toLowerCase().includes(search.toLowerCase());
108
+ }
109
+ function searchableText(entry) {
110
+ return [
111
+ entry.action,
112
+ entry.resource.title,
113
+ entry.actor.name,
114
+ entry.content ? JSON.stringify(entry.content) : "",
115
+ ...(entry.changes ?? []).flatMap((change) => [
116
+ change.field,
117
+ change.label,
118
+ stringifyValue(change.before),
119
+ stringifyValue(change.after)
120
+ ])
121
+ ].join(" ");
122
+ }
123
+ function compareRecords(a, b) {
124
+ const time = b.timestamp.getTime() - a.timestamp.getTime();
125
+ return time || b.id.localeCompare(a.id);
126
+ }
127
+ function freezeRecord(record) {
128
+ return freezeObject({
129
+ ...record,
130
+ resource: freezeObject(record.resource),
131
+ actor: freezeObject(record.actor),
132
+ changes: record.changes?.map((change) => freezeObject(change)),
133
+ content: record.content ? freezeObject(record.content) : void 0,
134
+ metadata: record.metadata ? freezeObject(record.metadata) : void 0
135
+ });
136
+ }
137
+ function freezeObject(value) {
138
+ return Object.freeze(value);
139
+ }
140
+ function stringifyValue(value) {
141
+ if (value === null || value === void 0) {
142
+ return "";
143
+ }
144
+ if (value instanceof Date) {
145
+ return value.toISOString();
146
+ }
147
+ return typeof value === "string" ? value : JSON.stringify(value);
148
+ }
149
+ function wait(ms) {
150
+ return new Promise((resolve) => globalThis.setTimeout(resolve, ms));
151
+ }
152
+ //# sourceMappingURL=memory.cjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/adapters/memory.ts", "../../src/activity.ts"],
4
+ "sourcesContent": ["export { createMemoryStorageAdapter } from \"../activity\";\n", "export type ActorType = \"user\" | \"system\" | \"api\" | \"agent\";\n\nexport type BuiltInAction =\n | \"create\"\n | \"update\"\n | \"delete\"\n | \"archive\"\n | \"restore\"\n | \"comment\"\n | \"attachment\";\n\nexport type Action = BuiltInAction | (string & {});\n\nexport type ValueType =\n | \"string\"\n | \"number\"\n | \"boolean\"\n | \"date\"\n | \"currency\"\n | \"user\"\n | \"enum\"\n | \"json\"\n | \"custom\";\n\nexport type Resource = {\n type: string;\n id: string;\n title?: string;\n};\n\nexport type ResourceReference = Pick<Resource, \"type\" | \"id\"> & {\n title?: string;\n};\n\nexport type Actor = {\n type: ActorType;\n id: string;\n name: string;\n avatarUrl?: string;\n};\n\nexport type Change = {\n field: string;\n label: string;\n before?: unknown;\n after?: unknown;\n valueType: ValueType;\n};\n\nexport type CommentContent = {\n type: \"comment\";\n text: string;\n};\n\nexport type AttachmentContent = {\n type: \"attachment\";\n fileName: string;\n mimeType: string;\n size: number;\n url?: string;\n};\n\nexport type CustomContent = {\n type: \"custom\";\n [key: string]: unknown;\n};\n\nexport type Content = CommentContent | AttachmentContent | CustomContent;\n\nexport type Metadata = {\n source?: string;\n version?: string;\n requestId?: string;\n ipAddress?: string;\n custom?: Record<string, unknown>;\n [key: string]: unknown;\n};\n\nexport type ActivityRecord = Readonly<{\n id: string;\n resource: Readonly<Resource>;\n action: Action;\n actor: Readonly<Actor>;\n timestamp: Date;\n changes?: readonly Readonly<Change>[];\n content?: Readonly<Content>;\n metadata?: Readonly<Metadata>;\n}>;\n\nexport type TrackInput = {\n resource: ResourceReference;\n actor: Actor;\n action: Action;\n changes?: Change[];\n content?: Content;\n metadata?: Metadata;\n timestamp?: Date;\n};\n\nexport type QueryOptions = {\n resource: ResourceReference;\n search?: string;\n actor?: string;\n actorId?: string;\n actions?: readonly Action[];\n from?: Date;\n to?: Date;\n limit?: number;\n offset?: number;\n};\n\nexport type QueryResult = {\n entries: ActivityRecord[];\n total: number;\n hasMore: boolean;\n};\n\nexport type StorageAdapter = {\n insert(entry: ActivityRecord): Promise<void>;\n query(options: QueryOptions): Promise<QueryResult>;\n};\n\nexport type Activity = {\n track(input: TrackInput): Promise<ActivityRecord>;\n query(options: QueryOptions): Promise<ActivityRecord[]>;\n};\n\nexport type ActivityOptions = {\n adapter: StorageAdapter;\n clock?: () => Date;\n idGenerator?: () => string;\n};\n\nexport class ActivityError extends Error {\n readonly code: string;\n readonly field?: string;\n\n constructor(code: string, message: string, field?: string) {\n super(message);\n this.name = \"ActivityError\";\n this.code = code;\n this.field = field;\n }\n}\n\nexport function createActivity({\n adapter,\n clock = () => new Date(),\n idGenerator = createId,\n}: ActivityOptions): Activity {\n return {\n async track(input) {\n const record = createRecord(input, clock, idGenerator);\n await adapter.insert(record);\n return record;\n },\n async query(options) {\n const normalized = normalizeQuery(options);\n const result = await adapter.query(normalized);\n return result.entries;\n },\n };\n}\n\nexport function createMemoryStorageAdapter(\n initialEntries: ActivityRecord[] = [],\n latency = 0,\n): StorageAdapter {\n let entries = initialEntries.map(freezeRecord);\n\n return {\n async insert(entry) {\n await wait(latency);\n entries = [freezeRecord(entry), ...entries];\n },\n async query(options) {\n await wait(latency);\n const normalized = normalizeQuery(options);\n const filtered = entries\n .filter((entry) => entry.resource.type === normalized.resource.type)\n .filter((entry) => entry.resource.id === normalized.resource.id)\n .filter((entry) => matchesActions(entry, normalized.actions))\n .filter((entry) => matchesActor(entry, normalized.actorId ?? normalized.actor))\n .filter((entry) => matchesDates(entry, normalized.from, normalized.to))\n .filter((entry) => matchesSearch(entry, normalized.search))\n .sort(compareRecords);\n const offset = normalized.offset ?? 0;\n const limit = normalized.limit ?? 50;\n const page = filtered.slice(offset, offset + limit);\n\n return {\n entries: page,\n total: filtered.length,\n hasMore: offset + page.length < filtered.length,\n };\n },\n };\n}\n\ntype Queryable = {\n query(sql: string, params?: readonly unknown[]): Promise<{ rows: unknown[] }>;\n};\n\ntype ReleasableQueryable = Queryable & { release(): void };\ntype Connectable = Queryable & { connect(): Promise<ReleasableQueryable> };\n\nexport function postgresAdapter(client: Queryable | Connectable): StorageAdapter {\n return {\n async insert(entry) {\n const connection = \"connect\" in client ? await client.connect() : client;\n await connection.query(\"BEGIN\");\n try {\n await connection.query(\n `insert into activity_entries (\n id, resource_type, resource_id, resource_title, action,\n actor_type, actor_id, actor_name, actor_avatar_url,\n content_type, content_json, metadata_json, created_at\n ) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`,\n [\n entry.id,\n entry.resource.type,\n entry.resource.id,\n entry.resource.title ?? null,\n entry.action,\n entry.actor.type,\n entry.actor.id,\n entry.actor.name,\n entry.actor.avatarUrl ?? null,\n entry.content?.type ?? null,\n entry.content ? JSON.stringify(entry.content) : null,\n entry.metadata ? JSON.stringify(entry.metadata) : null,\n entry.timestamp.toISOString(),\n ],\n );\n\n for (const [position, change] of (entry.changes ?? []).entries()) {\n await connection.query(\n `insert into activity_changes (\n id, entry_id, position, field, label, before_value, after_value, value_type\n ) values ($1, $2, $3, $4, $5, $6, $7, $8)`,\n [\n createId(),\n entry.id,\n position,\n change.field,\n change.label,\n JSON.stringify(change.before ?? null),\n JSON.stringify(change.after ?? null),\n change.valueType,\n ],\n );\n }\n\n await connection.query(\"COMMIT\");\n } catch (error) {\n await connection.query(\"ROLLBACK\");\n throw toActivityError(error, \"STORAGE_FAILURE\", \"Could not insert activity record\");\n } finally {\n if (\"release\" in connection && typeof connection.release === \"function\") {\n connection.release();\n }\n }\n },\n async query(options) {\n const normalized = normalizeQuery(options);\n const limit = normalized.limit ?? 50;\n const offset = normalized.offset ?? 0;\n const params: unknown[] = [normalized.resource.type, normalized.resource.id];\n const where = [\"e.resource_type = $1\", \"e.resource_id = $2\"];\n\n if (normalized.actions?.length) {\n params.push(normalized.actions);\n where.push(`e.action = any($${params.length}::text[])`);\n }\n\n const actorId = normalized.actorId ?? normalized.actor;\n if (actorId) {\n params.push(actorId);\n where.push(`e.actor_id = $${params.length}`);\n }\n\n if (normalized.from) {\n params.push(normalized.from.toISOString());\n where.push(`e.created_at >= $${params.length}`);\n }\n\n if (normalized.to) {\n params.push(normalized.to.toISOString());\n where.push(`e.created_at <= $${params.length}`);\n }\n\n if (normalized.search) {\n params.push(`%${normalized.search}%`);\n const index = params.length;\n where.push(`(\n e.actor_name ilike $${index}\n or e.resource_title ilike $${index}\n or e.content_json::text ilike $${index}\n or exists (\n select 1 from activity_changes c\n where c.entry_id = e.id\n and (\n c.label ilike $${index}\n or c.before_value::text ilike $${index}\n or c.after_value::text ilike $${index}\n )\n )\n )`);\n }\n\n const whereSql = where.join(\" and \");\n const count = await client.query(\n `select count(*)::int as total from activity_entries e where ${whereSql}`,\n params,\n );\n params.push(limit + 1, offset);\n const rows = await client.query(\n `select\n e.*,\n coalesce(\n json_agg(\n json_build_object(\n 'field', c.field,\n 'label', c.label,\n 'before', c.before_value,\n 'after', c.after_value,\n 'valueType', c.value_type\n )\n order by c.position\n ) filter (where c.id is not null),\n '[]'\n ) as changes\n from activity_entries e\n left join activity_changes c on c.entry_id = e.id\n where ${whereSql}\n group by e.id\n order by e.created_at desc, e.id desc\n limit $${params.length - 1} offset $${params.length}`,\n params,\n );\n\n const total = readTotal(count.rows[0]);\n const entries = rows.rows.slice(0, limit).map(mapPostgresRecord);\n\n return {\n entries,\n total,\n hasMore: rows.rows.length > limit,\n };\n },\n };\n}\n\nfunction createRecord(\n input: TrackInput,\n clock: () => Date,\n idGenerator: () => string,\n): ActivityRecord {\n validateTrackInput(input);\n\n const record: ActivityRecord = {\n id: idGenerator(),\n resource: freezeObject({\n type: input.resource.type.trim(),\n id: input.resource.id.trim(),\n title: input.resource.title?.trim(),\n }),\n action: input.action.trim().toLowerCase() as Action,\n actor: freezeObject({ ...input.actor, name: input.actor.name.trim() }),\n timestamp: input.timestamp ? new Date(input.timestamp) : clock(),\n changes: input.changes?.map(normalizeChange).map(freezeObject),\n content: input.content ? freezeObject(input.content) : undefined,\n metadata: input.metadata ? freezeObject(input.metadata) : undefined,\n };\n\n return freezeRecord(record);\n}\n\nfunction validateTrackInput(input: TrackInput) {\n if (!input.resource?.type?.trim()) {\n throw new ActivityError(\"INVALID_RESOURCE\", \"resource.type is required\", \"resource.type\");\n }\n\n if (!input.resource?.id?.trim()) {\n throw new ActivityError(\"INVALID_RESOURCE\", \"resource.id is required\", \"resource.id\");\n }\n\n if (![\"user\", \"system\", \"api\", \"agent\"].includes(input.actor?.type)) {\n throw new ActivityError(\"INVALID_ACTOR\", \"actor.type is invalid\", \"actor.type\");\n }\n\n if (!input.actor?.id?.trim()) {\n throw new ActivityError(\"INVALID_ACTOR\", \"actor.id is required\", \"actor.id\");\n }\n\n if (!input.actor?.name?.trim()) {\n throw new ActivityError(\"INVALID_ACTOR\", \"actor.name is required\", \"actor.name\");\n }\n\n if (!input.action?.trim()) {\n throw new ActivityError(\"INVALID_ACTION\", \"action is required\", \"action\");\n }\n\n if (input.timestamp && Number.isNaN(input.timestamp.getTime())) {\n throw new ActivityError(\"INVALID_TIMESTAMP\", \"timestamp must be a valid Date\", \"timestamp\");\n }\n\n if (input.action === \"update\" && !input.changes?.length) {\n throw new ActivityError(\"INVALID_ACTION\", \"update requires at least one change\", \"changes\");\n }\n\n if ((input.action === \"comment\" || input.action === \"attachment\") && !input.content) {\n throw new ActivityError(\"INVALID_ACTION\", `${input.action} requires content`, \"content\");\n }\n}\n\nfunction normalizeChange(change: Change): Change {\n if (!change.field.trim()) {\n throw new ActivityError(\"INVALID_CHANGE\", \"change.field is required\", \"changes.field\");\n }\n\n if (!change.label.trim()) {\n throw new ActivityError(\"INVALID_CHANGE\", \"change.label is required\", \"changes.label\");\n }\n\n return {\n ...change,\n field: change.field.trim(),\n label: change.label.trim(),\n valueType: change.valueType ?? inferValueType(change.after ?? change.before),\n };\n}\n\nfunction normalizeQuery(options: QueryOptions): QueryOptions {\n if (!options.resource?.type?.trim() || !options.resource?.id?.trim()) {\n throw new ActivityError(\"INVALID_RESOURCE\", \"query.resource is required\", \"resource\");\n }\n\n if (options.limit !== undefined && (options.limit < 1 || options.limit > 500)) {\n throw new ActivityError(\"INVALID_LIMIT\", \"limit must be between 1 and 500\", \"limit\");\n }\n\n if (options.offset !== undefined && options.offset < 0) {\n throw new ActivityError(\"INVALID_OFFSET\", \"offset must not be negative\", \"offset\");\n }\n\n if (options.from && options.to && options.from > options.to) {\n throw new ActivityError(\"INVALID_DATE_RANGE\", \"from must be before to\", \"from\");\n }\n\n return freezeObject({\n ...options,\n resource: freezeObject({\n type: options.resource.type.trim(),\n id: options.resource.id.trim(),\n title: options.resource.title?.trim(),\n }),\n search: options.search?.trim() || undefined,\n actor: options.actor?.trim() || undefined,\n actorId: options.actorId?.trim() || undefined,\n actions: options.actions\n ? Object.freeze(\n Array.from(\n new Set(options.actions.map((action) => action.trim().toLowerCase() as Action)),\n ),\n )\n : undefined,\n limit: options.limit ?? 50,\n offset: options.offset ?? 0,\n });\n}\n\nfunction matchesActions(entry: ActivityRecord, actions?: readonly Action[]) {\n return !actions?.length || actions.includes(entry.action);\n}\n\nfunction matchesActor(entry: ActivityRecord, actorId?: string) {\n return !actorId || entry.actor.id === actorId;\n}\n\nfunction matchesDates(entry: ActivityRecord, from?: Date, to?: Date) {\n return (!from || entry.timestamp >= from) && (!to || entry.timestamp <= to);\n}\n\nfunction matchesSearch(entry: ActivityRecord, search?: string) {\n if (!search) {\n return true;\n }\n\n return searchableText(entry).toLowerCase().includes(search.toLowerCase());\n}\n\nfunction searchableText(entry: ActivityRecord) {\n return [\n entry.action,\n entry.resource.title,\n entry.actor.name,\n entry.content ? JSON.stringify(entry.content) : \"\",\n ...(entry.changes ?? []).flatMap((change) => [\n change.field,\n change.label,\n stringifyValue(change.before),\n stringifyValue(change.after),\n ]),\n ].join(\" \");\n}\n\nfunction compareRecords(a: ActivityRecord, b: ActivityRecord) {\n const time = b.timestamp.getTime() - a.timestamp.getTime();\n return time || b.id.localeCompare(a.id);\n}\n\nfunction inferValueType(value: unknown): ValueType {\n if (value instanceof Date) {\n return \"date\";\n }\n\n const valueType = typeof value;\n if (valueType === \"string\" || valueType === \"number\" || valueType === \"boolean\") {\n return valueType;\n }\n\n return \"json\";\n}\n\nfunction freezeRecord(record: ActivityRecord): ActivityRecord {\n return freezeObject({\n ...record,\n resource: freezeObject(record.resource),\n actor: freezeObject(record.actor),\n changes: record.changes?.map((change) => freezeObject(change)),\n content: record.content ? freezeObject(record.content) : undefined,\n metadata: record.metadata ? freezeObject(record.metadata) : undefined,\n });\n}\n\nfunction freezeObject<T extends object>(value: T): Readonly<T> {\n return Object.freeze(value);\n}\n\nfunction stringifyValue(value: unknown) {\n if (value === null || value === undefined) {\n return \"\";\n }\n\n if (value instanceof Date) {\n return value.toISOString();\n }\n\n return typeof value === \"string\" ? value : JSON.stringify(value);\n}\n\nfunction createId() {\n if (globalThis.crypto?.randomUUID) {\n return globalThis.crypto.randomUUID();\n }\n\n return `act_${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`;\n}\n\nfunction wait(ms: number) {\n return new Promise((resolve) => globalThis.setTimeout(resolve, ms));\n}\n\nfunction readTotal(row: unknown) {\n if (isRecord(row) && typeof row.total === \"number\") {\n return row.total;\n }\n\n if (isRecord(row) && typeof row.total === \"string\") {\n return Number(row.total);\n }\n\n return 0;\n}\n\nfunction mapPostgresRecord(row: unknown): ActivityRecord {\n if (!isRecord(row)) {\n throw new ActivityError(\"STORAGE_FAILURE\", \"Invalid activity row returned by storage\");\n }\n\n return freezeRecord({\n id: String(row.id),\n resource: {\n type: String(row.resource_type),\n id: String(row.resource_id),\n title: typeof row.resource_title === \"string\" ? row.resource_title : undefined,\n },\n action: String(row.action) as Action,\n actor: {\n type: String(row.actor_type) as ActorType,\n id: String(row.actor_id),\n name: String(row.actor_name),\n avatarUrl: typeof row.actor_avatar_url === \"string\" ? row.actor_avatar_url : undefined,\n },\n timestamp: new Date(String(row.created_at)),\n changes: Array.isArray(row.changes) ? row.changes.map(mapPostgresChange) : undefined,\n content: isContent(row.content_json) ? row.content_json : undefined,\n metadata: isRecord(row.metadata_json) ? row.metadata_json : undefined,\n });\n}\n\nfunction mapPostgresChange(change: unknown): Change {\n if (!isRecord(change)) {\n throw new ActivityError(\"STORAGE_FAILURE\", \"Invalid change row returned by storage\");\n }\n\n return {\n field: String(change.field),\n label: String(change.label),\n before: change.before,\n after: change.after,\n valueType: String(change.valueType) as ValueType,\n };\n}\n\nfunction isContent(value: unknown): value is Content {\n return isRecord(value) && typeof value.type === \"string\";\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction toActivityError(error: unknown, code: string, fallback: string) {\n if (error instanceof ActivityError) {\n return error;\n }\n\n if (error instanceof Error) {\n return new ActivityError(code, error.message);\n }\n\n return new ActivityError(code, fallback);\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,2 @@
1
+ export { createMemoryStorageAdapter } from "../activity";
2
+ //# sourceMappingURL=memory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/adapters/memory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC"}