@based/db 0.0.35 → 0.0.37

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.
Files changed (55) hide show
  1. package/dist/lib/darwin_aarch64/include/selva/fields.h +11 -0
  2. package/dist/lib/darwin_aarch64/include/selva/types.h +2 -0
  3. package/dist/lib/darwin_aarch64/include/selva_lang_code.h +1 -0
  4. package/dist/lib/darwin_aarch64/libdeflate.dylib +0 -0
  5. package/dist/lib/darwin_aarch64/libjemalloc_selva.2.dylib +0 -0
  6. package/dist/lib/darwin_aarch64/libnode-v20.node +0 -0
  7. package/dist/lib/darwin_aarch64/libnode-v21.node +0 -0
  8. package/dist/lib/darwin_aarch64/libnode-v22.node +0 -0
  9. package/dist/lib/darwin_aarch64/libnode-v23.node +0 -0
  10. package/dist/lib/darwin_aarch64/libselva.dylib +0 -0
  11. package/dist/lib/linux_aarch64/include/selva/fields.h +11 -0
  12. package/dist/lib/linux_aarch64/include/selva/types.h +2 -0
  13. package/dist/lib/linux_aarch64/include/selva_lang_code.h +1 -0
  14. package/dist/lib/linux_aarch64/libjemalloc_selva.so.2 +0 -0
  15. package/dist/lib/linux_aarch64/libnode-v20.node +0 -0
  16. package/dist/lib/linux_aarch64/libnode-v21.node +0 -0
  17. package/dist/lib/linux_aarch64/libnode-v22.node +0 -0
  18. package/dist/lib/linux_aarch64/libnode-v23.node +0 -0
  19. package/dist/lib/linux_aarch64/libselva.so +0 -0
  20. package/dist/lib/linux_x86_64/include/selva/fields.h +11 -0
  21. package/dist/lib/linux_x86_64/include/selva/types.h +2 -0
  22. package/dist/lib/linux_x86_64/include/selva_lang_code.h +1 -0
  23. package/dist/lib/linux_x86_64/libnode-v20.node +0 -0
  24. package/dist/lib/linux_x86_64/libnode-v21.node +0 -0
  25. package/dist/lib/linux_x86_64/libnode-v22.node +0 -0
  26. package/dist/lib/linux_x86_64/libnode-v23.node +0 -0
  27. package/dist/lib/linux_x86_64/libselva.so +0 -0
  28. package/dist/src/client/index.d.ts +2 -1
  29. package/dist/src/client/index.js +30 -33
  30. package/dist/src/client/modify/fixed.js +1 -0
  31. package/dist/src/client/query/BasedDbQuery.d.ts +2 -1
  32. package/dist/src/client/query/BasedDbQuery.js +11 -4
  33. package/dist/src/client/query/BasedIterable.d.ts +2 -1
  34. package/dist/src/client/query/BasedIterable.js +4 -16
  35. package/dist/src/client/query/aggregates/aggregation.d.ts +5 -0
  36. package/dist/src/client/query/aggregates/aggregation.js +96 -0
  37. package/dist/src/client/query/aggregates/types.d.ts +7 -0
  38. package/dist/src/client/query/aggregates/types.js +2 -0
  39. package/dist/src/client/query/display.js +1 -10
  40. package/dist/src/client/query/query.d.ts +1 -1
  41. package/dist/src/client/query/query.js +1 -1
  42. package/dist/src/client/query/queryDef.js +1 -1
  43. package/dist/src/client/query/read/read.d.ts +1 -1
  44. package/dist/src/client/query/read/read.js +63 -65
  45. package/dist/src/client/query/toBuffer.js +38 -7
  46. package/dist/src/client/query/types.d.ts +13 -19
  47. package/dist/src/client/query/types.js +0 -2
  48. package/dist/src/index.d.ts +1 -1
  49. package/dist/src/index.js +4 -1
  50. package/dist/src/server/index.js +15 -3
  51. package/dist/src/utils.d.ts +1 -0
  52. package/dist/src/utils.js +47 -0
  53. package/package.json +3 -3
  54. package/dist/src/client/query/aggregation.d.ts +0 -3
  55. package/dist/src/client/query/aggregation.js +0 -9
@@ -4,6 +4,7 @@
4
4
  */
5
5
  #pragma once
6
6
 
7
+ #include <stdint.h>
7
8
  #include <sys/types.h>
8
9
  #include "selva/_export.h"
9
10
  #ifdef __zig
@@ -81,6 +82,16 @@ struct SelvaFieldsPointer {
81
82
  size_t len;
82
83
  };
83
84
 
85
+ /**
86
+ * Precalculated empty strings for text translations.
87
+ * The size of the string is 8 for better alignment but they are only
88
+ * filled upto 6 bytes.
89
+ */
90
+ SELVA_EXPORT
91
+ extern const uint8_t selva_fields_text_tl_empty[_selva_lang_last][8];
92
+
93
+ #define SELVA_FIELDS_TEXT_TL_EMPTY_LEN 6
94
+
84
95
  #if __has_c_attribute(unsequenced)
85
96
  [[unsequenced]]
86
97
  #else
@@ -45,6 +45,8 @@ struct EdgeFieldConstraint {
45
45
  * Skip saving this field while dumping.
46
46
  * If the field is of type SELVA_FIELD_TYPE_REFERENCES it's saved
47
47
  * regardless of this flag to preserve the original order of references.
48
+ * However, the meta is only save from one side, i.e. the side that's
49
+ * not skipped.
48
50
  */
49
51
  EDGE_FIELD_CONSTRAINT_FLAG_SKIP_DUMP = 0x80,
50
52
  } __packed flags;
@@ -152,6 +152,7 @@ enum selva_lang_code {
152
152
  selva_lang_yi,
153
153
  selva_lang_yo,
154
154
  selva_lang_zu,
155
+ _selva_lang_last,
155
156
  } __packed;
156
157
 
157
158
  #endif /* SELVA_LANG_CODE_H */
@@ -4,6 +4,7 @@
4
4
  */
5
5
  #pragma once
6
6
 
7
+ #include <stdint.h>
7
8
  #include <sys/types.h>
8
9
  #include "selva/_export.h"
