Build Core and Handles into in-memory CDN modules, add the marketing site and tutorial docs, and provide a SQLite-backed WebGPU playground with TypeScript tooling and profiling. Amp-Thread-ID: https://ampcode.com/threads/T-01a02485-5574-707c-bff4-5668d83bee8a Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
154 lines
3.8 KiB
Go
154 lines
3.8 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"time"
|
|
"unicode/utf8"
|
|
|
|
_ "github.com/mattn/go-sqlite3"
|
|
)
|
|
|
|
const (
|
|
maximumCodeBytes = 200 * 1024
|
|
maximumTitleRunes = 80
|
|
)
|
|
|
|
var playgroundIDPattern = regexp.MustCompile(`^[a-z0-9]{6,24}$`)
|
|
|
|
type playground struct {
|
|
ID string `json:"id"`
|
|
Revision int `json:"revision"`
|
|
Title string `json:"title"`
|
|
Code string `json:"code"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
}
|
|
|
|
type playgroundStore struct {
|
|
*sql.DB
|
|
}
|
|
|
|
func openPlaygroundStore(path string) (*playgroundStore, error) {
|
|
if path != ":memory:" {
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
database, err := sql.Open("sqlite3", path+"?_foreign_keys=on&_busy_timeout=5000&_journal_mode=WAL")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
database.SetMaxOpenConns(1)
|
|
store := &playgroundStore{DB: database}
|
|
if err := store.initialize(); err != nil {
|
|
database.Close()
|
|
return nil, err
|
|
}
|
|
return store, nil
|
|
}
|
|
|
|
func (store *playgroundStore) initialize() error {
|
|
_, err := store.Exec(`
|
|
CREATE TABLE IF NOT EXISTS playgrounds (
|
|
id TEXT NOT NULL,
|
|
revision INTEGER NOT NULL CHECK (revision > 0),
|
|
title TEXT NOT NULL,
|
|
code TEXT NOT NULL,
|
|
created_at TEXT NOT NULL,
|
|
PRIMARY KEY (id, revision)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS playgrounds_created_at
|
|
ON playgrounds (created_at DESC);
|
|
`)
|
|
return err
|
|
}
|
|
|
|
func (store *playgroundStore) save(ctx context.Context, id, title, code string) (playground, error) {
|
|
if id != "" && !playgroundIDPattern.MatchString(id) {
|
|
return playground{}, errors.New("invalid playground id")
|
|
}
|
|
if title == "" || utf8.RuneCountInString(title) > maximumTitleRunes {
|
|
return playground{}, fmt.Errorf("title must contain 1 to %d characters", maximumTitleRunes)
|
|
}
|
|
if code == "" || len(code) > maximumCodeBytes || !utf8.ValidString(code) {
|
|
return playground{}, fmt.Errorf("code must contain 1 to %d bytes of UTF-8", maximumCodeBytes)
|
|
}
|
|
if id == "" {
|
|
var err error
|
|
id, err = randomPlaygroundID()
|
|
if err != nil {
|
|
return playground{}, err
|
|
}
|
|
}
|
|
|
|
transaction, err := store.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return playground{}, err
|
|
}
|
|
defer transaction.Rollback()
|
|
|
|
var revision int
|
|
if err := transaction.QueryRowContext(
|
|
ctx,
|
|
`SELECT COALESCE(MAX(revision), 0) + 1 FROM playgrounds WHERE id = ?`,
|
|
id,
|
|
).Scan(&revision); err != nil {
|
|
return playground{}, err
|
|
}
|
|
createdAt := time.Now().UTC().Truncate(time.Millisecond)
|
|
if _, err := transaction.ExecContext(
|
|
ctx,
|
|
`INSERT INTO playgrounds (id, revision, title, code, created_at) VALUES (?, ?, ?, ?, ?)`,
|
|
id,
|
|
revision,
|
|
title,
|
|
code,
|
|
createdAt.Format(time.RFC3339Nano),
|
|
); err != nil {
|
|
return playground{}, err
|
|
}
|
|
if err := transaction.Commit(); err != nil {
|
|
return playground{}, err
|
|
}
|
|
return playground{ID: id, Revision: revision, Title: title, Code: code, CreatedAt: createdAt}, nil
|
|
}
|
|
|
|
func (store *playgroundStore) get(ctx context.Context, id string, revision int) (playground, error) {
|
|
if !playgroundIDPattern.MatchString(id) || revision < 1 {
|
|
return playground{}, sql.ErrNoRows
|
|
}
|
|
var result playground
|
|
var createdAt string
|
|
err := store.QueryRowContext(
|
|
ctx,
|
|
`SELECT id, revision, title, code, created_at FROM playgrounds WHERE id = ? AND revision = ?`,
|
|
id,
|
|
revision,
|
|
).Scan(&result.ID, &result.Revision, &result.Title, &result.Code, &createdAt)
|
|
if err != nil {
|
|
return playground{}, err
|
|
}
|
|
result.CreatedAt, err = time.Parse(time.RFC3339Nano, createdAt)
|
|
return result, err
|
|
}
|
|
|
|
func randomPlaygroundID() (string, error) {
|
|
const alphabet = "abcdefghijkmnopqrstuvwxyz23456789"
|
|
result := make([]byte, 10)
|
|
for index := range result {
|
|
value, err := rand.Int(rand.Reader, big.NewInt(int64(len(alphabet))))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
result[index] = alphabet[value.Int64()]
|
|
}
|
|
return string(result), nil
|
|
}
|