@frogfish/k2db 2.0.3 → 2.0.7

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.
package/README.md CHANGED
@@ -295,3 +295,21 @@ Returns by method
295
295
  - `setSchema(collection, zodSchema, { mode }?)`: `void`
296
296
  - `clearSchema(collection)`: `void`
297
297
  - `clearSchemas()`: `void`
298
+
299
+ ## UUID
300
+
301
+ _uuid = Crockford Base32 encoded UUID V7, Uppercase, with hyphens
302
+
303
+ 0J4F2-H6M8Q-7RX4V-9D3TN-8K2WZ
304
+
305
+ // Canonical uppercase form with hyphens
306
+ const CROCKFORD_ID_REGEX = /^[0-9A-HJKMNP-TV-Z]{5}-[0-9A-HJKMNP-TV-Z]{5}-[0-9A-HJKMNP-TV-Z]{5}-[0-9A-HJKMNP-TV-Z]{5}-[0-9A-HJKMNP-TV-Z]{6}$/;
307
+
308
+ // Example usage:
309
+ const id = "0J4F2-H6M8Q-7RX4V-9D3TN-8K2WZ";
310
+ console.log(CROCKFORD_ID_REGEX.test(id)); // true
311
+
312
+ Usage examples:
313
+
314
+ import { isK2ID, K2DB } from '@frogfish/k2db'
315
+ isK2ID('01HZY2AB-3JKM-4NPQ-5RST-6VWXYZ')
package/dist/README.md CHANGED
@@ -295,3 +295,21 @@ Returns by method
295
295
  - `setSchema(collection, zodSchema, { mode }?)`: `void`
296
296
  - `clearSchema(collection)`: `void`
297
297
  - `clearSchemas()`: `void`
