@fungi.computer/burrow 0.0.0-stage → 0.0.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/LICENSE +21 -0
- package/README.md +111 -2
- package/dist/artifacts.d.ts +53 -0
- package/dist/artifacts.js +1 -0
- package/dist/cloudflare.d.ts +98 -0
- package/dist/cloudflare.js +595 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/namespace.d.ts +93 -0
- package/dist/namespace.js +1 -0
- package/dist/rest.d.ts +17 -0
- package/dist/rest.js +288 -0
- package/docs/api/README.md +15 -0
- package/docs/api/cloudflare.md +271 -0
- package/docs/api/index.md +207 -0
- package/docs/api/namespace.md +234 -0
- package/docs/home.ts +27 -0
- package/package.json +68 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jamstack Consulting LLC
|
|
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
CHANGED
|
@@ -1,3 +1,112 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Burrow
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Burrow wraps Cloudflare Artifacts.
|
|
4
|
+
|
|
5
|
+
`@fungi.computer/burrow` is the configured repository owner for Cloudflare
|
|
6
|
+
Artifacts. Configure `createCloudflareArtifacts` with the Workers binding and a
|
|
7
|
+
Publisher-owned repository metadata store at the Worker boundary. Callers
|
|
8
|
+
receive stable repository identity and can request only the Git smart-HTTP
|
|
9
|
+
operations used by Git: upload-pack and receive-pack advertisements and
|
|
10
|
+
exchanges. The adapter persists the exact metadata returned by `create()` and
|
|
11
|
+
`fork()` before later Git/head operations use the saved remote. It treats
|
|
12
|
+
`get()` results as opaque method handles. Publisher stages each exact create or
|
|
13
|
+
fork result and its initial token in a pending D1 record, retries revocation
|
|
14
|
+
from that record, then promotes only the metadata after that exact token is no
|
|
15
|
+
longer present. Pending rows do not resolve as repositories; successful
|
|
16
|
+
promotion clears the token. The package never returns a token or token-bearing
|
|
17
|
+
URL. A repeated fork recovers a destination only when Publisher has recorded
|
|
18
|
+
that it created the fork from the exact template; an existing destination
|
|
19
|
+
without that record fails closed.
|
|
20
|
+
|
|
21
|
+
Cloudflare's create/fork call and the first D1 write cannot be atomic. If the
|
|
22
|
+
Worker stops after the provider returns but before the pending row is committed,
|
|
23
|
+
the repository can remain without recorded metadata and its initial token may
|
|
24
|
+
remain active. The opaque `get()` handle cannot recover that result. The adapter
|
|
25
|
+
fails closed; recovery requires a supported provider metadata recovery path or
|
|
26
|
+
guarded manual cleanup outside this package.
|
|
27
|
+
|
|
28
|
+
App identity and Team authorization stay with Publisher, App Release, and Hub.
|
|
29
|
+
This package has no source-file API and does not decide who may use a
|
|
30
|
+
repository.
|
|
31
|
+
|
|
32
|
+
`readRefs(name)` returns advertised ref names and object IDs through the same
|
|
33
|
+
short-lived read-token owner used by `readBranchHeads(name)`. The latter keeps
|
|
34
|
+
its branch-only hash projection for source provenance checks. Both revoke the
|
|
35
|
+
token on success or failure.
|
|
36
|
+
|
|
37
|
+
## REST transport
|
|
38
|
+
|
|
39
|
+
`createArtifactsRestNamespace({ accountId, namespace, apiToken, fetch? })`
|
|
40
|
+
provides the host-only namespace where a Workers binding is unavailable. Use it
|
|
41
|
+
with `createCloudflareArtifacts` and the same repository metadata store. Oyster
|
|
42
|
+
uses this transport for its installation-scoped Git binding; it has no separate
|
|
43
|
+
REST client. Installation permission checks remain in Oyster.
|
|
44
|
+
|
|
45
|
+
REST `get(name)` only creates a name-addressed handle. `info()` and all other
|
|
46
|
+
handle operations make fresh provider calls; no existence, result or authority
|
|
47
|
+
is cached. The metadata owner explicitly probes lazy handles before accepting
|
|
48
|
+
repository existence/readiness.
|
|
49
|
+
|
|
50
|
+
The REST control-plane credential stays inside the transport. Requests refuse
|
|
51
|
+
redirects and unsuccessful HTTP responses even if their envelope claims success.
|
|
52
|
+
For a newly created or forked destination, the transport requires a complete
|
|
53
|
+
listing with exactly one active token, revokes that token's ID, and verifies its
|
|
54
|
+
revoked state before activation. Ambiguity or failed verification removes only
|
|
55
|
+
that newly created destination and refuses activation. An existing-repository
|
|
56
|
+
refusal never enters cleanup; a fork's source is never removed. App-facing
|
|
57
|
+
repository results contain neither tokens nor authenticated URLs.
|
|
58
|
+
|
|
59
|
+
REST create/fork and revocation are separate provider operations. A process
|
|
60
|
+
crash between them can leave an unrecorded destination; it must fail closed on
|
|
61
|
+
replay, just like an unrecorded Workers-binding creation. There is no guessed
|
|
62
|
+
token-ID codec and no cleanup of another repository's tokens.
|
|
63
|
+
|
|
64
|
+
## Install
|
|
65
|
+
|
|
66
|
+
The packed-consumer proof verifies Burrow as an Oyster runtime dependency.
|
|
67
|
+
Running the proof does not publish an npm release.
|
|
68
|
+
|
|
69
|
+
The root entry exports the configured Artifacts adapter and host-only REST
|
|
70
|
+
namespace. `@fungi.computer/burrow/cloudflare` also exports the Cloudflare
|
|
71
|
+
adapter and its metadata-store types.
|
|
72
|
+
|
|
73
|
+
## Quickstart
|
|
74
|
+
|
|
75
|
+
Configure the provider namespace and metadata store at the host boundary:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import {
|
|
79
|
+
createCloudflareArtifacts,
|
|
80
|
+
type CloudflareArtifactsOptions,
|
|
81
|
+
} from "@fungi.computer/burrow";
|
|
82
|
+
|
|
83
|
+
export async function openRepository(options: CloudflareArtifactsOptions) {
|
|
84
|
+
const artifacts = createCloudflareArtifacts(options);
|
|
85
|
+
return artifacts.resolveRepository("notes");
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The supplied metadata store must implement the pending-record and activation
|
|
90
|
+
contract described above. Repository credentials and provider remotes remain
|
|
91
|
+
inside that host composition.
|
|
92
|
+
|
|
93
|
+
## Packed-consumer proof
|
|
94
|
+
|
|
95
|
+
After building Burrow, run `pnpm --dir packages/burrow run stranger` from the
|
|
96
|
+
repository root. The maintained checker verifies the exact tarball inventory,
|
|
97
|
+
installs it into a fresh npm consumer, runs local fake-provider behavior, and
|
|
98
|
+
compiles both entrypoints with strict NodeNext and `skipLibCheck: false`. It
|
|
99
|
+
makes no Cloudflare requests.
|
|
100
|
+
|
|
101
|
+
## Examples
|
|
102
|
+
|
|
103
|
+
[The local provider example](docs/home.ts) lists a repository through
|
|
104
|
+
`createArtifactsRestNamespace` using a fake fetch transport. It runs without
|
|
105
|
+
provider access or external credentials. After building Burrow, run it with:
|
|
106
|
+
|
|
107
|
+
```sh
|
|
108
|
+
pnpm exec tsx packages/burrow/docs/home.ts
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The packed-consumer proof above also exercises repository resolution and
|
|
112
|
+
read-token cleanup through the root and Cloudflare entrypoints.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/** Stable repository identity exposed to callers; no credentials or remote URL. */
|
|
2
|
+
export type ArtifactsRepository = Readonly<{
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
defaultBranch: string;
|
|
6
|
+
}>;
|
|
7
|
+
/** One admitted Git smart-HTTP advertisement or byte-stream exchange. */
|
|
8
|
+
export type GitExchange = Readonly<{
|
|
9
|
+
protocol?: string;
|
|
10
|
+
signal?: AbortSignal;
|
|
11
|
+
}> & (Readonly<{
|
|
12
|
+
repository: string;
|
|
13
|
+
operation: "upload-pack-advertisement" | "receive-pack-advertisement";
|
|
14
|
+
}> | Readonly<{
|
|
15
|
+
repository: string;
|
|
16
|
+
operation: "upload-pack" | "receive-pack";
|
|
17
|
+
body: ReadableStream<Uint8Array>;
|
|
18
|
+
}>);
|
|
19
|
+
/** Recorded fork identity, template main head and whether this call created it. */
|
|
20
|
+
export type ArtifactsForkResult = Readonly<{
|
|
21
|
+
repository: ArtifactsRepository;
|
|
22
|
+
mainHead: string;
|
|
23
|
+
disposition: "created" | "existing";
|
|
24
|
+
}>;
|
|
25
|
+
/** Configured repository owner; authorization belongs to its calling host. */
|
|
26
|
+
export type Artifacts = Readonly<{
|
|
27
|
+
/** Create or recover a recorded repository and finish initial-token revocation. */
|
|
28
|
+
ensureRepository(name: string): Promise<ArtifactsRepository>;
|
|
29
|
+
/** Fork the exact reviewed template main head and record its provenance. */
|
|
30
|
+
forkRepository(input: {
|
|
31
|
+
template: string;
|
|
32
|
+
repository: string;
|
|
33
|
+
expectedMainHead: string;
|
|
34
|
+
}): Promise<ArtifactsForkResult>;
|
|
35
|
+
/** Recover only a destination recorded for this exact template, or return null. */
|
|
36
|
+
recoverForkRepository(input: {
|
|
37
|
+
template: string;
|
|
38
|
+
repository: string;
|
|
39
|
+
}): Promise<ArtifactsForkResult | null>;
|
|
40
|
+
/** Resolve recorded repository identity after live existence/readiness checks. */
|
|
41
|
+
resolveRepository(name: string): Promise<ArtifactsRepository>;
|
|
42
|
+
/** Read the current main ref with a short-lived token revoked afterward. */
|
|
43
|
+
readDefaultBranchHead(name: string): Promise<string | null>;
|
|
44
|
+
/** Return advertised branch heads for revision provenance checks. */
|
|
45
|
+
readBranchHeads(name: string): Promise<readonly string[]>;
|
|
46
|
+
/** Return advertised ref names and object IDs under one short-lived read token. */
|
|
47
|
+
readRefs(name: string): Promise<readonly Readonly<{
|
|
48
|
+
name: string;
|
|
49
|
+
hash: string;
|
|
50
|
+
}>[]>;
|
|
51
|
+
/** Forward one supported Git protocol operation and revoke its token afterward. */
|
|
52
|
+
exchangeGit(input: GitExchange): Promise<Response>;
|
|
53
|
+
}>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { Artifacts as ArtifactsApi, GitExchange } from "./artifacts.js";
|
|
2
|
+
import http from "isomorphic-git/http/web";
|
|
3
|
+
import type { ArtifactsRepositoryInfo } from "./namespace.js";
|
|
4
|
+
/** Repository handle accepted by the Cloudflare Artifacts namespace. */
|
|
5
|
+
export type CloudflareArtifactsRepositoryHandle = Readonly<{
|
|
6
|
+
/** REST handles are lazy; info resolves their live existence/readiness. */
|
|
7
|
+
info?: () => Promise<ArtifactsRepositoryInfo>;
|
|
8
|
+
/** Mint a host-only read/write token for the requested lifetime. */
|
|
9
|
+
createToken(scope: "read" | "write", ttlSeconds: number): Promise<Readonly<{
|
|
10
|
+
id: string;
|
|
11
|
+
plaintext: string;
|
|
12
|
+
expiresAt: string;
|
|
13
|
+
}>>;
|
|
14
|
+
/** Revoke this exact credential before releasing a response or activating metadata. */
|
|
15
|
+
revokeToken(tokenOrId: string): Promise<boolean>;
|
|
16
|
+
/** Fork the source main branch and return the destination initial credential. */
|
|
17
|
+
fork(name: string, options: {
|
|
18
|
+
readOnly: false;
|
|
19
|
+
defaultBranchOnly: true;
|
|
20
|
+
}): Promise<Readonly<{
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
remote: string;
|
|
24
|
+
defaultBranch: string;
|
|
25
|
+
token: string;
|
|
26
|
+
}>>;
|
|
27
|
+
}>;
|
|
28
|
+
/** Provider identity recorded at creation; used to pin later remote access. */
|
|
29
|
+
export type CloudflareArtifactsRepositoryMetadata = Readonly<{
|
|
30
|
+
readonly id: string;
|
|
31
|
+
readonly name: string;
|
|
32
|
+
readonly defaultBranch: string;
|
|
33
|
+
readonly remote: string;
|
|
34
|
+
readonly parentRepository: string | null;
|
|
35
|
+
}>;
|
|
36
|
+
/** Host-owned durable metadata and pending initial-token revocation records. */
|
|
37
|
+
export type CloudflareArtifactsRepositoryMetadataStore = Readonly<{
|
|
38
|
+
/** Read activated metadata for this repository, or null if none was recorded. */
|
|
39
|
+
read(name: string): Promise<CloudflareArtifactsRepositoryMetadata | null>;
|
|
40
|
+
/** Read the exact staged identity and initial credential awaiting revocation. */
|
|
41
|
+
readPending(name: string): Promise<Readonly<{
|
|
42
|
+
metadata: CloudflareArtifactsRepositoryMetadata;
|
|
43
|
+
initialToken: string;
|
|
44
|
+
}> | null>;
|
|
45
|
+
/** Durably stage provider identity and its initial credential before activation. */
|
|
46
|
+
recordPending(metadata: CloudflareArtifactsRepositoryMetadata, initialToken: string): Promise<void>;
|
|
47
|
+
/** Promote the staged identity after revocation and clear the pending credential. */
|
|
48
|
+
activate(metadata: CloudflareArtifactsRepositoryMetadata, initialToken: string): Promise<void>;
|
|
49
|
+
}>;
|
|
50
|
+
/** The maintained Cloudflare namespace methods used by this private adapter. */
|
|
51
|
+
export type CloudflareArtifactsNamespace = Readonly<{
|
|
52
|
+
/** Resolve a provider repository handle or throw its typed availability error. */
|
|
53
|
+
get(name: string): Promise<CloudflareArtifactsRepositoryHandle>;
|
|
54
|
+
/** Read provider repository statuses with bounded cursor pagination. */
|
|
55
|
+
list(options?: {
|
|
56
|
+
limit?: number;
|
|
57
|
+
cursor?: string;
|
|
58
|
+
}): Promise<CloudflareArtifactsRepositoryPage>;
|
|
59
|
+
/** Create a repository with the main branch and return its initial credential. */
|
|
60
|
+
create(name: string, options: {
|
|
61
|
+
readOnly: false;
|
|
62
|
+
setDefaultBranch: "main";
|
|
63
|
+
}): Promise<Readonly<{
|
|
64
|
+
id: string;
|
|
65
|
+
name: string;
|
|
66
|
+
remote: string;
|
|
67
|
+
defaultBranch: string;
|
|
68
|
+
token: string;
|
|
69
|
+
}>>;
|
|
70
|
+
}>;
|
|
71
|
+
/** Repository listing page used to observe fork/import readiness. */
|
|
72
|
+
export type CloudflareArtifactsRepositoryPage = Readonly<{
|
|
73
|
+
repos: readonly Readonly<{
|
|
74
|
+
name: string;
|
|
75
|
+
}>[];
|
|
76
|
+
total: number;
|
|
77
|
+
cursor?: string;
|
|
78
|
+
}>;
|
|
79
|
+
/** A repository did not finish importing or forking within the readiness window. */
|
|
80
|
+
export declare class ArtifactsForkStuckError extends Error {
|
|
81
|
+
readonly repository: string;
|
|
82
|
+
readonly status: "missing" | "importing" | "forking";
|
|
83
|
+
readonly name = "ArtifactsForkStuckError";
|
|
84
|
+
constructor(repository: string, status: "missing" | "importing" | "forking");
|
|
85
|
+
}
|
|
86
|
+
/** Execute Git with an already-held repo credential. The custody owner chooses
|
|
87
|
+
* metadata and credential; caller URLs and authorization headers never enter. */
|
|
88
|
+
export declare function exchangeCloudflareArtifactsGit(metadata: CloudflareArtifactsRepositoryMetadata, input: GitExchange, credential: string, gitFetch: typeof fetch): Promise<Response>;
|
|
89
|
+
/** Provider, durable metadata store and optional Git protocol transports. */
|
|
90
|
+
export type CloudflareArtifactsOptions = Readonly<{
|
|
91
|
+
namespace: CloudflareArtifactsNamespace;
|
|
92
|
+
repositories: CloudflareArtifactsRepositoryMetadataStore;
|
|
93
|
+
/** Optional transport for Git protocol traffic; control remains on the namespace. */
|
|
94
|
+
gitHttp?: typeof http;
|
|
95
|
+
gitFetch?: typeof fetch;
|
|
96
|
+
}>;
|
|
97
|
+
/** Configure the Artifacts owner once; provider remotes and credentials stay private. */
|
|
98
|
+
export declare function createCloudflareArtifacts(options: CloudflareArtifactsOptions): ArtifactsApi;
|