@e280/quay 0.0.0-2 → 0.0.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
@@ -115,3 +115,31 @@ new Quay.Brain({
115
115
 
116
116
  In this way, you can setup sophisticated rules about what actions are permitted, and under whatever changing circumstances.
117
117
 
118
+ <br/>
119
+
120
+ ## MediaStore
121
+
122
+ `MediaStore` is a small persistent media-bin preset.
123
+
124
+ ```ts
125
+ import {MediaStore, brain, register, components} from "@e280/quay"
126
+
127
+ const media = await MediaStore.open("my-project")
128
+ brain.setGroup("media", media)
129
+
130
+ register(components)
131
+ ```
132
+
133
+ ```html
134
+ <div group="media">
135
+ <quay-dropzone></quay-dropzone>
136
+ <quay-browser></quay-browser>
137
+ </div>
138
+ ```
139
+
140
+ The scope passed to `open()` separates media libraries.
141
+
142
+ ```ts
143
+ const projectA = await MediaStore.open("project-a")
144
+ const projectB = await MediaStore.open("project-b")
145
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@e280/quay",
3
- "version": "0.0.0-2",
3
+ "version": "0.0.0-7",
4
4
  "description": "File-browser and outliner UI for the web",
5
5
  "author": "Przemysław Gałęzki",
6
6
  "license": "MIT",
package/s/index.ts CHANGED
@@ -4,6 +4,10 @@ export {register, css} from "@benev/slate"
4
4
  export * from "./cellar/index.js"
5
5
 
6
6
  export * from "./dom/components.js"
7
+ export {brain} from "./dom/context.js"
8
+ export * from "./logic/presets/media/schema.js"
9
+ export * from "./logic/presets/media/group.js"
10
+ export * from "./logic/presets/media/store.js"
7
11
  import {Permissions} from "./logic/permissions.js"
8
12
 
9
13
  const Quay = {
@@ -16,6 +16,9 @@ export type MediaSchema = AsSchema<{
16
16
  label: string
17
17
  format: MediaFormat
18
18
  previewUrl: string | null
19
+ hash?: string
20
+ mime?: string
21
+ size?: number
19
22
  }
20
23
  }
21
24
  }>
@@ -0,0 +1,41 @@
1
+
2
+ import {Txt} from "@e280/stz"
3
+ import {expect, Science, test} from "@e280/science"
4
+
5
+ import {MediaStore} from "./store.js"
6
+
7
+ delete (globalThis as any).localStorage
8
+
9
+ export default Science.suite({
10
+ "imports files into cellar and index": test(async() => {
11
+ const group = new MediaStore()
12
+ const file = new File([Txt.toBytes("hello")], "hello.txt", {type: "text/plain"})
13
+
14
+ const [record] = await group.importFiles([file])
15
+
16
+ expect(await group.cellar.has(record.hash)).is(true)
17
+ expect(group.findByHash(record.hash)?.specimen.label).is("hello.txt")
18
+ }),
19
+
20
+ "lists media records": test(async() => {
21
+ const store = new MediaStore()
22
+ const file = new File([Txt.toBytes("image")], "image.png", {type: "image/png"})
23
+ const record = await store.importFile(file)
24
+ const records = []
25
+ for await (const record of store.records())
26
+ records.push(record)
27
+
28
+ expect(records.some(r => r.hash === record.hash)).is(true)
29
+ }),
30
+
31
+ "removes media records and bytes": test(async() => {
32
+ const store = new MediaStore()
33
+ const file = new File([Txt.toBytes("image")], "image.png", {type: "image/png"})
34
+ const record = await store.importFile(file)
35
+
36
+ await store.remove(record.hash)
37
+
38
+ expect(await store.cellar.has(record.hash)).is(false)
39
+ expect(store.findByHash(record.hash)).is(undefined)
40
+ }),
41
+ })
@@ -0,0 +1,163 @@
1
+
2
+ import {Kv, StorageDriver} from "@e280/kv"
3
+
4
+ import {Cellar} from "../../../cellar/cellar.js"
5
+ import {MediaFormat} from "./schema.js"
6
+ import {MediaGroup} from "./group.js"
7
+
8
+ export type MediaRecord = {
9
+ hash: string
10
+ label: string
11
+ format: MediaFormat
12
+ mime: string
13
+ size: number
14
+ createdAt: number
15
+ updatedAt: number
16
+ }
17
+
18
+ export class MediaStore extends MediaGroup {
19
+ static async open(scope = "default") {
20
+ const group = new this()
21
+ group.cellar = await Cellar.opfs("media")
22
+ group.#index = mediaIndex(scope)
23
+ await group.#load()
24
+ return group
25
+ }
26
+
27
+ #objectUrls = new Map<string, string>()
28
+ #index: Kv<MediaRecord>
29
+
30
+ cellar = new Cellar()
31
+
32
+ constructor() {
33
+ super()
34
+ this.#index = mediaIndex("default")
35
+ this.on.upload.sub(({files, target}) => {
36
+ void this.importFiles(files, target)
37
+ })
38
+ }
39
+
40
+ async *records() {
41
+ for await (const [, record] of this.#index.entries())
42
+ yield record
43
+ }
44
+
45
+ async #load() {
46
+ for await (const record of this.records()) {
47
+ if (record.format === "image" && !this.#objectUrls.has(record.hash))
48
+ await this.#loadPreview(record)
49
+ this.#attachRecord(record, this.config.root)
50
+ }
51
+ }
52
+
53
+ async importFiles(files: File[], parent = this.config.root) {
54
+ return Promise.all(files.map(f => this.importFile(f, parent)))
55
+ }
56
+
57
+ async importFile(file: File, parent = this.config.root) {
58
+ const bytes = new Uint8Array(await file.arrayBuffer())
59
+ const cask = await this.cellar.save(bytes)
60
+ const existing = await this.#index.get(cask.hash)
61
+ const now = Date.now()
62
+ const record: MediaRecord = {
63
+ hash: cask.hash,
64
+ label: existing?.label ?? file.name,
65
+ format: mediaFormat(file.type),
66
+ mime: file.type,
67
+ size: file.size,
68
+ createdAt: existing?.createdAt ?? now,
69
+ updatedAt: now,
70
+ }
71
+ await this.#index.set(record.hash, record)
72
+ if (record.format === "image")
73
+ this.#setPreview(record.hash, URL.createObjectURL(file))
74
+ this.#attachRecord(record, parent)
75
+ return record
76
+ }
77
+
78
+ async remove(hash: string) {
79
+ await this.#index.del(hash)
80
+ await this.cellar.delete(hash)
81
+ const item = this.findByHash(hash)
82
+ if (item) {
83
+ item.detach()
84
+ item.destroy()
85
+ }
86
+ this.#revokePreview(hash)
87
+ }
88
+
89
+ findByHash(hash: string) {
90
+ for (const [item] of this.config.root.crawl()) {
91
+ if (item.isKind("file") && item.specimen.hash === hash)
92
+ return item
93
+ }
94
+ }
95
+
96
+ dispose() {
97
+ for (const url of this.#objectUrls.values())
98
+ URL.revokeObjectURL(url)
99
+ this.#objectUrls.clear()
100
+ }
101
+
102
+ #attachRecord(record: MediaRecord, parent = this.config.root) {
103
+ const existing = this.findByHash(record.hash)
104
+ if (existing)
105
+ return existing
106
+
107
+ const item = this.config.codex.create("file", {
108
+ hash: record.hash,
109
+ label: record.label,
110
+ format: record.format,
111
+ mime: record.mime,
112
+ size: record.size,
113
+ previewUrl: this.#objectUrls.get(record.hash) ?? null,
114
+ })
115
+ parent.attach(item)
116
+ return item
117
+ }
118
+
119
+ async #loadPreview(record: MediaRecord) {
120
+ const cask = await this.cellar.load(record.hash)
121
+ const blob = new Blob([cask.bytes], {type: record.mime || "image/*"})
122
+ return this.#setPreview(record.hash, URL.createObjectURL(blob))
123
+ }
124
+
125
+ #setPreview(hash: string, url: string) {
126
+ this.#revokePreview(hash)
127
+ this.#objectUrls.set(hash, url)
128
+ return url
129
+ }
130
+
131
+ #revokePreview(hash: string) {
132
+ const url = this.#objectUrls.get(hash)
133
+ if (url) {
134
+ URL.revokeObjectURL(url)
135
+ this.#objectUrls.delete(hash)
136
+ }
137
+ }
138
+ }
139
+
140
+ const mediaFormats = new Set(["video", "image", "audio"])
141
+ const memoryIndexes = new Map<string, Kv<MediaRecord>>()
142
+
143
+ function mediaFormat(mime: string): MediaFormat {
144
+ const [type] = mime.split("/")
145
+ return mediaFormats.has(type) ? type as MediaFormat : "other"
146
+ }
147
+
148
+ function mediaIndex(scope: string) {
149
+ const storage = globalThis.localStorage
150
+ if (storage)
151
+ return new Kv<MediaRecord>(new StorageDriver(storage))
152
+ .namespace("quay.media")
153
+ .namespace(scope)
154
+
155
+ const existing = memoryIndexes.get(scope)
156
+ if (existing)
157
+ return existing
158
+
159
+ const index = new Kv<MediaRecord>()
160
+ memoryIndexes.set(scope, index)
161
+ return index
162
+ }
163
+
package/s/tests.test.ts CHANGED
@@ -1,8 +1,11 @@
1
1
 
2
+ import "@benev/slate/x/node.js"
2
3
  import {Science} from "@e280/science"
3
4
  import cellar from "./cellar/cellar.test.js"
5
+ import media from "./logic/presets/media/store.test.js"
4
6
 
5
7
  await Science.run({
6
8
  cellar,
9
+ media
7
10
  })
8
11
 
package/x/index.d.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  export { register, css } from "@benev/slate";
2
2
  export * from "./cellar/index.js";
3
3
  export * from "./dom/components.js";
4
+ export { brain } from "./dom/context.js";
5
+ export * from "./logic/presets/media/schema.js";
6
+ export * from "./logic/presets/media/group.js";
7
+ export * from "./logic/presets/media/store.js";
4
8
  declare const Quay: {
5
9
  permissions: {
6
10
  all: import("./logic/permissions.js").Permission;
package/x/index.html CHANGED
@@ -10,7 +10,7 @@
10
10
  <link rel="stylesheet" href="demo/main.css?v=c15dc8e4"/>
11
11
 
12
12
  <link rel="icon" href="/assets/favicon.png"/>
13
- <meta data-commit-hash="9341593c11f695d0a9d4eccb81f82395152c5e54"/>
13
+ <meta data-commit-hash="46d801819c2f1f3a00bffaa2e49b894e77f56786"/>
14
14
 
15
15
 
16
16
  <meta name="theme-color" content="#eb6f1d">
@@ -123,7 +123,7 @@
123
123
  <img alt="" src="/assets/favicon.png"/>
124
124
  <div>
125
125
  <h1>Quay</h1>
126
- <span class=version>v0.0.0-2</span>
126
+ <span class=version>v0.0.0-7</span>
127
127
  </div>
128
128
  </header>
129
129
 
package/x/index.js CHANGED
@@ -1,6 +1,10 @@
1
1
  export { register, css } from "@benev/slate";
2
2
  export * from "./cellar/index.js";
3
3
  export * from "./dom/components.js";
4
+ export { brain } from "./dom/context.js";
5
+ export * from "./logic/presets/media/schema.js";
6
+ export * from "./logic/presets/media/group.js";
7
+ export * from "./logic/presets/media/store.js";
4
8
  import { Permissions } from "./logic/permissions.js";
5
9
  const Quay = {
6
10
  permissions: Permissions,
package/x/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../s/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,QAAQ,EAAE,GAAG,EAAC,MAAM,cAAc,CAAA;AAE1C,cAAc,mBAAmB,CAAA;AAEjC,cAAc,qBAAqB,CAAA;AACnC,OAAO,EAAC,WAAW,EAAC,MAAM,wBAAwB,CAAA;AAElD,MAAM,IAAI,GAAG;IACZ,WAAW,EAAE,WAAW;CACxB,CAAA;AAED,eAAe,IAAI,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../s/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,QAAQ,EAAE,GAAG,EAAC,MAAM,cAAc,CAAA;AAE1C,cAAc,mBAAmB,CAAA;AAEjC,cAAc,qBAAqB,CAAA;AACnC,OAAO,EAAC,KAAK,EAAC,MAAM,kBAAkB,CAAA;AACtC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,gCAAgC,CAAA;AAC9C,OAAO,EAAC,WAAW,EAAC,MAAM,wBAAwB,CAAA;AAElD,MAAM,IAAI,GAAG;IACZ,WAAW,EAAE,WAAW;CACxB,CAAA;AAED,eAAe,IAAI,CAAA"}
@@ -13,6 +13,9 @@ export type MediaSchema = AsSchema<{
13
13
  label: string;
14
14
  format: MediaFormat;
15
15
  previewUrl: string | null;
16
+ hash?: string;
17
+ mime?: string;
18
+ size?: number;
16
19
  };
17
20
  };
18
21
  }>;
@@ -0,0 +1,75 @@
1
+ import { Cellar } from "../../../cellar/cellar.js";
2
+ import { MediaFormat } from "./schema.js";
3
+ import { MediaGroup } from "./group.js";
4
+ export type MediaRecord = {
5
+ hash: string;
6
+ label: string;
7
+ format: MediaFormat;
8
+ mime: string;
9
+ size: number;
10
+ createdAt: number;
11
+ updatedAt: number;
12
+ };
13
+ export declare class MediaStore extends MediaGroup {
14
+ #private;
15
+ static open(scope?: string): Promise<MediaStore>;
16
+ cellar: Cellar;
17
+ constructor();
18
+ records(): AsyncGenerator<MediaRecord, void, unknown>;
19
+ importFiles(files: File[], parent?: import("../../aspects/codex/parts/codex-item.js").CodexItem<{
20
+ taxon: {
21
+ icon: import("@benev/slate").Content;
22
+ };
23
+ specimens: {
24
+ folder: {
25
+ label: string;
26
+ };
27
+ file: {
28
+ label: string;
29
+ format: MediaFormat;
30
+ previewUrl: string | null;
31
+ hash?: string;
32
+ mime?: string;
33
+ size?: number;
34
+ };
35
+ };
36
+ }, "file" | "folder">): Promise<MediaRecord[]>;
37
+ importFile(file: File, parent?: import("../../aspects/codex/parts/codex-item.js").CodexItem<{
38
+ taxon: {
39
+ icon: import("@benev/slate").Content;
40
+ };
41
+ specimens: {
42
+ folder: {
43
+ label: string;
44
+ };
45
+ file: {
46
+ label: string;
47
+ format: MediaFormat;
48
+ previewUrl: string | null;
49
+ hash?: string;
50
+ mime?: string;
51
+ size?: number;
52
+ };
53
+ };
54
+ }, "file" | "folder">): Promise<MediaRecord>;
55
+ remove(hash: string): Promise<void>;
56
+ findByHash(hash: string): import("../../aspects/codex/parts/codex-item.js").CodexItem<{
57
+ taxon: {
58
+ icon: import("@benev/slate").Content;
59
+ };
60
+ specimens: {
61
+ folder: {
62
+ label: string;
63
+ };
64
+ file: {
65
+ label: string;
66
+ format: MediaFormat;
67
+ previewUrl: string | null;
68
+ hash?: string;
69
+ mime?: string;
70
+ size?: number;
71
+ };
72
+ };
73
+ }, "file"> | undefined;
74
+ dispose(): void;
75
+ }
@@ -0,0 +1,129 @@
1
+ import { Kv, StorageDriver } from "@e280/kv";
2
+ import { Cellar } from "../../../cellar/cellar.js";
3
+ import { MediaGroup } from "./group.js";
4
+ export class MediaStore extends MediaGroup {
5
+ static async open(scope = "default") {
6
+ const group = new this();
7
+ group.cellar = await Cellar.opfs("media");
8
+ group.#index = mediaIndex(scope);
9
+ await group.#load();
10
+ return group;
11
+ }
12
+ #objectUrls = new Map();
13
+ #index;
14
+ cellar = new Cellar();
15
+ constructor() {
16
+ super();
17
+ this.#index = mediaIndex("default");
18
+ this.on.upload.sub(({ files, target }) => {
19
+ void this.importFiles(files, target);
20
+ });
21
+ }
22
+ async *records() {
23
+ for await (const [, record] of this.#index.entries())
24
+ yield record;
25
+ }
26
+ async #load() {
27
+ for await (const record of this.records()) {
28
+ if (record.format === "image" && !this.#objectUrls.has(record.hash))
29
+ await this.#loadPreview(record);
30
+ this.#attachRecord(record, this.config.root);
31
+ }
32
+ }
33
+ async importFiles(files, parent = this.config.root) {
34
+ return Promise.all(files.map(f => this.importFile(f, parent)));
35
+ }
36
+ async importFile(file, parent = this.config.root) {
37
+ const bytes = new Uint8Array(await file.arrayBuffer());
38
+ const cask = await this.cellar.save(bytes);
39
+ const existing = await this.#index.get(cask.hash);
40
+ const now = Date.now();
41
+ const record = {
42
+ hash: cask.hash,
43
+ label: existing?.label ?? file.name,
44
+ format: mediaFormat(file.type),
45
+ mime: file.type,
46
+ size: file.size,
47
+ createdAt: existing?.createdAt ?? now,
48
+ updatedAt: now,
49
+ };
50
+ await this.#index.set(record.hash, record);
51
+ if (record.format === "image")
52
+ this.#setPreview(record.hash, URL.createObjectURL(file));
53
+ this.#attachRecord(record, parent);
54
+ return record;
55
+ }
56
+ async remove(hash) {
57
+ await this.#index.del(hash);
58
+ await this.cellar.delete(hash);
59
+ const item = this.findByHash(hash);
60
+ if (item) {
61
+ item.detach();
62
+ item.destroy();
63
+ }
64
+ this.#revokePreview(hash);
65
+ }
66
+ findByHash(hash) {
67
+ for (const [item] of this.config.root.crawl()) {
68
+ if (item.isKind("file") && item.specimen.hash === hash)
69
+ return item;
70
+ }
71
+ }
72
+ dispose() {
73
+ for (const url of this.#objectUrls.values())
74
+ URL.revokeObjectURL(url);
75
+ this.#objectUrls.clear();
76
+ }
77
+ #attachRecord(record, parent = this.config.root) {
78
+ const existing = this.findByHash(record.hash);
79
+ if (existing)
80
+ return existing;
81
+ const item = this.config.codex.create("file", {
82
+ hash: record.hash,
83
+ label: record.label,
84
+ format: record.format,
85
+ mime: record.mime,
86
+ size: record.size,
87
+ previewUrl: this.#objectUrls.get(record.hash) ?? null,
88
+ });
89
+ parent.attach(item);
90
+ return item;
91
+ }
92
+ async #loadPreview(record) {
93
+ const cask = await this.cellar.load(record.hash);
94
+ const blob = new Blob([cask.bytes], { type: record.mime || "image/*" });
95
+ return this.#setPreview(record.hash, URL.createObjectURL(blob));
96
+ }
97
+ #setPreview(hash, url) {
98
+ this.#revokePreview(hash);
99
+ this.#objectUrls.set(hash, url);
100
+ return url;
101
+ }
102
+ #revokePreview(hash) {
103
+ const url = this.#objectUrls.get(hash);
104
+ if (url) {
105
+ URL.revokeObjectURL(url);
106
+ this.#objectUrls.delete(hash);
107
+ }
108
+ }
109
+ }
110
+ const mediaFormats = new Set(["video", "image", "audio"]);
111
+ const memoryIndexes = new Map();
112
+ function mediaFormat(mime) {
113
+ const [type] = mime.split("/");
114
+ return mediaFormats.has(type) ? type : "other";
115
+ }
116
+ function mediaIndex(scope) {
117
+ const storage = globalThis.localStorage;
118
+ if (storage)
119
+ return new Kv(new StorageDriver(storage))
120
+ .namespace("quay.media")
121
+ .namespace(scope);
122
+ const existing = memoryIndexes.get(scope);
123
+ if (existing)
124
+ return existing;
125
+ const index = new Kv();
126
+ memoryIndexes.set(scope, index);
127
+ return index;
128
+ }
129
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../../s/logic/presets/media/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { Science } from "@e280/science";
2
+ declare const _default: {
3
+ "imports files into cellar and index": Science.Test;
4
+ "lists media records": Science.Test;
5
+ "removes media records and bytes": Science.Test;
6
+ };
7
+ export default _default;
@@ -0,0 +1,31 @@
1
+ import { Txt } from "@e280/stz";
2
+ import { expect, Science, test } from "@e280/science";
3
+ import { MediaStore } from "./store.js";
4
+ delete globalThis.localStorage;
5
+ export default Science.suite({
6
+ "imports files into cellar and index": test(async () => {
7
+ const group = new MediaStore();
8
+ const file = new File([Txt.toBytes("hello")], "hello.txt", { type: "text/plain" });
9
+ const [record] = await group.importFiles([file]);
10
+ expect(await group.cellar.has(record.hash)).is(true);
11
+ expect(group.findByHash(record.hash)?.specimen.label).is("hello.txt");
12
+ }),
13
+ "lists media records": test(async () => {
14
+ const store = new MediaStore();
15
+ const file = new File([Txt.toBytes("image")], "image.png", { type: "image/png" });
16
+ const record = await store.importFile(file);
17
+ const records = [];
18
+ for await (const record of store.records())
19
+ records.push(record);
20
+ expect(records.some(r => r.hash === record.hash)).is(true);
21
+ }),
22
+ "removes media records and bytes": test(async () => {
23
+ const store = new MediaStore();
24
+ const file = new File([Txt.toBytes("image")], "image.png", { type: "image/png" });
25
+ const record = await store.importFile(file);
26
+ await store.remove(record.hash);
27
+ expect(await store.cellar.has(record.hash)).is(false);
28
+ expect(store.findByHash(record.hash)).is(undefined);
29
+ }),
30
+ });
31
+ //# sourceMappingURL=store.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.test.js","sourceRoot":"","sources":["../../../../s/logic/presets/media/store.test.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,GAAG,EAAC,MAAM,WAAW,CAAA;AAC7B,OAAO,EAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAC,MAAM,eAAe,CAAA;AAEnD,OAAO,EAAC,UAAU,EAAC,MAAM,YAAY,CAAA;AAErC,OAAQ,UAAkB,CAAC,YAAY,CAAA;AAEvC,eAAe,OAAO,CAAC,KAAK,CAAC;IAC5B,qCAAqC,EAAE,IAAI,CAAC,KAAK,IAAG,EAAE;QACrD,MAAM,KAAK,GAAG,IAAI,UAAU,EAAE,CAAA;QAC9B,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,EAAE,WAAW,EAAE,EAAC,IAAI,EAAE,YAAY,EAAC,CAAC,CAAA;QAEhF,MAAM,CAAC,MAAM,CAAC,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;QAEhD,MAAM,CAAC,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAA;QACpD,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,WAAW,CAAC,CAAA;IACtE,CAAC,CAAC;IAEF,qBAAqB,EAAE,IAAI,CAAC,KAAK,IAAG,EAAE;QACrC,MAAM,KAAK,GAAG,IAAI,UAAU,EAAE,CAAA;QAC9B,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,EAAE,WAAW,EAAE,EAAC,IAAI,EAAE,WAAW,EAAC,CAAC,CAAA;QAC/E,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;QAC3C,MAAM,OAAO,GAAG,EAAE,CAAA;QAClB,IAAI,KAAK,EAAE,MAAM,MAAM,IAAI,KAAK,CAAC,OAAO,EAAE;YACzC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAErB,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAA;IAC3D,CAAC,CAAC;IAEF,iCAAiC,EAAE,IAAI,CAAC,KAAK,IAAG,EAAE;QACjD,MAAM,KAAK,GAAG,IAAI,UAAU,EAAE,CAAA;QAC9B,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,EAAE,WAAW,EAAE,EAAC,IAAI,EAAE,WAAW,EAAC,CAAC,CAAA;QAC/E,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;QAE3C,MAAM,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QAE/B,MAAM,CAAC,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAA;QACrD,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,CAAA;IACpD,CAAC,CAAC;CACF,CAAC,CAAA"}
package/x/tests.test.d.ts CHANGED
@@ -1 +1 @@
1
- export {};
1
+ import "@benev/slate/x/node.js";
package/x/tests.test.js CHANGED
@@ -1,6 +1,9 @@
1
+ import "@benev/slate/x/node.js";
1
2
  import { Science } from "@e280/science";
2
3
  import cellar from "./cellar/cellar.test.js";
4
+ import media from "./logic/presets/media/store.test.js";
3
5
  await Science.run({
4
6
  cellar,
7
+ media
5
8
  });
6
9
  //# sourceMappingURL=tests.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tests.test.js","sourceRoot":"","sources":["../s/tests.test.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,OAAO,EAAC,MAAM,eAAe,CAAA;AACrC,OAAO,MAAM,MAAM,yBAAyB,CAAA;AAE5C,MAAM,OAAO,CAAC,GAAG,CAAC;IACjB,MAAM;CACN,CAAC,CAAA"}
1
+ {"version":3,"file":"tests.test.js","sourceRoot":"","sources":["../s/tests.test.ts"],"names":[],"mappings":"AACA,OAAO,wBAAwB,CAAA;AAC/B,OAAO,EAAC,OAAO,EAAC,MAAM,eAAe,CAAA;AACrC,OAAO,MAAM,MAAM,yBAAyB,CAAA;AAC5C,OAAO,KAAK,MAAM,qCAAqC,CAAA;AAEvD,MAAM,OAAO,CAAC,GAAG,CAAC;IACjB,MAAM;IACN,KAAK;CACL,CAAC,CAAA"}