298
+
299
+ ## UUID
300
+
301
+ _uuid = Crockford Base32 encoded UUID V7, Uppercase, with hyphens
302
+
303
+ 0J4F2-H6M8Q-7RX4V-9D3TN-8K2WZ
304
+
305
+ // Canonical uppercase form with hyphens
306
+ const CROCKFORD_ID_REGEX = /^[0-9A-HJKMNP-TV-Z]{5}-[0-9A-HJKMNP-TV-Z]{5}-[0-9A-HJKMNP-TV-Z]{5}-[0-9A-HJKMNP-TV-Z]{5}-[0-9A-HJKMNP-TV-Z]{6}$/;
307
+
308
+ // Example usage:
309
+ const id = "0J4F2-H6M8Q-7RX4V-9D3TN-8K2WZ";
310
+ console.log(CROCKFORD_ID_REGEX.test(id)); // true
311
+
312
+ Usage examples:
313
+
314
+ import { isK2ID, K2DB } from '@frogfish/k2db'
315
+ isK2ID('01HZY2AB-3JKM-4NPQ-5RST-6VWXYZ')
package/dist/data.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import type { K2DB, BaseDocument, CreateResult, UpdateResult, DeleteResult, RestoreResult, CountResult, DropResult, VersionedUpdateResult, VersionInfo } from "./db.js";
1
+ import { K2DB } from "./db.js";
2
+ import type { BaseDocument, CreateResult, UpdateResult, DeleteResult, RestoreResult, CountResult, DropResult, VersionedUpdateResult, VersionInfo } from "./db.js";
2
3
  export declare class K2Data {
3
4
  private db;
4
5
  private owner;
@@ -100,4 +101,5 @@ export declare class K2Data {
100
101
  isHealthy(): Promise<boolean>;
101
102
  }
102
103
  export { K2DB } from "./db.js";
104
+ export declare const isK2ID: (id: string) => boolean;
103
105
  export type { DatabaseConfig, BaseDocument, CreateResult, UpdateResult, DeleteResult, RestoreResult, CountResult, DropResult, VersionedUpdateResult, VersionInfo, } from "./db.js";
package/dist/data.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { K2DB } from "./db.js";
1
2
  export class K2Data {
2
3
  db;
3
4
  owner;
@@ -148,3 +149,4 @@ export class K2Data {
148
149
  }
149
150
  // Re-export K2DB (runtime) and types from the root entry
150
151
  export { K2DB } from "./db.js";
152
+ export const isK2ID = (id) => K2DB.isK2ID(id);
package/dist/db.d.ts CHANGED
@@ -47,6 +47,9 @@ export interface DropResult {
47
47
  export interface PurgeResult {
48
48
  id: string;
49
49
  }
50
+ export interface PurgeManyResult {
51
+ purged: number;
52
+ }
50
53
  export interface VersionedUpdateResult {
51
54
  updated: number;
52
55
  versionSaved: number;
@@ -150,6 +153,14 @@ export declare class K2DB {
150
153
  * @param id - UUID of the document.
151
154
  */
152
155
  purge(collectionName: string, id: string): Promise<PurgeResult>;
156
+ /**
157
+ * Permanently deletes all documents that are soft-deleted and whose _updated
158
+ * timestamp is older than the provided threshold (in milliseconds ago).
159
+ * @param collectionName - Name of the collection.
160
+ * @param olderThanMs - Age threshold in milliseconds; documents with
161
+ * `_updated <= (Date.now() - olderThanMs)` will be purged.
162
+ */
163
+ purgeDeletedOlderThan(collectionName: string, olderThanMs: number): Promise<PurgeManyResult>;
153
164
  /**
154
165
  * Restores a soft-deleted document.
155
166
  * @param collectionName - Name of the collection.
@@ -172,8 +183,16 @@ export declare class K2DB {
172
183
  * @param criteria - Aggregation stage criteria.
173
184
  */
174
185
  private static sanitiseCriteria;
186
+ /** Recursively uppercases any values for fields named `_uuid` within a query object. */
187
+ private static normalizeCriteriaIds;
188
+ /** Uppercase helper for `_uuid` field supporting operators like $in/$nin/$eq/$ne and arrays. */
189
+ private static normalizeUuidField;
175
190
  /** Strip any user-provided fields that start with '_' (reserved). */
176
191
  private static stripReservedFields;
192
+ /** True if string matches K2 ID format (Crockford Base32, 8-4-4-4-6, uppercase). */
193
+ static isK2ID(id: string): boolean;
194
+ /** Uppercase incoming IDs for case-insensitive lookups. */
195
+ private static normalizeId;
177
196
  /**
178
197
  * Run an async DB operation with timing, slow logging, and hooks.
179
198
  */
package/dist/db.js CHANGED
@@ -1,10 +1,62 @@
1
1
  // src/db.ts
2
2
  import { K2Error, ServiceError } from "@frogfish/k2error"; // Keep the existing error structure
3
3
  import { MongoClient, } from "mongodb";
4
- import { v4 as uuidv4 } from "uuid";
4
+ import { randomBytes } from "crypto";
5
5
  import debugLib from "debug";
6
6
  import { z } from "zod";
7
7
  const debug = debugLib("k2:db");
8
+ // Crockford Base32 alphabet (no I, L, O, U)
9
+ const CROCKFORD32 = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
10
+ /**
11
+ * Generates a UUIDv7 (time-ordered) and encodes it as Crockford Base32 with hyphens.
12
+ * Format: 26 base32 chars grouped as 8-4-4-4-6 (total 26)
13
+ */
14
+ function uuidv7Base32Hyphenated() {
15
+ // 1) Build UUIDv7 bytes
16
+ // Layout per RFC: time_low(32) | time_mid(16) | time_hi_and_version(16) | clock_seq(16) | node(48)
17
+ // Encode 60-bit ms timestamp across time_* fields, version 7, RFC4122 variant in clock_seq_hi.
18
+ const ts = BigInt(Date.now()); // milliseconds
19
+ const timeLow = Number((ts >> 28n) & 0xffffffffn);
20
+ const timeMid = Number((ts >> 12n) & 0xffffn);
21
+ const timeHi = Number(ts & 0xfffn); // lower 12 bits
22
+ const bytes = new Uint8Array(16);
23
+ // time_low (big-endian)
24
+ bytes[0] = (timeLow >>> 24) & 0xff;
25
+ bytes[1] = (timeLow >>> 16) & 0xff;
26
+ bytes[2] = (timeLow >>> 8) & 0xff;
27
+ bytes[3] = timeLow & 0xff;
28
+ // time_mid (big-endian)
29
+ bytes[4] = (timeMid >>> 8) & 0xff;
30
+ bytes[5] = timeMid & 0xff;
31
+ // time_high_and_version: version 7 in high nibble + top 4 bits of timeHi
32
+ bytes[6] = 0x70 | ((timeHi >>> 8) & 0x0f); // 0x7- version
33
+ bytes[7] = timeHi & 0xff;
34
+ // clock_seq + node: 8 random bytes; set RFC4122 variant (10xxxxxx)
35
+ const rnd = randomBytes(8);
36
+ bytes.set(rnd, 8);
37
+ bytes[8] = (bytes[8] & 0x3f) | 0x80; // set variant 10xxxxxx
38
+ // 2) Encode as Crockford Base32 (26 chars). 128 bits -> 26*5 bits (pad 2 high bits)
39
+ let value = 0n;
40
+ for (let i = 0; i < 16; i++) {
41
+ value = (value << 8n) | BigInt(bytes[i]);
42
+ }
43
+ value <<= 2n; // pad to 130 bits so we can take 26 groups cleanly
44
+ let encoded = "";
45
+ for (let i = 25; i >= 0; i--) {
46
+ const idx = Number((value >> BigInt(i * 5)) & 0x1fn);
47
+ encoded += CROCKFORD32[idx];
48
+ }
49
+ // 3) Insert hyphens in groups: 8-4-4-4-6
50
+ return (encoded.slice(0, 8) +
51
+ "-" +
52
+ encoded.slice(8, 12) +
53
+ "-" +
54
+ encoded.slice(12, 16) +
55
+ "-" +
56
+ encoded.slice(16, 20) +
57
+ "-" +
58
+ encoded.slice(20));
59
+ }
8
60
  export class K2DB {
9
61
  conf;
10
62
  db;
@@ -110,8 +162,9 @@ export class K2DB {
110
162
  }
111
163
  }
112
164
  async get(collectionName, uuid) {
165
+ const id = K2DB.normalizeId(uuid);
113
166
  const res = await this.findOne(collectionName, {
114
- _uuid: uuid,
167
+ _uuid: id,
115
168
  _deleted: { $ne: true },
116
169
  });
117
170
  if (!res) {
@@ -130,8 +183,9 @@ export class K2DB {
130
183
  const collection = await this.getCollection(collectionName);
131
184
  const projection = {};
132
185
  // Exclude soft-deleted documents by default unless caller specifies otherwise
186
+ const normalizedCriteria = K2DB.normalizeCriteriaIds(criteria || {});
133
187
  const query = {
134
- ...criteria,
188
+ ...normalizedCriteria,
135
189
  ...(criteria && Object.prototype.hasOwnProperty.call(criteria, "_deleted")
136
190
  ? {}
137
191
  : { _deleted: { $ne: true } }),
@@ -164,7 +218,8 @@ export class K2DB {
164
218
  async find(collectionName, filter, params = {}, skip = 0, limit = 100) {
165
219
  const collection = await this.getCollection(collectionName);
166
220
  // Ensure filter is valid, defaulting to an empty object
167
- const criteria = { ...(filter || {}) };
221
+ let criteria = { ...(filter || {}) };
222
+ criteria = K2DB.normalizeCriteriaIds(criteria);
168
223
  // Handle the _deleted field if params specify not to include deleted documents
169
224
  if (!params?.includeDeleted && !Object.prototype.hasOwnProperty.call(criteria, "_deleted")) {
170
225
  if (params?.deleted === true) {
@@ -358,8 +413,8 @@ export class K2DB {
358
413
  }
359
414
  const collection = await this.getCollection(collectionName);
360
415
  const timestamp = Date.now();
361
- // Generate a new UUID
362
- const newUuid = uuidv4();
416
+ // Generate a new UUIDv7 encoded as Crockford Base32 with hyphens
417
+ const newUuid = uuidv7Base32Hyphenated();
363
418
  // Remove reserved fields from user data, then validate/transform via schema if present
364
419
  const safeData = K2DB.stripReservedFields(data);
365
420
  const validated = this.applySchema(collectionName, safeData, /*partial*/ false);
@@ -398,9 +453,20 @@ export class K2DB {
398
453
  this.validateCollectionName(collectionName);
399
454
  const collection = await this.getCollection(collectionName);
400
455
  debug(`Updating ${collectionName} with criteria: ${JSON.stringify(criteria)}`);
456
+ // Preserve intent to set _deleted during internal soft-delete operations.
457
+ // stripReservedFields removes underscore-prefixed keys (by design) to protect
458
+ // internal fields from user updates. However, deleteAll() legitimately passes
459
+ // {_deleted: true}. Capture and restore it here.
460
+ const deletedFlag = Object.prototype.hasOwnProperty.call(values, "_deleted")
461
+ ? values._deleted
462
+ : undefined;
401
463
  values = K2DB.stripReservedFields(values);
402
464
  values = this.applySchema(collectionName, values, /*partial*/ true);
403
465
  values._updated = Date.now();
466
+ if (deletedFlag !== undefined) {
467
+ values._deleted = deletedFlag;
468
+ }
469
+ criteria = K2DB.normalizeCriteriaIds(criteria || {});
404
470
  criteria = {
405
471
  ...criteria,
406
472
  _deleted: { $ne: true },
@@ -424,6 +490,7 @@ export class K2DB {
424
490
  * @param replace - If true, replaces the entire document (PUT), otherwise patches (PATCH).
425
491
  */
426
492
  async update(collectionName, id, data, replace = false) {
493
+ id = K2DB.normalizeId(id);
427
494
  this.validateCollectionName(collectionName);
428
495
  const collection = await this.getCollection(collectionName);
429
496
  data = K2DB.stripReservedFields(data);
@@ -489,6 +556,7 @@ export class K2DB {
489
556
  * @param id - UUID of the document.
490
557
  */
491
558
  async delete(collectionName, id) {
559
+ id = K2DB.normalizeId(id);
492
560
  try {
493
561
  // Call deleteAll to soft delete the document by UUID
494
562
  const result = await this.deleteAll(collectionName, { _uuid: id });
@@ -507,6 +575,10 @@ export class K2DB {
507
575
  }
508
576
  }
509
577
  catch (err) {
578
+ // Preserve existing K2Error classifications (e.g., NOT_FOUND)
579
+ if (err instanceof K2Error) {
580
+ throw err;
581
+ }
510
582
  throw new K2Error(ServiceError.SYSTEM_ERROR, "Error removing object from collection", "sys_mdb_remove_upd", this.normalizeError(err));
511
583
  }
512
584
  }
@@ -516,6 +588,7 @@ export class K2DB {
516
588
  * @param id - UUID of the document.
517
589
  */
518
590
  async purge(collectionName, id) {
591
+ id = K2DB.normalizeId(id);
519
592
  const collection = await this.getCollection(collectionName);
520
593
  try {
521
594
  const item = await this.runTimed("findOne", { collectionName, _uuid: id, _deleted: true }, async () => await collection.findOne({
@@ -535,6 +608,31 @@ export class K2DB {
535
608
  throw new K2Error(ServiceError.SYSTEM_ERROR, `Error purging item with id: ${id}`, "sys_mdb_pg", this.normalizeError(err));
536
609
  }
537
610
  }
611
+ /**
612
+ * Permanently deletes all documents that are soft-deleted and whose _updated
613
+ * timestamp is older than the provided threshold (in milliseconds ago).
614
+ * @param collectionName - Name of the collection.
615
+ * @param olderThanMs - Age threshold in milliseconds; documents with
616
+ * `_updated <= (Date.now() - olderThanMs)` will be purged.
617
+ */
618
+ async purgeDeletedOlderThan(collectionName, olderThanMs) {
619
+ this.validateCollectionName(collectionName);
620
+ if (typeof olderThanMs !== 'number' || !isFinite(olderThanMs) || olderThanMs < 0) {
621
+ throw new K2Error(ServiceError.BAD_REQUEST, 'olderThanMs must be a non-negative number', 'sys_mdb_purge_older_invalid');
622
+ }
623
+ const collection = await this.getCollection(collectionName);
624
+ const cutoff = Date.now() - olderThanMs;
625
+ try {
626
+ const res = await this.runTimed('deleteMany', { collectionName, olderThanMs, cutoff }, async () => await collection.deleteMany({
627
+ _deleted: true,
628
+ _updated: { $lte: cutoff },
629
+ }));
630
+ return { purged: res.deletedCount ?? 0 };
631
+ }
632
+ catch (err) {
633
+ throw new K2Error(ServiceError.SYSTEM_ERROR, 'Error purging deleted items by age', 'sys_mdb_purge_older', this.normalizeError(err));
634
+ }
635
+ }
538
636
  /**
539
637
  * Restores a soft-deleted document.
540
638
  * @param collectionName - Name of the collection.
@@ -542,10 +640,12 @@ export class K2DB {
542
640
  */
543
641
  async restore(collectionName, criteria) {
544
642
  const collection = await this.getCollection(collectionName);
545
- const query = { ...(criteria || {}), _deleted: true };
643
+ const crit = K2DB.normalizeCriteriaIds(criteria || {});
644
+ const query = { ...crit, _deleted: true };
546
645
  try {
547
646
  const res = await this.runTimed("updateMany", { collectionName, query }, async () => await collection.updateMany(query, {
548
- $set: { _deleted: false },
647
+ // Restoring is a data change: flip _deleted and bump _updated
648
+ $set: { _deleted: false, _updated: Date.now() },
549
649
  }));
550
650
  return { status: "restored", modified: res.modifiedCount };
551
651
  }
@@ -561,8 +661,9 @@ export class K2DB {
561
661
  async count(collectionName, criteria) {
562
662
  const collection = await this.getCollection(collectionName);
563
663
  try {
664
+ const norm = K2DB.normalizeCriteriaIds(criteria || {});
564
665
  const query = {
565
- ...criteria,
666
+ ...norm,
566
667
  ...(criteria && Object.prototype.hasOwnProperty.call(criteria, "_deleted")
567
668
  ? {}
568
669
  : { _deleted: { $ne: true } }),
@@ -594,6 +695,8 @@ export class K2DB {
594
695
  */
595
696
  static sanitiseCriteria(criteria) {
596
697
  if (criteria.$match) {
698
+ // Normalize any _uuid values in the match object to uppercase
699
+ criteria.$match = K2DB.normalizeCriteriaIds(criteria.$match);
597
700
  for (const key of Object.keys(criteria.$match)) {
598
701
  if (typeof criteria.$match[key] !== "string") {
599
702
  criteria.$match[key] = K2DB.sanitiseCriteria({
@@ -609,6 +712,45 @@ export class K2DB {
609
712
  }
610
713
  return criteria;
611
714
  }
715
+ /** Recursively uppercases any values for fields named `_uuid` within a query object. */
716
+ static normalizeCriteriaIds(obj) {
717
+ if (!obj || typeof obj !== "object")
718
+ return obj;
719
+ if (Array.isArray(obj))
720
+ return obj.map((v) => K2DB.normalizeCriteriaIds(v));
721
+ const out = Array.isArray(obj) ? [] : { ...obj };
722
+ for (const [k, v] of Object.entries(obj)) {
723
+ if (k === "_uuid") {
724
+ out[k] = K2DB.normalizeUuidField(v);
725
+ }
726
+ else if (v && typeof v === "object") {
727
+ out[k] = K2DB.normalizeCriteriaIds(v);
728
+ }
729
+ else if (Array.isArray(v)) {
730
+ out[k] = v.map((x) => K2DB.normalizeCriteriaIds(x));
731
+ }
732
+ else {
733
+ out[k] = v;
734
+ }
735
+ }
736
+ return out;
737
+ }
738
+ /** Uppercase helper for `_uuid` field supporting operators like $in/$nin/$eq/$ne and arrays. */
739
+ static normalizeUuidField(val) {
740
+ if (typeof val === "string")
741
+ return val.toUpperCase();
742
+ if (Array.isArray(val))
743
+ return val.map((x) => (typeof x === "string" ? x.toUpperCase() : x));
744
+ if (val && typeof val === "object") {
745
+ const out = { ...val };
746
+ for (const op of ["$in", "$nin", "$eq", "$ne", "$all"]) {
747
+ if (op in out)
748
+ out[op] = K2DB.normalizeUuidField(out[op]);
749
+ }
750
+ return out;
751
+ }
752
+ return val;
753
+ }
612
754
  /** Strip any user-provided fields that start with '_' (reserved). */
613
755
  static stripReservedFields(obj) {
614
756
  const out = {};
@@ -618,6 +760,18 @@ export class K2DB {
618
760
  }
619
761
  return out;
620
762
  }
763
+ /** True if string matches K2 ID format (Crockford Base32, 8-4-4-4-6, uppercase). */
764
+ static isK2ID(id) {
765
+ if (typeof id !== "string")
766
+ return false;
767
+ const s = id.trim().toUpperCase();
768
+ const CROCK_RE = /^[0-9A-HJKMNPQRSTVWXYZ]{8}-[0-9A-HJKMNPQRSTVWXYZ]{4}-[0-9A-HJKMNPQRSTVWXYZ]{4}-[0-9A-HJKMNPQRSTVWXYZ]{4}-[0-9A-HJKMNPQRSTVWXYZ]{6}$/;
769
+ return CROCK_RE.test(s);
770
+ }
771
+ /** Uppercase incoming IDs for case-insensitive lookups. */
772
+ static normalizeId(id) {
773
+ return id.toUpperCase();
774
+ }
621
775
  /**
622
776
  * Run an async DB operation with timing, slow logging, and hooks.
623
777
  */
@@ -656,7 +810,9 @@ export class K2DB {
656
810
  const { uuidUnique = false, uuidPartialUnique = true, ownerIndex = true, deletedIndex = true, } = opts;
657
811
  const collection = await this.getCollection(collectionName);
658
812
  if (uuidPartialUnique) {
659
- await collection.createIndex({ _uuid: 1 }, { unique: true, partialFilterExpression: { _deleted: { $ne: true } } });
813
+ // Use a compound unique index to ensure at most one non-deleted document per _uuid
814
+ // without relying on partialFilterExpression (which may be limited in some environments).
815
+ await collection.createIndex({ _uuid: 1, _deleted: 1 }, { unique: true });
660
816
  }
661
817
  else if (uuidUnique) {
662
818
  await collection.createIndex({ _uuid: 1 }, { unique: true });
@@ -820,6 +976,7 @@ export class K2DB {
820
976
  }
821
977
  /** Compute the next version number for a document. */
822
978
  async nextVersion(collectionName, id) {
979
+ id = K2DB.normalizeId(id);
823
980
  const hc = await this.getHistoryCollection(collectionName);
824
981
  const last = await hc
825
982
  .find({ _uuid: id })
@@ -846,6 +1003,7 @@ export class K2DB {
846
1003
  * If maxVersions is provided, prunes oldest snapshots beyond that number.
847
1004
  */
848
1005
  async updateVersioned(collectionName, id, data, replace = false, maxVersions) {
1006
+ id = K2DB.normalizeId(id);
849
1007
  // Get current doc (excludes deleted) and snapshot it
850
1008
  const current = await this.get(collectionName, id);
851
1009
  await this.ensureHistoryIndexes(collectionName);
@@ -874,6 +1032,7 @@ export class K2DB {
874
1032
  }
875
1033
  /** List versions (latest first). */
876
1034
  async listVersions(collectionName, id, skip = 0, limit = 20) {
1035
+ id = K2DB.normalizeId(id);
877
1036
  const hc = await this.getHistoryCollection(collectionName);
878
1037
  const rows = await hc
879
1038
  .find({ _uuid: id })
@@ -886,6 +1045,7 @@ export class K2DB {
886
1045
  }
887
1046
  /** Revert the current document to a specific historical version (preserves metadata). */
888
1047
  async revertToVersion(collectionName, id, version) {
1048
+ id = K2DB.normalizeId(id);
889
1049
  const hc = await this.getHistoryCollection(collectionName);
890
1050
  const row = await hc.findOne({ _uuid: id, _v: version });
891
1051
  if (!row) {
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frogfish/k2db",
3
- "version": "2.0.3",
3
+ "version": "2.0.7",
4
4
  "description": "A data handling library for K2 applications.",
5
5
  "type": "module",
6
6
  "main": "data.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frogfish/k2db",
3
- "version": "2.0.3",
3
+ "version": "2.0.7",
4
4
  "description": "A data handling library for K2 applications.",
5
5
  "main": "./dist/data.js",
6
6
  "types": "./dist/data.d.ts",