mirror of
https://github.com/syumai/workers.git
synced 2025-03-10 17:29:11 +00:00
move R2 features to cloudflare/r2 package
This commit is contained in:
parent
2d9f50ba40
commit
443e50a8bb
135
cloudflare/r2/bucket.go
Normal file
135
cloudflare/r2/bucket.go
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
package r2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"syscall/js"
|
||||||
|
|
||||||
|
"github.com/syumai/workers/cloudflare/internal/cfruntimecontext"
|
||||||
|
"github.com/syumai/workers/internal/jsutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bucket represents interface of Cloudflare Worker's R2 Bucket instance.
|
||||||
|
// - https://developers.cloudflare.com/r2/runtime-apis/#bucket-method-definitions
|
||||||
|
// - https://github.com/cloudflare/workers-types/blob/3012f263fb1239825e5f0061b267c8650d01b717/index.d.ts#L1006
|
||||||
|
type Bucket struct {
|
||||||
|
instance js.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBucket returns Bucket for given variable name.
|
||||||
|
// - variable name must be defined in wrangler.toml.
|
||||||
|
// - see example: https://github.com/syumai/workers/tree/main/_examples/r2-image-viewer
|
||||||
|
// - if the given variable name doesn't exist on runtime context, returns error.
|
||||||
|
// - This function panics when a runtime context is not found.
|
||||||
|
func NewBucket(varName string) (*Bucket, error) {
|
||||||
|
inst := cfruntimecontext.MustGetRuntimeContextEnv().Get(varName)
|
||||||
|
if inst.IsUndefined() {
|
||||||
|
return nil, fmt.Errorf("%s is undefined", varName)
|
||||||
|
}
|
||||||
|
return &Bucket{instance: inst}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Head returns the result of `head` call to Bucket.
|
||||||
|
// - Body field of *Object is always nil for Head call.
|
||||||
|
// - if the object for given key doesn't exist, returns nil.
|
||||||
|
// - if a network error happens, returns error.
|
||||||
|
func (r *Bucket) Head(key string) (*Object, error) {
|
||||||
|
p := r.instance.Call("head", key)
|
||||||
|
v, err := jsutil.AwaitPromise(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if v.IsNull() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return toObject(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the result of `get` call to Bucket.
|
||||||
|
// - if the object for given key doesn't exist, returns nil.
|
||||||
|
// - if a network error happens, returns error.
|
||||||
|
func (r *Bucket) Get(key string) (*Object, error) {
|
||||||
|
p := r.instance.Call("get", key)
|
||||||
|
v, err := jsutil.AwaitPromise(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if v.IsNull() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return toObject(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutOptions represents Cloudflare R2 put options.
|
||||||
|
// - https://github.com/cloudflare/workers-types/blob/3012f263fb1239825e5f0061b267c8650d01b717/index.d.ts#L1128
|
||||||
|
type PutOptions struct {
|
||||||
|
HTTPMetadata HTTPMetadata
|
||||||
|
CustomMetadata map[string]string
|
||||||
|
MD5 string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opts *PutOptions) toJS() js.Value {
|
||||||
|
if opts == nil {
|
||||||
|
return js.Undefined()
|
||||||
|
}
|
||||||
|
obj := jsutil.NewObject()
|
||||||
|
if opts.HTTPMetadata != (HTTPMetadata{}) {
|
||||||
|
obj.Set("httpMetadata", opts.HTTPMetadata.toJS())
|
||||||
|
}
|
||||||
|
if opts.CustomMetadata != nil {
|
||||||
|
// convert map[string]string to map[string]any.
|
||||||
|
// This makes the map convertible to JS.
|
||||||
|
// see: https://pkg.go.dev/syscall/js#ValueOf
|
||||||
|
customMeta := make(map[string]any, len(opts.CustomMetadata))
|
||||||
|
for k, v := range opts.CustomMetadata {
|
||||||
|
customMeta[k] = v
|
||||||
|
}
|
||||||
|
obj.Set("customMetadata", customMeta)
|
||||||
|
}
|
||||||
|
if opts.MD5 != "" {
|
||||||
|
obj.Set("md5", opts.MD5)
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put returns the result of `put` call to Bucket.
|
||||||
|
// - This method copies all bytes into memory for implementation restriction.
|
||||||
|
// - Body field of *Object is always nil for Put call.
|
||||||
|
// - if a network error happens, returns error.
|
||||||
|
func (r *Bucket) Put(key string, value io.ReadCloser, opts *PutOptions) (*Object, error) {
|
||||||
|
// fetch body cannot be ReadableStream. see: https://github.com/whatwg/fetch/issues/1438
|
||||||
|
b, err := io.ReadAll(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer value.Close()
|
||||||
|
ua := jsutil.NewUint8Array(len(b))
|
||||||
|
js.CopyBytesToJS(ua, b)
|
||||||
|
p := r.instance.Call("put", key, ua.Get("buffer"), opts.toJS())
|
||||||
|
v, err := jsutil.AwaitPromise(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return toObject(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete returns the result of `delete` call to Bucket.
|
||||||
|
// - if a network error happens, returns error.
|
||||||
|
func (r *Bucket) Delete(key string) error {
|
||||||
|
p := r.instance.Call("delete", key)
|
||||||
|
if _, err := jsutil.AwaitPromise(p); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// List returns the result of `list` call to Bucket.
|
||||||
|
// - if a network error happens, returns error.
|
||||||
|
func (r *Bucket) List() (*Objects, error) {
|
||||||
|
p := r.instance.Call("list")
|
||||||
|
v, err := jsutil.AwaitPromise(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return toObjects(v)
|
||||||
|
}
|
120
cloudflare/r2/object.go
Normal file
120
cloudflare/r2/object.go
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
package r2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"syscall/js"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/syumai/workers/internal/jsutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Object represents Cloudflare R2 object.
|
||||||
|
// - https://github.com/cloudflare/workers-types/blob/3012f263fb1239825e5f0061b267c8650d01b717/index.d.ts#L1094
|
||||||
|
type Object struct {
|
||||||
|
instance js.Value
|
||||||
|
Key string
|
||||||
|
Version string
|
||||||
|
Size int
|
||||||
|
ETag string
|
||||||
|
HTTPETag string
|
||||||
|
Uploaded time.Time
|
||||||
|
HTTPMetadata HTTPMetadata
|
||||||
|
CustomMetadata map[string]string
|
||||||
|
// Body is a body of Object.
|
||||||
|
// This value is nil for the result of the `Head` or `Put` method.
|
||||||
|
Body io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: implement
|
||||||
|
// - https://github.com/cloudflare/workers-types/blob/3012f263fb1239825e5f0061b267c8650d01b717/index.d.ts#L1106
|
||||||
|
// func (o *Object) WriteHTTPMetadata(headers http.Header) {
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (o *Object) BodyUsed() (bool, error) {
|
||||||
|
v := o.instance.Get("bodyUsed")
|
||||||
|
if v.IsUndefined() {
|
||||||
|
return false, errors.New("bodyUsed doesn't exist for this Object")
|
||||||
|
}
|
||||||
|
return v.Bool(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// toObject converts JavaScript side's Object to *Object.
|
||||||
|
// - https://github.com/cloudflare/workers-types/blob/3012f263fb1239825e5f0061b267c8650d01b717/index.d.ts#L1094
|
||||||
|
func toObject(v js.Value) (*Object, error) {
|
||||||
|
uploaded, err := jsutil.DateToTime(v.Get("uploaded"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error converting uploaded: %w", err)
|
||||||
|
}
|
||||||
|
r2Meta, err := toHTTPMetadata(v.Get("httpMetadata"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error converting httpMetadata: %w", err)
|
||||||
|
}
|
||||||
|
bodyVal := v.Get("body")
|
||||||
|
var body io.Reader
|
||||||
|
if !bodyVal.IsUndefined() {
|
||||||
|
body = jsutil.ConvertReadableStreamToReadCloser(v.Get("body"))
|
||||||
|
}
|
||||||
|
return &Object{
|
||||||
|
instance: v,
|
||||||
|
Key: v.Get("key").String(),
|
||||||
|
Version: v.Get("version").String(),
|
||||||
|
Size: v.Get("size").Int(),
|
||||||
|
ETag: v.Get("etag").String(),
|
||||||
|
HTTPETag: v.Get("httpEtag").String(),
|
||||||
|
Uploaded: uploaded,
|
||||||
|
HTTPMetadata: r2Meta,
|
||||||
|
CustomMetadata: jsutil.StrRecordToMap(v.Get("customMetadata")),
|
||||||
|
Body: body,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPMetadata represents metadata of Object.
|
||||||
|
// - https://github.com/cloudflare/workers-types/blob/3012f263fb1239825e5f0061b267c8650d01b717/index.d.ts#L1053
|
||||||
|
type HTTPMetadata struct {
|
||||||
|
ContentType string
|
||||||
|
ContentLanguage string
|
||||||
|
ContentDisposition string
|
||||||
|
ContentEncoding string
|
||||||
|
CacheControl string
|
||||||
|
CacheExpiry time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func toHTTPMetadata(v js.Value) (HTTPMetadata, error) {
|
||||||
|
if v.IsUndefined() || v.IsNull() {
|
||||||
|
return HTTPMetadata{}, nil
|
||||||
|
}
|
||||||
|
cacheExpiry, err := jsutil.MaybeDate(v.Get("cacheExpiry"))
|
||||||
|
if err != nil {
|
||||||
|
return HTTPMetadata{}, fmt.Errorf("error converting cacheExpiry: %w", err)
|
||||||
|
}
|
||||||
|
return HTTPMetadata{
|
||||||
|
ContentType: jsutil.MaybeString(v.Get("contentType")),
|
||||||
|
ContentLanguage: jsutil.MaybeString(v.Get("contentLanguage")),
|
||||||
|
ContentDisposition: jsutil.MaybeString(v.Get("contentDisposition")),
|
||||||
|
ContentEncoding: jsutil.MaybeString(v.Get("contentEncoding")),
|
||||||
|
CacheControl: jsutil.MaybeString(v.Get("cacheControl")),
|
||||||
|
CacheExpiry: cacheExpiry,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *HTTPMetadata) toJS() js.Value {
|
||||||
|
obj := jsutil.NewObject()
|
||||||
|
kv := map[string]string{
|
||||||
|
"contentType": md.ContentType,
|
||||||
|
"contentLanguage": md.ContentLanguage,
|
||||||
|
"contentDisposition": md.ContentDisposition,
|
||||||
|
"contentEncoding": md.ContentEncoding,
|
||||||
|
"cacheControl": md.CacheControl,
|
||||||
|
}
|
||||||
|
for k, v := range kv {
|
||||||
|
if v != "" {
|
||||||
|
obj.Set(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !md.CacheExpiry.IsZero() {
|
||||||
|
obj.Set("cacheExpiry", jsutil.TimeToDate(md.CacheExpiry))
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
44
cloudflare/r2/objects.go
Normal file
44
cloudflare/r2/objects.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package r2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"syscall/js"
|
||||||
|
|
||||||
|
"github.com/syumai/workers/internal/jsutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Objects represents Cloudflare R2 objects.
|
||||||
|
// - https://github.com/cloudflare/workers-types/blob/3012f263fb1239825e5f0061b267c8650d01b717/index.d.ts#L1121
|
||||||
|
type Objects struct {
|
||||||
|
Objects []*Object
|
||||||
|
Truncated bool
|
||||||
|
// Cursor indicates next cursor of Objects.
|
||||||
|
// - This becomes empty string if cursor doesn't exist.
|
||||||
|
Cursor string
|
||||||
|
DelimitedPrefixes []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// toObjects converts JavaScript side's Objects to *Objects.
|
||||||
|
// - https://github.com/cloudflare/workers-types/blob/3012f263fb1239825e5f0061b267c8650d01b717/index.d.ts#L1121
|
||||||
|
func toObjects(v js.Value) (*Objects, error) {
|
||||||
|
objectsVal := v.Get("objects")
|
||||||
|
objects := make([]*Object, objectsVal.Length())
|
||||||
|
for i := 0; i < len(objects); i++ {
|
||||||
|
obj, err := toObject(objectsVal.Index(i))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error converting to Object: %w", err)
|
||||||
|
}
|
||||||
|
objects[i] = obj
|
||||||
|
}
|
||||||
|
prefixesVal := v.Get("delimitedPrefixes")
|
||||||
|
prefixes := make([]string, prefixesVal.Length())
|
||||||
|
for i := 0; i < len(prefixes); i++ {
|
||||||
|
prefixes[i] = prefixesVal.Index(i).String()
|
||||||
|
}
|
||||||
|
return &Objects{
|
||||||
|
Objects: objects,
|
||||||
|
Truncated: v.Get("truncated").Bool(),
|
||||||
|
Cursor: jsutil.MaybeString(v.Get("cursor")),
|
||||||
|
DelimitedPrefixes: prefixes,
|
||||||
|
}, nil
|
||||||
|
}
|
@ -1,135 +1,19 @@
|
|||||||
package cloudflare
|
package cloudflare
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"github.com/syumai/workers/cloudflare/r2"
|
||||||
"io"
|
|
||||||
"syscall/js"
|
|
||||||
|
|
||||||
"github.com/syumai/workers/cloudflare/internal/cfruntimecontext"
|
|
||||||
"github.com/syumai/workers/internal/jsutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// R2Bucket represents interface of Cloudflare Worker's R2 Bucket instance.
|
// R2Bucket represents interface of Cloudflare Worker's R2 Bucket instance.
|
||||||
// - https://developers.cloudflare.com/r2/runtime-apis/#bucket-method-definitions
|
// Deprecated: use r2.Bucket instead.
|
||||||
// - https://github.com/cloudflare/workers-types/blob/3012f263fb1239825e5f0061b267c8650d01b717/index.d.ts#L1006
|
type R2Bucket = r2.Bucket
|
||||||
type R2Bucket struct {
|
|
||||||
instance js.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewR2Bucket returns R2Bucket for given variable name.
|
// NewR2Bucket returns R2Bucket for given variable name.
|
||||||
// - variable name must be defined in wrangler.toml.
|
// Deprecated: use r2.NewBucket instead.
|
||||||
// - see example: https://github.com/syumai/workers/tree/main/_examples/r2-image-viewer
|
|
||||||
// - if the given variable name doesn't exist on runtime context, returns error.
|
|
||||||
// - This function panics when a runtime context is not found.
|
|
||||||
func NewR2Bucket(varName string) (*R2Bucket, error) {
|
func NewR2Bucket(varName string) (*R2Bucket, error) {
|
||||||
inst := cfruntimecontext.MustGetRuntimeContextEnv().Get(varName)
|
return r2.NewBucket(varName)
|
||||||
if inst.IsUndefined() {
|
|
||||||
return nil, fmt.Errorf("%s is undefined", varName)
|
|
||||||
}
|
|
||||||
return &R2Bucket{instance: inst}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Head returns the result of `head` call to R2Bucket.
|
|
||||||
// - Body field of *R2Object is always nil for Head call.
|
|
||||||
// - if the object for given key doesn't exist, returns nil.
|
|
||||||
// - if a network error happens, returns error.
|
|
||||||
func (r *R2Bucket) Head(key string) (*R2Object, error) {
|
|
||||||
p := r.instance.Call("head", key)
|
|
||||||
v, err := jsutil.AwaitPromise(p)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if v.IsNull() {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return toR2Object(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns the result of `get` call to R2Bucket.
|
|
||||||
// - if the object for given key doesn't exist, returns nil.
|
|
||||||
// - if a network error happens, returns error.
|
|
||||||
func (r *R2Bucket) Get(key string) (*R2Object, error) {
|
|
||||||
p := r.instance.Call("get", key)
|
|
||||||
v, err := jsutil.AwaitPromise(p)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if v.IsNull() {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return toR2Object(v)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// R2PutOptions represents Cloudflare R2 put options.
|
// R2PutOptions represents Cloudflare R2 put options.
|
||||||
// - https://github.com/cloudflare/workers-types/blob/3012f263fb1239825e5f0061b267c8650d01b717/index.d.ts#L1128
|
// Deprecated: use r2.PutOptions instead.
|
||||||
type R2PutOptions struct {
|
type R2PutOptions = r2.PutOptions
|
||||||
HTTPMetadata R2HTTPMetadata
|
|
||||||
CustomMetadata map[string]string
|
|
||||||
MD5 string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (opts *R2PutOptions) toJS() js.Value {
|
|
||||||
if opts == nil {
|
|
||||||
return js.Undefined()
|
|
||||||
}
|
|
||||||
obj := jsutil.NewObject()
|
|
||||||
if opts.HTTPMetadata != (R2HTTPMetadata{}) {
|
|
||||||
obj.Set("httpMetadata", opts.HTTPMetadata.toJS())
|
|
||||||
}
|
|
||||||
if opts.CustomMetadata != nil {
|
|
||||||
// convert map[string]string to map[string]any.
|
|
||||||
// This makes the map convertible to JS.
|
|
||||||
// see: https://pkg.go.dev/syscall/js#ValueOf
|
|
||||||
customMeta := make(map[string]any, len(opts.CustomMetadata))
|
|
||||||
for k, v := range opts.CustomMetadata {
|
|
||||||
customMeta[k] = v
|
|
||||||
}
|
|
||||||
obj.Set("customMetadata", customMeta)
|
|
||||||
}
|
|
||||||
if opts.MD5 != "" {
|
|
||||||
obj.Set("md5", opts.MD5)
|
|
||||||
}
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put returns the result of `put` call to R2Bucket.
|
|
||||||
// - This method copies all bytes into memory for implementation restriction.
|
|
||||||
// - Body field of *R2Object is always nil for Put call.
|
|
||||||
// - if a network error happens, returns error.
|
|
||||||
func (r *R2Bucket) Put(key string, value io.ReadCloser, opts *R2PutOptions) (*R2Object, error) {
|
|
||||||
// fetch body cannot be ReadableStream. see: https://github.com/whatwg/fetch/issues/1438
|
|
||||||
b, err := io.ReadAll(value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer value.Close()
|
|
||||||
ua := jsutil.NewUint8Array(len(b))
|
|
||||||
js.CopyBytesToJS(ua, b)
|
|
||||||
p := r.instance.Call("put", key, ua.Get("buffer"), opts.toJS())
|
|
||||||
v, err := jsutil.AwaitPromise(p)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return toR2Object(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete returns the result of `delete` call to R2Bucket.
|
|
||||||
// - if a network error happens, returns error.
|
|
||||||
func (r *R2Bucket) Delete(key string) error {
|
|
||||||
p := r.instance.Call("delete", key)
|
|
||||||
if _, err := jsutil.AwaitPromise(p); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// List returns the result of `list` call to R2Bucket.
|
|
||||||
// - if a network error happens, returns error.
|
|
||||||
func (r *R2Bucket) List() (*R2Objects, error) {
|
|
||||||
p := r.instance.Call("list")
|
|
||||||
v, err := jsutil.AwaitPromise(p)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return toR2Objects(v)
|
|
||||||
}
|
|
||||||
|
@ -1,120 +1,13 @@
|
|||||||
package cloudflare
|
package cloudflare
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"github.com/syumai/workers/cloudflare/r2"
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"syscall/js"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/syumai/workers/internal/jsutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// R2Object represents Cloudflare R2 object.
|
// R2Object represents Cloudflare R2 object.
|
||||||
// - https://github.com/cloudflare/workers-types/blob/3012f263fb1239825e5f0061b267c8650d01b717/index.d.ts#L1094
|
// Deprecated: use r2.Object instead.
|
||||||
type R2Object struct {
|
type R2Object = r2.Object
|
||||||
instance js.Value
|
|
||||||
Key string
|
|
||||||
Version string
|
|
||||||
Size int
|
|
||||||
ETag string
|
|
||||||
HTTPETag string
|
|
||||||
Uploaded time.Time
|
|
||||||
HTTPMetadata R2HTTPMetadata
|
|
||||||
CustomMetadata map[string]string
|
|
||||||
// Body is a body of R2Object.
|
|
||||||
// This value is nil for the result of the `Head` or `Put` method.
|
|
||||||
Body io.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: implement
|
|
||||||
// - https://github.com/cloudflare/workers-types/blob/3012f263fb1239825e5f0061b267c8650d01b717/index.d.ts#L1106
|
|
||||||
// func (o *R2Object) WriteHTTPMetadata(headers http.Header) {
|
|
||||||
// }
|
|
||||||
|
|
||||||
func (o *R2Object) BodyUsed() (bool, error) {
|
|
||||||
v := o.instance.Get("bodyUsed")
|
|
||||||
if v.IsUndefined() {
|
|
||||||
return false, errors.New("bodyUsed doesn't exist for this R2Object")
|
|
||||||
}
|
|
||||||
return v.Bool(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// toR2Object converts JavaScript side's R2Object to *R2Object.
|
|
||||||
// - https://github.com/cloudflare/workers-types/blob/3012f263fb1239825e5f0061b267c8650d01b717/index.d.ts#L1094
|
|
||||||
func toR2Object(v js.Value) (*R2Object, error) {
|
|
||||||
uploaded, err := jsutil.DateToTime(v.Get("uploaded"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error converting uploaded: %w", err)
|
|
||||||
}
|
|
||||||
r2Meta, err := toR2HTTPMetadata(v.Get("httpMetadata"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error converting httpMetadata: %w", err)
|
|
||||||
}
|
|
||||||
bodyVal := v.Get("body")
|
|
||||||
var body io.Reader
|
|
||||||
if !bodyVal.IsUndefined() {
|
|
||||||
body = jsutil.ConvertReadableStreamToReadCloser(v.Get("body"))
|
|
||||||
}
|
|
||||||
return &R2Object{
|
|
||||||
instance: v,
|
|
||||||
Key: v.Get("key").String(),
|
|
||||||
Version: v.Get("version").String(),
|
|
||||||
Size: v.Get("size").Int(),
|
|
||||||
ETag: v.Get("etag").String(),
|
|
||||||
HTTPETag: v.Get("httpEtag").String(),
|
|
||||||
Uploaded: uploaded,
|
|
||||||
HTTPMetadata: r2Meta,
|
|
||||||
CustomMetadata: jsutil.StrRecordToMap(v.Get("customMetadata")),
|
|
||||||
Body: body,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// R2HTTPMetadata represents metadata of R2Object.
|
// R2HTTPMetadata represents metadata of R2Object.
|
||||||
// - https://github.com/cloudflare/workers-types/blob/3012f263fb1239825e5f0061b267c8650d01b717/index.d.ts#L1053
|
// Deprecated: use r2.HTTPMetadata instead.
|
||||||
type R2HTTPMetadata struct {
|
type R2HTTPMetadata = r2.HTTPMetadata
|
||||||
ContentType string
|
|
||||||
ContentLanguage string
|
|
||||||
ContentDisposition string
|
|
||||||
ContentEncoding string
|
|
||||||
CacheControl string
|
|
||||||
CacheExpiry time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func toR2HTTPMetadata(v js.Value) (R2HTTPMetadata, error) {
|
|
||||||
if v.IsUndefined() || v.IsNull() {
|
|
||||||
return R2HTTPMetadata{}, nil
|
|
||||||
}
|
|
||||||
cacheExpiry, err := jsutil.MaybeDate(v.Get("cacheExpiry"))
|
|
||||||
if err != nil {
|
|
||||||
return R2HTTPMetadata{}, fmt.Errorf("error converting cacheExpiry: %w", err)
|
|
||||||
}
|
|
||||||
return R2HTTPMetadata{
|
|
||||||
ContentType: jsutil.MaybeString(v.Get("contentType")),
|
|
||||||
ContentLanguage: jsutil.MaybeString(v.Get("contentLanguage")),
|
|
||||||
ContentDisposition: jsutil.MaybeString(v.Get("contentDisposition")),
|
|
||||||
ContentEncoding: jsutil.MaybeString(v.Get("contentEncoding")),
|
|
||||||
CacheControl: jsutil.MaybeString(v.Get("cacheControl")),
|
|
||||||
CacheExpiry: cacheExpiry,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (md *R2HTTPMetadata) toJS() js.Value {
|
|
||||||
obj := jsutil.NewObject()
|
|
||||||
kv := map[string]string{
|
|
||||||
"contentType": md.ContentType,
|
|
||||||
"contentLanguage": md.ContentLanguage,
|
|
||||||
"contentDisposition": md.ContentDisposition,
|
|
||||||
"contentEncoding": md.ContentEncoding,
|
|
||||||
"cacheControl": md.CacheControl,
|
|
||||||
}
|
|
||||||
for k, v := range kv {
|
|
||||||
if v != "" {
|
|
||||||
obj.Set(k, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !md.CacheExpiry.IsZero() {
|
|
||||||
obj.Set("cacheExpiry", jsutil.TimeToDate(md.CacheExpiry))
|
|
||||||
}
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
@ -1,44 +1,9 @@
|
|||||||
package cloudflare
|
package cloudflare
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"github.com/syumai/workers/cloudflare/r2"
|
||||||
"syscall/js"
|
|
||||||
|
|
||||||
"github.com/syumai/workers/internal/jsutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// R2Objects represents Cloudflare R2 objects.
|
// R2Objects represents Cloudflare R2 objects.
|
||||||
// - https://github.com/cloudflare/workers-types/blob/3012f263fb1239825e5f0061b267c8650d01b717/index.d.ts#L1121
|
// Deprecated: use r2.Objects instead.
|
||||||
type R2Objects struct {
|
type R2Objects = r2.Objects
|
||||||
Objects []*R2Object
|
|
||||||
Truncated bool
|
|
||||||
// Cursor indicates next cursor of R2Objects.
|
|
||||||
// - This becomes empty string if cursor doesn't exist.
|
|
||||||
Cursor string
|
|
||||||
DelimitedPrefixes []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// toR2Objects converts JavaScript side's R2Objects to *R2Objects.
|
|
||||||
// - https://github.com/cloudflare/workers-types/blob/3012f263fb1239825e5f0061b267c8650d01b717/index.d.ts#L1121
|
|
||||||
func toR2Objects(v js.Value) (*R2Objects, error) {
|
|
||||||
objectsVal := v.Get("objects")
|
|
||||||
objects := make([]*R2Object, objectsVal.Length())
|
|
||||||
for i := 0; i < len(objects); i++ {
|
|
||||||
obj, err := toR2Object(objectsVal.Index(i))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error converting to R2Object: %w", err)
|
|
||||||
}
|
|
||||||
objects[i] = obj
|
|
||||||
}
|
|
||||||
prefixesVal := v.Get("delimitedPrefixes")
|
|
||||||
prefixes := make([]string, prefixesVal.Length())
|
|
||||||
for i := 0; i < len(prefixes); i++ {
|
|
||||||
prefixes[i] = prefixesVal.Index(i).String()
|
|
||||||
}
|
|
||||||
return &R2Objects{
|
|
||||||
Objects: objects,
|
|
||||||
Truncated: v.Get("truncated").Bool(),
|
|
||||||
Cursor: jsutil.MaybeString(v.Get("cursor")),
|
|
||||||
DelimitedPrefixes: prefixes,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user