9
10
  #ifdef __zig
@@ -81,6 +82,16 @@ struct SelvaFieldsPointer {
81
82
  size_t len;
82
83
  };
83
84
 
85
+ /**
86
+ * Precalculated empty strings for text translations.
87
+ * The size of the string is 8 for better alignment but they are only
88
+ * filled upto 6 bytes.
89
+ */
90
+ SELVA_EXPORT
91
+ extern const uint8_t selva_fields_text_tl_empty[_selva_lang_last][8];
92
+
93
+ #define SELVA_FIELDS_TEXT_TL_EMPTY_LEN 6
94
+
84
95
  #if __has_c_attribute(unsequenced)
85
96
  [[unsequenced]]
86
97
  #else
@@ -45,6 +45,8 @@ struct EdgeFieldConstraint {
45
45
  * Skip saving this field while dumping.
46
46
  * If the field is of type SELVA_FIELD_TYPE_REFERENCES it's saved
47
47
  * regardless of this flag to preserve the original order of references.
48
+ * However, the meta is only save from one side, i.e. the side that's
49
+ * not skipped.
48
50
  */
49
51
  EDGE_FIELD_CONSTRAINT_FLAG_SKIP_DUMP = 0x80,
50
52
  } __packed flags;
@@ -152,6 +152,7 @@ enum selva_lang_code {
152
152
  selva_lang_yi,
153
153
  selva_lang_yo,
154
154
  selva_lang_zu,
155
+ _selva_lang_last,
155
156
  } __packed;
156
157
 
157
158
  #endif /* SELVA_LANG_CODE_H */
Binary file
@@ -4,6 +4,7 @@
4
4
  */
5
5
  #pragma once
6
6
 
7
+ #include <stdint.h>
7
8
  #include <sys/types.h>
8
9
  #include "selva/_export.h"
9
10
  #ifdef __zig
@@ -81,6 +82,16 @@ struct SelvaFieldsPointer {
81
82
  size_t len;
82
83
  };
83
84
 
85
+ /**
86
+ * Precalculated empty strings for text translations.
87
+ * The size of the string is 8 for better alignment but they are only
88
+ * filled upto 6 bytes.
89
+ */
90
+ SELVA_EXPORT
91
+ extern const uint8_t selva_fields_text_tl_empty[_selva_lang_last][8];
92
+
93
+ #define SELVA_FIELDS_TEXT_TL_EMPTY_LEN 6
94
+
84
95
  #if __has_c_attribute(unsequenced)
85
96
  [[unsequenced]]
86
97
  #else
@@ -45,6 +45,8 @@ struct EdgeFieldConstraint {
45
45
  * Skip saving this field while dumping.
46
46
  * If the field is of type SELVA_FIELD_TYPE_REFERENCES it's saved
47
47
  * regardless of this flag to preserve the original order of references.
48
+ * However, the meta is only save from one side, i.e. the side that's
49
+ * not skipped.
48
50
  */
49
51
  EDGE_FIELD_CONSTRAINT_FLAG_SKIP_DUMP = 0x80,
50
52
  } __packed flags;
@@ -152,6 +152,7 @@ enum selva_lang_code {
152
152
  selva_lang_yi,
153
153
  selva_lang_yo,
154
154
  selva_lang_zu,
155
+ _selva_lang_last,
155
156
  } __packed;
156
157
 
157
158
  #endif /* SELVA_LANG_CODE_H */
