@pierre/storage 0.0.11 → 0.1.1

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/src/schemas.ts ADDED
@@ -0,0 +1,138 @@
1
+ import { z } from 'zod';
2
+
3
+ export const listFilesResponseSchema = z.object({
4
+ paths: z.array(z.string()),
5
+ ref: z.string(),
6
+ });
7
+
8
+ export const branchInfoSchema = z.object({
9
+ cursor: z.string(),
10
+ name: z.string(),
11
+ head_sha: z.string(),
12
+ created_at: z.string(),
13
+ });
14
+
15
+ export const listBranchesResponseSchema = z.object({
16
+ branches: z.array(branchInfoSchema),
17
+ next_cursor: z.string().nullable().optional(),
18
+ has_more: z.boolean(),
19
+ });
20
+
21
+ export const commitInfoRawSchema = z.object({
22
+ sha: z.string(),
23
+ message: z.string(),
24
+ author_name: z.string(),
25
+ author_email: z.string(),
26
+ committer_name: z.string(),
27
+ committer_email: z.string(),
28
+ date: z.string(),
29
+ });
30
+
31
+ export const listCommitsResponseSchema = z.object({
32
+ commits: z.array(commitInfoRawSchema),
33
+ next_cursor: z.string().nullable().optional(),
34
+ has_more: z.boolean(),
35
+ });
36
+
37
+ export const diffStatsSchema = z.object({
38
+ files: z.number(),
39
+ additions: z.number(),
40
+ deletions: z.number(),
41
+ changes: z.number(),
42
+ });
43
+
44
+ export const diffFileRawSchema = z.object({
45
+ path: z.string(),
46
+ state: z.string(),
47
+ old_path: z.string().nullable().optional(),
48
+ raw: z.string(),
49
+ bytes: z.number(),
50
+ is_eof: z.boolean(),
51
+ });
52
+
53
+ export const filteredFileRawSchema = z.object({
54
+ path: z.string(),
55
+ state: z.string(),
56
+ old_path: z.string().nullable().optional(),
57
+ bytes: z.number(),
58
+ is_eof: z.boolean(),
59
+ });
60
+
61
+ export const branchDiffResponseSchema = z.object({
62
+ branch: z.string(),
63
+ base: z.string(),
64
+ stats: diffStatsSchema,
65
+ files: z.array(diffFileRawSchema),
66
+ filtered_files: z.array(filteredFileRawSchema),
67
+ });
68
+
69
+ export const commitDiffResponseSchema = z.object({
70
+ sha: z.string(),
71
+ stats: diffStatsSchema,
72
+ files: z.array(diffFileRawSchema),
73
+ filtered_files: z.array(filteredFileRawSchema),
74
+ });
75
+
76
+ export const refUpdateResultSchema = z.object({
77
+ branch: z.string(),
78
+ old_sha: z.string(),
79
+ new_sha: z.string(),
80
+ success: z.boolean(),
81
+ status: z.string(),
82
+ message: z.string().optional(),
83
+ });
84
+
85
+ export const commitPackCommitSchema = z.object({
86
+ commit_sha: z.string(),
87
+ tree_sha: z.string(),
88
+ target_branch: z.string(),
89
+ pack_bytes: z.number(),
90
+ blob_count: z.number(),
91
+ });
92
+
93
+ export const restoreCommitCommitSchema = commitPackCommitSchema.omit({ blob_count: true });
94
+
95
+ export const refUpdateResultWithOptionalsSchema = z.object({
96
+ branch: z.string().optional(),
97
+ old_sha: z.string().optional(),
98
+ new_sha: z.string().optional(),
99
+ success: z.boolean().optional(),
100
+ status: z.string(),
101
+ message: z.string().optional(),
102
+ });
103
+
104
+ export const commitPackAckSchema = z.object({
105
+ commit: commitPackCommitSchema,
106
+ result: refUpdateResultSchema,
107
+ });
108
+
109
+ export const restoreCommitAckSchema = z.object({
110
+ commit: restoreCommitCommitSchema,
111
+ result: refUpdateResultSchema.extend({ success: z.literal(true) }),
112
+ });
113
+
114
+ export const commitPackResponseSchema = z.object({
115
+ commit: commitPackCommitSchema.partial().optional().nullable(),
116
+ result: refUpdateResultWithOptionalsSchema,
117
+ });
118
+
119
+ export const restoreCommitResponseSchema = z.object({
120
+ commit: restoreCommitCommitSchema.partial().optional().nullable(),
121
+ result: refUpdateResultWithOptionalsSchema,
122
+ });
123
+
124
+ export const errorEnvelopeSchema = z.object({
125
+ error: z.string(),
126
+ });
127
+
128
+ export type ListFilesResponseRaw = z.infer<typeof listFilesResponseSchema>;
129
+ export type RawBranchInfo = z.infer<typeof branchInfoSchema>;
130
+ export type ListBranchesResponseRaw = z.infer<typeof listBranchesResponseSchema>;
131
+ export type RawCommitInfo = z.infer<typeof commitInfoRawSchema>;
132
+ export type ListCommitsResponseRaw = z.infer<typeof listCommitsResponseSchema>;
133
+ export type RawFileDiff = z.infer<typeof diffFileRawSchema>;
134
+ export type RawFilteredFile = z.infer<typeof filteredFileRawSchema>;
135
+ export type GetBranchDiffResponseRaw = z.infer<typeof branchDiffResponseSchema>;
136
+ export type GetCommitDiffResponseRaw = z.infer<typeof commitDiffResponseSchema>;
137
+ export type CommitPackAckRaw = z.infer<typeof commitPackAckSchema>;
138
+ export type RestoreCommitAckRaw = z.infer<typeof restoreCommitAckSchema>;
package/src/types.ts CHANGED
@@ -2,10 +2,23 @@
2
2
  * Type definitions for Pierre Git Storage SDK
