@pierre/storage 0.7.0 → 0.8.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 CHANGED
@@ -116,6 +116,10 @@ const repo = await store.createRepo();
116
116
  // or
117
117
  const repo = await store.findOne({ id: 'existing-repo-id' });
118
118
 
119
+ // List repositories for the org
120
+ const repos = await store.listRepos({ limit: 20 });
121
+ console.log(repos.repos);
122
+
119
123
  // Get file content (streaming)
120
124
  const resp = await repo.getFileStream({
121
125
  path: 'README.md',
@@ -145,6 +149,27 @@ const commits = await repo.listCommits({
145
149
  });
146
150
  console.log(commits.commits);
147
151
 
152
+ // Read a git note for a commit
153
+ const note = await repo.getNote({ sha: 'abc123...' });
154
+ console.log(note.note);
155
+
156
+ // Add a git note
157
+ const noteResult = await repo.createNote({
158
+ sha: 'abc123...',
159
+ note: 'Release QA approved',
160
+ author: { name: 'Release Bot', email: 'release@example.com' },
161
+ });
162
+ console.log(noteResult.newRefSha);
163
+
164
+ // Append to an existing git note
165
+ await repo.appendNote({
166
+ sha: 'abc123...',
167
+ note: 'Follow-up review complete',
168
+ });
169
+
170
+ // Delete a git note
171
+ await repo.deleteNote({ sha: 'abc123...' });
172
+
148
173
  // Get branch diff
149
174
  const branchDiff = await repo.getBranchDiff({
150
175
  branch: 'feature-branch',
@@ -327,6 +352,10 @@ interface Repo {
327
352
  listFiles(options?: ListFilesOptions): Promise<ListFilesResult>;
328
353
  listBranches(options?: ListBranchesOptions): Promise<ListBranchesResult>;
329
354
  listCommits(options?: ListCommitsOptions): Promise<ListCommitsResult>;
355
+ getNote(options: GetNoteOptions): Promise<GetNoteResult>;
356
+ createNote(options: CreateNoteOptions): Promise<NoteWriteResult>;
357
+ appendNote(options: AppendNoteOptions): Promise<NoteWriteResult>;
358
+ deleteNote(options: DeleteNoteOptions): Promise<NoteWriteResult>;
330
359
  getBranchDiff(options: GetBranchDiffOptions): Promise<GetBranchDiffResult>;
331
360
  getCommitDiff(options: GetCommitDiffOptions): Promise<GetCommitDiffResult>;
332
361
  }
@@ -360,6 +389,52 @@ interface ListFilesResult {
360
389
  ref: string;
361
390
  }
362
391
 
392
+ interface GetNoteOptions {
393
+ sha: string; // Commit SHA to look up notes for
394
+ ttl?: number;
395
+ }
396
+
397
+ interface GetNoteResult {
398
+ sha: string;
399
+ note: string;
400
+ refSha: string;
401
+ }
402
+
403
+ interface CreateNoteOptions {
404
+ sha: string;
405
+ note: string;
406
+ expectedRefSha?: string;
407
+ author?: { name: string; email: string };
408
+ ttl?: number;
409
+ }
410
+
411
+ interface AppendNoteOptions {
412
+ sha: string;
413
+ note: string;
414
+ expectedRefSha?: string;
415
+ author?: { name: string; email: string };
416
+ ttl?: number;
417
+ }
418
+
419
+ interface DeleteNoteOptions {
420
+ sha: string;
421
+ expectedRefSha?: string;
422
+ author?: { name: string; email: string };
423
+ ttl?: number;
424
+ }
425
+
426
+ interface NoteWriteResult {
427
+ sha: string;
428
+ targetRef: string;
429
+ baseCommit?: string;
430
+ newRefSha: string;
431
+ result: {
432
+ success: boolean;
433
+ status: string;
434
+ message?: string;
435
+ };
436
+ }
437
+
363
438
  interface ListBranchesOptions {
364
439
  cursor?: string;
365
440
  limit?: number;
package/dist/index.cjs CHANGED
@@ -76,6 +76,40 @@ var listCommitsResponseSchema = zod.z.object({
76
76
  next_cursor: zod.z.string().nullable().optional(),
77
77
  has_more: zod.z.boolean()
78
78
  });
79
+ var repoBaseInfoSchema = zod.z.object({
80
+ provider: zod.z.string(),
81
+ owner: zod.z.string(),
82
+ name: zod.z.string()
83
+ });
84
+ var repoInfoSchema = zod.z.object({
85
+ repo_id: zod.z.string(),
86
+ url: zod.z.string(),
87
+ default_branch: zod.z.string(),
88
+ created_at: zod.z.string(),
89
+ base_repo: repoBaseInfoSchema.optional().nullable()
90
+ });
91
+ var listReposResponseSchema = zod.z.object({
92
+ repos: zod.z.array(repoInfoSchema),
93
+ next_cursor: zod.z.string().nullable().optional(),
94
+ has_more: zod.z.boolean()
95
+ });
96
+ var noteReadResponseSchema = zod.z.object({
97
+ sha: zod.z.string(),
98
+ note: zod.z.string(),
99
+ ref_sha: zod.z.string()
100
+ });
101
+ var noteResultSchema = zod.z.object({
102
+ success: zod.z.boolean(),
103
+ status: zod.z.string(),
104
+ message: zod.z.string().optional()
105
+ });
106
+ var noteWriteResponseSchema = zod.z.object({
107
+ sha: zod.z.string(),
108
+ target_ref: zod.z.string(),
109
+ base_commit: zod.z.string().optional(),
110
+ new_ref_sha: zod.z.string(),
111
+ result: noteResultSchema
112
+ });
79
113
  var diffStatsSchema = zod.z.object({
80
114
  files: zod.z.number(),
81
115
  additions: zod.z.number(),
@@ -472,7 +506,7 @@ function concatChunks(a, b) {
472
506
 
473
507
  // package.json
474
508
  var package_default = {
475
- version: "0.7.0"};
509
+ version: "0.8.0"};
476
510
 
477
511
  // src/version.ts
478
512
  var PACKAGE_NAME = "code-storage-sdk";
@@ -1364,6 +1398,36 @@ var RESTORE_COMMIT_ALLOWED_STATUS = [
1364
1398
  504
1365
1399
  // Gateway Timeout - long-running storage operations
1366
1400
  ];
1401
+ var NOTE_WRITE_ALLOWED_STATUS = [
1402
+ 400,
1403
+ // Bad Request - validation errors
1404
+ 401,
1405
+ // Unauthorized - missing/invalid auth header
1406
+ 403,
1407
+ // Forbidden - missing git:write scope
1408
+ 404,
1409
+ // Not Found - repo or note lookup failures
1410
+ 408,
1411
+ // Request Timeout - client cancelled
1412
+ 409,
1413
+ // Conflict - concurrent ref updates
1414
+ 412,
1415
+ // Precondition Failed - optimistic concurrency
1416
+ 422,
1417
+ // Unprocessable Entity - metadata issues
1418
+ 429,
1419
+ // Too Many Requests - upstream throttling
1420
+ 499,
1421
+ // Client Closed Request - storage cancellation
1422
+ 500,
1423
+ // Internal Server Error - generic failure
1424
+ 502,
1425
+ // Bad Gateway - storage/gateway bridge issues
1426
+ 503,
1427
+ // Service Unavailable - storage selection failures
1428
+ 504
1429
+ // Gateway Timeout - long-running storage operations
1430
+ ];
1367
1431
  function resolveInvocationTtlSeconds(options, defaultValue = DEFAULT_TOKEN_TTL_SECONDS) {
1368
1432
  if (typeof options?.ttl === "number" && options.ttl > 0) {
1369
1433
  return options.ttl;
@@ -1551,6 +1615,23 @@ function transformCreateBranchResult(raw) {
1551
1615
  commitSha: raw.commit_sha ?? void 0
1552
1616
  };
1553
1617
  }
1618
+ function transformListReposResult(raw) {
1619
+ return {
1620
+ repos: raw.repos.map((repo) => ({
1621
+ repoId: repo.repo_id,
1622
+ url: repo.url,
1623
+ defaultBranch: repo.default_branch,
1624
+ createdAt: repo.created_at,
1625
+ baseRepo: repo.base_repo ? {
1626
+ provider: repo.base_repo.provider,
1627
+ owner: repo.base_repo.owner,
1628
+ name: repo.base_repo.name
1629
+ } : void 0
1630
+ })),
1631
+ nextCursor: raw.next_cursor ?? void 0,
1632
+ hasMore: raw.has_more
1633
+ };
1634
+ }
1554
1635
  function transformGrepLine(raw) {
1555
1636
  return {
1556
1637
  lineNumber: raw.line_number,
@@ -1564,6 +1645,88 @@ function transformGrepFileMatch(raw) {
1564
1645
  lines: raw.lines.map(transformGrepLine)
1565
1646
  };
1566
1647
  }
1648
+ function transformNoteReadResult(raw) {
1649
+ return {
1650
+ sha: raw.sha,
1651
+ note: raw.note,
1652
+ refSha: raw.ref_sha
1653
+ };
1654
+ }
1655
+ function transformNoteWriteResult(raw) {
1656
+ return {
1657
+ sha: raw.sha,
1658
+ targetRef: raw.target_ref,
1659
+ baseCommit: raw.base_commit,
1660
+ newRefSha: raw.new_ref_sha,
1661
+ result: {
1662
+ success: raw.result.success,
1663
+ status: raw.result.status,
1664
+ message: raw.result.message
1665
+ }
1666
+ };
1667
+ }
1668
+ function buildNoteWriteBody(sha, note, action, options) {
1669
+ const body = {
1670
+ sha,
1671
+ action,
1672
+ note
1673
+ };
1674
+ const expectedRefSha = options.expectedRefSha?.trim();
1675
+ if (expectedRefSha) {
1676
+ body.expected_ref_sha = expectedRefSha;
1677
+ }
1678
+ if (options.author) {
1679
+ const authorName = options.author.name?.trim();
1680
+ const authorEmail = options.author.email?.trim();
1681
+ if (!authorName || !authorEmail) {
1682
+ throw new Error("note author name and email are required when provided");
1683
+ }
1684
+ body.author = {
1685
+ name: authorName,
1686
+ email: authorEmail
1687
+ };
1688
+ }
1689
+ return body;
1690
+ }
1691
+ async function parseNoteWriteResponse(response, method) {
1692
+ let jsonBody;
1693
+ const contentType = response.headers.get("content-type") ?? "";
1694
+ try {
1695
+ if (contentType.includes("application/json")) {
1696
+ jsonBody = await response.json();
1697
+ } else {
1698
+ jsonBody = await response.text();
1699
+ }
1700
+ } catch {
1701
+ jsonBody = void 0;
1702
+ }
1703
+ if (jsonBody && typeof jsonBody === "object") {
1704
+ const parsed = noteWriteResponseSchema.safeParse(jsonBody);
1705
+ if (parsed.success) {
1706
+ return transformNoteWriteResult(parsed.data);
1707
+ }
1708
+ const parsedError = errorEnvelopeSchema.safeParse(jsonBody);
1709
+ if (parsedError.success) {
1710
+ throw new ApiError({
1711
+ message: parsedError.data.error,
1712
+ status: response.status,
1713
+ statusText: response.statusText,
1714
+ method,
1715
+ url: response.url,
1716
+ body: jsonBody
1717
+ });
1718
+ }
1719
+ }
1720
+ const fallbackMessage = typeof jsonBody === "string" && jsonBody.trim() !== "" ? jsonBody.trim() : `Request ${method} ${response.url} failed with status ${response.status} ${response.statusText}`;
1721
+ throw new ApiError({
1722
+ message: fallbackMessage,
1723
+ status: response.status,
1724
+ statusText: response.statusText,
1725
+ method,
1726
+ url: response.url,
1727
+ body: jsonBody
1728
+ });
1729
+ }
1567
1730
  var RepoImpl = class {
1568
1731
  constructor(id, defaultBranch, options, generateJWT) {
1569
1732
  this.id = id;
@@ -1679,6 +1842,132 @@ var RepoImpl = class {
1679
1842
  next_cursor: raw.next_cursor ?? void 0
1680
1843
  });
1681
1844
  }
1845
+ async getNote(options) {
1846
+ const sha = options?.sha?.trim();
1847
+ if (!sha) {
1848
+ throw new Error("getNote sha is required");
1849
+ }
1850
+ const ttl = resolveInvocationTtlSeconds(options, DEFAULT_TOKEN_TTL_SECONDS);
1851
+ const jwt = await this.generateJWT(this.id, {
1852
+ permissions: ["git:read"],
1853
+ ttl
1854
+ });
1855
+ const response = await this.api.get({ path: "repos/notes", params: { sha } }, jwt);
1856
+ const raw = noteReadResponseSchema.parse(await response.json());
1857
+ return transformNoteReadResult(raw);
1858
+ }
1859
+ async createNote(options) {
1860
+ const sha = options?.sha?.trim();
1861
+ if (!sha) {
1862
+ throw new Error("createNote sha is required");
1863
+ }
1864
+ const note = options?.note?.trim();
1865
+ if (!note) {
1866
+ throw new Error("createNote note is required");
1867
+ }
1868
+ const ttl = resolveInvocationTtlSeconds(options, DEFAULT_TOKEN_TTL_SECONDS);
1869
+ const jwt = await this.generateJWT(this.id, {
1870
+ permissions: ["git:write"],
1871
+ ttl
1872
+ });
1873
+ const body = buildNoteWriteBody(sha, note, "add", {
1874
+ expectedRefSha: options.expectedRefSha,
1875
+ author: options.author
1876
+ });
1877
+ const response = await this.api.post({ path: "repos/notes", body }, jwt, {
1878
+ allowedStatus: [...NOTE_WRITE_ALLOWED_STATUS]
1879
+ });
1880
+ const result = await parseNoteWriteResponse(response, "POST");
1881
+ if (!result.result.success) {
1882
+ throw new RefUpdateError(
1883
+ result.result.message ?? `createNote failed with status ${result.result.status}`,
1884
+ {
1885
+ status: result.result.status,
1886
+ message: result.result.message,
1887
+ refUpdate: toPartialRefUpdate(result.targetRef, result.baseCommit, result.newRefSha)
1888
+ }
1889
+ );
1890
+ }
1891
+ return result;
1892
+ }
1893
+ async appendNote(options) {
1894
+ const sha = options?.sha?.trim();
1895
+ if (!sha) {
1896
+ throw new Error("appendNote sha is required");
1897
+ }
1898
+ const note = options?.note?.trim();
1899
+ if (!note) {
1900
+ throw new Error("appendNote note is required");
1901
+ }
1902
+ const ttl = resolveInvocationTtlSeconds(options, DEFAULT_TOKEN_TTL_SECONDS);
1903
+ const jwt = await this.generateJWT(this.id, {
1904
+ permissions: ["git:write"],
1905
+ ttl
1906
+ });
1907
+ const body = buildNoteWriteBody(sha, note, "append", {
1908
+ expectedRefSha: options.expectedRefSha,
1909
+ author: options.author
1910
+ });
1911
+ const response = await this.api.post({ path: "repos/notes", body }, jwt, {
1912
+ allowedStatus: [...NOTE_WRITE_ALLOWED_STATUS]
1913
+ });
1914
+ const result = await parseNoteWriteResponse(response, "POST");
1915
+ if (!result.result.success) {
1916
+ throw new RefUpdateError(
1917
+ result.result.message ?? `appendNote failed with status ${result.result.status}`,
1918
+ {
1919
+ status: result.result.status,
1920
+ message: result.result.message,
1921
+ refUpdate: toPartialRefUpdate(result.targetRef, result.baseCommit, result.newRefSha)
1922
+ }
1923
+ );
1924
+ }
1925
+ return result;
1926
+ }
1927
+ async deleteNote(options) {
1928
+ const sha = options?.sha?.trim();
1929
+ if (!sha) {
1930
+ throw new Error("deleteNote sha is required");
1931
+ }
1932
+ const ttl = resolveInvocationTtlSeconds(options, DEFAULT_TOKEN_TTL_SECONDS);
1933
+ const jwt = await this.generateJWT(this.id, {
1934
+ permissions: ["git:write"],
1935
+ ttl
1936
+ });
1937
+ const body = {
1938
+ sha
1939
+ };
1940
+ const expectedRefSha = options.expectedRefSha?.trim();
1941
+ if (expectedRefSha) {
1942
+ body.expected_ref_sha = expectedRefSha;
1943
+ }
1944
+ if (options.author) {
1945
+ const authorName = options.author.name?.trim();
1946
+ const authorEmail = options.author.email?.trim();
1947
+ if (!authorName || !authorEmail) {
1948
+ throw new Error("deleteNote author name and email are required when provided");
1949
+ }
1950
+ body.author = {
1951
+ name: authorName,
1952
+ email: authorEmail
1953
+ };
1954
+ }
1955
+ const response = await this.api.delete({ path: "repos/notes", body }, jwt, {
1956
+ allowedStatus: [...NOTE_WRITE_ALLOWED_STATUS]
1957
+ });
1958
+ const result = await parseNoteWriteResponse(response, "DELETE");
1959
+ if (!result.result.success) {
1960
+ throw new RefUpdateError(
1961
+ result.result.message ?? `deleteNote failed with status ${result.result.status}`,
1962
+ {
1963
+ status: result.result.status,
1964
+ message: result.result.message,
1965
+ refUpdate: toPartialRefUpdate(result.targetRef, result.baseCommit, result.newRefSha)
1966
+ }
1967
+ );
1968
+ }
1969
+ return result;
1970
+ }
1682
1971
  async getBranchDiff(options) {
1683
1972
  const ttl = resolveInvocationTtlSeconds(options, DEFAULT_TOKEN_TTL_SECONDS);
1684
1973
  const jwt = await this.generateJWT(this.id, {
@@ -2002,6 +2291,33 @@ var GitStorage = class _GitStorage {
2002
2291
  }
2003
2292
  return new RepoImpl(repoId, defaultBranch, this.options, this.generateJWT.bind(this));
2004
2293
  }
2294
+ /**
2295
+ * List repositories for the authenticated organization
2296
+ * @returns Paginated repositories list
2297
+ */
2298
+ async listRepos(options) {
2299
+ const ttl = resolveInvocationTtlSeconds(options, DEFAULT_TOKEN_TTL_SECONDS);
2300
+ const jwt = await this.generateJWT("org", {
2301
+ permissions: ["org:read"],
2302
+ ttl
2303
+ });
2304
+ let params;
2305
+ if (options?.cursor || typeof options?.limit === "number") {
2306
+ params = {};
2307
+ if (options.cursor) {
2308
+ params.cursor = options.cursor;
2309
+ }
2310
+ if (typeof options.limit === "number") {
2311
+ params.limit = options.limit.toString();
2312
+ }
2313
+ }
2314
+ const response = await this.api.get({ path: "repos", params }, jwt);
2315
+ const raw = listReposResponseSchema.parse(await response.json());
2316
+ return transformListReposResult({
2317
+ ...raw,
2318
+ next_cursor: raw.next_cursor ?? void 0
2319
+ });
2320
+ }
2005
2321
  /**
2006
2322
  * Find a repository by ID
2007
2323
  * @param options The search options