@archiva/archiva-nextjs 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +166 -0
- package/dist/index.d.mts +125 -0
- package/dist/index.d.ts +125 -0
- package/dist/index.js +382 -0
- package/dist/index.mjs +341 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# @archiva/archiva-nextjs
|
|
2
|
+
|
|
3
|
+
Next.js SDK for Archiva - Server Actions and Timeline Component
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @archiva/archiva-nextjs
|
|
9
|
+
# or
|
|
10
|
+
pnpm add @archiva/archiva-nextjs
|
|
11
|
+
# or
|
|
12
|
+
yarn add @archiva/archiva-nextjs
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Setup
|
|
16
|
+
|
|
17
|
+
Set the `ARCHIVA_SECRET_KEY` environment variable in your `.env.local` file:
|
|
18
|
+
|
|
19
|
+
```env
|
|
20
|
+
ARCHIVA_SECRET_KEY=pk_test_xxxxx
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
### ArchivaProvider (Server Component)
|
|
26
|
+
|
|
27
|
+
Wrap your application (or specific routes) with the ArchivaProvider component:
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { ArchivaProvider } from '@archiva/archiva-nextjs';
|
|
31
|
+
|
|
32
|
+
export default function Layout({ children }) {
|
|
33
|
+
return (
|
|
34
|
+
<ArchivaProvider>
|
|
35
|
+
{children}
|
|
36
|
+
</ArchivaProvider>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Server Actions
|
|
42
|
+
|
|
43
|
+
#### loadEvents
|
|
44
|
+
|
|
45
|
+
Load audit events with filtering and pagination:
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import { loadEvents } from '@archiva/archiva-nextjs';
|
|
49
|
+
|
|
50
|
+
// In a Server Component or Server Action
|
|
51
|
+
const events = await loadEvents({
|
|
52
|
+
entityId: 'entity_123',
|
|
53
|
+
actorId: 'actor_456',
|
|
54
|
+
entityType: 'invoice',
|
|
55
|
+
limit: 25,
|
|
56
|
+
cursor: 'optional_cursor',
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
#### createEvent
|
|
61
|
+
|
|
62
|
+
Create a single audit event:
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
import { createEvent } from '@archiva/archiva-nextjs';
|
|
66
|
+
|
|
67
|
+
const result = await createEvent({
|
|
68
|
+
action: 'update',
|
|
69
|
+
entityType: 'invoice',
|
|
70
|
+
entityId: 'inv_123',
|
|
71
|
+
actorType: 'user',
|
|
72
|
+
actorId: 'usr_123',
|
|
73
|
+
actorDisplay: 'John Doe',
|
|
74
|
+
occurredAt: new Date().toISOString(),
|
|
75
|
+
source: 'web',
|
|
76
|
+
context: {
|
|
77
|
+
requestId: 'req_123',
|
|
78
|
+
},
|
|
79
|
+
changes: [
|
|
80
|
+
{
|
|
81
|
+
op: 'set',
|
|
82
|
+
path: 'status',
|
|
83
|
+
before: 'draft',
|
|
84
|
+
after: 'sent',
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
#### createEvents (Bulk)
|
|
91
|
+
|
|
92
|
+
Create multiple events at once:
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
import { createEvents } from '@archiva/archiva-nextjs';
|
|
96
|
+
|
|
97
|
+
const result = await createEvents([
|
|
98
|
+
{
|
|
99
|
+
action: 'create',
|
|
100
|
+
entityType: 'invoice',
|
|
101
|
+
entityId: 'inv_123',
|
|
102
|
+
// ... other fields
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
action: 'update',
|
|
106
|
+
entityType: 'invoice',
|
|
107
|
+
entityId: 'inv_123',
|
|
108
|
+
// ... other fields
|
|
109
|
+
},
|
|
110
|
+
]);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Timeline Component (Client Component)
|
|
114
|
+
|
|
115
|
+
Display a timeline of audit events:
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
'use client';
|
|
119
|
+
|
|
120
|
+
import { Timeline } from '@archiva/archiva-nextjs';
|
|
121
|
+
|
|
122
|
+
export function InvoiceTimeline({ invoiceId }: { invoiceId: string }) {
|
|
123
|
+
return (
|
|
124
|
+
<Timeline
|
|
125
|
+
entityId={invoiceId}
|
|
126
|
+
entityType="invoice"
|
|
127
|
+
initialLimit={25}
|
|
128
|
+
/>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## API Reference
|
|
134
|
+
|
|
135
|
+
### LoadEventsParams
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
type LoadEventsParams = {
|
|
139
|
+
entityId?: string;
|
|
140
|
+
actorId?: string;
|
|
141
|
+
entityType?: string;
|
|
142
|
+
limit?: number;
|
|
143
|
+
cursor?: string;
|
|
144
|
+
};
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### CreateEventInput
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
type CreateEventInput = {
|
|
151
|
+
action: string;
|
|
152
|
+
entityType: string;
|
|
153
|
+
entityId: string;
|
|
154
|
+
actorType?: string;
|
|
155
|
+
actorId?: string;
|
|
156
|
+
actorDisplay?: string;
|
|
157
|
+
occurredAt?: string;
|
|
158
|
+
source?: string;
|
|
159
|
+
context?: Record<string, unknown>;
|
|
160
|
+
changes?: EventChange[];
|
|
161
|
+
};
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## License
|
|
165
|
+
|
|
166
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
type ArchivaProviderProps = {
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
apiKey?: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Server-only provider component for Archiva.
|
|
10
|
+
* This component validates the API key format and provides configuration
|
|
11
|
+
* for server actions (loadEvents, createEvents).
|
|
12
|
+
*
|
|
13
|
+
* The API key should be passed via props or set as ARCHIVA_SECRET_KEY environment variable.
|
|
14
|
+
*/
|
|
15
|
+
declare function ArchivaProvider({ children }: ArchivaProviderProps): react_jsx_runtime.JSX.Element;
|
|
16
|
+
|
|
17
|
+
type EventChange = {
|
|
18
|
+
op: "set" | "unset" | "add" | "remove" | "replace" | string;
|
|
19
|
+
path: string;
|
|
20
|
+
before?: unknown;
|
|
21
|
+
after?: unknown;
|
|
22
|
+
};
|
|
23
|
+
type CreateEventInput = {
|
|
24
|
+
action: string;
|
|
25
|
+
entityType: string;
|
|
26
|
+
entityId: string;
|
|
27
|
+
actorType?: string;
|
|
28
|
+
actorId?: string;
|
|
29
|
+
actorDisplay?: string;
|
|
30
|
+
occurredAt?: string;
|
|
31
|
+
source?: string;
|
|
32
|
+
context?: Record<string, unknown>;
|
|
33
|
+
changes?: EventChange[];
|
|
34
|
+
};
|
|
35
|
+
type CreateEventOptions = {
|
|
36
|
+
idempotencyKey?: string;
|
|
37
|
+
requestId?: string;
|
|
38
|
+
};
|
|
39
|
+
type AuditEventListItem = {
|
|
40
|
+
id: string;
|
|
41
|
+
receivedAt: string;
|
|
42
|
+
action: string;
|
|
43
|
+
entityType: string;
|
|
44
|
+
entityId: string;
|
|
45
|
+
actorId: string | null;
|
|
46
|
+
source: string | null;
|
|
47
|
+
};
|
|
48
|
+
type PageResult<T> = {
|
|
49
|
+
items: T[];
|
|
50
|
+
nextCursor?: string;
|
|
51
|
+
};
|
|
52
|
+
type LoadEventsParams = {
|
|
53
|
+
entityId?: string;
|
|
54
|
+
actorId?: string;
|
|
55
|
+
entityType?: string;
|
|
56
|
+
limit?: number;
|
|
57
|
+
cursor?: string;
|
|
58
|
+
};
|
|
59
|
+
declare class ArchivaError extends Error {
|
|
60
|
+
statusCode: number;
|
|
61
|
+
code: "UNAUTHORIZED" | "FORBIDDEN" | "PAYLOAD_TOO_LARGE" | "RATE_LIMITED" | "IDEMPOTENCY_CONFLICT" | "HTTP_ERROR";
|
|
62
|
+
retryAfterSeconds?: number;
|
|
63
|
+
details?: unknown;
|
|
64
|
+
constructor(params: {
|
|
65
|
+
statusCode: number;
|
|
66
|
+
code: "UNAUTHORIZED" | "FORBIDDEN" | "PAYLOAD_TOO_LARGE" | "RATE_LIMITED" | "IDEMPOTENCY_CONFLICT" | "HTTP_ERROR";
|
|
67
|
+
message: string;
|
|
68
|
+
retryAfterSeconds?: number;
|
|
69
|
+
details?: unknown;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Server action to load audit events
|
|
75
|
+
*
|
|
76
|
+
* @param params - Query parameters for filtering events
|
|
77
|
+
* @param apiKey - Optional API key (otherwise uses ARCHIVA_SECRET_KEY env var)
|
|
78
|
+
* @returns Paginated list of audit events
|
|
79
|
+
*/
|
|
80
|
+
declare function loadEvents(params: LoadEventsParams, apiKey?: string): Promise<PageResult<AuditEventListItem>>;
|
|
81
|
+
/**
|
|
82
|
+
* Server action to create a single audit event
|
|
83
|
+
*
|
|
84
|
+
* @param event - Event data to create
|
|
85
|
+
* @param options - Optional idempotency and request ID options
|
|
86
|
+
* @param apiKey - Optional API key (otherwise uses ARCHIVA_SECRET_KEY env var)
|
|
87
|
+
* @returns Created event ID and replay status
|
|
88
|
+
*/
|
|
89
|
+
declare function createEvent(event: CreateEventInput, options?: CreateEventOptions, apiKey?: string): Promise<{
|
|
90
|
+
eventId: string;
|
|
91
|
+
replayed: boolean;
|
|
92
|
+
}>;
|
|
93
|
+
/**
|
|
94
|
+
* Server action to create multiple audit events (bulk)
|
|
95
|
+
*
|
|
96
|
+
* @param events - Array of events to create
|
|
97
|
+
* @param options - Optional idempotency and request ID options
|
|
98
|
+
* @param apiKey - Optional API key (otherwise uses ARCHIVA_SECRET_KEY env var)
|
|
99
|
+
* @returns Array of created event IDs
|
|
100
|
+
*/
|
|
101
|
+
declare function createEvents(events: CreateEventInput[], options?: CreateEventOptions, apiKey?: string): Promise<{
|
|
102
|
+
eventIds: string[];
|
|
103
|
+
}>;
|
|
104
|
+
|
|
105
|
+
type TimelineItem = {
|
|
106
|
+
id: string | number;
|
|
107
|
+
title: string;
|
|
108
|
+
description?: string;
|
|
109
|
+
timestamp: Date | string;
|
|
110
|
+
badge?: string | number;
|
|
111
|
+
data?: unknown;
|
|
112
|
+
className?: string;
|
|
113
|
+
};
|
|
114
|
+
type TimelineProps = {
|
|
115
|
+
entityId?: string;
|
|
116
|
+
actorId?: string;
|
|
117
|
+
entityType?: string;
|
|
118
|
+
initialLimit?: number;
|
|
119
|
+
className?: string;
|
|
120
|
+
emptyMessage?: string;
|
|
121
|
+
apiKey?: string;
|
|
122
|
+
};
|
|
123
|
+
declare function Timeline({ entityId, actorId, entityType, initialLimit, className, emptyMessage, apiKey, }: TimelineProps): react_jsx_runtime.JSX.Element;
|
|
124
|
+
|
|
125
|
+
export { ArchivaError, ArchivaProvider, type ArchivaProviderProps, type AuditEventListItem, type CreateEventInput, type CreateEventOptions, type EventChange, type LoadEventsParams, type PageResult, Timeline, type TimelineItem, type TimelineProps, createEvent, createEvents, loadEvents };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
type ArchivaProviderProps = {
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
apiKey?: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Server-only provider component for Archiva.
|
|
10
|
+
* This component validates the API key format and provides configuration
|
|
11
|
+
* for server actions (loadEvents, createEvents).
|
|
12
|
+
*
|
|
13
|
+
* The API key should be passed via props or set as ARCHIVA_SECRET_KEY environment variable.
|
|
14
|
+
*/
|
|
15
|
+
declare function ArchivaProvider({ children }: ArchivaProviderProps): react_jsx_runtime.JSX.Element;
|
|
16
|
+
|
|
17
|
+
type EventChange = {
|
|
18
|
+
op: "set" | "unset" | "add" | "remove" | "replace" | string;
|
|
19
|
+
path: string;
|
|
20
|
+
before?: unknown;
|
|
21
|
+
after?: unknown;
|
|
22
|
+
};
|
|
23
|
+
type CreateEventInput = {
|
|
24
|
+
action: string;
|
|
25
|
+
entityType: string;
|
|
26
|
+
entityId: string;
|
|
27
|
+
actorType?: string;
|
|
28
|
+
actorId?: string;
|
|
29
|
+
actorDisplay?: string;
|
|
30
|
+
occurredAt?: string;
|
|
31
|
+
source?: string;
|
|
32
|
+
context?: Record<string, unknown>;
|
|
33
|
+
changes?: EventChange[];
|
|
34
|
+
};
|
|
35
|
+
type CreateEventOptions = {
|
|
36
|
+
idempotencyKey?: string;
|
|
37
|
+
requestId?: string;
|
|
38
|
+
};
|
|
39
|
+
type AuditEventListItem = {
|
|
40
|
+
id: string;
|
|
41
|
+
receivedAt: string;
|
|
42
|
+
action: string;
|
|
43
|
+
entityType: string;
|
|
44
|
+
entityId: string;
|
|
45
|
+
actorId: string | null;
|
|
46
|
+
source: string | null;
|
|
47
|
+
};
|
|
48
|
+
type PageResult<T> = {
|
|
49
|
+
items: T[];
|
|
50
|
+
nextCursor?: string;
|
|
51
|
+
};
|
|
52
|
+
type LoadEventsParams = {
|
|
53
|
+
entityId?: string;
|
|
54
|
+
actorId?: string;
|
|
55
|
+
entityType?: string;
|
|
56
|
+
limit?: number;
|
|
57
|
+
cursor?: string;
|
|
58
|
+
};
|
|
59
|
+
declare class ArchivaError extends Error {
|
|
60
|
+
statusCode: number;
|
|
61
|
+
code: "UNAUTHORIZED" | "FORBIDDEN" | "PAYLOAD_TOO_LARGE" | "RATE_LIMITED" | "IDEMPOTENCY_CONFLICT" | "HTTP_ERROR";
|
|
62
|
+
retryAfterSeconds?: number;
|
|
63
|
+
details?: unknown;
|
|
64
|
+
constructor(params: {
|
|
65
|
+
statusCode: number;
|
|
66
|
+
code: "UNAUTHORIZED" | "FORBIDDEN" | "PAYLOAD_TOO_LARGE" | "RATE_LIMITED" | "IDEMPOTENCY_CONFLICT" | "HTTP_ERROR";
|
|
67
|
+
message: string;
|
|
68
|
+
retryAfterSeconds?: number;
|
|
69
|
+
details?: unknown;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Server action to load audit events
|
|
75
|
+
*
|
|
76
|
+
* @param params - Query parameters for filtering events
|
|
77
|
+
* @param apiKey - Optional API key (otherwise uses ARCHIVA_SECRET_KEY env var)
|
|
78
|
+
* @returns Paginated list of audit events
|
|
79
|
+
*/
|
|
80
|
+
declare function loadEvents(params: LoadEventsParams, apiKey?: string): Promise<PageResult<AuditEventListItem>>;
|
|
81
|
+
/**
|
|
82
|
+
* Server action to create a single audit event
|
|
83
|
+
*
|
|
84
|
+
* @param event - Event data to create
|
|
85
|
+
* @param options - Optional idempotency and request ID options
|
|
86
|
+
* @param apiKey - Optional API key (otherwise uses ARCHIVA_SECRET_KEY env var)
|
|
87
|
+
* @returns Created event ID and replay status
|
|
88
|
+
*/
|
|
89
|
+
declare function createEvent(event: CreateEventInput, options?: CreateEventOptions, apiKey?: string): Promise<{
|
|
90
|
+
eventId: string;
|
|
91
|
+
replayed: boolean;
|
|
92
|
+
}>;
|
|
93
|
+
/**
|
|
94
|
+
* Server action to create multiple audit events (bulk)
|
|
95
|
+
*
|
|
96
|
+
* @param events - Array of events to create
|
|
97
|
+
* @param options - Optional idempotency and request ID options
|
|
98
|
+
* @param apiKey - Optional API key (otherwise uses ARCHIVA_SECRET_KEY env var)
|
|
99
|
+
* @returns Array of created event IDs
|
|
100
|
+
*/
|
|
101
|
+
declare function createEvents(events: CreateEventInput[], options?: CreateEventOptions, apiKey?: string): Promise<{
|
|
102
|
+
eventIds: string[];
|
|
103
|
+
}>;
|
|
104
|
+
|
|
105
|
+
type TimelineItem = {
|
|
106
|
+
id: string | number;
|
|
107
|
+
title: string;
|
|
108
|
+
description?: string;
|
|
109
|
+
timestamp: Date | string;
|
|
110
|
+
badge?: string | number;
|
|
111
|
+
data?: unknown;
|
|
112
|
+
className?: string;
|
|
113
|
+
};
|
|
114
|
+
type TimelineProps = {
|
|
115
|
+
entityId?: string;
|
|
116
|
+
actorId?: string;
|
|
117
|
+
entityType?: string;
|
|
118
|
+
initialLimit?: number;
|
|
119
|
+
className?: string;
|
|
120
|
+
emptyMessage?: string;
|
|
121
|
+
apiKey?: string;
|
|
122
|
+
};
|
|
123
|
+
declare function Timeline({ entityId, actorId, entityType, initialLimit, className, emptyMessage, apiKey, }: TimelineProps): react_jsx_runtime.JSX.Element;
|
|
124
|
+
|
|
125
|
+
export { ArchivaError, ArchivaProvider, type ArchivaProviderProps, type AuditEventListItem, type CreateEventInput, type CreateEventOptions, type EventChange, type LoadEventsParams, type PageResult, Timeline, type TimelineItem, type TimelineProps, createEvent, createEvents, loadEvents };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.tsx
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
ArchivaProvider: () => ArchivaProvider,
|
|
34
|
+
Timeline: () => Timeline,
|
|
35
|
+
createEvent: () => createEvent2,
|
|
36
|
+
createEvents: () => createEvents2,
|
|
37
|
+
loadEvents: () => loadEvents2
|
|
38
|
+
});
|
|
39
|
+
module.exports = __toCommonJS(index_exports);
|
|
40
|
+
|
|
41
|
+
// src/provider.tsx
|
|
42
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
43
|
+
function ArchivaProvider({ children }) {
|
|
44
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/client.ts
|
|
48
|
+
var DEFAULT_BASE_URL = "https://api.archiva.app";
|
|
49
|
+
function buildHeaders(apiKey, overrides) {
|
|
50
|
+
const headers = new Headers(overrides);
|
|
51
|
+
headers.set("X-Project-Key", apiKey);
|
|
52
|
+
return headers;
|
|
53
|
+
}
|
|
54
|
+
async function parseError(response) {
|
|
55
|
+
const retryAfterHeader = response.headers.get("Retry-After");
|
|
56
|
+
const retryAfterSeconds = retryAfterHeader ? Number(retryAfterHeader) : void 0;
|
|
57
|
+
let payload = void 0;
|
|
58
|
+
try {
|
|
59
|
+
payload = await response.json();
|
|
60
|
+
} catch {
|
|
61
|
+
payload = void 0;
|
|
62
|
+
}
|
|
63
|
+
const errorMessage = typeof payload === "object" && payload !== null && "error" in payload ? String(payload.error) : response.statusText;
|
|
64
|
+
let code = "HTTP_ERROR";
|
|
65
|
+
if (response.status === 401) {
|
|
66
|
+
code = "UNAUTHORIZED";
|
|
67
|
+
} else if (response.status === 403) {
|
|
68
|
+
code = "FORBIDDEN";
|
|
69
|
+
} else if (response.status === 413) {
|
|
70
|
+
code = "PAYLOAD_TOO_LARGE";
|
|
71
|
+
} else if (response.status === 429) {
|
|
72
|
+
code = "RATE_LIMITED";
|
|
73
|
+
} else if (response.status === 409) {
|
|
74
|
+
code = "IDEMPOTENCY_CONFLICT";
|
|
75
|
+
}
|
|
76
|
+
throw new ArchivaError({
|
|
77
|
+
statusCode: response.status,
|
|
78
|
+
code,
|
|
79
|
+
message: errorMessage,
|
|
80
|
+
retryAfterSeconds,
|
|
81
|
+
details: payload
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
function createRequestId() {
|
|
85
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
86
|
+
return crypto.randomUUID();
|
|
87
|
+
}
|
|
88
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
|
|
89
|
+
}
|
|
90
|
+
function createIdempotencyKey() {
|
|
91
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
92
|
+
return `idem_${crypto.randomUUID()}`;
|
|
93
|
+
}
|
|
94
|
+
return `idem_${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
|
|
95
|
+
}
|
|
96
|
+
async function loadEvents(apiKey, params, baseUrl = DEFAULT_BASE_URL) {
|
|
97
|
+
const url = new URL(`${baseUrl}/api/events`);
|
|
98
|
+
if (params.entityId) {
|
|
99
|
+
url.searchParams.set("entityId", params.entityId);
|
|
100
|
+
}
|
|
101
|
+
if (params.actorId) {
|
|
102
|
+
url.searchParams.set("actorId", params.actorId);
|
|
103
|
+
}
|
|
104
|
+
if (params.entityType) {
|
|
105
|
+
url.searchParams.set("entityType", params.entityType);
|
|
106
|
+
}
|
|
107
|
+
if (params.limit) {
|
|
108
|
+
url.searchParams.set("limit", String(params.limit));
|
|
109
|
+
}
|
|
110
|
+
if (params.cursor) {
|
|
111
|
+
url.searchParams.set("cursor", params.cursor);
|
|
112
|
+
}
|
|
113
|
+
const response = await fetch(url.toString(), {
|
|
114
|
+
headers: buildHeaders(apiKey)
|
|
115
|
+
});
|
|
116
|
+
if (!response.ok) {
|
|
117
|
+
await parseError(response);
|
|
118
|
+
}
|
|
119
|
+
const payload = await response.json();
|
|
120
|
+
if (!payload || typeof payload !== "object" || !Array.isArray(payload.items)) {
|
|
121
|
+
throw new ArchivaError({
|
|
122
|
+
statusCode: response.status,
|
|
123
|
+
code: "HTTP_ERROR",
|
|
124
|
+
message: "Invalid response format",
|
|
125
|
+
details: payload
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
items: payload.items,
|
|
130
|
+
nextCursor: typeof payload.nextCursor === "string" ? payload.nextCursor : void 0
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
async function createEvent(apiKey, event, options, baseUrl = DEFAULT_BASE_URL) {
|
|
134
|
+
const idempotencyKey = options?.idempotencyKey ?? createIdempotencyKey();
|
|
135
|
+
const requestId = options?.requestId ?? createRequestId();
|
|
136
|
+
const headers = buildHeaders(apiKey, {
|
|
137
|
+
"Content-Type": "application/json",
|
|
138
|
+
"Idempotency-Key": idempotencyKey,
|
|
139
|
+
"X-Request-Id": requestId
|
|
140
|
+
});
|
|
141
|
+
const response = await fetch(`${baseUrl}/api/ingest/event`, {
|
|
142
|
+
method: "POST",
|
|
143
|
+
headers,
|
|
144
|
+
body: JSON.stringify(event)
|
|
145
|
+
});
|
|
146
|
+
if (!response.ok) {
|
|
147
|
+
await parseError(response);
|
|
148
|
+
}
|
|
149
|
+
const payload = await response.json();
|
|
150
|
+
if (!payload || typeof payload !== "object" || typeof payload.eventId !== "string") {
|
|
151
|
+
throw new ArchivaError({
|
|
152
|
+
statusCode: response.status,
|
|
153
|
+
code: "HTTP_ERROR",
|
|
154
|
+
message: "Invalid response format",
|
|
155
|
+
details: payload
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
eventId: payload.eventId,
|
|
160
|
+
replayed: response.status === 200
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
async function createEvents(apiKey, events, options, baseUrl = DEFAULT_BASE_URL) {
|
|
164
|
+
const results = await Promise.all(
|
|
165
|
+
events.map(
|
|
166
|
+
(event, index) => createEvent(
|
|
167
|
+
apiKey,
|
|
168
|
+
event,
|
|
169
|
+
{
|
|
170
|
+
...options,
|
|
171
|
+
idempotencyKey: options?.idempotencyKey ? `${options.idempotencyKey}_${index}` : void 0
|
|
172
|
+
},
|
|
173
|
+
baseUrl
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
);
|
|
177
|
+
return {
|
|
178
|
+
eventIds: results.map((r) => r.eventId)
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/actions.ts
|
|
183
|
+
var DEFAULT_BASE_URL2 = "https://api.archiva.app";
|
|
184
|
+
function getApiKey(apiKey) {
|
|
185
|
+
const resolvedKey = apiKey || process.env.ARCHIVA_SECRET_KEY;
|
|
186
|
+
if (!resolvedKey) {
|
|
187
|
+
throw new Error("ARCHIVA_SECRET_KEY environment variable is required, or provide apiKey prop to ArchivaProvider");
|
|
188
|
+
}
|
|
189
|
+
return resolvedKey;
|
|
190
|
+
}
|
|
191
|
+
async function loadEvents2(params, apiKey) {
|
|
192
|
+
const resolvedApiKey = getApiKey(apiKey);
|
|
193
|
+
return loadEvents(resolvedApiKey, params, DEFAULT_BASE_URL2);
|
|
194
|
+
}
|
|
195
|
+
async function createEvent2(event, options, apiKey) {
|
|
196
|
+
const resolvedApiKey = getApiKey(apiKey);
|
|
197
|
+
return createEvent(resolvedApiKey, event, options, DEFAULT_BASE_URL2);
|
|
198
|
+
}
|
|
199
|
+
async function createEvents2(events, options, apiKey) {
|
|
200
|
+
const resolvedApiKey = getApiKey(apiKey);
|
|
201
|
+
return createEvents(resolvedApiKey, events, options, DEFAULT_BASE_URL2);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// src/timeline.tsx
|
|
205
|
+
var React = __toESM(require("react"));
|
|
206
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
207
|
+
function formatTimestamp(timestamp) {
|
|
208
|
+
const date = typeof timestamp === "string" ? new Date(timestamp) : timestamp;
|
|
209
|
+
return date.toLocaleDateString(void 0, {
|
|
210
|
+
month: "short",
|
|
211
|
+
day: "numeric",
|
|
212
|
+
year: "numeric",
|
|
213
|
+
hour: "numeric",
|
|
214
|
+
minute: "2-digit"
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
function eventToTimelineItem(event) {
|
|
218
|
+
return {
|
|
219
|
+
id: event.id,
|
|
220
|
+
title: event.action,
|
|
221
|
+
description: `${event.entityType} ${event.entityId}`,
|
|
222
|
+
timestamp: event.receivedAt,
|
|
223
|
+
badge: event.actorId ?? void 0,
|
|
224
|
+
data: event
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function Timeline({
|
|
228
|
+
entityId,
|
|
229
|
+
actorId,
|
|
230
|
+
entityType,
|
|
231
|
+
initialLimit = 25,
|
|
232
|
+
className,
|
|
233
|
+
emptyMessage = "No events yet.",
|
|
234
|
+
apiKey
|
|
235
|
+
}) {
|
|
236
|
+
const [items, setItems] = React.useState([]);
|
|
237
|
+
const [cursor, setCursor] = React.useState(void 0);
|
|
238
|
+
const [loading, setLoading] = React.useState(false);
|
|
239
|
+
const [error, setError] = React.useState(null);
|
|
240
|
+
const [hasMore, setHasMore] = React.useState(false);
|
|
241
|
+
const load = React.useCallback(async (options) => {
|
|
242
|
+
setLoading(true);
|
|
243
|
+
setError(null);
|
|
244
|
+
try {
|
|
245
|
+
const params = {
|
|
246
|
+
entityId,
|
|
247
|
+
actorId,
|
|
248
|
+
entityType,
|
|
249
|
+
limit: initialLimit,
|
|
250
|
+
cursor: options?.reset ? void 0 : options?.currentCursor ?? cursor
|
|
251
|
+
};
|
|
252
|
+
const response = await loadEvents2(params, apiKey);
|
|
253
|
+
const newItems = response.items.map(eventToTimelineItem);
|
|
254
|
+
setItems((prev) => options?.reset ? newItems : [...prev, ...newItems]);
|
|
255
|
+
setCursor(response.nextCursor);
|
|
256
|
+
setHasMore(Boolean(response.nextCursor));
|
|
257
|
+
} catch (err) {
|
|
258
|
+
setError(err.message);
|
|
259
|
+
} finally {
|
|
260
|
+
setLoading(false);
|
|
261
|
+
}
|
|
262
|
+
}, [entityId, actorId, entityType, initialLimit, apiKey]);
|
|
263
|
+
React.useEffect(() => {
|
|
264
|
+
setCursor(void 0);
|
|
265
|
+
setItems([]);
|
|
266
|
+
void load({ reset: true });
|
|
267
|
+
}, [entityId, actorId, entityType, load]);
|
|
268
|
+
if (loading && items.length === 0) {
|
|
269
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: className || "", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: "Loading events..." }) });
|
|
270
|
+
}
|
|
271
|
+
if (error) {
|
|
272
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: className || "", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { color: "red" }, children: [
|
|
273
|
+
"Error: ",
|
|
274
|
+
error
|
|
275
|
+
] }) });
|
|
276
|
+
}
|
|
277
|
+
if (items.length === 0) {
|
|
278
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: className || "", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: emptyMessage }) });
|
|
279
|
+
}
|
|
280
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: className || "", style: { width: "100%" }, children: [
|
|
281
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { position: "relative" }, children: items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
282
|
+
"div",
|
|
283
|
+
{
|
|
284
|
+
style: {
|
|
285
|
+
position: "relative",
|
|
286
|
+
display: "flex",
|
|
287
|
+
gap: "1rem",
|
|
288
|
+
paddingBottom: index < items.length - 1 ? "2rem" : "0"
|
|
289
|
+
},
|
|
290
|
+
children: [
|
|
291
|
+
index < items.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
292
|
+
"div",
|
|
293
|
+
{
|
|
294
|
+
style: {
|
|
295
|
+
position: "absolute",
|
|
296
|
+
left: "1.25rem",
|
|
297
|
+
top: "3rem",
|
|
298
|
+
height: "calc(100% - 3rem)",
|
|
299
|
+
width: "2px",
|
|
300
|
+
backgroundColor: "#e5e7eb"
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
),
|
|
304
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
305
|
+
"div",
|
|
306
|
+
{
|
|
307
|
+
style: {
|
|
308
|
+
position: "relative",
|
|
309
|
+
zIndex: 10,
|
|
310
|
+
width: "2.5rem",
|
|
311
|
+
height: "2.5rem",
|
|
312
|
+
borderRadius: "50%",
|
|
313
|
+
backgroundColor: "#f3f4f6",
|
|
314
|
+
border: "2px solid #fff",
|
|
315
|
+
display: "flex",
|
|
316
|
+
alignItems: "center",
|
|
317
|
+
justifyContent: "center",
|
|
318
|
+
flexShrink: 0
|
|
319
|
+
},
|
|
320
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
321
|
+
"div",
|
|
322
|
+
{
|
|
323
|
+
style: {
|
|
324
|
+
width: "0.75rem",
|
|
325
|
+
height: "0.75rem",
|
|
326
|
+
borderRadius: "50%",
|
|
327
|
+
backgroundColor: "#6b7280"
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
)
|
|
331
|
+
}
|
|
332
|
+
),
|
|
333
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { flex: 1, paddingBottom: "2rem" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { display: "flex", alignItems: "start", justifyContent: "space-between", gap: "0.5rem" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { flex: 1 }, children: [
|
|
334
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
|
|
335
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h4", { style: { fontWeight: 600, margin: 0 }, children: item.title }),
|
|
336
|
+
item.badge && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
337
|
+
"span",
|
|
338
|
+
{
|
|
339
|
+
style: {
|
|
340
|
+
fontSize: "0.75rem",
|
|
341
|
+
padding: "0.125rem 0.5rem",
|
|
342
|
+
borderRadius: "0.375rem",
|
|
343
|
+
backgroundColor: "#f3f4f6"
|
|
344
|
+
},
|
|
345
|
+
children: item.badge
|
|
346
|
+
}
|
|
347
|
+
)
|
|
348
|
+
] }),
|
|
349
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { fontSize: "0.875rem", color: "#6b7280", margin: "0.25rem 0 0 0" }, children: formatTimestamp(item.timestamp) }),
|
|
350
|
+
item.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { fontSize: "0.875rem", color: "#6b7280", margin: "0.5rem 0 0 0" }, children: item.description })
|
|
351
|
+
] }) }) })
|
|
352
|
+
]
|
|
353
|
+
},
|
|
354
|
+
item.id
|
|
355
|
+
)) }),
|
|
356
|
+
hasMore && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { marginTop: "1rem" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
357
|
+
"button",
|
|
358
|
+
{
|
|
359
|
+
type: "button",
|
|
360
|
+
onClick: () => load(),
|
|
361
|
+
disabled: loading,
|
|
362
|
+
style: {
|
|
363
|
+
padding: "0.5rem 1rem",
|
|
364
|
+
backgroundColor: loading ? "#d1d5db" : "#3b82f6",
|
|
365
|
+
color: "white",
|
|
366
|
+
border: "none",
|
|
367
|
+
borderRadius: "0.375rem",
|
|
368
|
+
cursor: loading ? "not-allowed" : "pointer"
|
|
369
|
+
},
|
|
370
|
+
children: loading ? "Loading..." : "Load more"
|
|
371
|
+
}
|
|
372
|
+
) })
|
|
373
|
+
] });
|
|
374
|
+
}
|
|
375
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
376
|
+
0 && (module.exports = {
|
|
377
|
+
ArchivaProvider,
|
|
378
|
+
Timeline,
|
|
379
|
+
createEvent,
|
|
380
|
+
createEvents,
|
|
381
|
+
loadEvents
|
|
382
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
// src/provider.tsx
|
|
2
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
3
|
+
function ArchivaProvider({ children }) {
|
|
4
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// src/client.ts
|
|
8
|
+
var DEFAULT_BASE_URL = "https://api.archiva.app";
|
|
9
|
+
function buildHeaders(apiKey, overrides) {
|
|
10
|
+
const headers = new Headers(overrides);
|
|
11
|
+
headers.set("X-Project-Key", apiKey);
|
|
12
|
+
return headers;
|
|
13
|
+
}
|
|
14
|
+
async function parseError(response) {
|
|
15
|
+
const retryAfterHeader = response.headers.get("Retry-After");
|
|
16
|
+
const retryAfterSeconds = retryAfterHeader ? Number(retryAfterHeader) : void 0;
|
|
17
|
+
let payload = void 0;
|
|
18
|
+
try {
|
|
19
|
+
payload = await response.json();
|
|
20
|
+
} catch {
|
|
21
|
+
payload = void 0;
|
|
22
|
+
}
|
|
23
|
+
const errorMessage = typeof payload === "object" && payload !== null && "error" in payload ? String(payload.error) : response.statusText;
|
|
24
|
+
let code = "HTTP_ERROR";
|
|
25
|
+
if (response.status === 401) {
|
|
26
|
+
code = "UNAUTHORIZED";
|
|
27
|
+
} else if (response.status === 403) {
|
|
28
|
+
code = "FORBIDDEN";
|
|
29
|
+
} else if (response.status === 413) {
|
|
30
|
+
code = "PAYLOAD_TOO_LARGE";
|
|
31
|
+
} else if (response.status === 429) {
|
|
32
|
+
code = "RATE_LIMITED";
|
|
33
|
+
} else if (response.status === 409) {
|
|
34
|
+
code = "IDEMPOTENCY_CONFLICT";
|
|
35
|
+
}
|
|
36
|
+
throw new ArchivaError({
|
|
37
|
+
statusCode: response.status,
|
|
38
|
+
code,
|
|
39
|
+
message: errorMessage,
|
|
40
|
+
retryAfterSeconds,
|
|
41
|
+
details: payload
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function createRequestId() {
|
|
45
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
46
|
+
return crypto.randomUUID();
|
|
47
|
+
}
|
|
48
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
|
|
49
|
+
}
|
|
50
|
+
function createIdempotencyKey() {
|
|
51
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
52
|
+
return `idem_${crypto.randomUUID()}`;
|
|
53
|
+
}
|
|
54
|
+
return `idem_${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
|
|
55
|
+
}
|
|
56
|
+
async function loadEvents(apiKey, params, baseUrl = DEFAULT_BASE_URL) {
|
|
57
|
+
const url = new URL(`${baseUrl}/api/events`);
|
|
58
|
+
if (params.entityId) {
|
|
59
|
+
url.searchParams.set("entityId", params.entityId);
|
|
60
|
+
}
|
|
61
|
+
if (params.actorId) {
|
|
62
|
+
url.searchParams.set("actorId", params.actorId);
|
|
63
|
+
}
|
|
64
|
+
if (params.entityType) {
|
|
65
|
+
url.searchParams.set("entityType", params.entityType);
|
|
66
|
+
}
|
|
67
|
+
if (params.limit) {
|
|
68
|
+
url.searchParams.set("limit", String(params.limit));
|
|
69
|
+
}
|
|
70
|
+
if (params.cursor) {
|
|
71
|
+
url.searchParams.set("cursor", params.cursor);
|
|
72
|
+
}
|
|
73
|
+
const response = await fetch(url.toString(), {
|
|
74
|
+
headers: buildHeaders(apiKey)
|
|
75
|
+
});
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
await parseError(response);
|
|
78
|
+
}
|
|
79
|
+
const payload = await response.json();
|
|
80
|
+
if (!payload || typeof payload !== "object" || !Array.isArray(payload.items)) {
|
|
81
|
+
throw new ArchivaError({
|
|
82
|
+
statusCode: response.status,
|
|
83
|
+
code: "HTTP_ERROR",
|
|
84
|
+
message: "Invalid response format",
|
|
85
|
+
details: payload
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
items: payload.items,
|
|
90
|
+
nextCursor: typeof payload.nextCursor === "string" ? payload.nextCursor : void 0
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
async function createEvent(apiKey, event, options, baseUrl = DEFAULT_BASE_URL) {
|
|
94
|
+
const idempotencyKey = options?.idempotencyKey ?? createIdempotencyKey();
|
|
95
|
+
const requestId = options?.requestId ?? createRequestId();
|
|
96
|
+
const headers = buildHeaders(apiKey, {
|
|
97
|
+
"Content-Type": "application/json",
|
|
98
|
+
"Idempotency-Key": idempotencyKey,
|
|
99
|
+
"X-Request-Id": requestId
|
|
100
|
+
});
|
|
101
|
+
const response = await fetch(`${baseUrl}/api/ingest/event`, {
|
|
102
|
+
method: "POST",
|
|
103
|
+
headers,
|
|
104
|
+
body: JSON.stringify(event)
|
|
105
|
+
});
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
await parseError(response);
|
|
108
|
+
}
|
|
109
|
+
const payload = await response.json();
|
|
110
|
+
if (!payload || typeof payload !== "object" || typeof payload.eventId !== "string") {
|
|
111
|
+
throw new ArchivaError({
|
|
112
|
+
statusCode: response.status,
|
|
113
|
+
code: "HTTP_ERROR",
|
|
114
|
+
message: "Invalid response format",
|
|
115
|
+
details: payload
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
eventId: payload.eventId,
|
|
120
|
+
replayed: response.status === 200
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
async function createEvents(apiKey, events, options, baseUrl = DEFAULT_BASE_URL) {
|
|
124
|
+
const results = await Promise.all(
|
|
125
|
+
events.map(
|
|
126
|
+
(event, index) => createEvent(
|
|
127
|
+
apiKey,
|
|
128
|
+
event,
|
|
129
|
+
{
|
|
130
|
+
...options,
|
|
131
|
+
idempotencyKey: options?.idempotencyKey ? `${options.idempotencyKey}_${index}` : void 0
|
|
132
|
+
},
|
|
133
|
+
baseUrl
|
|
134
|
+
)
|
|
135
|
+
)
|
|
136
|
+
);
|
|
137
|
+
return {
|
|
138
|
+
eventIds: results.map((r) => r.eventId)
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/actions.ts
|
|
143
|
+
var DEFAULT_BASE_URL2 = "https://api.archiva.app";
|
|
144
|
+
function getApiKey(apiKey) {
|
|
145
|
+
const resolvedKey = apiKey || process.env.ARCHIVA_SECRET_KEY;
|
|
146
|
+
if (!resolvedKey) {
|
|
147
|
+
throw new Error("ARCHIVA_SECRET_KEY environment variable is required, or provide apiKey prop to ArchivaProvider");
|
|
148
|
+
}
|
|
149
|
+
return resolvedKey;
|
|
150
|
+
}
|
|
151
|
+
async function loadEvents2(params, apiKey) {
|
|
152
|
+
const resolvedApiKey = getApiKey(apiKey);
|
|
153
|
+
return loadEvents(resolvedApiKey, params, DEFAULT_BASE_URL2);
|
|
154
|
+
}
|
|
155
|
+
async function createEvent2(event, options, apiKey) {
|
|
156
|
+
const resolvedApiKey = getApiKey(apiKey);
|
|
157
|
+
return createEvent(resolvedApiKey, event, options, DEFAULT_BASE_URL2);
|
|
158
|
+
}
|
|
159
|
+
async function createEvents2(events, options, apiKey) {
|
|
160
|
+
const resolvedApiKey = getApiKey(apiKey);
|
|
161
|
+
return createEvents(resolvedApiKey, events, options, DEFAULT_BASE_URL2);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/timeline.tsx
|
|
165
|
+
import * as React from "react";
|
|
166
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
167
|
+
function formatTimestamp(timestamp) {
|
|
168
|
+
const date = typeof timestamp === "string" ? new Date(timestamp) : timestamp;
|
|
169
|
+
return date.toLocaleDateString(void 0, {
|
|
170
|
+
month: "short",
|
|
171
|
+
day: "numeric",
|
|
172
|
+
year: "numeric",
|
|
173
|
+
hour: "numeric",
|
|
174
|
+
minute: "2-digit"
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
function eventToTimelineItem(event) {
|
|
178
|
+
return {
|
|
179
|
+
id: event.id,
|
|
180
|
+
title: event.action,
|
|
181
|
+
description: `${event.entityType} ${event.entityId}`,
|
|
182
|
+
timestamp: event.receivedAt,
|
|
183
|
+
badge: event.actorId ?? void 0,
|
|
184
|
+
data: event
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function Timeline({
|
|
188
|
+
entityId,
|
|
189
|
+
actorId,
|
|
190
|
+
entityType,
|
|
191
|
+
initialLimit = 25,
|
|
192
|
+
className,
|
|
193
|
+
emptyMessage = "No events yet.",
|
|
194
|
+
apiKey
|
|
195
|
+
}) {
|
|
196
|
+
const [items, setItems] = React.useState([]);
|
|
197
|
+
const [cursor, setCursor] = React.useState(void 0);
|
|
198
|
+
const [loading, setLoading] = React.useState(false);
|
|
199
|
+
const [error, setError] = React.useState(null);
|
|
200
|
+
const [hasMore, setHasMore] = React.useState(false);
|
|
201
|
+
const load = React.useCallback(async (options) => {
|
|
202
|
+
setLoading(true);
|
|
203
|
+
setError(null);
|
|
204
|
+
try {
|
|
205
|
+
const params = {
|
|
206
|
+
entityId,
|
|
207
|
+
actorId,
|
|
208
|
+
entityType,
|
|
209
|
+
limit: initialLimit,
|
|
210
|
+
cursor: options?.reset ? void 0 : options?.currentCursor ?? cursor
|
|
211
|
+
};
|
|
212
|
+
const response = await loadEvents2(params, apiKey);
|
|
213
|
+
const newItems = response.items.map(eventToTimelineItem);
|
|
214
|
+
setItems((prev) => options?.reset ? newItems : [...prev, ...newItems]);
|
|
215
|
+
setCursor(response.nextCursor);
|
|
216
|
+
setHasMore(Boolean(response.nextCursor));
|
|
217
|
+
} catch (err) {
|
|
218
|
+
setError(err.message);
|
|
219
|
+
} finally {
|
|
220
|
+
setLoading(false);
|
|
221
|
+
}
|
|
222
|
+
}, [entityId, actorId, entityType, initialLimit, apiKey]);
|
|
223
|
+
React.useEffect(() => {
|
|
224
|
+
setCursor(void 0);
|
|
225
|
+
setItems([]);
|
|
226
|
+
void load({ reset: true });
|
|
227
|
+
}, [entityId, actorId, entityType, load]);
|
|
228
|
+
if (loading && items.length === 0) {
|
|
229
|
+
return /* @__PURE__ */ jsx2("div", { className: className || "", children: /* @__PURE__ */ jsx2("div", { children: "Loading events..." }) });
|
|
230
|
+
}
|
|
231
|
+
if (error) {
|
|
232
|
+
return /* @__PURE__ */ jsx2("div", { className: className || "", children: /* @__PURE__ */ jsxs("div", { style: { color: "red" }, children: [
|
|
233
|
+
"Error: ",
|
|
234
|
+
error
|
|
235
|
+
] }) });
|
|
236
|
+
}
|
|
237
|
+
if (items.length === 0) {
|
|
238
|
+
return /* @__PURE__ */ jsx2("div", { className: className || "", children: /* @__PURE__ */ jsx2("div", { children: emptyMessage }) });
|
|
239
|
+
}
|
|
240
|
+
return /* @__PURE__ */ jsxs("div", { className: className || "", style: { width: "100%" }, children: [
|
|
241
|
+
/* @__PURE__ */ jsx2("div", { style: { position: "relative" }, children: items.map((item, index) => /* @__PURE__ */ jsxs(
|
|
242
|
+
"div",
|
|
243
|
+
{
|
|
244
|
+
style: {
|
|
245
|
+
position: "relative",
|
|
246
|
+
display: "flex",
|
|
247
|
+
gap: "1rem",
|
|
248
|
+
paddingBottom: index < items.length - 1 ? "2rem" : "0"
|
|
249
|
+
},
|
|
250
|
+
children: [
|
|
251
|
+
index < items.length - 1 && /* @__PURE__ */ jsx2(
|
|
252
|
+
"div",
|
|
253
|
+
{
|
|
254
|
+
style: {
|
|
255
|
+
position: "absolute",
|
|
256
|
+
left: "1.25rem",
|
|
257
|
+
top: "3rem",
|
|
258
|
+
height: "calc(100% - 3rem)",
|
|
259
|
+
width: "2px",
|
|
260
|
+
backgroundColor: "#e5e7eb"
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
),
|
|
264
|
+
/* @__PURE__ */ jsx2(
|
|
265
|
+
"div",
|
|
266
|
+
{
|
|
267
|
+
style: {
|
|
268
|
+
position: "relative",
|
|
269
|
+
zIndex: 10,
|
|
270
|
+
width: "2.5rem",
|
|
271
|
+
height: "2.5rem",
|
|
272
|
+
borderRadius: "50%",
|
|
273
|
+
backgroundColor: "#f3f4f6",
|
|
274
|
+
border: "2px solid #fff",
|
|
275
|
+
display: "flex",
|
|
276
|
+
alignItems: "center",
|
|
277
|
+
justifyContent: "center",
|
|
278
|
+
flexShrink: 0
|
|
279
|
+
},
|
|
280
|
+
children: /* @__PURE__ */ jsx2(
|
|
281
|
+
"div",
|
|
282
|
+
{
|
|
283
|
+
style: {
|
|
284
|
+
width: "0.75rem",
|
|
285
|
+
height: "0.75rem",
|
|
286
|
+
borderRadius: "50%",
|
|
287
|
+
backgroundColor: "#6b7280"
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
)
|
|
291
|
+
}
|
|
292
|
+
),
|
|
293
|
+
/* @__PURE__ */ jsx2("div", { style: { flex: 1, paddingBottom: "2rem" }, children: /* @__PURE__ */ jsx2("div", { style: { display: "flex", alignItems: "start", justifyContent: "space-between", gap: "0.5rem" }, children: /* @__PURE__ */ jsxs("div", { style: { flex: 1 }, children: [
|
|
294
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
|
|
295
|
+
/* @__PURE__ */ jsx2("h4", { style: { fontWeight: 600, margin: 0 }, children: item.title }),
|
|
296
|
+
item.badge && /* @__PURE__ */ jsx2(
|
|
297
|
+
"span",
|
|
298
|
+
{
|
|
299
|
+
style: {
|
|
300
|
+
fontSize: "0.75rem",
|
|
301
|
+
padding: "0.125rem 0.5rem",
|
|
302
|
+
borderRadius: "0.375rem",
|
|
303
|
+
backgroundColor: "#f3f4f6"
|
|
304
|
+
},
|
|
305
|
+
children: item.badge
|
|
306
|
+
}
|
|
307
|
+
)
|
|
308
|
+
] }),
|
|
309
|
+
/* @__PURE__ */ jsx2("p", { style: { fontSize: "0.875rem", color: "#6b7280", margin: "0.25rem 0 0 0" }, children: formatTimestamp(item.timestamp) }),
|
|
310
|
+
item.description && /* @__PURE__ */ jsx2("p", { style: { fontSize: "0.875rem", color: "#6b7280", margin: "0.5rem 0 0 0" }, children: item.description })
|
|
311
|
+
] }) }) })
|
|
312
|
+
]
|
|
313
|
+
},
|
|
314
|
+
item.id
|
|
315
|
+
)) }),
|
|
316
|
+
hasMore && /* @__PURE__ */ jsx2("div", { style: { marginTop: "1rem" }, children: /* @__PURE__ */ jsx2(
|
|
317
|
+
"button",
|
|
318
|
+
{
|
|
319
|
+
type: "button",
|
|
320
|
+
onClick: () => load(),
|
|
321
|
+
disabled: loading,
|
|
322
|
+
style: {
|
|
323
|
+
padding: "0.5rem 1rem",
|
|
324
|
+
backgroundColor: loading ? "#d1d5db" : "#3b82f6",
|
|
325
|
+
color: "white",
|
|
326
|
+
border: "none",
|
|
327
|
+
borderRadius: "0.375rem",
|
|
328
|
+
cursor: loading ? "not-allowed" : "pointer"
|
|
329
|
+
},
|
|
330
|
+
children: loading ? "Loading..." : "Load more"
|
|
331
|
+
}
|
|
332
|
+
) })
|
|
333
|
+
] });
|
|
334
|
+
}
|
|
335
|
+
export {
|
|
336
|
+
ArchivaProvider,
|
|
337
|
+
Timeline,
|
|
338
|
+
createEvent2 as createEvent,
|
|
339
|
+
createEvents2 as createEvents,
|
|
340
|
+
loadEvents2 as loadEvents
|
|
341
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@archiva/archiva-nextjs",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Archiva Next.js SDK - Server Actions and Timeline Component",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"import": "./dist/index.mjs"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": ["dist"],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsup src/index.tsx --format esm,cjs --dts",
|
|
18
|
+
"test": "vitest run --config vitest.config.ts"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"react": ">=18",
|
|
22
|
+
"react-dom": ">=18",
|
|
23
|
+
"next": ">=13"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"zod": "^3.22.4"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^20.11.19",
|
|
30
|
+
"@types/react": "^18.2.55",
|
|
31
|
+
"@types/react-dom": "^18.2.19",
|
|
32
|
+
"react": "^18.2.0",
|
|
33
|
+
"react-dom": "^18.2.0",
|
|
34
|
+
"typescript": "^5.3.3",
|
|
35
|
+
"tsup": "^8.0.1",
|
|
36
|
+
"vitest": "^1.2.2"
|
|
37
|
+
}
|
|
38
|
+
}
|