3
3
  */
4
4
 
5
+ import type {
6
+ GetBranchDiffResponseRaw,
7
+ GetCommitDiffResponseRaw,
8
+ ListBranchesResponseRaw,
9
+ ListCommitsResponseRaw,
10
+ ListFilesResponseRaw,
11
+ RawBranchInfo as SchemaRawBranchInfo,
12
+ RawCommitInfo as SchemaRawCommitInfo,
13
+ RawFileDiff as SchemaRawFileDiff,
14
+ RawFilteredFile as SchemaRawFilteredFile,
15
+ } from './schemas';
16
+
5
17
  export interface OverrideableGitStorageOptions {
6
18
  apiBaseUrl?: string;
7
19
  storageBaseUrl?: string;
8
20
  apiVersion?: ValidAPIVersion;
21
+ defaultTTL?: number;
9
22
  }
10
23
 
11
24
  export interface GitStorageOptions extends OverrideableGitStorageOptions {
@@ -26,13 +39,13 @@ export interface Repo {
26
39
  getRemoteURL(options?: GetRemoteURLOptions): Promise<string>;
27
40
 
28
41
  getFileStream(options: GetFileOptions): Promise<Response>;
29
- listFiles(options?: ListFilesOptions): Promise<ListFilesResponse>;
30
- listBranches(options?: ListBranchesOptions): Promise<ListBranchesResponse>;
31
- listCommits(options?: ListCommitsOptions): Promise<ListCommitsResponse>;
32
- getBranchDiff(options: GetBranchDiffOptions): Promise<GetBranchDiffResponse>;
33
- getCommitDiff(options: GetCommitDiffOptions): Promise<GetCommitDiffResponse>;
34
- getCommit(options: GetCommitOptions): Promise<GetCommitResponse>;
42
+ listFiles(options?: ListFilesOptions): Promise<ListFilesResult>;
43
+ listBranches(options?: ListBranchesOptions): Promise<ListBranchesResult>;
44
+ listCommits(options?: ListCommitsOptions): Promise<ListCommitsResult>;
45
+ getBranchDiff(options: GetBranchDiffOptions): Promise<GetBranchDiffResult>;
46
+ getCommitDiff(options: GetCommitDiffOptions): Promise<GetCommitDiffResult>;
35
47
  pullUpstream(options: PullUpstreamOptions): Promise<void>;
48
+ restoreCommit(options: RestoreCommitOptions): Promise<RestoreCommitResult>;
36
49
  createCommit(options: CreateCommitOptions): CommitBuilder;
37
50
  }
38
51
 
@@ -71,11 +84,6 @@ export interface CreateRepoOptions extends GitStorageInvocationOptions {
71
84
  defaultBranch?: string;
72
85
  }
73
86
 
74
- export interface CreateRepoResponse {
75
- repo_id: string;
76
- url: string;
77
- }
78
-
79
87
  // Get File API types
80
88
  export interface GetFileOptions extends GitStorageInvocationOptions {
81
89
  path: string;
@@ -91,7 +99,9 @@ export interface ListFilesOptions extends GitStorageInvocationOptions {
91
99
  ref?: string;
92
100
  }
93
101
 
94
- export interface ListFilesResponse {
102
+ export type ListFilesResponse = ListFilesResponseRaw;
103
+
104
+ export interface ListFilesResult {
95
105
  paths: string[];
96
106
  ref: string;
97
107
  }
@@ -102,17 +112,21 @@ export interface ListBranchesOptions extends GitStorageInvocationOptions {
102
112
  limit?: number;
103
113
  }
104
114
 
115
+ export type RawBranchInfo = SchemaRawBranchInfo;
116
+
105
117
  export interface BranchInfo {
106
118
  cursor: string;
107
119
  name: string;
108
- head_sha: string;
109
- created_at: string;
120
+ headSha: string;
121
+ createdAt: string;
110
122
  }
111
123
 
112
- export interface ListBranchesResponse {
124
+ export type ListBranchesResponse = ListBranchesResponseRaw;
125
+
126
+ export interface ListBranchesResult {
113
127
  branches: BranchInfo[];
114
- next_cursor?: string;
115
- has_more: boolean;
128
+ nextCursor?: string;
129
+ hasMore: boolean;
116
130
  }
117
131
 
118
132
  // List Commits API types
@@ -122,20 +136,25 @@ export interface ListCommitsOptions extends GitStorageInvocationOptions {
122
136
  limit?: number;
123
137
  }
124
138
 
139
+ export type RawCommitInfo = SchemaRawCommitInfo;
140
+
125
141
  export interface CommitInfo {
126
142
  sha: string;
127
143
  message: string;
128
- author_name: string;
129
- author_email: string;
130
- committer_name: string;
131
- committer_email: string;
132
- date: string;
144
+ authorName: string;
145
+ authorEmail: string;
146
+ committerName: string;
147
+ committerEmail: string;
148
+ date: Date;
149
+ rawDate: string;
133
150
  }
134
151
 
135
- export interface ListCommitsResponse {
152
+ export type ListCommitsResponse = ListCommitsResponseRaw;
153
+
154
+ export interface ListCommitsResult {
136
155
  commits: CommitInfo[];
137
- next_cursor?: string;
138
- has_more: boolean;
156
+ nextCursor?: string;
157
+ hasMore: boolean;
139
158
  }
140
159
 
141
160
  // Branch Diff API types
@@ -144,12 +163,14 @@ export interface GetBranchDiffOptions extends GitStorageInvocationOptions {
144
163
  base?: string;
145
164
  }
146
165
 
147
- export interface GetBranchDiffResponse {
166
+ export type GetBranchDiffResponse = GetBranchDiffResponseRaw;
167
+
168
+ export interface GetBranchDiffResult {
148
169
  branch: string;
149
170
  base: string;
150
171
  stats: DiffStats;
151
172
  files: FileDiff[];
152
- filtered_files: FilteredFile[];
173
+ filteredFiles: FilteredFile[];
153
174
  }
154
175
 
155
176
  // Commit Diff API types
@@ -157,11 +178,13 @@ export interface GetCommitDiffOptions extends GitStorageInvocationOptions {
157
178
  sha: string;
158
179
  }
159
180
 
160
- export interface GetCommitDiffResponse {
181
+ export type GetCommitDiffResponse = GetCommitDiffResponseRaw;
182
+
183
+ export interface GetCommitDiffResult {
161
184
  sha: string;
162
185
  stats: DiffStats;
163
186
  files: FileDiff[];
164
- filtered_files: FilteredFile[];
187
+ filteredFiles: FilteredFile[];
165
188
  }
166
189
 
167
190
  // Shared diff types
@@ -172,52 +195,40 @@ export interface DiffStats {
172
195
  changes: number;
173
196
  }
174
197
 
175
- export interface FileDiff {
176
- path: string;
177
- state: string;
178
- old_path?: string;
179
- bytes: number;
180
- is_eof: boolean;
181
- diff: string;
182
- }
198
+ export type RawFileDiff = SchemaRawFileDiff;
183
199
 
184
- export interface FilteredFile {
200
+ export type RawFilteredFile = SchemaRawFilteredFile;
201
+
202
+ export type DiffFileState =
203
+ | 'added'
204
+ | 'modified'
205
+ | 'deleted'
206
+ | 'renamed'
207
+ | 'copied'
208
+ | 'type_changed'
209
+ | 'unmerged'
210
+ | 'unknown';
211
+
212
+ export interface DiffFileBase {
185
213
  path: string;
186
- state: string;
187
- old_path?: string;
214
+ state: DiffFileState;
215
+ rawState: string;
216
+ oldPath?: string;
188
217
  bytes: number;
189
- is_eof: boolean;
218
+ isEof: boolean;
190
219
  }
191
220
 
192
- // Get Commit API types
193
- export interface GetCommitOptions extends GitStorageInvocationOptions {
194
- sha: string;
221
+ export interface FileDiff extends DiffFileBase {
222
+ raw: string;
195
223
  }
196
224
 
197
- export interface GetCommitResponse {
198
- sha: string;
199
- message: string;
200
- author: {
201
- name: string;
202
- email: string;
203
- };
204
- committer: {
205
- name: string;
206
- email: string;
207
- };
208
- date: string;
209
- stats: {
210
- additions: number;
211
- deletions: number;
212
- total: number;
213
- };
214
- }
225
+ export interface FilteredFile extends DiffFileBase {}
215
226
 
216
227
  export interface CreateCommitOptions extends GitStorageInvocationOptions {
217
- targetRef: string;
228
+ targetBranch: string;
218
229
  commitMessage: string;
219
- baseRef?: string;
220
- author?: CommitSignature;
230
+ expectedHeadSha?: string;
231
+ author: CommitSignature;
221
232
  committer?: CommitSignature;
222
233
  signal?: AbortSignal;
223
234
  }
@@ -225,47 +236,110 @@ export interface CreateCommitOptions extends GitStorageInvocationOptions {
225
236
  export interface CommitSignature {
226
237
  name: string;
227
238
  email: string;
228
- date?: string;
229
239
  }
230
240
 
241
+ export interface ReadableStreamReaderLike<T> {
242
+ read(): Promise<{ value?: T; done: boolean }>;
243
+ releaseLock?(): void;
244
+ }
245
+
246
+ export interface ReadableStreamLike<T> {
247
+ getReader(): ReadableStreamReaderLike<T>;
248
+ }
249
+
250
+ export interface BlobLike {
251
+ stream(): unknown;
252
+ }
253
+
254
+ export interface FileLike extends BlobLike {
255
+ name: string;
256
+ lastModified?: number;
257
+ }
258
+
259
+ export type GitFileMode = '100644' | '100755' | '120000' | '160000';
260
+
261
+ export type TextEncoding =
262
+ | 'ascii'
263
+ | 'utf8'
264
+ | 'utf-8'
265
+ | 'utf16le'
266
+ | 'utf-16le'
267
+ | 'ucs2'
268
+ | 'ucs-2'
269
+ | 'base64'
270
+ | 'base64url'
271
+ | 'latin1'
272
+ | 'binary'
273
+ | 'hex';
274
+
231
275
  export type CommitFileSource =
232
276
  | string
233
277
  | Uint8Array
234
278
  | ArrayBuffer
235
- | AsyncIterable<Uint8Array | ArrayBuffer>
236
- | Iterable<Uint8Array | ArrayBuffer>;
279
+ | BlobLike
280
+ | FileLike
281
+ | ReadableStreamLike<Uint8Array | ArrayBuffer | ArrayBufferView | string>
282
+ | AsyncIterable<Uint8Array | ArrayBuffer | ArrayBufferView | string>
283
+ | Iterable<Uint8Array | ArrayBuffer | ArrayBufferView | string>;
237
284
 
238
285
  export interface CommitFileOptions {
239
- mode?: string;
286
+ mode?: GitFileMode;
240
287
  }
241
288
 
242
289
  export interface CommitTextFileOptions extends CommitFileOptions {
243
- encoding?: 'utf8' | 'utf-8';
290
+ encoding?: TextEncoding;
244
291
  }
245
292
 
246
293
  export interface CommitBuilder {
247
294
  addFile(path: string, source: CommitFileSource, options?: CommitFileOptions): CommitBuilder;
248
295
  addFileFromString(path: string, contents: string, options?: CommitTextFileOptions): CommitBuilder;
249
296
  deletePath(path: string): CommitBuilder;
250
- send(): Promise<CommitResponse>;
297
+ send(): Promise<CommitResult>;
251
298
  }
252
299
 
253
- export interface CommitResponse {
254
- commit: {
255
- commit_sha: string;
256
- tree_sha: string;
257
- target_ref: string;
258
- pack_bytes: number;
259
- blob_count: number;
260
- };
261
- result: {
262
- ref: string;
263
- old_sha: string; // 40-hex previous tip; all zeroes when the ref is newly created.
264
- new_sha: string;
265
- success: boolean;
266
- status: string;
267
- message?: string;
268
- };
300
+ export interface RefUpdate {
301
+ branch: string;
302
+ oldSha: string;
303
+ newSha: string;
304
+ }
305
+
306
+ export type RefUpdateReason =
307
+ | 'precondition_failed'
308
+ | 'conflict'
309
+ | 'not_found'
310
+ | 'invalid'
311
+ | 'timeout'
312
+ | 'unauthorized'
313
+ | 'forbidden'
314
+ | 'unavailable'
315
+ | 'internal'
316
+ | 'failed'
317
+ | 'unknown';
318
+
319
+ export interface CommitResult {
320
+ commitSha: string;
321
+ treeSha: string;
322
+ targetBranch: string;
323
+ packBytes: number;
324
+ blobCount: number;
325
+ refUpdate: RefUpdate;
326
+ }
327
+
328
+ export interface RestoreCommitOptions extends GitStorageInvocationOptions {
329
+ targetBranch: string;
330
+ targetCommitSha: string;
331
+ commitMessage?: string;
332
+ expectedHeadSha?: string;
333
+ author: CommitSignature;
334
+ committer?: CommitSignature;
335
+ }
336
+
337
+ export interface RestoreCommitResult {
338
+ commitSha: string;
339
+ treeSha: string;
340
+ targetBranch: string;
341
+ packBytes: number;
342
+ refUpdate: RefUpdate;
269
343
  }
270
344
 
271
345
  // Webhook types
@@ -297,7 +371,7 @@ export interface WebhookValidationResult {
297
371
  }
298
372
 
299
373
  // Webhook event payloads
300
- export interface WebhookPushEvent {
374
+ export interface RawWebhookPushEvent {
301
375
  repository: {
302
376
  id: string;
303
377
  url: string;
@@ -309,7 +383,26 @@ export interface WebhookPushEvent {
309
383
  pushed_at: string; // RFC3339 timestamp
310
384
  }
311
385
 
312
- export type WebhookEventPayload = WebhookPushEvent;
386
+ export interface WebhookPushEvent {
387
+ type: 'push';
388
+ repository: {
389
+ id: string;
390
+ url: string;
391
+ };
392
+ ref: string;
393
+ before: string;
394
+ after: string;
395
+ customerId: string;
396
+ pushedAt: Date;
397
+ rawPushedAt: string;
398
+ }
399
+
400
+ export interface WebhookUnknownEvent {
401
+ type: string;
402
+ raw: unknown;
403
+ }
404
+
405
+ export type WebhookEventPayload = WebhookPushEvent | WebhookUnknownEvent;
313
406
 
314
407
  export interface ParsedWebhookSignature {
315
408
  timestamp: string;
package/src/util.ts CHANGED
@@ -42,21 +42,3 @@ export async function createHmac(algorithm: string, secret: string, data: string
42
42
  .map((b) => b.toString(16).padStart(2, '0'))
43
43
  .join('');
44
44
  }
45
-
46
- // Keep the legacy async function for backward compatibility
47
- export async function createHmacAsync(secret: string, data: string): Promise<string> {
48
- const crypto = await getEnvironmentCrypto();
49
- const encoder = new TextEncoder();
50
- const key = await crypto.subtle.importKey(
51
- 'raw',
52
- encoder.encode(secret),
53
- { name: 'HMAC', hash: 'SHA-256' },
54
- false,
55
- ['sign'],
56
- );
57
-
58
- const signature = await crypto.subtle.sign('HMAC', key, encoder.encode(data));
59
- return Array.from(new Uint8Array(signature))
60
- .map((b) => b.toString(16).padStart(2, '0'))
61
- .join('');
62
- }
package/src/webhook.ts CHANGED
@@ -4,7 +4,9 @@
4
4
 
5
5
  import type {
6
6
  ParsedWebhookSignature,
7
+ RawWebhookPushEvent,
7
8
  WebhookEventPayload,
9
+ WebhookPushEvent,
8
10
  WebhookValidationOptions,
9
11
  WebhookValidationResult,
10
12
  } from './types';
@@ -231,9 +233,9 @@ export async function validateWebhook(
231
233
 
232
234
  // Parse payload
233
235
  const payloadStr = typeof payload === 'string' ? payload : payload.toString('utf8');
234
- let parsedPayload: WebhookEventPayload;
236
+ let parsedJson: unknown;
235
237
  try {
236
- parsedPayload = JSON.parse(payloadStr);
238
+ parsedJson = JSON.parse(payloadStr);
237
239
  } catch {
238
240
  return {
239
241
  valid: false,
@@ -242,10 +244,80 @@ export async function validateWebhook(
242
244
  };
243
245
  }
244
246
 
247
+ const conversion = convertWebhookPayload(String(eventType), parsedJson);
248
+ if (!conversion.valid) {
249
+ return {
250
+ valid: false,
251
+ error: conversion.error,
252
+ timestamp: validationResult.timestamp,
253
+ };
254
+ }
255
+
245
256
  return {
246
257
  valid: true,
247
258
  eventType,
248
259
  timestamp: validationResult.timestamp,
249
- payload: parsedPayload,
260
+ payload: conversion.payload,
250
261
  };
251
262
  }
263
+
264
+ function convertWebhookPayload(
265
+ eventType: string,
266
+ raw: unknown,
267
+ ): { valid: true; payload: WebhookEventPayload } | { valid: false; error: string } {
268
+ if (eventType === 'push') {
269
+ if (!isRawWebhookPushEvent(raw)) {
270
+ return {
271
+ valid: false,
272
+ error: 'Invalid push payload',
273
+ };
274
+ }
275
+ return {
276
+ valid: true,
277
+ payload: transformPushEvent(raw),
278
+ };
279
+ }
280
+ const fallbackPayload = { type: eventType, raw };
281
+ return {
282
+ valid: true,
283
+ payload: fallbackPayload,
284
+ };
285
+ }
286
+
287
+ function transformPushEvent(raw: RawWebhookPushEvent): WebhookPushEvent {
288
+ return {
289
+ type: 'push' as const,
290
+ repository: {
291
+ id: raw.repository.id,
292
+ url: raw.repository.url,
293
+ },
294
+ ref: raw.ref,
295
+ before: raw.before,
296
+ after: raw.after,
297
+ customerId: raw.customer_id,
298
+ pushedAt: new Date(raw.pushed_at),
299
+ rawPushedAt: raw.pushed_at,
300
+ };
301
+ }
302
+
303
+ function isRawWebhookPushEvent(value: unknown): value is RawWebhookPushEvent {
304
+ if (!isRecord(value)) {
305
+ return false;
306
+ }
307
+ if (!isRecord(value.repository)) {
308
+ return false;
309
+ }
310
+ return (
311
+ typeof value.repository.id === 'string' &&
312
+ typeof value.repository.url === 'string' &&
313
+ typeof value.ref === 'string' &&
314
+ typeof value.before === 'string' &&
315
+ typeof value.after === 'string' &&
316
+ typeof value.customer_id === 'string' &&
317
+ typeof value.pushed_at === 'string'
318
+ );
319
+ }
320
+
321
+ function isRecord(value: unknown): value is Record<string, unknown> {
322
+ return typeof value === 'object' && value !== null;
323
+ }