Browse Source

Separate project

Extract project from monorepo.
master
TheoryOfNekomata 3 years ago
commit
6983443f63
7 changed files with 162 additions and 0 deletions
  1. +2
    -0
      .npmignore
  2. +3
    -0
      README.md
  3. +18
    -0
      package.json
  4. +70
    -0
      src/Engine.ts
  5. +27
    -0
      src/Storage.ts
  6. +15
    -0
      src/index.ts
  7. +27
    -0
      tsconfig.json

+ 2
- 0
.npmignore View File

@@ -0,0 +1,2 @@
*.ts
tsconfig.json

+ 3
- 0
README.md View File

@@ -0,0 +1,3 @@
# Zeichen - Remote Storage Plugin

Save and load notes in Zeichen using an external API.

+ 18
- 0
package.json View File

@@ -0,0 +1,18 @@
{
"name": "zeichen-plugin-remote-storage",
"author": "TheoryOfNekomata <allan.crisostomo@outlook.com>",
"version": "0.1.0",
"description": "Save and load notes in Zeichen using an external API.",
"keywords": [
"zeichen",
"plugin",
"storage",
"API"
],
"devDependencies": {
"typescript": "^4.0.3"
},
"scripts": {
"build": "tsc **/*.ts"
}
}

+ 70
- 0
src/Engine.ts View File

@@ -0,0 +1,70 @@
import { Deserializer, Serializer } from '../../core/src/storage'

const CREATED = 201
const NO_CONTENT = 204
const NOT_FOUND = 404
const GONE = 410

export default class RemoteStorage<U, T> {
constructor(
private readonly baseUrl: string,
private readonly getItemId = item => item['id'],
private readonly serializers: Map<string, Serializer> = new Map([
['*/*', JSON.stringify],
['application/json', JSON.stringify],
['text/json', JSON.stringify],
]),
private readonly deserializers: Map<string, Deserializer> = new Map([
['*/*', JSON.parse],
['application/json', JSON.parse],
['text/json', JSON.parse],
]),
) {}

async getCollection(collectionId: string) {
const response = await window.fetch([this.baseUrl, collectionId].join('/'))
const contentType = response.headers.get('content-type')
const { [contentType]: deserializer = this.deserializers.get('*/*'), } = Object.fromEntries(this.deserializers.entries())
const payload = await response.text()
return deserializer(payload)
}

async setItem(collectionId: string, item: U, contentType = 'application/json') {
const { [contentType]: serializer = this.serializers.get('application/json'), } = Object.fromEntries(this.serializers.entries())
const response = await window.fetch([this.baseUrl, collectionId, this.getItemId(item)].join('/'), {
method: 'put',
body: serializer(item),
})
// resource is created
if (response.status === CREATED) {
return
}
if (100 <= response.status && response.status <= 399) {
console.warn(`Expected response is ${CREATED}, got ${response.status}.`)
return
}
throw new Error(response.statusText)
}

async removeItem(collectionId: string, item: U) {
const response = await window.fetch([this.baseUrl, collectionId, this.getItemId(item)].join('/'), {
method: 'delete',
})
// resource is deleted
if (response.status === NO_CONTENT) {
return true
}
// resource is already deleted
if (response.status === NOT_FOUND || response.status === GONE) {
return false
}
if (100 <= response.status && response.status <= 399) {
console.warn(`Expected response is ${NO_CONTENT}, got ${response.status}.`)
// assume there's a change in the collection
return true
}
throw new Error(response.statusText)
}

// TODO do removeCollection for account closing?
}

+ 27
- 0
src/Storage.ts View File

@@ -0,0 +1,27 @@
import Storage, { Collection } from '../../core/src/storage'
import Engine from './Engine'

export default class RemoteStorage<T> implements Storage<T> {
private readonly engine: Engine<T, Collection<T>>

constructor(
private ownerId: string,
private baseUrl: string,
private collectionId: string,
) {
this.engine = new Engine(baseUrl)
}

async deleteItem(item: T) {
return this.engine.removeItem(this.collectionId, item)
}

async queryItems() {
const response = await this.engine.getCollection(this.collectionId) as Collection<T>;
return response.items;
}

async saveItem(newItem: T) {
return this.engine.setItem(this.collectionId, newItem)
}
}

+ 15
- 0
src/index.ts View File

@@ -0,0 +1,15 @@
import { Plugin } from '../../core/src/plugin'
import Storage from './Storage'

type PluginConfig = {
baseUrl: string,
}

const RemoteStoragePlugin: Plugin<PluginConfig> = config => ({
currentUserId,
}) => {
new Storage(currentUserId, config.baseUrl, 'notes')
new Storage(currentUserId, config.baseUrl, 'folders')
}

export default RemoteStoragePlugin

+ 27
- 0
tsconfig.json View File

@@ -0,0 +1,27 @@
{
"compilerOptions": {
"target": "es6",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"declaration": true,
"declarationDir": "./dist",
"sourceMap": true,
"strict": false
},
"exclude": [
"node_modules",
"**/*.test.ts"
],
"include": [
"**/*.ts"
]
}

Loading…
Cancel
Save