@dxos/echo-db 2.31.5-dev.573190a2 → 2.31.6-dev.3f2c3477

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,179 +1,96 @@
1
1
  //
2
- // Copyright 2022 DXOS.org
2
+ // Copyright 2021 DXOS.org
3
3
  //
4
4
 
5
- import chalk from 'chalk';
6
- import columnify from 'columnify';
7
5
  import expect from 'expect';
8
- import faker from 'faker';
6
+ import { it as test } from 'mocha';
9
7
 
10
- import { truncate, truncateKey } from '@dxos/debug';
8
+ import { ModelFactory } from '@dxos/model-factory';
11
9
  import { ObjectModel } from '@dxos/object-model';
10
+ import { afterTest } from '@dxos/testutils';
12
11
 
13
- import { createData, createSchemas, log, SchemaDefWithGenerator, setup } from '../testing';
12
+ import { createInMemoryDatabase } from '../database';
14
13
  import { Database } from './database';
15
- import { Item } from './item';
16
- import { Schema, SchemaField, TYPE_SCHEMA } from './schema';
17
-
18
- enum TestType {
19
- Org = 'example:type/org',
20
- Person = 'example:type/person'
21
- }
22
-
23
- const schemaDefs: { [schema: string]: SchemaDefWithGenerator } = {
24
- [TestType.Org]: {
25
- schema: 'example:type/schema/organization',
26
- fields: [
27
- {
28
- key: 'title',
29
- required: true,
30
- generator: () => faker.company.companyName()
31
- },
32
- {
33
- key: 'website',
34
- required: false,
35
- generator: () => faker.internet.url()
36
- },
37
- {
38
- key: 'collaborators',
39
- required: false,
40
- generator: () => faker.datatype.number().toString()
41
- }
42
- ]
43
- },
44
- [TestType.Person]: {
45
- schema: 'example:type/schema/person',
46
- fields: [
47
- {
48
- key: 'title',
49
- required: true,
50
- generator: () => `${faker.name.firstName()} ${faker.name.lastName()}`
51
- }
52
- ]
53
- }
14
+ import { Schema, TYPE_SCHEMA } from './schema';
15
+
16
+ const SCHEMA = 'example:type/schema/organization';
17
+ const createTestSchema = async (database: Database) => {
18
+ const schemaItem = await database.createItem({
19
+ model: ObjectModel,
20
+ type: TYPE_SCHEMA,
21
+ props: {
22
+ schema: SCHEMA
23
+ }
24
+ });
25
+ return new Schema(schemaItem.model);
54
26
  };
55
27
 
56
- describe('Schemas', () => {
57
- it('creation of Schema', async () => setup(async (database) => {
58
- const [schema] = await createSchemas(database, [schemaDefs[TestType.Org]]);
59
- expect(schema.schema).toBe(schemaDefs[TestType.Org].schema);
60
- expect(schema.fields[0].key).toBe('title');
61
- }));
62
-
63
- it('add Schema field', async () => setup(async (database) => {
64
- const [schema] = await createSchemas(database, [schemaDefs[TestType.Org]]);
28
+ describe('Schema', () => {
29
+ const setup = async () => {
30
+ const modelFactory = new ModelFactory().registerModel(ObjectModel);
31
+ const backend = await createInMemoryDatabase(modelFactory);
32
+ afterTest(() => backend.destroy());
33
+ return backend;
34
+ };
35
+
36
+ test('class creation', async () => {
37
+ const database = await setup();
38
+ const schema = await createTestSchema(database);
39
+ expect(schema).toBeTruthy();
40
+ expect(schema.schema).toBeTruthy();
41
+ });
65
42
 
66
- const newField: SchemaField = {
67
- key: 'location',
43
+ test('add and delete field', async () => {
44
+ const database = await setup();
45
+ const key = 'name';
46
+ const schema = await createTestSchema(database);
47
+ const newField = {
48
+ key: key,
68
49
  required: true
69
50
  };
70
51
  await schema.addField(newField);
52
+ expect(schema.fields.length).toBe(1);
53
+ expect(schema.getField(key)).toBeTruthy();
54
+ await schema.deleteField(key);
55
+ expect(schema.fields.length).toBe(0);
56
+ });
71
57
 
72
- expect(schema.getField('location')).toBeTruthy();
73
- }));
74
-
75
- it('add Schema linked field', async () => setup(async (database) => {
76
- const [orgSchema, personSchema] = await createSchemas(database, Object.values(schemaDefs));
77
-
78
- const fieldRef: SchemaField = {
79
- key: 'organization',
80
- required: false,
81
- ref: {
82
- schema: orgSchema.schema,
83
- field: orgSchema.fields[0].key
84
- }
58
+ test('edit field', async () => {
59
+ const database = await setup();
60
+ const key = 'name';
61
+ const schema = await createTestSchema(database);
62
+ const newField = {
63
+ key: key,
64
+ required: true
85
65
  };
86
- await personSchema.addField(fieldRef);
87
-
88
- await createData(database, Object.values(schemaDefs), {
89
- [schemaDefs[TestType.Org].schema]: 8,
90
- [schemaDefs[TestType.Person].schema]: 16
91
- });
92
-
93
- const items = await database.select().exec().entities;
94
-
95
- [orgSchema, personSchema].forEach(schema => {
96
- items.forEach(item => {
97
- expect(schema.validate(item.model)).toBeTruthy();
98
- });
99
- });
100
- }));
101
-
102
- it('Use schema to validate the fields of an item', () => setup(async (database) => {
103
- await createSchemas(database, Object.values(schemaDefs));
104
- await createData(database, Object.values(schemaDefs), {
105
- [schemaDefs[TestType.Org].schema]: 8,
106
- [schemaDefs[TestType.Person].schema]: 16
107
- });
108
-
109
- const { entities: schemas } = database
110
- .select({ type: TYPE_SCHEMA })
111
- .exec();
112
-
113
- const { entities: orgs } = database
114
- .select({ type: TestType.Org })
115
- .exec();
116
-
117
- const { entities: people } = database
118
- .select({ type: TestType.Person })
119
- .exec();
120
-
121
- [...orgs, ...people].forEach(item => {
122
- const schemaItem = schemas.find(schema => schema.model.get('schema') === item.type);
123
- const schema = new Schema(schemaItem!.model);
124
- expect(schema.validate(item.model)).toBeTruthy();
125
- });
66
+ await schema.addField(newField);
67
+ newField.required = false;
68
+ await schema.editField(key, newField);
69
+ expect(schema.getField(key)?.required).toBeFalsy();
70
+ });
126
71
 
127
- // Log tables.
128
- schemas.forEach(schema => {
129
- const type = schema.model.get('schema');
130
- const { entities: items } = database.select({ type }).exec();
131
- log(renderItems(schema, items, database));
72
+ test('validate data item', async () => {
73
+ const database = await setup();
74
+ const key = 'name';
75
+ const schema = await createTestSchema(database);
76
+ const firstField = {
77
+ key: key,
78
+ required: true
79
+ };
80
+ await schema.addField(firstField);
81
+ const item = await database.createItem({
82
+ model: ObjectModel,
83
+ type: schema.schema
132
84
  });
133
- }));
134
- });
135
-
136
- /**
137
- * Log the items for the given schema.
138
- * @param schema
139
- * @param items
140
- * @param [party]
141
- */
142
- const renderItems = (schema: Item<ObjectModel>, items: Item<ObjectModel>[], database?: Database) => {
143
- const fields = Object.values(schema.model.get('fields')) as SchemaField[];
144
- const columns = fields.map(({ key }) => key);
145
-
146
- const logKey = (id: string) => truncateKey(id, 4);
147
- const logString = (value: string) => truncate(value, 24, true);
148
-
149
- const values = items.map(item => {
150
- return fields.reduce<{ [key: string]: any }>((row, { key, type, ref }) => {
151
- const value = item.model.get(key);
152
- switch (type) {
153
- case 'string': {
154
- row[key] = chalk.green(logString(value));
155
- break;
156
- }
85
+ expect(schema.validate(item.model)).toBeFalsy();
157
86
 
158
- case 'ref': {
159
- if (database) {
160
- const { field } = ref!;
161
- const item = database.getItem(value);
162
- row[key] = chalk.red(logString(item?.model.get(field)));
163
- } else {
164
- row[key] = chalk.red(logKey(value));
165
- }
166
- break;
167
- }
87
+ firstField.required = false;
88
+ await schema.editField(key, firstField);
89
+ expect(schema.validate(item.model)).toBeTruthy();
168
90
 
169
- default: {
170
- row[key] = value;
171
- }
172
- }
173
-
174
- return row;
175
- }, { id: chalk.blue(logKey(item.id)) });
91
+ firstField.required = true;
92
+ await schema.editField(key, firstField);
93
+ await item.model.set(key, 'Test');
94
+ expect(schema.validate(item.model)).toBeTruthy();
176
95
  });
177
-
178
- return columnify(values, { columns: ['id', ...columns] });
179
- };
96
+ });
package/src/api/schema.ts CHANGED
@@ -49,7 +49,7 @@ export class Schema {
49
49
 
50
50
  // TODO(kaplanski): What happens if an item has extra properties?
51
51
  validate (model: ObjectModel) {
52
- this.fields.forEach(field => {
52
+ return this.fields.every(field => {
53
53
  const value = model.get(field.key);
54
54
  if (field.required) {
55
55
  if (!value) {
@@ -67,8 +67,8 @@ export class Schema {
67
67
  // TODO(kaplanski): Should this class have access to all items in the party to validate?
68
68
  // Or maybe possible values should be provided?
69
69
  }
70
+ return true;
70
71
  });
71
- return true;
72
72
  }
73
73
 
74
74
  // TODO(kaplanski): Should the field be added to each item using the schema in the party? (Empty value?)
@@ -3,17 +3,12 @@
3
3
  //
4
4
 
5
5
  import debug from 'debug';
6
- import faker from 'faker';
7
6
 
8
7
  import { createKeyPair } from '@dxos/crypto';
9
- import { ModelFactory } from '@dxos/model-factory';
10
8
  import { NetworkManagerOptions } from '@dxos/network-manager';
11
- import { ObjectModel } from '@dxos/object-model';
12
9
  import { IStorage } from '@dxos/random-access-multi-storage';
13
10
  import { jsonReplacer } from '@dxos/util';
14
11
 
15
- import { Database, Schema, SchemaDef, SchemaField, TYPE_SCHEMA } from '../api';
16
- import { createInMemoryDatabase } from '../database';
17
12
  import { ECHO } from '../echo';
18
13
  import { PartyInternal } from '../parties';
19
14
  import { createRamStorage } from '../util';
@@ -84,90 +79,3 @@ export const inviteTestPeer = async (party: PartyInternal, peer: ECHO): Promise<
84
79
 
85
80
  return peer.joinParty(invitation, async () => Buffer.from('0000'));
86
81
  };
87
-
88
- export type SchemaFieldWithGenerator = SchemaField & { generator: () => string }
89
- export type SchemaDefWithGenerator = Omit<SchemaDef, 'fields'> & { fields: SchemaFieldWithGenerator[] };
90
-
91
- type Callback = (party: Database) => Promise<void>
92
-
93
- export const setup = async (callback: Callback) => {
94
- const modelFactory = new ModelFactory().registerModel(ObjectModel);
95
- const database = await createInMemoryDatabase(modelFactory);
96
- try {
97
- await callback(database);
98
- } finally {
99
- await database.destroy();
100
- }
101
- };
102
-
103
- /**
104
- * Create schema items.
105
- */
106
- export const createSchemas = async (database: Database, schemas: SchemaDefWithGenerator[]) => {
107
- log(`Creating schemas: [${schemas.map(({ schema }) => schema).join()}]`);
108
-
109
- const schemaItems = await Promise.all(schemas.map(({ schema, fields }) => {
110
- const schemaFields = fields.map(fieldWithGenerator => {
111
- // eslint-disable-next-line unused-imports/no-unused-vars
112
- const { generator, ...field } = fieldWithGenerator;
113
- return field;
114
- }).flat();
115
-
116
- return database.createItem({
117
- model: ObjectModel,
118
- type: TYPE_SCHEMA,
119
- props: {
120
- schema,
121
- fields: schemaFields
122
- }
123
- });
124
- }));
125
-
126
- return schemaItems.map(item => new Schema(item.model));
127
- };
128
-
129
- /**
130
- * Create items for a given schema.
131
- * NOTE: Assumes that referenced items have already been constructed.
132
- */
133
- export const createItems = async (database: Database, { schema, fields }: SchemaDefWithGenerator, numItems: number) => {
134
- log(`Creating items for: ${schema}`);
135
-
136
- return await Promise.all(Array.from({ length: numItems }).map(async () => {
137
- const values = fields.map(field => {
138
- if (field.ref) {
139
- // Look-up item.
140
- const { entities: items } = database.select().filter({ type: field.ref.schema }).exec();
141
- if (items.length) {
142
- return {
143
- [field.key]: faker.random.arrayElement(items).id
144
- };
145
- }
146
- } else {
147
- return {
148
- [field.key]: field.generator()
149
- };
150
- }
151
-
152
- return undefined;
153
- }).filter(Boolean);
154
-
155
- return await database.createItem({
156
- type: schema,
157
- props: Object.assign({}, ...values)
158
- });
159
- }));
160
- };
161
-
162
- /**
163
- * Create data for all schemas.
164
- */
165
- export const createData = async (database: Database, schemas: SchemaDefWithGenerator[], options: { [key: string]: number } = {}) => {
166
- // Synchronous loop.
167
- for (const schema of schemas) {
168
- const count = options[schema.schema] ?? 0;
169
- if (count) {
170
- await createItems(database, schema, count);
171
- }
172
- }
173
- };