Merge branch 'master' of github.com:Budibase/budibase into linked-records

This commit is contained in:
Andrew Kingston 2020-10-07 10:45:26 +01:00
commit 3dc30262d7
34 changed files with 8222 additions and 58 deletions

View File

@ -1,5 +1,5 @@
{
"version": "0.1.23",
"version": "0.1.25",
"npmClient": "yarn",
"packages": [
"packages/*"

View File

@ -30,7 +30,6 @@
"test:e2e:ci": "lerna run cy:ci"
},
"dependencies": {
"@fortawesome/fontawesome": "^1.1.8",
"pouchdb-replication-stream": "^1.2.9"
"@fortawesome/fontawesome": "^1.1.8"
}
}
}

View File

@ -50,13 +50,11 @@ context("Create a View", () => {
it("creates a stats calculation view based on age", () => {
cy.contains("Calculate").click()
// we may reinstate this - have commented this dropdown for now as there is only one option
//cy.get(".menu-container").find("select").first().select("Statistics")
cy.get(".menu-container")
.find("select")
.first()
.select("Statistics")
cy.get(".menu-container")
.find("select")
.eq(1)
.eq(0)
.select("age")
cy.contains("Save").click()
cy.get("thead th div").should($headers => {

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/builder",
"version": "0.1.23",
"version": "0.1.25",
"license": "AGPL-3.0",
"private": true,
"scripts": {
@ -64,7 +64,7 @@
},
"dependencies": {
"@budibase/bbui": "^1.40.1",
"@budibase/client": "^0.1.23",
"@budibase/client": "^0.1.25",
"@budibase/colorpicker": "^1.0.1",
"@fortawesome/fontawesome-free": "^5.14.0",
"@sentry/browser": "5.19.1",

View File

@ -8,6 +8,7 @@
import Table from "./Table.svelte"
let data = []
let loading = false
$: title = $backendUiStore.selectedModel.name
$: schema = $backendUiStore.selectedModel.schema
@ -19,14 +20,16 @@
// Fetch records for specified model
$: {
if ($backendUiStore.selectedView?.name?.startsWith("all_")) {
loading = true
api.fetchDataForView($backendUiStore.selectedView).then(records => {
data = records || []
loading = false
})
}
}
</script>
<Table {title} {schema} {data} allowEditing={true}>
<Table {title} {schema} {data} allowEditing={true} {loading}>
<CreateColumnButton />
{#if Object.keys(schema).length > 0}
<CreateRowButton />

View File

@ -1,6 +1,7 @@
<script>
import { goto, params } from "@sveltech/routify"
import { onMount } from "svelte"
import { fade } from "svelte/transition"
import fsort from "fast-sort"
import getOr from "lodash/fp/getOr"
import { store, backendUiStore } from "builderStore"
@ -16,6 +17,7 @@
import ColumnHeaderPopover from "./popovers/ColumnPopover.svelte"
import EditRowPopover from "./popovers/RowPopover.svelte"
import CalculationPopover from "./buttons/CalculateButton.svelte"
import Spinner from "components/common/Spinner.svelte"
const ITEMS_PER_PAGE = 10
@ -23,6 +25,7 @@
export let data = []
export let title
export let allowEditing = false
export let loading = false
let currentPage = 0
@ -50,7 +53,14 @@
<section>
<div class="table-controls">
<h2 class="title">{title}</h2>
<h2 class="title">
<span>{title}</span>
{#if loading}
<div transition:fade>
<Spinner size="10" />
</div>
{/if}
</h2>
<div class="popovers">
<slot />
</div>
@ -123,6 +133,13 @@
text-rendering: optimizeLegibility;
text-transform: capitalize;
margin-top: 0;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
}
.title > span {
margin-right: var(--spacing-xs);
}
table {

View File

@ -24,6 +24,7 @@
)
function saveView() {
if (!view.calculation) view.calculation = "stats"
backendUiStore.actions.views.save(view)
notifier.success(`View ${view.name} saved.`)
onClosed()
@ -34,14 +35,15 @@
<div class="actions">
<h5>Calculate</h5>
<div class="input-group-row">
<p>The</p>
<!-- <p>The</p>
<Select secondary thin bind:value={view.calculation}>
<option value="">Choose an option</option>
{#each CALCULATIONS as calculation}
<option value={calculation.key}>{calculation.name}</option>
{/each}
</Select>
<p>of</p>
<p>of</p> -->
<p>The statistics of</p>
<Select secondary thin bind:value={view.field}>
<option value="">Choose an option</option>
{#each fields as field}
@ -74,7 +76,7 @@
.input-group-row {
display: grid;
grid-template-columns: 30px 1fr 20px 1fr;
grid-template-columns: auto 1fr 20px 1fr;
gap: var(--spacing-s);
align-items: center;
}

View File

@ -34,6 +34,7 @@
$: modelOptions = $backendUiStore.models.filter(
model => model._id !== $backendUiStore.draftModel._id
)
$: required = !!field?.constraints?.presence
async function saveColumn() {
backendUiStore.update(state => {
@ -53,6 +54,12 @@
field.type = type
field.constraints = constraints
}
function onChangeRequired(e) {
const req = e.target.checked
field.constraints.presence = req ? { allowEmpty: false } : false
required = req
}
</script>
<div class="actions">
@ -71,8 +78,8 @@
{#if field.type !== 'link'}
<Toggle
checked={!field.constraints.presence.allowEmpty}
on:change={e => (field.constraints.presence.allowEmpty = !e.target.checked)}
checked={required}
on:change={onChangeRequired}
thin
text="Required" />
{/if}

View File

@ -1,5 +1,5 @@
<script>
import { Button, Input, Select } from "@budibase/bbui"
import { Button, Input, Select, DatePicker } from "@budibase/bbui"
import { backendUiStore } from "builderStore"
import { notifier } from "builderStore/store/notifications"
import analytics from "analytics"
@ -71,11 +71,38 @@
function isMultipleChoice(field) {
return (
viewModel.schema[field].constraints &&
viewModel.schema[field].constraints.inclusion &&
viewModel.schema[field].constraints.inclusion.length
(viewModel.schema[field].constraints &&
viewModel.schema[field].constraints.inclusion &&
viewModel.schema[field].constraints.inclusion.length) ||
viewModel.schema[field].type === "boolean"
)
}
function fieldOptions(field) {
return viewModel.schema[field].type === "string"
? viewModel.schema[field].constraints.inclusion
: [true, false]
}
function isDate(field) {
return viewModel.schema[field].type === "datetime"
}
function isNumber(field) {
return viewModel.schema[field].type === "number"
}
const fieldChanged = filter => ev => {
// reset if type changed
if (
filter.key &&
ev.target.value &&
viewModel.schema[filter.key].type !==
viewModel.schema[ev.target.value].type
) {
filter.value = ""
}
}
</script>
<div class="actions">
@ -99,6 +126,16 @@
<option value={field}>{field}</option>
{/each}
</Select>
<Select
secondary
thin
bind:value={filter.key}
on:change={fieldChanged(filter)}>
<option value="">Choose an option</option>
{#each fields as field}
<option value={field}>{field}</option>
{/each}
</Select>
<Select secondary thin bind:value={filter.condition}>
<option value="">Choose an option</option>
{#each CONDITIONS as condition}
@ -108,12 +145,25 @@
{#if filter.key && isMultipleChoice(filter.key)}
<Select secondary thin bind:value={filter.value}>
<option value="">Choose an option</option>
{#each viewModel.schema[filter.key].constraints.inclusion as option}
<option value={option}>{option}</option>
{#each fieldOptions(filter.key) as option}
<option value={option}>{option.toString()}</option>
{/each}
</Select>
{:else if filter.key && isDate(filter.key)}
<DatePicker
bind:value={filter.value}
placeholder={filter.key || fields[0]} />
{:else if filter.key && isNumber(filter.key)}
<Input
thin
bind:value={filter.value}
placeholder={filter.key || fields[0]}
type="number" />
{:else}
<Input thin placeholder="Value" bind:value={filter.value} />
<Input
thin
placeholder={filter.key || fields[0]}
bind:value={filter.value} />
{/if}
<i class="ri-close-circle-fill" on:click={() => removeFilter(idx)} />
{/each}

View File

@ -0,0 +1,189 @@
<script>
import { Heading, Body, Button, Select } from "@budibase/bbui"
import { notifier } from "builderStore/store/notifications"
import { FIELDS } from "constants/backend"
import api from "builderStore/api"
const BYTES_IN_KB = 1000
const BYTES_IN_MB = 1000000
const FILE_SIZE_LIMIT = BYTES_IN_MB * 1
export let files = []
export let dataImport = {
valid: true,
schema: {},
}
let parseResult
$: schema = parseResult && parseResult.schema
$: valid =
!schema || Object.keys(schema).every(column => schema[column].success)
$: dataImport = {
valid,
schema: buildModelSchema(schema),
path: files[0] && files[0].path,
}
function buildModelSchema(schema) {
const modelSchema = {}
for (let key in schema) {
const type = schema[key].type
if (type === "omit") continue
modelSchema[key] = {
name: key,
type,
constraints: FIELDS[type.toUpperCase()].constraints,
}
}
return modelSchema
}
async function validateCSV() {
const response = await api.post("/api/models/csv/validate", {
file: files[0],
schema: schema || {},
})
parseResult = await response.json()
if (response.status !== 200) {
notifier.danger("CSV Invalid, please try another CSV file")
return []
}
}
async function handleFile(evt) {
const fileArray = Array.from(evt.target.files)
const filesToProcess = fileArray.map(({ name, path, size }) => ({
name,
path,
size,
}))
if (filesToProcess.some(file => file.size >= FILE_SIZE_LIMIT)) {
notifier.danger(
`Files cannot exceed ${FILE_SIZE_LIMIT /
BYTES_IN_MB}MB. Please try again with smaller files.`
)
return
}
files = filesToProcess
await validateCSV()
}
async function omitColumn(columnName) {
schema[columnName].type = "omit"
await validateCSV()
}
const handleTypeChange = column => evt => {
schema[column].type = evt.target.value
validateCSV()
}
</script>
<div class="dropzone">
<input id="file-upload" accept=".csv" type="file" on:change={handleFile} />
<label for="file-upload" class:uploaded={files[0]}>
{#if files[0]}{files[0].name}{:else}Upload{/if}
</label>
</div>
<div class="schema-fields">
{#if schema}
{#each Object.keys(schema).filter(key => schema[key].type !== 'omit') as columnName}
<div class="field">
<span>{columnName}</span>
<Select
secondary
thin
bind:value={schema[columnName].type}
on:change={handleTypeChange(columnName)}>
<option value={'string'}>Text</option>
<option value={'number'}>Number</option>
<option value={'datetime'}>Date</option>
</Select>
<span class="field-status" class:error={!schema[columnName].success}>
{schema[columnName].success ? 'Success' : 'Failure'}
</span>
<i
class="omit-button ri-close-circle-fill"
on:click={() => omitColumn(columnName)} />
</div>
{/each}
{/if}
</div>
<style>
.dropzone {
text-align: center;
display: flex;
align-items: center;
flex-direction: column;
border-radius: 10px;
transition: all 0.3s;
}
.field-status {
color: var(--green);
justify-self: center;
font-weight: 500;
}
.error {
color: var(--red);
}
.uploaded {
color: var(--blue);
}
input[type="file"] {
display: none;
}
label {
font-family: var(--font-sans);
cursor: pointer;
font-weight: 500;
box-sizing: border-box;
overflow: hidden;
border-radius: var(--border-radius-s);
color: var(--ink);
padding: var(--spacing-s) var(--spacing-l);
transition: all 0.2s ease 0s;
display: inline-flex;
text-rendering: optimizeLegibility;
min-width: auto;
outline: none;
font-feature-settings: "case" 1, "rlig" 1, "calt" 0;
-webkit-box-align: center;
user-select: none;
flex-shrink: 0;
align-items: center;
justify-content: center;
width: 100%;
background-color: var(--grey-2);
font-size: var(--font-size-xs);
}
.omit-button {
font-size: 1.2em;
color: var(--grey-7);
cursor: pointer;
justify-self: flex-end;
}
.field {
display: grid;
grid-template-columns: repeat(4, 1fr);
margin-top: var(--spacing-m);
align-items: center;
grid-gap: var(--spacing-m);
font-size: var(--font-size-xs);
}
</style>

View File

@ -2,23 +2,30 @@
import { goto } from "@sveltech/routify"
import { backendUiStore } from "builderStore"
import { notifier } from "builderStore/store/notifications"
import { Popover, Button, Icon, Input, Select } from "@budibase/bbui"
import { Popover, Button, Icon, Input, Select, Label } from "@budibase/bbui"
import Spinner from "components/common/Spinner.svelte"
import TableDataImport from "../TableDataImport.svelte"
import analytics from "analytics"
let anchor
let dropdown
let name
let dataImport
let loading
async function saveTable() {
loading = true
const model = await backendUiStore.actions.models.save({
name,
schema: {},
schema: dataImport.schema || {},
dataImport,
})
notifier.success(`Table ${name} created successfully.`)
$goto(`./model/${model._id}`)
analytics.captureEvent("Table Created", { name })
name = ""
dropdown.hide()
analytics.captureEvent("Table Created", { name })
loading = false
}
const onClosed = () => {
@ -38,9 +45,21 @@
thin
label="Table Name"
bind:value={name} />
<div>
<Label grey extraSmall>Create Table from CSV (Optional)</Label>
<TableDataImport bind:dataImport />
</div>
<footer>
<Button secondary on:click={onClosed}>Cancel</Button>
<Button primary on:click={saveTable}>Save</Button>
<Button
disabled={!name || (dataImport && !dataImport.valid)}
primary
on:click={saveTable}>
<span style={`margin-right: ${loading ? '10px' : 0};`}>Save</span>
{#if loading}
<Spinner size="10" />
{/if}
</Button>
</footer>
</div>
</Popover>

View File

@ -146,7 +146,7 @@
})
const appJson = await appResp.json()
analytics.captureEvent("App Created", {
name,
name: $createAppStore.values.applicationName,
appId: appJson._id,
template,
})

View File

@ -11,7 +11,7 @@
on:input={() => (blurred.api = true)}
label="API Key"
name="apiKey"
placeholder="Enter your API Key"
placeholder="Use command-V to paste your API Key"
type="password"
error={blurred.api && validationErrors.apiKey} />
<a target="_blank" href="https://portal.budi.live/">Get API Key</a>

View File

@ -19,7 +19,7 @@
label="Password"
name="password"
placeholder="Password"
type="pasword"
type="password"
error={blurred.password && validationErrors.password} />
<Select label="Access Level" secondary name="accessLevelId">
<option value="ADMIN">Admin</option>

View File

@ -6,7 +6,7 @@ export const FIELDS = {
constraints: {
type: "string",
length: {},
presence: { allowEmpty: true },
presence: false,
},
},
OPTIONS: {
@ -25,7 +25,7 @@ export const FIELDS = {
type: "number",
constraints: {
type: "number",
presence: { allowEmpty: true },
presence: false,
numericality: { greaterThanOrEqualTo: "", lessThanOrEqualTo: "" },
},
},
@ -35,7 +35,7 @@ export const FIELDS = {
type: "boolean",
constraints: {
type: "boolean",
presence: { allowEmpty: true },
presence: false,
},
},
DATETIME: {
@ -45,7 +45,7 @@ export const FIELDS = {
constraints: {
type: "string",
length: {},
presence: { allowEmpty: true },
presence: false,
datetime: {
latest: "",
earliest: "",
@ -58,7 +58,7 @@ export const FIELDS = {
type: "attachment",
constraints: {
type: "array",
presence: { allowEmpty: true },
presence: false,
},
},
LINK: {

View File

@ -1,6 +1,6 @@
{
"name": "budibase",
"version": "0.1.23",
"version": "0.1.25",
"description": "Budibase CLI",
"repository": "https://github.com/Budibase/Budibase",
"homepage": "https://www.budibase.com",
@ -17,7 +17,7 @@
"author": "Budibase",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@budibase/server": "^0.1.23",
"@budibase/server": "^0.1.25",
"@inquirer/password": "^0.0.6-alpha.0",
"chalk": "^2.4.2",
"dotenv": "^8.2.0",

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/client",
"version": "0.1.23",
"version": "0.1.25",
"license": "MPL-2.0",
"main": "dist/budibase-client.js",
"module": "dist/budibase-client.esm.mjs",

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/server",
"version": "0.1.23",
"version": "0.1.25",
"description": "Budibase Web Server",
"main": "src/electron.js",
"repository": {
@ -42,13 +42,14 @@
"author": "Michael Shanks",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@budibase/client": "^0.1.23",
"@budibase/client": "^0.1.25",
"@koa/router": "^8.0.0",
"@sendgrid/mail": "^7.1.1",
"@sentry/node": "^5.19.2",
"aws-sdk": "^2.706.0",
"bcryptjs": "^2.4.3",
"chmodr": "^1.2.0",
"csvtojson": "^2.0.10",
"dotenv": "^8.2.0",
"download": "^8.0.0",
"electron-is-dev": "^1.2.0",
@ -71,6 +72,7 @@
"pino-pretty": "^4.0.0",
"pouchdb": "^7.2.1",
"pouchdb-all-dbs": "^1.0.2",
"pouchdb-replication-stream": "^1.2.9",
"sharp": "^0.26.0",
"squirrelly": "^7.5.0",
"tar-fs": "^2.1.0",

View File

@ -1,9 +1,11 @@
const CouchDB = require("../../db")
const linkRecords = require("../../db/linkedRecords")
const csvParser = require("../../utilities/csvParser")
const {
getRecordParams,
getModelParams,
generateModelID,
generateRecordID,
} = require("../../db/utils")
exports.fetch = async function(ctx) {
@ -24,11 +26,12 @@ exports.find = async function(ctx) {
exports.save = async function(ctx) {
const instanceId = ctx.user.instanceId
const db = new CouchDB(instanceId)
const { dataImport, ...rest } = ctx.request.body
const modelToSave = {
type: "model",
_id: generateModelID(),
views: {},
...ctx.request.body,
...rest,
}
// get the model in its previous state for differencing
let oldModel
@ -89,6 +92,19 @@ exports.save = async function(ctx) {
ctx.eventEmitter &&
ctx.eventEmitter.emitModel(`model:save`, instanceId, modelToSave)
if (dataImport && dataImport.path) {
// Populate the table with records imported from CSV in a bulk update
const data = await csvParser.transform(dataImport)
for (let row of data) {
row._id = generateRecordID(modelToSave._id)
row.modelId = modelToSave._id
}
await db.bulkDocs(data)
}
ctx.status = 200
ctx.message = `Model ${ctx.request.body.name} saved successfully.`
ctx.body = modelToSave
@ -124,3 +140,12 @@ exports.destroy = async function(ctx) {
ctx.status = 200
ctx.message = `Model ${ctx.params.modelId} deleted.`
}
exports.validateCSVSchema = async function(ctx) {
const { file, schema = {} } = ctx.request.body
const result = await csvParser.parse(file.path, schema)
ctx.body = {
schema: result,
path: file.path,
}
}

View File

@ -2,6 +2,7 @@ const CouchDB = require("../../db")
const validateJs = require("validate.js")
const linkRecords = require("../../db/linkedRecords")
const { getRecordParams, generateRecordID } = require("../../db/utils")
const { cloneDeep } = require("lodash")
const MODEL_VIEW_BEGINS_WITH = "all_model:"
@ -21,6 +22,7 @@ exports.patch = async function(ctx) {
let record = await db.get(ctx.params.id)
const model = await db.get(record.modelId)
const patchfields = ctx.request.body
record = coerceRecordValues(record, model)
for (let key of Object.keys(patchfields)) {
if (!model.schema[key]) continue
@ -72,6 +74,8 @@ exports.save = async function(ctx) {
const model = await db.get(record.modelId)
record = coerceRecordValues(record, model)
const validateResult = await validate({
record,
model,
@ -284,3 +288,50 @@ exports.fetchEnrichedRecord = async function(ctx) {
ctx.body = record
ctx.status = 200
}
function coerceRecordValues(rec, model) {
const record = cloneDeep(rec)
for (let [key, value] of Object.entries(record)) {
const field = model.schema[key]
if (!field) continue
// eslint-disable-next-line no-prototype-builtins
if (TYPE_TRANSFORM_MAP[field.type].hasOwnProperty(value)) {
record[key] = TYPE_TRANSFORM_MAP[field.type][value]
} else if (TYPE_TRANSFORM_MAP[field.type].parse) {
record[key] = TYPE_TRANSFORM_MAP[field.type].parse(value)
}
}
return record
}
const TYPE_TRANSFORM_MAP = {
string: {
"": "",
[null]: "",
[undefined]: undefined,
},
number: {
"": null,
[null]: null,
[undefined]: undefined,
parse: n => parseFloat(n),
},
datetime: {
"": null,
[undefined]: undefined,
[null]: null,
},
attachment: {
"": [],
[null]: [],
[undefined]: undefined,
},
boolean: {
"": null,
[null]: null,
[undefined]: undefined,
true: true,
false: false,
},
}

View File

@ -61,8 +61,11 @@ function parseFilterExpression(filters) {
`doc["${filter.key}"].${TOKEN_MAP[filter.condition]}("${filter.value}")`
)
} else {
const value =
typeof filter.value == "string" ? `"${filter.value}"` : filter.value
expression.push(
`doc["${filter.key}"] ${TOKEN_MAP[filter.condition]} "${filter.value}"`
`doc["${filter.key}"] ${TOKEN_MAP[filter.condition]} ${value}`
)
}
}

View File

@ -13,6 +13,11 @@ router
modelController.find
)
.post("/api/models", authorized(BUILDER), modelController.save)
.post(
"/api/models/csv/validate",
authorized(BUILDER),
modelController.validateCSVSchema
)
.delete(
"/api/models/:modelId/:revId",
authorized(BUILDER),

View File

@ -46,13 +46,13 @@ exports.createModel = async (request, appId, instanceId, model) => {
key: "name",
schema: {
name: {
type: "text",
type: "string",
constraints: {
type: "string",
},
},
description: {
type: "text",
type: "string",
constraints: {
type: "string",
},

View File

@ -180,7 +180,7 @@ describe("/models", () => {
key: "name",
schema: {
name: {
type: "text",
type: "string",
constraints: {
type: "string",
},

View File

@ -38,7 +38,7 @@ describe("/records", () => {
const createRecord = async r =>
await request
.post(`/api/${model._id}/records`)
.post(`/api/${r ? r.modelId : record.modelId}/records`)
.send(r || record)
.set(defaultHeaders(app._id, instance._id))
.expect('Content-Type', /json/)
@ -152,6 +152,95 @@ describe("/records", () => {
.expect('Content-Type', /json/)
.expect(404)
})
it("record values are coerced", async () => {
const str = {type:"string", constraints: { type: "string", presence: false }}
const attachment = {type:"attachment", constraints: { type: "array", presence: false }}
const bool = {type:"boolean", constraints: { type: "boolean", presence: false }}
const number = {type:"number", constraints: { type: "number", presence: false }}
const datetime = {type:"datetime", constraints: { type: "string", presence: false, datetime: {earliest:"", latest: ""} }}
model = await createModel(request, app._id, instance._id, {
name: "TestModel2",
type: "model",
key: "name",
schema: {
name: str,
stringUndefined: str,
stringNull: str,
stringString: str,
numberEmptyString: number,
numberNull: number,
numberUndefined: number,
numberString: number,
datetimeEmptyString: datetime,
datetimeNull: datetime,
datetimeUndefined: datetime,
datetimeString: datetime,
datetimeDate: datetime,
boolNull: bool,
boolEmpty: bool,
boolUndefined: bool,
boolString: bool,
boolBool: bool,
attachmentNull : attachment,
attachmentUndefined : attachment,
attachmentEmpty : attachment,
},
})
record = {
name: "Test Record",
stringUndefined: undefined,
stringNull: null,
stringString: "i am a string",
numberEmptyString: "",
numberNull: null,
numberUndefined: undefined,
numberString: "123",
numberNumber: 123,
datetimeEmptyString: "",
datetimeNull: null,
datetimeUndefined: undefined,
datetimeString: "1984-04-20T00:00:00.000Z",
datetimeDate: new Date("1984-04-20"),
boolNull: null,
boolEmpty: "",
boolUndefined: undefined,
boolString: "true",
boolBool: true,
modelId: model._id,
attachmentNull : null,
attachmentUndefined : undefined,
attachmentEmpty : "",
}
const id = (await createRecord(record)).body._id
const saved = (await loadRecord(id)).body
expect(saved.stringUndefined).toBe(undefined)
expect(saved.stringNull).toBe("")
expect(saved.stringString).toBe("i am a string")
expect(saved.numberEmptyString).toBe(null)
expect(saved.numberNull).toBe(null)
expect(saved.numberUndefined).toBe(undefined)
expect(saved.numberString).toBe(123)
expect(saved.numberNumber).toBe(123)
expect(saved.datetimeEmptyString).toBe(null)
expect(saved.datetimeNull).toBe(null)
expect(saved.datetimeUndefined).toBe(undefined)
expect(saved.datetimeString).toBe(new Date(record.datetimeString).toISOString())
expect(saved.datetimeDate).toBe(record.datetimeDate.toISOString())
expect(saved.boolNull).toBe(null)
expect(saved.boolEmpty).toBe(null)
expect(saved.boolUndefined).toBe(undefined)
expect(saved.boolString).toBe(true)
expect(saved.boolBool).toBe(true)
expect(saved.attachmentNull).toEqual([])
expect(saved.attachmentUndefined).toBe(undefined)
expect(saved.attachmentEmpty).toEqual([])
})
})
describe("patch", () => {

View File

@ -69,13 +69,13 @@ describe("/views", () => {
filters: [],
schema: {
name: {
type: "text",
type: "string",
constraints: {
type: "string"
},
},
description: {
type: "text",
type: "string",
constraints: {
type: "string"
},

View File

@ -0,0 +1,73 @@
const csv = require("csvtojson")
const VALIDATORS = {
string: () => true,
number: attribute => !isNaN(Number(attribute)),
datetime: attribute => !isNaN(new Date(attribute).getTime()),
}
const PARSERS = {
datetime: attribute => new Date(attribute).toISOString(),
}
function parse(path, parsers) {
const result = csv().fromFile(path)
const schema = {}
return new Promise((resolve, reject) => {
result.on("header", headers => {
for (let header of headers) {
schema[header] = {
type: parsers[header] ? parsers[header].type : "string",
success: true,
}
}
})
result.fromFile(path).subscribe(row => {
// For each CSV row parse all the columns that need parsed
for (let key in parsers) {
if (!schema[key] || schema[key].success) {
// get the validator for the column type
const validator = VALIDATORS[parsers[key].type]
try {
// allow null/undefined values
schema[key].success = !row[key] || validator(row[key])
} catch (err) {
schema[key].success = false
}
}
}
})
result.on("done", error => {
if (error) {
console.error(error)
reject(error)
}
resolve(schema)
})
})
}
async function transform({ schema, path }) {
const colParser = {}
for (let key in schema) {
colParser[key] = PARSERS[schema[key].type] || schema[key].type
}
try {
const json = await csv({ colParser }).fromFile(path)
return json
} catch (err) {
console.error(`Error transforming CSV to JSON for data import`, err)
throw err
}
}
module.exports = {
parse,
transform,
}

View File

@ -0,0 +1,21 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CSV Parser transformation transforms a CSV file into JSON 1`] = `
Array [
Object {
"Address": "5 Sesame Street",
"Age": 4324,
"Name": "Bert",
},
Object {
"Address": "1 World Trade Center",
"Age": 34,
"Name": "Ernie",
},
Object {
"Address": "44 Second Avenue",
"Age": 23423,
"Name": "Big Bird",
},
]
`;

View File

@ -0,0 +1,108 @@
const csvParser = require("../csvParser");
const CSV_PATH = __dirname + "/test.csv";
const SCHEMAS = {
VALID: {
Age: {
type: "number",
},
},
INVALID: {
Address: {
type: "number",
},
Age: {
type: "number",
},
},
IGNORE: {
Address: {
type: "omit",
},
Age: {
type: "omit",
},
},
BROKEN: {
Address: {
type: "datetime",
}
},
};
describe("CSV Parser", () => {
describe("parsing", () => {
it("returns status and types for a valid CSV transformation", async () => {
expect(
await csvParser.parse(CSV_PATH, SCHEMAS.VALID)
).toEqual({
Address: {
success: true,
type: "string",
},
Age: {
success: true,
type: "number",
},
Name: {
success: true,
type: "string",
},
});
});
it("returns status and types for an invalid CSV transformation", async () => {
expect(
await csvParser.parse(CSV_PATH, SCHEMAS.INVALID)
).toEqual({
Address: {
success: false,
type: "number",
},
Age: {
success: true,
type: "number",
},
Name: {
success: true,
type: "string",
},
});
});
});
describe("transformation", () => {
it("transforms a CSV file into JSON", async () => {
expect(
await csvParser.transform({
schema: SCHEMAS.VALID,
path: CSV_PATH,
})
).toMatchSnapshot();
});
it("transforms a CSV file into JSON ignoring certain fields", async () => {
expect(
await csvParser.transform({
schema: SCHEMAS.IGNORE,
path: CSV_PATH,
})
).toEqual([
{
Name: "Bert"
},
{
Name: "Ernie"
},
{
Name: "Big Bird"
}
]);
});
it("throws an error on invalid schema", async () => {
await expect(csvParser.transform({ schema: SCHEMAS.BROKEN, path: CSV_PATH })).rejects.toThrow()
});
});
});

View File

@ -0,0 +1,4 @@
"Name","Age","Address"
"Bert","4324","5 Sesame Street"
"Ernie","34","1 World Trade Center"
"Big Bird","23423","44 Second Avenue"
1 Name Age Address
2 Bert 4324 5 Sesame Street
3 Ernie 34 1 World Trade Center
4 Big Bird 23423 44 Second Avenue

View File

@ -172,6 +172,15 @@
lodash "^4.17.13"
to-fast-properties "^2.0.0"
"@budibase/client@^0.1.24":
version "0.1.24"
resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.1.24.tgz#d2967c050af9f559791e0189137f80e621ea2d69"
integrity sha512-2Plu9PpF3TOPTDkAAIkPFEjZFolGkty0Sc0vbLk8lee4yqeonBj5paXT44O6kpxLFW47YjN5VCA4+EnwGl358w==
dependencies:
deep-equal "^2.0.1"
mustache "^4.0.1"
regexparam "^1.3.0"
"@cnakazawa/watch@^1.0.3":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a"
@ -867,6 +876,11 @@ array-equal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93"
array-filter@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-1.0.0.tgz#baf79e62e6ef4c2a4c0b831232daffec251f9d83"
integrity sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=
array-unique@^0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
@ -925,6 +939,13 @@ atomic-sleep@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b"
available-typed-arrays@^1.0.0, available-typed-arrays@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz#6b098ca9d8039079ee3f77f7b783c4480ba513f5"
integrity sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ==
dependencies:
array-filter "^1.0.0"
aws-sdk@^2.706.0:
version "2.706.0"
resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.706.0.tgz#09f65e9a91ecac5a635daf934082abae30eca953"
@ -1057,7 +1078,7 @@ bluebird-lst@^1.0.9:
dependencies:
bluebird "^3.5.5"
bluebird@^3.5.5:
bluebird@^3.5.1, bluebird@^3.5.5:
version "3.7.2"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
@ -1596,6 +1617,15 @@ cssstyle@^1.0.0:
dependencies:
cssom "0.3.x"
csvtojson@^2.0.10:
version "2.0.10"
resolved "https://registry.yarnpkg.com/csvtojson/-/csvtojson-2.0.10.tgz#11e7242cc630da54efce7958a45f443210357574"
integrity sha512-lUWFxGKyhraKCW8Qghz6Z0f2l/PqB1W3AO0HKJzGIQ5JRSlR651ekJDiGJbBT4sRNNv5ddnSGVEnsxP9XRCVpQ==
dependencies:
bluebird "^3.5.1"
lodash "^4.17.3"
strip-bom "^2.0.0"
dashdash@^1.12.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
@ -1725,6 +1755,26 @@ decompress@^4.2.1:
pify "^2.3.0"
strip-dirs "^2.0.0"
deep-equal@^2.0.1:
version "2.0.4"
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.0.4.tgz#6b0b407a074666033169df3acaf128e1c6f3eab6"
integrity sha512-BUfaXrVoCfgkOQY/b09QdO9L3XNoF2XH0A3aY9IQwQL/ZjLOe8FQgCNVl1wiolhsFo8kFdO9zdPViCPbmaJA5w==
dependencies:
es-abstract "^1.18.0-next.1"
es-get-iterator "^1.1.0"
is-arguments "^1.0.4"
is-date-object "^1.0.2"
is-regex "^1.1.1"
isarray "^2.0.5"
object-is "^1.1.3"
object-keys "^1.1.1"
object.assign "^4.1.1"
regexp.prototype.flags "^1.3.0"
side-channel "^1.0.3"
which-boxed-primitive "^1.0.1"
which-collection "^1.0.1"
which-typed-array "^1.1.2"
deep-equal@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
@ -2073,6 +2123,54 @@ es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5:
string.prototype.trimleft "^2.1.1"
string.prototype.trimright "^2.1.1"
es-abstract@^1.17.4:
version "1.17.7"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c"
integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==
dependencies:
es-to-primitive "^1.2.1"
function-bind "^1.1.1"
has "^1.0.3"
has-symbols "^1.0.1"
is-callable "^1.2.2"
is-regex "^1.1.1"
object-inspect "^1.8.0"
object-keys "^1.1.1"
object.assign "^4.1.1"
string.prototype.trimend "^1.0.1"
string.prototype.trimstart "^1.0.1"
es-abstract@^1.18.0-next.0, es-abstract@^1.18.0-next.1:
version "1.18.0-next.1"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68"
integrity sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==
dependencies:
es-to-primitive "^1.2.1"
function-bind "^1.1.1"
has "^1.0.3"
has-symbols "^1.0.1"
is-callable "^1.2.2"
is-negative-zero "^2.0.0"
is-regex "^1.1.1"
object-inspect "^1.8.0"
object-keys "^1.1.1"
object.assign "^4.1.1"
string.prototype.trimend "^1.0.1"
string.prototype.trimstart "^1.0.1"
es-get-iterator@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.0.tgz#bb98ad9d6d63b31aacdc8f89d5d0ee57bcb5b4c8"
integrity sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==
dependencies:
es-abstract "^1.17.4"
has-symbols "^1.0.1"
is-arguments "^1.0.4"
is-map "^2.0.1"
is-set "^2.0.1"
is-string "^1.0.5"
isarray "^2.0.5"
es-to-primitive@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a"
@ -3112,6 +3210,11 @@ is-accessor-descriptor@^1.0.0:
dependencies:
kind-of "^6.0.0"
is-arguments@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3"
integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==
is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
@ -3121,12 +3224,22 @@ is-arrayish@^0.3.1:
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03"
integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==
is-bigint@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.0.tgz#73da8c33208d00f130e9b5e15d23eac9215601c4"
integrity sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g==
is-binary-path@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
dependencies:
binary-extensions "^2.0.0"
is-boolean-object@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.1.tgz#10edc0900dd127697a92f6f9807c7617d68ac48e"
integrity sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ==
is-buffer@^1.1.5:
version "1.1.6"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
@ -3135,6 +3248,11 @@ is-callable@^1.1.4, is-callable@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab"
is-callable@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9"
integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==
is-ci@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c"
@ -3157,7 +3275,7 @@ is-data-descriptor@^1.0.0:
dependencies:
kind-of "^6.0.0"
is-date-object@^1.0.1:
is-date-object@^1.0.1, is-date-object@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e"
@ -3227,15 +3345,30 @@ is-installed-globally@^0.3.1:
global-dirs "^2.0.1"
is-path-inside "^3.0.1"
is-map@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.1.tgz#520dafc4307bb8ebc33b813de5ce7c9400d644a1"
integrity sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==
is-natural-number@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8"
integrity sha1-q5124dtM7VHjXeDHLr7PCfc0zeg=
is-negative-zero@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461"
integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=
is-npm@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d"
is-number-object@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197"
integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==
is-number@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195"
@ -3276,15 +3409,32 @@ is-regex@^1.0.5:
dependencies:
has "^1.0.3"
is-regex@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9"
integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==
dependencies:
has-symbols "^1.0.1"
is-retry-allowed@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4"
integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==
is-set@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.1.tgz#d1604afdab1724986d30091575f54945da7e5f43"
integrity sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==
is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
is-string@^1.0.4, is-string@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6"
integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==
is-symbol@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937"
@ -3299,10 +3449,35 @@ is-type-of@^1.0.0:
is-class-hotfix "~0.0.6"
isstream "~0.1.2"
is-typed-array@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.3.tgz#a4ff5a5e672e1a55f99c7f54e59597af5c1df04d"
integrity sha512-BSYUBOK/HJibQ30wWkWold5txYwMUXQct9YHAQJr8fSwvZoiglcqB0pd7vEN23+Tsi9IUEjztdOSzl4qLVYGTQ==
dependencies:
available-typed-arrays "^1.0.0"
es-abstract "^1.17.4"
foreach "^2.0.5"
has-symbols "^1.0.1"
is-typedarray@^1.0.0, is-typedarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
is-utf8@^0.2.0:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=
is-weakmap@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2"
integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==
is-weakset@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.1.tgz#e9a0af88dbd751589f5e50d80f4c98b780884f83"
integrity sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw==
is-windows@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
@ -3323,6 +3498,11 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
isarray@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
isbinaryfile@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.6.tgz#edcb62b224e2b4710830b67498c8e4e5a4d2610b"
@ -4180,6 +4360,13 @@ lie@3.0.4:
inline-process-browser "^1.0.0"
unreachable-branch-transform "^0.3.0"
lie@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
integrity sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=
dependencies:
immediate "~3.0.5"
lines-and-columns@^1.1.6:
version "1.1.6"
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
@ -4242,6 +4429,11 @@ lodash.once@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
lodash.pick@^4.0.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"
integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=
lodash.sortby@^4.7.0:
version "4.7.0"
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
@ -4250,6 +4442,11 @@ lodash@^4.17.10, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15:
version "4.17.19"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
lodash@^4.17.3:
version "4.17.20"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
loose-envify@^1.0.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
@ -4504,6 +4701,16 @@ natural-compare@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
ndjson@^1.4.3:
version "1.5.0"
resolved "https://registry.yarnpkg.com/ndjson/-/ndjson-1.5.0.tgz#ae603b36b134bcec347b452422b0bf98d5832ec8"
integrity sha1-rmA7NrE0vOw0e0UkIrC/mNWDLsg=
dependencies:
json-stringify-safe "^5.0.1"
minimist "^1.2.0"
split2 "^2.1.0"
through2 "^2.0.3"
negotiator@0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
@ -4668,6 +4875,19 @@ object-inspect@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67"
object-inspect@^1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0"
integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==
object-is@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.3.tgz#2e3b9e65560137455ee3bd62aec4d90a2ea1cc81"
integrity sha512-teyqLvFWzLkq5B9ki8FVWA902UER2qkxmdA4nLf+wjOLAWgxzCWZNCxpDq9MvE8MmhWNr+I8w3BN49Vx36Y6Xg==
dependencies:
define-properties "^1.1.3"
es-abstract "^1.18.0-next.1"
object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.0.6, object-keys@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
@ -4687,6 +4907,16 @@ object.assign@^4.1.0:
has-symbols "^1.0.0"
object-keys "^1.0.11"
object.assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.1.tgz#303867a666cdd41936ecdedfb1f8f3e32a478cdd"
integrity sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==
dependencies:
define-properties "^1.1.3"
es-abstract "^1.18.0-next.0"
has-symbols "^1.0.1"
object-keys "^1.1.1"
object.getownpropertydescriptors@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649"
@ -4974,6 +5204,14 @@ posix-character-classes@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
pouch-stream@^0.4.0:
version "0.4.1"
resolved "https://registry.yarnpkg.com/pouch-stream/-/pouch-stream-0.4.1.tgz#0c6d8475c9307677627991a2f079b301c3b89bdd"
integrity sha1-DG2EdckwdndieZGi8HmzAcO4m90=
dependencies:
inherits "^2.0.1"
readable-stream "^1.0.27-1"
pouchdb-adapter-leveldb-core@7.2.1:
version "7.2.1"
resolved "https://registry.yarnpkg.com/pouchdb-adapter-leveldb-core/-/pouchdb-adapter-leveldb-core-7.2.1.tgz#71bf2a05755689e2b05e78e796003a18ebf65a69"
@ -5061,6 +5299,26 @@ pouchdb-promise@5.4.3:
dependencies:
lie "3.0.4"
pouchdb-promise@^6.0.4:
version "6.4.3"
resolved "https://registry.yarnpkg.com/pouchdb-promise/-/pouchdb-promise-6.4.3.tgz#74516f4acf74957b54debd0fb2c0e5b5a68ca7b3"
integrity sha512-ruJaSFXwzsxRHQfwNHjQfsj58LBOY1RzGzde4PM5CWINZwFjCQAhZwfMrch2o/0oZT6d+Xtt0HTWhq35p3b0qw==
dependencies:
lie "3.1.1"
pouchdb-replication-stream@^1.2.9:
version "1.2.9"
resolved "https://registry.yarnpkg.com/pouchdb-replication-stream/-/pouchdb-replication-stream-1.2.9.tgz#aa4fa5d8f52df4825392f18e07c7e11acffc650a"
integrity sha1-qk+l2PUt9IJTkvGOB8fhGs/8ZQo=
dependencies:
argsarray "0.0.1"
inherits "^2.0.3"
lodash.pick "^4.0.0"
ndjson "^1.4.3"
pouch-stream "^0.4.0"
pouchdb-promise "^6.0.4"
through2 "^2.0.0"
pouchdb-utils@7.2.1:
version "7.2.1"
resolved "https://registry.yarnpkg.com/pouchdb-utils/-/pouchdb-utils-7.2.1.tgz#5dec1c53c8ecba717e5762311e9a1def2d4ebf9c"
@ -5321,7 +5579,17 @@ readable-stream@1.0.33:
isarray "0.0.1"
string_decoder "~0.10.x"
readable-stream@^2.0.0, readable-stream@^2.0.6, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.5:
readable-stream@^1.0.27-1:
version "1.1.14"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk=
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.1"
isarray "0.0.1"
string_decoder "~0.10.x"
readable-stream@^2.0.0, readable-stream@^2.0.6, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.5, readable-stream@~2.3.6:
version "2.3.7"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
dependencies:
@ -5374,6 +5642,19 @@ regex-not@^1.0.0, regex-not@^1.0.2:
extend-shallow "^3.0.2"
safe-regex "^1.1.0"
regexp.prototype.flags@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz#7aba89b3c13a64509dabcf3ca8d9fbb9bdf5cb75"
integrity sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==
dependencies:
define-properties "^1.1.3"
es-abstract "^1.17.0-next.1"
regexparam@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/regexparam/-/regexparam-1.3.0.tgz#2fe42c93e32a40eff6235d635e0ffa344b92965f"
integrity sha512-6IQpFBv6e5vz1QAqI+V4k8P2e/3gRrqfCJ9FI+O1FLQTO+Uz6RXZEZOPmTJ6hlGj7gkERzY5BRCv09whKP96/g==
regexpp@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f"
@ -5692,6 +5973,14 @@ shellwords@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b"
side-channel@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.3.tgz#cdc46b057550bbab63706210838df5d4c19519c3"
integrity sha512-A6+ByhlLkksFoUepsGxfj5x1gTSrs+OydsRptUxeNCabQpCFUvcwIczgOigI8vhY/OJCnPnyE9rGiwgvr9cS1g==
dependencies:
es-abstract "^1.18.0-next.0"
object-inspect "^1.8.0"
signal-exit@^3.0.0, signal-exit@^3.0.2:
version "3.0.3"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
@ -5877,6 +6166,13 @@ split-string@^3.0.1, split-string@^3.0.2:
dependencies:
extend-shallow "^3.0.0"
split2@^2.1.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/split2/-/split2-2.2.0.tgz#186b2575bcf83e85b7d18465756238ee4ee42493"
integrity sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==
dependencies:
through2 "^2.0.2"
split2@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/split2/-/split2-3.1.1.tgz#c51f18f3e06a8c4469aaab487687d8d956160bb6"
@ -5981,7 +6277,7 @@ string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0:
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.0"
string.prototype.trimend@^1.0.0:
string.prototype.trimend@^1.0.0, string.prototype.trimend@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913"
dependencies:
@ -6004,7 +6300,7 @@ string.prototype.trimright@^2.1.1:
es-abstract "^1.17.5"
string.prototype.trimend "^1.0.0"
string.prototype.trimstart@^1.0.0:
string.prototype.trimstart@^1.0.0, string.prototype.trimstart@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54"
dependencies:
@ -6051,6 +6347,13 @@ strip-ansi@^6.0.0:
dependencies:
ansi-regex "^5.0.0"
strip-bom@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=
dependencies:
is-utf8 "^0.2.0"
strip-bom@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
@ -6234,6 +6537,14 @@ through2@^0.6.2, through2@^0.6.5:
readable-stream ">=1.0.33-1 <1.1.0-0"
xtend ">=4.0.0 <4.1.0-0"
through2@^2.0.0, through2@^2.0.2, through2@^2.0.3:
version "2.0.5"
resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==
dependencies:
readable-stream "~2.3.6"
xtend "~4.0.1"
through@^2.3.6, through@^2.3.8, through@~2.3.4:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
@ -6611,6 +6922,27 @@ whatwg-url@^7.0.0:
tr46 "^1.0.1"
webidl-conversions "^4.0.2"
which-boxed-primitive@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz#cbe8f838ebe91ba2471bb69e9edbda67ab5a5ec1"
integrity sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ==
dependencies:
is-bigint "^1.0.0"
is-boolean-object "^1.0.0"
is-number-object "^1.0.3"
is-string "^1.0.4"
is-symbol "^1.0.2"
which-collection@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906"
integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==
dependencies:
is-map "^2.0.1"
is-set "^2.0.1"
is-weakmap "^2.0.1"
is-weakset "^2.0.1"
which-module@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
@ -6620,6 +6952,18 @@ which-pm-runs@^1.0.0:
resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb"
integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=
which-typed-array@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.2.tgz#e5f98e56bda93e3dac196b01d47c1156679c00b2"
integrity sha512-KT6okrd1tE6JdZAy3o2VhMoYPh3+J6EMZLyrxBQsZflI1QCZIxMrIYLkosd8Twf+YfknVIHmYQPgJt238p8dnQ==
dependencies:
available-typed-arrays "^1.0.2"
es-abstract "^1.17.5"
foreach "^2.0.5"
function-bind "^1.1.1"
has-symbols "^1.0.1"
is-typed-array "^1.1.3"
which@^1.2.9, which@^1.3.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
@ -6724,7 +7068,7 @@ xmlbuilder@~9.0.1:
version "9.0.7"
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@^4.0.2, xtend@~4.0.0:
"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@^4.0.2, xtend@~4.0.0, xtend@~4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,7 @@
"dev:builder": "rollup -cw"
},
"devDependencies": {
"@budibase/client": "^0.1.23",
"@budibase/client": "^0.1.25",
"@rollup/plugin-commonjs": "^11.1.0",
"lodash": "^4.17.15",
"rollup": "^1.11.0",
@ -31,7 +31,7 @@
"keywords": [
"svelte"
],
"version": "0.1.23",
"version": "0.1.25",
"license": "MIT",
"gitHead": "284cceb9b703c38566c6e6363c022f79a08d5691",
"dependencies": {