Binary file
@@ -33,7 +33,6 @@ export declare class DbClient {
33
33
  flushIsReady: Promise<void>;
34
34
  hooks: DbClientHooks;
35
35
  schema: DbClientSchema;
36
- schemaIsSetValue: boolean;
37
36
  schemaTypesParsed: Record<string, SchemaTypeDef>;
38
37
  schemaTypesParsedById: Record<number, SchemaTypeDef>;
39
38
  writeTime: number;
@@ -45,6 +44,8 @@ export declare class DbClient {
45
44
  p: Promise<number | ModifyRes>;
46
45
  }>;
47
46
  schemaChecksum: number;
47
+ schemaProcessing: number;
48
+ schemaPromise: Promise<DbServer['schema']>;
48
49
  setSchema(schema: Schema, fromStart?: boolean, transformFns?: TransformFns): Promise<StrictSchema>;
49
50
  putLocalSchema(schema: any): DbClientSchema;
50
51
  create(type: string, obj?: CreateObj, opts?: ModifyOpts): ModifyRes;
@@ -7,10 +7,10 @@ import { ModifyState } from './modify/ModifyRes.js';
7
7
  import { upsert } from './modify/upsert.js';
8
8
  import { update } from './modify/update.js';
9
9
  import { deleteFn } from './modify/delete.js';
10
- import { deepEqual } from '@saulx/utils';
10
+ import { wait } from '@saulx/utils';
11
11
  import { hash } from '@saulx/hash';
12
12
  import { expire } from './modify/expire.js';
13
- import { debugMode } from '../utils.js';
13
+ import { debugMode, schemaLooseEqual } from '../utils.js';
14
14
  const makeFlushIsReady = (dbClient) => {
15
15
  dbClient.flushIsReady = new Promise((resolve) => {
16
16
  dbClient.flushReady = () => {
@@ -34,11 +34,7 @@ export class DbClient {
34
34
  flushReady;
35
35
  flushIsReady;
36
36
  hooks;
37
- schema = {
38
- lastId: 1, // we reserve one for root props
39
- types: {},
40
- };
41
- schemaIsSetValue = false;
37
+ schema;
42
38
  schemaTypesParsed = {};
43
39
  schemaTypesParsedById = {};
44
40
  // modify
@@ -48,29 +44,31 @@ export class DbClient {
48
44
  maxModifySize;
49
45
  upserting = new Map();
50
46
  schemaChecksum;
47
+ schemaProcessing;
48
+ schemaPromise;
51
49
  async setSchema(schema, fromStart, transformFns) {
52
- this.schemaIsSetValue = true;
53
- const checksum = hash(schema);
54
- if (checksum === this.schemaChecksum) {
50
+ const strictSchema = fromStart ? schema : parse(schema).schema;
51
+ // this one excludes all the ids
52
+ if (schemaLooseEqual(strictSchema, this.schema)) {
55
53
  return this.schema;
56
54
  }
57
- const strictSchema = fromStart ? schema : parse(schema).schema;
58
- const remoteSchema = await this.hooks.setSchema(strictSchema, fromStart, transformFns);
59
- this.schemaChecksum = checksum;
55
+ const checksum = hash(strictSchema);
56
+ if (checksum !== this.schemaProcessing) {
57
+ this.schemaProcessing = checksum;
58
+ this.schemaPromise = this.hooks.setSchema(strictSchema, fromStart, transformFns);
59
+ }
60
+ const remoteSchema = await this.schemaPromise;
61
+ this.schemaProcessing = null;
62
+ this.schemaPromise = null;
60
63
  return this.putLocalSchema(remoteSchema);
61
64
  }
62
65
  putLocalSchema(schema) {
63
- this.schemaIsSetValue = true;
64
- if (deepEqual(this.schema, schema)) {
66
+ const checksum = hash(schema);
67
+ if (this.schemaChecksum === checksum) {
65
68
  return this.schema;
66
69
  }
70
+ this.schemaChecksum = checksum;
67
71
  this.schema = schema;
68
- for (const field in this.schema.types) {
69
- if (!('id' in this.schema.types[field])) {
70
- this.schema.lastId++;
71
- this.schema.types[field].id = this.schema.lastId;
72
- }
73
- }
74
72
  updateTypeDefs(this.schema, this.schemaTypesParsed, this.schemaTypesParsedById);
75
73
  // Adds bidrectional refs on defs
76
74
  schemaToSelvaBuffer(this.schemaTypesParsed);
@@ -199,18 +197,17 @@ export class DbClient {
199
197
  await this.flushIsReady;
200
198
  return;
201
199
  }
202
- schemaIsSet() {
203
- return new Promise((resolve) => {
204
- if (this.schemaIsSetValue) {
205
- resolve(true);
206
- }
207
- else {
208
- setTimeout(() => {
209
- // TODO use subscription when its done
210
- resolve(this.schemaIsSet());
211
- }, 12);
212
- }
213
- });
200
+ async schemaIsSet() {
201
+ if (this.schema) {
202
+ return true;
203
+ }
204
+ if (this.schemaPromise) {
205
+ await this.schemaPromise;
206
+ }
207
+ else {
208
+ await wait(12);
209
+ }
210
+ return this.schemaIsSet();
214
211
  }
215
212
  listeners;
216
213
  on(event, cb) {
@@ -96,6 +96,7 @@ map[TIMESTAMP] = (ctx, val, def) => {
96
96
  }
97
97
  const view = new DataView(ctx.buf.buffer, ctx.buf.byteOffset + ctx.len, 8);
98
98
  ctx.len += 8;
99
+ // Todo use new utils and store as uint64
99
100
  view.setFloat64(0, parsedValue, true);
100
101
  const ts = view.getFloat64(0);
101
102
  };
@@ -17,7 +17,8 @@ export declare class QueryBranch<T> {
17
17
  filterBatch(f: FilterAst): this;
18
18
  search(query: string, ...fields: Search[]): T;
19
19
  search(query: ArrayBufferView, field: string, opts?: Omit<FilterOpts, 'lowerCase'>): T;
20
- count(): T;
20
+ groupBy(field: string): T;
21
+ sum(...fields: (string | string[])[]): T;
21
22
  or(fn: FilterBranchFn): T;
22
23
  or(field: string, operator?: Operator | boolean, value?: any, opts?: FilterOpts): T;
23
24
  range(start: number, end?: number): T;
@@ -1,4 +1,4 @@
1
- import { createQueryDef, QueryDefType, filter, sort, defToBuffer, filterOr, isAlias, includeField, includeFields, count, } from './query.js';
1
+ import { createQueryDef, QueryDefType, filter, sort, defToBuffer, filterOr, isAlias, includeField, includeFields, addAggregate, groupBy, } from './query.js';
2
2
  import { BasedQueryResponse } from './BasedIterable.js';
3
3
  import { createOrGetEdgeRefQueryDef, createOrGetRefQueryDef, } from './include/utils.js';
4
4
  import { FilterBranch } from './filter/FilterBranch.js';
@@ -94,8 +94,15 @@ export class QueryBranch {
94
94
  // @ts-ignore
95
95
  return this;
96
96
  }
97
- count() {
98
- count(this.def);
97
+ groupBy(field) {
98
+ groupBy(this.def, field);
99
+ // only works with aggregates for now
100
+ // @ts-ignore
101
+ return this;
102
+ }
103
+ // x
104
+ sum(...fields) {
105
+ addAggregate(1 /* AggregateType.SUM */, this.def, fields);
99
106
  // @ts-ignore
100
107
  return this;
101
108
  }
@@ -213,7 +220,7 @@ export class BasedDbQuery extends QueryBranch {
213
220
  }
214
221
  }
215
222
  }
216
- if (!db.schemaIsSetValue) {
223
+ if (!db.schema) {
217
224
  throw new Error('Query: No schema yet - use await db.schemaIsSet()');
218
225
  }
219
226
  const def = createQueryDef(db, QueryDefType.Root, target, skipValidation);
@@ -1,5 +1,6 @@
1
1
  import { inspect } from 'node:util';
2
2
  import { QueryDef } from './types.js';
3
+ import { Item } from './query.js';
3
4
  import { size, time, inspectData } from './display.js';
4
5
  export { time, size, inspectData };
5
6
  export declare class BasedQueryResponse {
@@ -14,7 +15,7 @@ export declare class BasedQueryResponse {
14
15
  [inspect.custom](depth: number): string;
15
16
  debug(): this;
16
17
  node(index?: number): any;
17
- [Symbol.iterator](): Generator<Partial<import("./query.js").Item>, void, unknown>;
18
+ [Symbol.iterator](): Generator<Item, void, unknown>;
18
19
  inspect(depth?: number, raw?: boolean): this;
19
20
  forEach(fn: (item: any, key: number) => void): void;
20
21
  map(fn: (item: any, key: number) => any): any[];
@@ -65,16 +65,9 @@ export class BasedQueryResponse {
65
65
  while (i < result.byteLength - 4) {
66
66
  let id = readUint32(result, i);
67
67
  i += 4;
68
- let item;
69
- if (this.def.aggregation == 255 /* AggFlag.TEMP */) {
70
- item = {};
71
- this.def.aggregation = 4 /* AggFlag.COUNT */;
72
- }
73
- else {
74
- item = {
75
- id,
76
- };
77
- }
68
+ let item = {
69
+ id,
70
+ };
78
71
  if (this.def.search) {
79
72
  item.$searchScore = readFloatLE(result, i);
80
73
  i += 4;
@@ -114,12 +107,7 @@ export class BasedQueryResponse {
114
107
  }
115
108
  get length() {
116
109
  const l = readUint32(this.result, 0);
117
- if (this.def.aggregation != 0 /* AggFlag.NONE */ && this.def.aggregation != null) {
118
- return l + 1;
119
- }
120
- else {
121
- return l;
122
- }
110
+ return l;
123
111
  }
124
112
  toObject() {
125
113
  return resultToObject(this.def, this.result, this.end - 4, 0);
@@ -0,0 +1,5 @@
1
+ import { QueryDef, QueryDefAggregation } from '../types.js';
2
+ import { AggregateType } from './types.js';
3
+ export declare const aggregateToBuffer: (aggregates: QueryDefAggregation) => Uint8Array;
4
+ export declare const groupBy: (def: QueryDef, field: string) => void;
5
+ export declare const addAggregate: (type: AggregateType, def: QueryDef, fields: (string | string[])[]) => void;
@@ -0,0 +1,96 @@
1
+ import { writeUint16 } from '@saulx/utils';
2
+ export const aggregateToBuffer = (aggregates) => {
3
+ const aggBuffer = new Uint8Array(aggregates.size);
4
+ let i = 0;
5
+ if (aggregates.groupBy) {
6
+ aggBuffer[i] = 255 /* GroupBy.HAS_GROUP */;
7
+ i += 1;
8
+ aggBuffer[i] = aggregates.groupBy.prop;
9
+ i += 1;
10
+ aggBuffer[i] = aggregates.groupBy.typeIndex;
11
+ i += 1;
12
+ writeUint16(aggBuffer, aggregates.groupBy.start, i);
13
+ i += 2;
14
+ writeUint16(aggBuffer, aggregates.groupBy.len, i);
15
+ i += 2;
16
+ }
17
+ else {
18
+ aggBuffer[i] = 1 /* GroupBy.NONE */;
19
+ i += 1;
20
+ }
21
+ writeUint16(aggBuffer, aggregates.totalResultsPos, i);
22
+ i += 2;
23
+ for (const [prop, aggregatesArray] of aggregates.aggregates.entries()) {
24
+ aggBuffer[i] = prop;
25
+ i += 1;
26
+ let sizeIndex = i;
27
+ let size = 0;
28
+ i += 2;
29
+ for (const agg of aggregatesArray) {
30
+ let startI = i;
31
+ aggBuffer[i] = agg.type;
32
+ i += 1;
33
+ aggBuffer[i] = agg.propDef.typeIndex;
34
+ i += 1;
35
+ writeUint16(aggBuffer, agg.propDef.start, i);
36
+ i += 2;
37
+ writeUint16(aggBuffer, agg.resultPos, i);
38
+ i += 2;
39
+ size += i - startI;
40
+ }
41
+ writeUint16(aggBuffer, size, sizeIndex);
42
+ }
43
+ return aggBuffer;
44
+ };
45
+ const ensureAggregate = (def) => {
46
+ if (!def.aggregate) {
47
+ def.aggregate = {
48
+ size: 3,
49
+ aggregates: new Map(),
50
+ totalResultsPos: 0,
51
+ };
52
+ }
53
+ };
54
+ // Group by is great for normal stuff as well (do later)
55
+ export const groupBy = (def, field) => {
56
+ const fieldDef = def.schema.props[field];
57
+ if (!fieldDef) {
58
+ throw new Error(`Field for agg:groupBy does not exists "${field}" make better error later...`);
59
+ }
60
+ ensureAggregate(def);
61
+ if (!def.aggregate.groupBy) {
62
+ def.aggregate.size += 6;
63
+ }
64
+ def.aggregate.groupBy = fieldDef;
65
+ };
66
+ export const addAggregate = (type, def, fields) => {
67
+ ensureAggregate(def);
68
+ const aggregates = def.aggregate.aggregates;
69
+ for (const field of fields) {
70
+ if (Array.isArray(field)) {
71
+ addAggregate(type, def, field);
72
+ }
73
+ else {
74
+ const fieldDef = def.schema.props[field];
75
+ if (!fieldDef) {
76
+ throw new Error(`Field for agg does not exists ${field} make better error later...`);
77
+ }
78
+ if (!aggregates.get(fieldDef.prop)) {
79
+ aggregates.set(fieldDef.prop, []);
80
+ def.aggregate.size += 3;
81
+ }
82
+ const aggregateField = aggregates.get(fieldDef.prop);
83
+ aggregateField.push({
84
+ propDef: fieldDef,
85
+ type,
86
+ resultPos: def.aggregate.totalResultsPos,
87
+ });
88
+ // IF FLOAT // NUMBER ETC USE 8!
89
+ // do this better
90
+ def.aggregate.totalResultsPos += 4;
91
+ // needs to add an extra field WRITE TO
92
+ def.aggregate.size += 6;
93
+ }
94
+ }
95
+ };
96
+ //# sourceMappingURL=aggregation.js.map
@@ -0,0 +1,7 @@
1
+ export declare const enum AggregateType {
2
+ SUM = 1
3
+ }
4
+ export declare const enum GroupBy {
5
+ NONE = 1,
6
+ HAS_GROUP = 255
7
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -145,16 +145,7 @@ const inspectObject = (object, q, path, level, isLast, isFirst, isObject, depth)
145
145
  str += ',\n';
146
146
  }
147
147
  else if (!def) {
148
- if (Object.keys(object)[0] == 'count') {
149
- // TODO: to flag the agg someway. This is ugly as hell!!!
150
- str += picocolors.blue(v);
151
- str += picocolors.italic(picocolors.dim(' count'));
152
- str += ',\n';
153
- }
154
- else {
155
- str +=
156
- inspectObject(v, q, key, level + 2, false, false, true, depth) + '';
157
- }
148
+ str += inspectObject(v, q, key, level + 2, false, false, true, depth) + '';
158
149
  }
159
150
  else if ('__isPropDef' in def) {
160
151
  if (def.typeIndex === REFERENCES) {
@@ -8,4 +8,4 @@ export * from './filter/toBuffer.js';
8
8
  export * from './sort.js';
9
9
  export * from './debug.js';
10
10
  export * from './read/read.js';
11
- export * from './aggregation.js';
11
+ export * from './aggregates/aggregation.js';
@@ -8,5 +8,5 @@ export * from './filter/toBuffer.js';
8
8
  export * from './sort.js';
9
9
  export * from './debug.js';
10
10
  export * from './read/read.js';
11
- export * from './aggregation.js';
11
+ export * from './aggregates/aggregation.js';
12
12
  //# sourceMappingURL=query.js.map
@@ -19,9 +19,9 @@ const createEmptySharedDef = (skipValidation) => {
19
19
  include: {},
20
20
  },
21
21
  },
22
+ aggregate: null,
22
23
  sort: null,
23
24
  references: new Map(),
24
- aggregation: 0 /* AggFlag.NONE */,
25
25
  };
26
26
  return q;
27
27
  };
@@ -5,5 +5,5 @@ export type Item = {
5
5
  [key: string]: any;
6
6
  };
7
7
  export type AggItem = Partial<Item>;
8
- export declare const readAllFields: (q: QueryDef, result: Uint8Array, offset: number, end: number, item: Item | AggItem, id: number) => number;
8
+ export declare const readAllFields: (q: QueryDef, result: Uint8Array, offset: number, end: number, item: Item, id: number) => number;
9
9
  export declare const resultToObject: (q: QueryDef, result: Uint8Array, end: number, offset?: number) => any;
@@ -1,9 +1,11 @@
1
1
  import { ALIAS, ALIASES, BINARY, BOOLEAN, ENUM, INT16, INT32, INT8, NUMBER, STRING, TEXT, TIMESTAMP, UINT16, UINT32, UINT8, VECTOR, JSON, CARDINALITY, } from '@based/schema/def';
2
2
  import { QueryDefType } from '../types.js';
3
3
  import { read, readUtf8 } from '../../string.js';
4
- import { readDoubleLE, readFloatLE, readInt16, readInt32, readUint16, readUint32, } from '@saulx/utils';
4
+ import { DECODER, readDoubleLE, readFloatLE, readInt16, readInt32, readUint16, readUint32, setByPath, } from '@saulx/utils';
5
5
  import { inverseLangMap } from '@based/schema';
6
- import { READ_EDGE, READ_ID, READ_REFERENCE, READ_REFERENCES, READ_AGGREGATION, CREATE_AGGREGATION, } from '../types.js';
6
+ import { READ_EDGE, READ_ID, READ_REFERENCE, READ_REFERENCES,
7
+ // AggFlag,
8
+ } from '../types.js';
7
9
  const addField = (p, value, item, defaultOnly = false, lang = 0) => {
8
10
  let i = p.__isEdge === true ? 1 : 0;
9
11
  // TODO OPTMIZE
@@ -125,45 +127,43 @@ const readMain = (q, result, offset, item) => {
125
127
  return i - offset;
126
128
  };
127
129
  const handleUndefinedProps = (id, q, item) => {
128
- if (q.aggregation === 0 /* AggFlag.NONE */ || q.aggregation === null) {
129
- for (const k in q.include.propsRead) {
130
- if (q.include.propsRead[k] !== id) {
131
- // Only relevant for seperate props
132
- const prop = q.schema.reverseProps[k];
133
- if (prop.typeIndex === CARDINALITY) {
134
- addField(prop, 0, item);
135
- }
136
- else if (prop.typeIndex === TEXT && q.lang == 0) {
137
- const lan = getEmptyField(prop, item);
138
- const lang = q.include.langTextFields.get(prop.prop).codes;
139
- if (lang.has(0)) {
140
- for (const locale in q.schema.locales) {
141
- if (lan[locale] == undefined) {
142
- lan[locale] = prop.default[locale] || '';
143
- }
130
+ for (const k in q.include.propsRead) {
131
+ if (q.include.propsRead[k] !== id) {
132
+ // Only relevant for seperate props
133
+ const prop = q.schema.reverseProps[k];
134
+ if (prop.typeIndex === CARDINALITY) {
135
+ addField(prop, 0, item);
136
+ }
137
+ else if (prop.typeIndex === TEXT && q.lang == 0) {
138
+ const lan = getEmptyField(prop, item);
139
+ const lang = q.include.langTextFields.get(prop.prop).codes;
140
+ if (lang.has(0)) {
141
+ for (const locale in q.schema.locales) {
142
+ if (lan[locale] == undefined) {
143
+ lan[locale] = prop.default[locale] || '';
144
144
  }
145
145
  }
146
- else {
147
- for (const code of lang) {
148
- const locale = inverseLangMap.get(code);
149
- if (!lan[locale]) {
150
- lan[locale] = prop.default[locale] || '';
151
- }
146
+ }
147
+ else {
148
+ for (const code of lang) {
149
+ const locale = inverseLangMap.get(code);
150
+ if (!lan[locale]) {
151
+ lan[locale] = prop.default[locale] || '';
152
152
  }
153
153
  }
154
154
  }
155
- else if (prop.typeIndex === BINARY) {
155
+ }
156
+ else if (prop.typeIndex === BINARY) {
157
+ addField(prop, prop.default, item);
158
+ }
159
+ else if (prop.typeIndex === TEXT) {
160
+ addField(prop, '', item);
161
+ }
162
+ else {
163
+ 1;
164
+ if (prop.default !== undefined) {
156
165
  addField(prop, prop.default, item);
157
166
  }
158
- else if (prop.typeIndex === TEXT) {
159
- addField(prop, '', item);
160
- }
161
- else {
162
- 1;
163
- if (prop.default !== undefined) {
164
- addField(prop, prop.default, item);
165
- }
166
- }
167
167
  }
168
168
  }
169
169
  }
@@ -290,27 +290,6 @@ export const readAllFields = (q, result, offset, end, item, id) => {
290
290
  addField(ref.target.propDef, refs, item);
291
291
  i += size + 4;
292
292
  }
293
- else if (index === CREATE_AGGREGATION) {
294
- i--;
295
- result[i] = READ_AGGREGATION;
296
- result[0] = result[0] + 1;
297
- q.aggregation = 255 /* AggFlag.TEMP */;
298
- return i - offset - 4 - (q.search ? 4 : 0);
299
- }
300
- else if (index === READ_AGGREGATION) {
301
- // TODO: Change to a map and also to get the aggregate field name from a query function parameter
302
- const propAgg = {
303
- name: 'count',
304
- path: ['count'],
305
- typeIndex: UINT32,
306
- };
307
- const size = readUint32(result, i);
308
- addField(propAgg, readUint32(result, i + 4), item);
309
- result[0] = result[0] - 1;
310
- i--;
311
- result[i] = CREATE_AGGREGATION;
312
- i += 4 + size + 4;
313
- }
314
293
  else if (index === 0) {
315
294
  i += readMain(q, result, i, item);
316
295
  }
@@ -391,6 +370,32 @@ export const readAllFields = (q, result, offset, end, item, id) => {
391
370
  };
392
371
  let cnt = 0;
393
372
  export const resultToObject = (q, result, end, offset = 0) => {
373
+ if (q.aggregate) {
374
+ const results = {};
375
+ if (q.aggregate.groupBy) {
376
+ // key size = 2 for now... not perfect...
377
+ let i = 0;
378
+ while (i < result.byteLength - 4) {
379
+ const key = DECODER.decode(result.subarray(i, i + 2));
380
+ i += 2;
381
+ const resultKey = (results[key] = {});
382
+ for (const aggregatesArray of q.aggregate.aggregates.values()) {
383
+ for (const agg of aggregatesArray) {
384
+ setByPath(resultKey, agg.propDef.path, readUint32(result, agg.resultPos + i));
385
+ }
386
+ }
387
+ i += q.aggregate.totalResultsPos;
388
+ }
389
+ }
390
+ else {
391
+ for (const aggregatesArray of q.aggregate.aggregates.values()) {
392
+ for (const agg of aggregatesArray) {
393
+ setByPath(results, agg.propDef.path, readUint32(result, agg.resultPos));
394
+ }
395
+ }
396
+ }
397
+ return results;
398
+ }
394
399
  const len = readUint32(result, offset);
395
400
  if (len === 0) {
396
401
  if ('id' in q.target || 'alias' in q.target) {
@@ -403,16 +408,9 @@ export const resultToObject = (q, result, end, offset = 0) => {
403
408
  while (i < end) {
404
409
  const id = readUint32(result, i);
405
410
  i += 4;
406
- let item;
407
- if (q.aggregation == 255 /* AggFlag.TEMP */) {
408
- item = {};
409
- q.aggregation = 4 /* AggFlag.COUNT */;
410
- }
411
- else {
412
- item = {
413
- id,
414
- };
415
- }
411
+ let item = {
412
+ id,
413
+ };
416
414
  if (q.search) {
417
415
  item.$searchScore = readFloatLE(result, i);
418
416
  i += 4;
@@ -3,8 +3,8 @@ import { QueryDefType, QueryType } from './types.js';
3
3
  import { includeToBuffer } from './include/toBuffer.js';
4
4
  import { filterToBuffer } from './query.js';
5
5
  import { searchToBuffer } from './search/index.js';
6
- import { createAggFlagBuffer } from './aggregation.js';
7
6
  import { ENCODER } from '@saulx/utils';
7
+ import { aggregateToBuffer } from './aggregates/aggregation.js';
8
8
  const byteSize = (arr) => {
9
9
  return arr.reduce((a, b) => {
10
10
  return a + b.byteLength;
@@ -20,7 +20,6 @@ export function defToBuffer(db, def) {
20
20
  def.errors.push(...ref.errors);
21
21
  }
22
22
  });
23
- const aggregation = createAggFlagBuffer(def.aggregation || 0 /* AggFlag.NONE */);
24
23
  let edges;
25
24
  let edgesSize = 0;
26
25
  if (def.edges) {
@@ -37,6 +36,40 @@ export function defToBuffer(db, def) {
37
36
  // ---------------------------------------
38
37
  // move down and will handle size after store the size Var
39
38
  // only for references | edges
39
+ if (def.aggregate) {
40
+ const aggregateSize = def.aggregate.size || 0;
41
+ if (aggregateSize === 0) {
42
+ throw new Error('Wrong aggregate size (0)');
43
+ }
44
+ const filterSize = def.filter.size || 0;
45
+ const buf = new Uint8Array(16 + filterSize + aggregateSize);
46
+ buf[0] = QueryType.aggregates;
47
+ buf[1] = def.schema.idUint8[0];
48
+ buf[2] = def.schema.idUint8[1];
49
+ buf[3] = def.range.offset;
50
+ buf[4] = def.range.offset >>> 8;
51
+ buf[5] = def.range.offset >>> 16;
52
+ buf[6] = def.range.offset >>> 24;
53
+ buf[7] = def.range.limit;
54
+ buf[8] = def.range.limit >>> 8;
55
+ buf[9] = def.range.limit >>> 16;
56
+ buf[10] = def.range.limit >>> 24;
57
+ buf[11] = filterSize;
58
+ buf[12] = filterSize >>> 8;
59
+ if (filterSize) {
60
+ buf.set(filterToBuffer(def.filter), 13);
61
+ }
62
+ const aggregateBuffer = aggregateToBuffer(def.aggregate);
63
+ buf[14 + filterSize] = aggregateSize;
64
+ buf[15 + filterSize] = aggregateSize >>> 8;
65
+ buf.set(aggregateBuffer, 16 + filterSize);
66
+ result.push(buf);
67
+ // ignore this for now...
68
+ // result.push(...include)
69
+ // console.log('toBuffer > result: ', result)
70
+ // later this has to be a branch
71
+ return result;
72
+ }
40
73
  if (def.type === QueryDefType.Root) {
41
74
  let search;
42
75
  let searchSize = 0;
@@ -130,7 +163,7 @@ export function defToBuffer(db, def) {
130
163
  result.push(buf);
131
164
  }
132
165
  else {
133
- const buf = new Uint8Array(18 + filterSize + sortSize + searchSize);
166
+ const buf = new Uint8Array(17 + filterSize + sortSize + searchSize);
134
167
  buf[0] = QueryType.default;
135
168
  buf[1] = def.schema.idUint8[0];
136
169
  buf[2] = def.schema.idUint8[1];
@@ -157,7 +190,6 @@ export function defToBuffer(db, def) {
157
190
  if (searchSize) {
158
191
  buf.set(search, 17 + filterSize + sortSize);
159
192
  }
160
- buf.set(aggregation, 17 + filterSize + sortSize + searchSize);
161
193
  result.push(buf);
162
194
  }
163
195
  }
@@ -170,8 +202,8 @@ export function defToBuffer(db, def) {
170
202
  }
171
203
  const sortSize = sort?.byteLength ?? 0;
172
204
  const modsSize = filterSize + sortSize;
173
- const meta = new Uint8Array(modsSize + 10 + 8 + 1);
174
- const sz = size + 7 + modsSize + 8 + 1;
205
+ const meta = new Uint8Array(modsSize + 10 + 8);
206
+ const sz = size + 7 + modsSize + 8;
175
207
  meta[0] = 254;
176
208
  meta[1] = sz;
177
209
  meta[2] = sz >>> 8;
@@ -196,7 +228,6 @@ export function defToBuffer(db, def) {
196
228
  meta[15 + modsSize] = def.schema.idUint8[0];
197
229
  meta[15 + 1 + modsSize] = def.schema.idUint8[1];
198
230
  meta[15 + 2 + modsSize] = def.target.propDef.prop;
199
- meta[15 + 3 + modsSize] = aggregation[0];
200
231
  result.push(meta);
201
232
  }
202
233
  else if (def.type === QueryDefType.Reference) {
@@ -2,6 +2,7 @@ import { LangCode } from '@based/schema';
2
2
  import { PropDef, PropDefEdge, SchemaTypeDef } from '@based/schema/def';
3
3
  import { FilterOpts } from './filter/types.js';
4
4
  import { QueryError } from './validation.js';
5
+ import { AggregateType } from './aggregates/types.js';
5
6
  export type MainIncludes = {
6
7
  [start: string]: [number, PropDef];
7
8
  };
@@ -72,10 +73,22 @@ export type QueryDefSort = {
72
73
  order: 0 | 1;
73
74
  lang: LangCode;
74
75
  };
76
+ export type Aggregation = {
77
+ type: AggregateType;
78
+ propDef: PropDef;
79
+ resultPos: number;
80
+ };
81
+ export type QueryDefAggregation = {
82
+ size: number;
83
+ groupBy?: PropDef;
84
+ aggregates: Map<number, Aggregation[]>;
85
+ totalResultsPos: number;
86
+ };
75
87
  export type QueryDefShared = {
76
88
  errors: QueryError[];
77
89
  lang: LangCode;
78
90
  filter: QueryDefFilter;
91
+ aggregate: null | QueryDefAggregation;
79
92
  search: null | QueryDefSearch;
80
93
  sort: null | QueryDefSort;
81
94
  skipValidation: boolean;
@@ -100,7 +113,6 @@ export type QueryDefShared = {
100
113
  };
101
114
  references: Map<number, QueryDef>;
102
115
  edges?: QueryDef;
103
- aggregation: AggFlag;
104
116
  };
105
117
  export type QueryDefEdges = {
106
118
  type: QueryDefType.Edge;
@@ -126,21 +138,3 @@ export declare const READ_ID = 255;
126
138
  export declare const READ_EDGE = 252;
127
139
  export declare const READ_REFERENCES = 253;
128
140
  export declare const READ_REFERENCE = 254;
129
- export declare const CREATE_AGGREGATION = 250;
130
- export declare const READ_AGGREGATION = 251;
131
- export declare const enum AggFlag {
132
- NONE = 0,
133
- AVG = 1,
134
- CARDINALITY = 2,
135
- CONCAT = 3,// string aggregation, delimiter should be an argument
136
- COUNT = 4,
137
- MAX = 5,
138
- MIN = 6,
139
- MODE = 7,// ordered-set
140
- PERCENTILE = 8,// continuous or discrete should be optional parameters, default = discrete
141
- RANK = 9,// hypothetical-set, dense should be optional parameter
142
- STDDEV = 10,// population or sample should be optional parameters, default = sample
143
- SUM = 11,
144
- VARIANCE = 12,
145
- TEMP = 255
146
- }
@@ -30,6 +30,4 @@ export const READ_ID = 255;
30
30
  export const READ_EDGE = 252;
31
31
  export const READ_REFERENCES = 253;
32
32
  export const READ_REFERENCE = 254;
33
- export const CREATE_AGGREGATION = 250;
34
- export const READ_AGGREGATION = 251;
35
33
  //# sourceMappingURL=types.js.map
@@ -22,7 +22,7 @@ export declare class BasedDb {
22
22
  constructor(opts: {
23
23
  path: string;
24
24
  maxModifySize?: number;
25
- debug?: boolean | 'server';
25
+ debug?: boolean | 'server' | 'client';
26
26
  saveIntervalInSeconds?: number;
27
27
  });
28
28
  create: DbClient['create'];
package/dist/src/index.js CHANGED
@@ -24,7 +24,10 @@ export class BasedDb {
24
24
  constructor(opts) {
25
25
  this.#init(opts);
26
26
  if (opts.debug) {
27
- if (opts.debug === 'server') {
27
+ if (opts.debug === 'client') {
28
+ debugServer(this.server);
29
+ }
30
+ else if (opts.debug === 'server') {
28
31
  debugServer(this.server);
29
32
  }
30
33
  else {
@@ -11,7 +11,7 @@ import { Worker, MessageChannel } from 'node:worker_threads';
11
11
  import { fileURLToPath } from 'node:url';
12
12
  import { setTimeout } from 'node:timers/promises';
13
13
  import { migrate } from './migrate/index.js';
14
- import { debugServer } from '../utils.js';
14
+ import { debugServer, schemaLooseEqual } from '../utils.js';
15
15
  export const SCHEMA_FILE = 'schema.json';
16
16
  export const WRITELOG_FILE = 'writelog.json';
17
17
  const __filename = fileURLToPath(import.meta.url);
@@ -87,7 +87,7 @@ export class DbServer {
87
87
  processingQueries = 0;
88
88
  modifyQueue = [];
89
89
  queryQueue = new Map();
90
- stopped;
90
+ stopped; // = true does not work
91
91
  onSchemaChange;
92
92
  unlistenExit;
93
93
  saveIntervalInSeconds;
@@ -119,6 +119,7 @@ export class DbServer {
119
119
  }
120
120
  }
121
121
  start(opts) {
122
+ this.stopped = false;
122
123
  return start(this, opts);
123
124
  }
124
125
  save(opts) {
@@ -283,6 +284,9 @@ export class DbServer {
283
284
  }
284
285
  setSchema(strictSchema, fromStart = false, transformFns) {
285
286
  if (!fromStart && Object.keys(this.schema.types).length > 0) {
287
+ if (schemaLooseEqual(strictSchema, this.schema)) {
288
+ return this.schema;
289
+ }
286
290
  return this.migrateSchema(strictSchema, transformFns);
287
291
  }
288
292
  const { lastId } = this.schema;
@@ -404,6 +408,10 @@ export class DbServer {
404
408
  return offsets;
405
409
  }
406
410
  #modify(buf) {
411
+ if (this.stopped) {
412
+ console.error('Db is stopped - trying to modify');
413
+ return;
414
+ }
407
415
  const end = buf.length - 4;
408
416
  const dataLen = readUint32LE(buf, end);
409
417
  let typesSize = readUint16LE(buf, dataLen);
@@ -452,6 +460,10 @@ export class DbServer {
452
460
  this.queryQueue.set(resolve, buf);
453
461
  }
454
462
  getQueryBuf(buf, fromQueue = false) {
463
+ if (this.stopped) {
464
+ console.error('Db is stopped - trying to query', buf.byteLength);
465
+ return Promise.resolve(new Uint8Array(8));
466
+ }
455
467
  if (this.modifyQueue.length) {
456
468
  return new Promise((resolve) => {
457
469
  this.addToQueryQueue(resolve, buf);
@@ -533,7 +545,7 @@ export class DbServer {
533
545
  await Promise.all(this.workers.map(({ worker }) => worker.terminate()));
534
546
  this.workers = [];
535
547
  native.stop(this.dbCtxExternal);
536
- await setTimeout(20);
548
+ await setTimeout(100);
537
549
  }
538
550
  catch (e) {
539
551
  this.stopped = false;
@@ -3,3 +3,4 @@ export declare const DECODER: TextDecoder;
3
3
  export declare const ENCODER: TextEncoder;
4
4
  export declare const debugMode: (target: any, getInfo?: any) => void;
5
5
  export declare const debugServer: (server: DbServer) => void;
6
+ export declare const schemaLooseEqual: (a: any, b: any, key?: string) => boolean;
package/dist/src/utils.js CHANGED
@@ -43,4 +43,51 @@ export const debugMode = (target, getInfo = null) => {
43
43
  }
44
44
  };
45
45
  export const debugServer = (server) => debugMode(server, () => `p: ${server.processingQueries} m: ${server.modifyQueue.length} q: ${server.queryQueue.size}`);
46
+ const exclude = new Set(['id', 'lastId']);
47
+ export const schemaLooseEqual = (a, b, key) => {
48
+ if (a === b) {
49
+ return true;
50
+ }
51
+ const typeofA = typeof a;
52
+ if (typeofA !== 'object') {
53
+ return exclude.has(key);
54
+ }
55
+ const typeofB = typeof b;
56
+ if (typeofA !== typeofB) {
57
+ return exclude.has(key);
58
+ }
59
+ if (a === null || b === null) {
60
+ return false;
61
+ }
62
+ if (a.constructor !== b.constructor) {
63
+ return false;
64
+ }
65
+ if (Array.isArray(a)) {
66
+ let i = a.length;
67
+ if (i !== b.length) {
68
+ return false;
69
+ }
70
+ while (i--) {
71
+ if (!schemaLooseEqual(a[i], b[i])) {
72
+ return false;
73
+ }
74
+ }
75
+ }
76
+ else {
77
+ for (const k in a) {
78
+ if (!schemaLooseEqual(a[k], b[k], k)) {
79
+ return false;
80
+ }
81
+ }
82
+ for (const k in b) {
83
+ if (k in a) {
84
+ continue;
85
+ }
86
+ if (!schemaLooseEqual(a[k], b[k], k)) {
87
+ return false;
88
+ }
89
+ }
90
+ }
91
+ return true;
92
+ };
46
93
  //# sourceMappingURL=utils.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@based/db",
3
- "version": "0.0.35",
3
+ "version": "0.0.37",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",
@@ -38,9 +38,9 @@
38
38
  "basedDbNative.cjs"
39
39
  ],
40
40
  "dependencies": {
41
- "@based/schema": "5.0.0-alpha.12",
41
+ "@based/schema": "5.0.0-alpha.13",
42
42
  "@saulx/hash": "^3.0.0",
43
- "@saulx/utils": "^6.5.0",
43
+ "@saulx/utils": "^6.6.0",
44
44
  "exit-hook": "^4.0.0",
45
45
  "picocolors": "^1.1.0",
46
46
  "@based/crc32c": "^1.0.0"
@@ -1,3 +0,0 @@
1
- import { QueryDef, AggFlag } from './types.js';
2
- export declare const createAggFlagBuffer: (aggregation: AggFlag) => Uint8Array;
3
- export declare const count: (def: QueryDef) => void;
@@ -1,9 +0,0 @@
1
- export const createAggFlagBuffer = (aggregation) => {
2
- const buf = new Uint8Array(1);
3
- buf[0] = aggregation;
4
- return buf;
5
- };
6
- export const count = (def) => {
7
- def.aggregation = 4 /* AggFlag.COUNT */;
8
- };
9
- //# sourceMappingURL=aggregation.js.map