initial-commit

This commit is contained in:
nicwands
2026-02-27 17:08:23 -05:00
commit 904d3ac8f0
8 changed files with 2093 additions and 0 deletions

38
.gitignore vendored Normal file
View File

@@ -0,0 +1,38 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
.env
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
.npmrc
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo
.eslintcache
# Cypress
/cypress/videos/
/cypress/screenshots/
# Vitest
__screenshots__/

1840
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

29
package.json Normal file
View File

@@ -0,0 +1,29 @@
{
"name": "@takerofnotes/plugin-supabase",
"version": "0.1.0",
"repository": {
"type": "git",
"url": "git+https://github.com/nicwands/takerofnotes-plugin-supabase.git"
},
"type": "module",
"main": "dist/index.js",
"exports": "./dist/index.js",
"scripts": {
"build": "rollup -c",
"test": "vitest",
"prepublishOnly": "npm run build"
},
"author": "nicwands",
"license": "MIT",
"description": "Supabase storage plugin for Taker of Notes",
"dependencies": {
"@supabase/supabase-js": "^2.98.0",
"@takerofnotes/plugin-sdk": "^0.2.0"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^29.0.0",
"@rollup/plugin-node-resolve": "^16.0.3",
"rollup": "^4.59.0",
"vitest": "^4.0.18"
}
}

13
rollup.config.js Normal file
View File

@@ -0,0 +1,13 @@
// rollup.config.js
import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
export default {
input: 'src/index.js',
output: {
file: 'dist/index.js',
format: 'esm',
sourcemap: true,
},
plugins: [resolve(), commonjs()],
}

88
src/SupabaseAdapter.js Normal file
View File

@@ -0,0 +1,88 @@
import { BaseNotesAdapter } from '@takerofnotes/plugin-sdk'
import { createClient } from '@supabase/supabase-js'
export default class SupabaseAdapter extends BaseNotesAdapter {
constructor(config) {
super()
for (const field in config) {
this[field] = config[field]
}
}
async init() {
const supabaseUrl = this.supabaseUrl
const supabaseKey = this.supabaseKey
const bucket = this.bucket || 'notes'
this.supabase = createClient(supabaseUrl, supabaseKey)
this.bucket = bucket
const { error } = await this.supabase.storage.getBucket(bucket)
if (error) {
await this.supabase.storage.createBucket(bucket, { public: false })
}
}
async getAll() {
const { data, error } = await this.supabase.storage
.from(this.bucket)
.list('', { limit: 1000 })
if (error) {
throw new Error(`Failed to list notes: ${error.message}`)
}
const notes = []
for (const file of data) {
if (!file.name.endsWith('.json')) continue
const { data: fileData, error: downloadError } =
await this.supabase.storage
.from(this.bucket)
.download(file.name)
if (downloadError) continue
const text = await fileData.text()
const note = JSON.parse(text)
notes.push(note)
}
return notes
}
async create(note) {
await this._write(note)
}
async update(note) {
await this._write(note)
}
async delete(id) {
const { error } = await this.supabase.storage
.from(this.bucket)
.remove([`${id}.json`])
if (error) {
throw new Error(`Failed to delete note: ${error.message}`)
}
}
async _write(note) {
const fileName = `${note.id}.json`
const fileContent = JSON.stringify(note, null, 2)
const { error } = await this.supabase.storage
.from(this.bucket)
.upload(fileName, fileContent, {
contentType: 'application/json',
upsert: true,
})
if (error) {
throw new Error(`Failed to save note: ${error.message}`)
}
}
}

36
src/index.js Normal file
View File

@@ -0,0 +1,36 @@
import { definePlugin } from '@takerofnotes/plugin-sdk'
import SupabaseAdapter from './SupabaseAdapter'
export default definePlugin({
id: 'supabase',
name: 'Supabase',
description: 'Store notes using Supabase storage',
version: '0.1.0',
apiVersion: '0.1.0',
configSchema: [
{
key: 'supabaseUrl',
label: 'Supabase URL',
type: 'text',
default: 'https://supabase.com',
required: true,
},
{
key: 'supabaseKey',
label: 'Supabase Anon Key',
type: 'password',
default: 'supabase_key',
required: true,
},
{
key: 'bucket',
label: 'Storage Bucket',
type: 'text',
default: 'notes',
required: false,
},
],
createAdapter(config) {
return new SupabaseAdapter(config)
},
})

42
test/plugin.test.js Normal file
View File

@@ -0,0 +1,42 @@
import { describe, it } from 'vitest'
import { runPluginTests } from '@takerofnotes/plugin-sdk'
import plugin from './src/index.js'
import SupabaseAdapter from '../src/SupabaseAdapter.js'
const adapter = new SupabaseAdapter({
supabaseUrl: import.meta.env.VITE_SUPABASE_URL,
supabaseKey: import.meta.env.VITE_SUPABASE_KEY,
})
const tests = runPluginTests({ plugin, adapter })
describe('Plugin Validation', () => {
tests.validatePlugin(it, (a, b) => expect(a).toBe(b))
})
describe('Adapter: init()', () => {
tests.init(it)
})
describe('Adapter: getAll()', () => {
tests.getAll(it)
})
describe('Adapter: create()', () => {
tests.create(it)
})
describe('Adapter: update()', () => {
tests.update(it)
})
describe('Adapter: delete()', () => {
tests.delete(it)
})
describe(
'Full CRUD Cycle',
() => {
tests.crudCycle(it)
},
10 * 1000,
)

7
vitest.config.js Normal file
View File

@@ -0,0 +1,7 @@
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
globals: true,
},
})