@storecraft/database-sql-base 1.0.12 → 1.0.14

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.
@@ -42,6 +42,7 @@ export const upsert = (client) => {
42
42
  created_at: item.created_at,
43
43
  updated_at: item.updated_at,
44
44
  id: item.id,
45
+ active: item.active ? 1 : 0,
45
46
  title: item.title,
46
47
  handle: item.handle,
47
48
  template_html: decode_if_base64(item.template_html),
@@ -95,7 +96,8 @@ const remove = (driver) => {
95
96
  async (trx) => {
96
97
 
97
98
  // entities
98
- await delete_search_of(trx, id_or_handle);
99
+ await delete_search_of(trx, id_or_handle, id_or_handle, table_name);
100
+
99
101
  // delete me
100
102
  await delete_me(trx, table_name, id_or_handle);
101
103
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @import { ReplaceValuesOfKeys } from './utils.types.js'
2
+ * @import { ReplaceValuesOfKeys, ReplaceValuesOfKeys2 } from './utils.types.js'
3
3
  */
4
4
 
5
5
  export const isDef = v => v!==undefined && v!==null;
@@ -29,7 +29,7 @@ export const delete_keys = (...keys) => {
29
29
  * Sanitize null/undefined valued keys
30
30
  * @template T
31
31
  * @param {T} o
32
- * @return {ReplaceValuesOfKeys<T, 'active' | 'confirmed_mail', boolean>}
32
+ * @return {ReplaceValuesOfKeys2<T, 'active' | 'confirmed_mail', boolean>}
33
33
  */
34
34
  export const sanitize = o => {
35
35
  for (const key in o) {
@@ -39,6 +39,10 @@ export const sanitize = o => {
39
39
  delete o[key];
40
40
  continue;
41
41
  }
42
+ if(key.startsWith('_')) {
43
+ delete o[key];
44
+ continue;
45
+ }
42
46
  if(key==='active') {
43
47
  // @ts-ignore
44
48
  o[key] = Boolean(value);
@@ -2,7 +2,7 @@
2
2
  * @import { ApiQuery, Cursor } from '@storecraft/core/api'
3
3
  * @import { VQL } from '@storecraft/core/vql'
4
4
  * @import { Database } from '../types.sql.tables.js'
5
- * @import { ExpressionBuilder } from 'kysely'
5
+ * @import { BinaryOperator, ExpressionBuilder } from 'kysely'
6
6
  */
7
7
 
8
8
  import { parse } from "@storecraft/core/vql";
@@ -22,7 +22,9 @@ import { parse } from "@storecraft/core/vql";
22
22
  */
23
23
  export const query_cursor_to_eb = (eb, c, relation, transformer=(x)=>x) => {
24
24
 
25
+ /** @type {BinaryOperator} */
25
26
  let rel_key_1; // relation in last conjunction term in [0, n-1] disjunctions
27
+ /** @type {BinaryOperator} */
26
28
  let rel_key_2; // relation in last conjunction term in last disjunction
27
29
 
28
30
  if (relation==='>' || relation==='>=') {
@@ -73,9 +75,8 @@ export const query_cursor_to_eb = (eb, c, relation, transformer=(x)=>x) => {
73
75
  // return result;
74
76
  }
75
77
 
76
-
77
78
  /**
78
- * @template {keyof Database} T
79
+ * @template {QueryableTables} T
79
80
  * @param {ExpressionBuilder<Database>} eb
80
81
  * @param {VQL.Node} node
81
82
  * @param {T} table_name
@@ -125,7 +126,7 @@ export const query_vql_node_to_eb = (eb, node, table_name) => {
125
126
  /**
126
127
  * @param {ExpressionBuilder<Database>} eb
127
128
  * @param {VQL.Node} root
128
- * @param {keyof Database} table_name
129
+ * @param {QueryableTables} table_name
129
130
  */
130
131
  export const query_vql_to_eb = (eb, root, table_name) => {
131
132
  return root ? query_vql_node_to_eb(eb, root, table_name) : undefined;
@@ -135,26 +136,28 @@ export const query_vql_to_eb = (eb, root, table_name) => {
135
136
  /**
136
137
  *
137
138
  * @param {[k: string, v: any]} kv
139
+ * @param {keyof Database} table_name
138
140
  * @returns {[k: string, v: any]}
139
141
  */
140
- const transform_boolean_to_0_or_1 = (kv) => {
141
- if(typeof kv[1] === 'boolean') {
142
- return [
143
- kv[0],
144
- kv[1] ? 1 : 0
145
- ]
146
- }
142
+ const transform_boolean_to_0_or_1 = (kv, table_name) => {
143
+
144
+ // console.log('transform_boolean_to_0_or_1', kv)
145
+ kv = [
146
+ table_name ? `${table_name}.${kv[0]}` : kv[0],
147
+ typeof kv[1] === 'boolean' ? (kv[1] ? 1 : 0) : kv[1]
148
+ ];
149
+
147
150
  return kv;
148
151
  }
149
152
 
150
153
  /**
151
- * Convert an API Query into mongo dialect, also sanitize.
154
+ * Convert an API Query into dialect, also sanitize.
152
155
  *
153
156
  * @template {any} [T=any]
154
157
  *
155
158
  * @param {ExpressionBuilder<Database>} eb
156
159
  * @param {ApiQuery<T>} q
157
- * @param {keyof Database} table_name
160
+ * @param {QueryableTables} table_name
158
161
  *
159
162
  */
160
163
  export const query_to_eb = (eb, q={}, table_name) => {
@@ -162,18 +165,19 @@ export const query_to_eb = (eb, q={}, table_name) => {
162
165
 
163
166
  const sort_sign = q.order === 'asc' ? 1 : -1;
164
167
  const asc = sort_sign==1;
168
+ const transformer = (x) => transform_boolean_to_0_or_1(x, table_name);
165
169
 
166
170
  // compute index clauses
167
171
  if(q.startAt) {
168
- clauses.push(query_cursor_to_eb(eb, q.startAt, asc ? '>=' : '<=', transform_boolean_to_0_or_1));
172
+ clauses.push(query_cursor_to_eb(eb, q.startAt, asc ? '>=' : '<=', transformer));
169
173
  } else if(q.startAfter) {
170
- clauses.push(query_cursor_to_eb(eb, q.startAfter, asc ? '>' : '<', transform_boolean_to_0_or_1));
174
+ clauses.push(query_cursor_to_eb(eb, q.startAfter, asc ? '>' : '<', transformer));
171
175
  }
172
176
 
173
177
  if(q.endAt) {
174
- clauses.push(query_cursor_to_eb(eb, q.endAt, asc ? '<=' : '>=', transform_boolean_to_0_or_1));
178
+ clauses.push(query_cursor_to_eb(eb, q.endAt, asc ? '<=' : '>=', transformer));
175
179
  } else if(q.endBefore) {
176
- clauses.push(query_cursor_to_eb(eb, q.endBefore, asc ? '<' : '>', transform_boolean_to_0_or_1));
180
+ clauses.push(query_cursor_to_eb(eb, q.endBefore, asc ? '<' : '>', transformer));
177
181
  }
178
182
 
179
183
  // compute VQL clauses
@@ -197,7 +201,7 @@ const SIGN = {
197
201
  // export type DirectedOrderByStringReference<DB, TB extends keyof DB, O> = `${StringReference<DB, TB> | (keyof O & string)} ${OrderByDirection}`;
198
202
 
199
203
  /**
200
- * @import {DirectedOrderByStringReference} from './utils.types.js'
204
+ * @import {DirectedOrderByStringReference, QueryableTables} from './utils.types.js'
201
205
  */
202
206
  // OE extends OrderByExpression<DB, TB, O>
203
207
  /**
@@ -219,9 +223,9 @@ export const query_to_sort = (q={}, table) => {
219
223
  // compute sort fields and order
220
224
  const keys = q.sortBy?.length ? q.sortBy : ['updated_at', 'id'];
221
225
  const sort = keys.map(
222
- s => `${s} ${SIGN[sort_sign]}`
226
+ s => table ? `${table}.${s} ${SIGN[sort_sign]}` : `${s} ${SIGN[sort_sign]}`
223
227
  )
224
228
  // it's too complicated to map each ket to table column.
225
229
  // kysely was designed to do this in place
226
- return sort;
230
+ return (/** @type {DirectedOrderByStringReference<Database, Table, Database[Table]>[]} */ (sort));
227
231
  }
@@ -1,4 +1,7 @@
1
+ import { App } from "@storecraft/core";
1
2
  import { StringReference } from "kysely";
3
+ import { Database } from "../types.sql.tables.js";
4
+ import { type ApiQuery } from "@storecraft/core/api";
2
5
 
3
6
  export type NoActive<T> = T extends {active:number} ? Omit<T, 'active'> & {active: boolean} : T;
4
7
  export type ReplaceValues<T, Find extends any=any, ReplaceWith extends any=any> = {
@@ -9,9 +12,31 @@ export type ReplaceValuesOfKeys<T, Find extends keyof T=keyof T, ReplaceWith ext
9
12
  [K in keyof T]: K extends Find ? ReplaceWith : T[K]
10
13
  };
11
14
 
15
+ export type ReplaceValuesOfKeys2<T, Find extends keyof T=keyof T, ReplaceWith extends any=any> = {
16
+ [K in keyof T]: K extends Find ? ReplaceWith : ReplaceValuesOfKeys2<T[K], Find, ReplaceWith>
17
+ };
18
+
19
+ // export type ENV<T> = Partial<
20
+ // {
21
+ // readonly [K in keyof T]: T[K] extends (number | string | boolean | Function | any[]) ? string : ENV<T[K]>
22
+ // // readonly [K in keyof T]: T[K] extends (any[] | Function) ? string : T[K] extends Record<string, any> ? ENV<T[K]> : (string)
23
+ // }
24
+ // >;
25
+
26
+
12
27
  export type NO<T> = {
13
28
  [P in keyof T]: T[P]
14
29
  }
15
30
 
16
31
  export type OrderByDirection = 'asc' | 'desc';
17
32
  export type DirectedOrderByStringReference<DB, TB extends keyof DB, O> = `${StringReference<DB, TB> | (keyof O & string)} ${OrderByDirection}`;
33
+
34
+ /**
35
+ * Those that are queryable with {@link ApiQuery} object
36
+ */
37
+ export type QueryableTables = Exclude<
38
+ keyof Database,
39
+ 'storefronts_to_other' | 'entity_to_media' | 'entity_to_tags_projections' |
40
+ 'entity_to_search_terms' | 'products_to_collections' | 'products_to_discounts' |
41
+ 'products_to_variants' | 'products_to_related_products'
42
+ >;
@@ -0,0 +1,19 @@
1
+ -- database: ../../../playground/node-libsql/data.db
2
+
3
+ -- Use the ▷ button in the top right corner to run the entire file.
4
+
5
+ SELECT
6
+ -- products.id,
7
+ -- products.handle,
8
+ -- products_to_collections.reporter as collection,
9
+ -- products.*,
10
+ -- products_to_collections.*,
11
+ entity_to_tags_projections.value as tag
12
+ FROM products
13
+ INNER JOIN
14
+ products_to_collections ON products.id = products_to_collections.entity_id
15
+ INNER JOIN
16
+ entity_to_tags_projections ON products.id = entity_to_tags_projections.entity_id
17
+ WHERE products_to_collections.reporter = 'playstation-4-games'
18
+ GROUP BY tag
19
+
@@ -0,0 +1,210 @@
1
+ /**
2
+ * @import {Database} from '../types.sql.tables.js';
3
+ */
4
+ import 'dotenv/config';
5
+ import { App } from '@storecraft/core';
6
+ import { SQL } from '@storecraft/database-sql-base';
7
+ import { migrateToLatest } from '@storecraft/database-sql-base/migrate.js';
8
+ import { NodePlatform } from '@storecraft/core/platform/node';
9
+ import { api } from '@storecraft/core/test-runner'
10
+ import SQLite from 'better-sqlite3'
11
+ import { SqliteDialect } from 'kysely';
12
+ import { homedir } from 'node:os';
13
+ import { join } from 'node:path';
14
+ import { jsonArrayFrom, stringArrayFrom } from '../src/con.helpers.json.js';
15
+ import { query_to_sort } from '../src/utils.query.js';
16
+ import { products_with_collections, products_with_discounts, products_with_related_products, products_with_variants, with_media, with_tags } from '../src/con.shared.js';
17
+
18
+ export const sqlite_dialect = new SqliteDialect({
19
+ database: async () => new SQLite(join(homedir(), 'db.sqlite')),
20
+ });
21
+
22
+ export const create_app = async () => {
23
+ const app = new App(
24
+ {
25
+ auth_admins_emails: ['admin@sc.com'],
26
+ auth_secret_access_token: 'auth_secret_access_token',
27
+ auth_secret_refresh_token: 'auth_secret_refresh_token'
28
+ }
29
+ )
30
+ .withPlatform(new NodePlatform())
31
+ .withDatabase(
32
+ new SQL({
33
+ dialect: sqlite_dialect,
34
+ dialect_type: 'SQLITE'
35
+ })
36
+ );
37
+
38
+ await app.init();
39
+ await migrateToLatest(app.db, false);
40
+
41
+ return app;
42
+ }
43
+
44
+ /**
45
+ * @template {keyof Database} [T=keyof Database]
46
+ * @typedef {{}}
47
+ */
48
+
49
+ /**
50
+ * @type {Record<keyof Database, (keyof Database['collections'])[]>}
51
+ */
52
+
53
+ /**
54
+ * @type {{[K in keyof Database]?: (keyof Database[K])[]}}
55
+ */
56
+ const resource_to_props = (
57
+ {
58
+ 'collections': ['active', 'attributes', 'created_at', 'description', 'handle', 'id', 'published', 'title', 'updated_at'],
59
+ 'discounts': ['active', 'application', 'attributes', 'created_at', 'description', 'handle', 'id', 'info', 'priority', 'published', 'title', 'updated_at'],
60
+ 'products': ['active', 'attributes', 'compare_at_price', 'created_at', 'description', 'handle', 'id', 'isbn', 'parent_handle', 'parent_id', 'price', 'qty', 'title', 'updated_at', 'variant_hint', 'variants_options', 'video'],
61
+ 'shipping_methods': ['active', 'attributes', 'created_at', 'description', 'handle', 'id', 'price', 'title', 'updated_at'],
62
+ 'posts': ['active', 'attributes', 'created_at', 'description', 'handle', 'id', 'text', 'title', 'updated_at'],
63
+ }
64
+ );
65
+
66
+
67
+ async function test() {
68
+ const app = await create_app();
69
+ const client = app.db.client;
70
+ const limit = 0;
71
+ const items = await client.selectNoFrom(
72
+ eb => [
73
+ jsonArrayFrom(
74
+ eb
75
+ .selectFrom('collections')
76
+ .select(resource_to_props.collections)
77
+ .select(
78
+ eb => [
79
+ with_tags(eb, eb.ref('collections.id'), app.db.dialectType),
80
+ with_media(eb, eb.ref('collections.id'), app.db.dialectType),
81
+ ]
82
+ )
83
+ .where('active', '=', 1)
84
+ .orderBy(['updated_at asc'])
85
+ .limit(limit),
86
+ app.db.dialectType
87
+ ).as('collections'),
88
+
89
+ jsonArrayFrom(
90
+ eb
91
+ .selectFrom('products')
92
+ .select(resource_to_props.products)
93
+ .select(
94
+ eb => [
95
+ with_tags(eb, eb.ref('products.id'), app.db.dialectType),
96
+ with_media(eb, eb.ref('products.id'), app.db.dialectType),
97
+ products_with_collections(eb, eb.ref('products.id'), app.db.dialectType),
98
+ products_with_discounts(eb, eb.ref('products.id'), app.db.dialectType),
99
+ products_with_variants(eb, eb.ref('products.id'), app.db.dialectType),
100
+ products_with_related_products(eb, eb.ref('products.id'), app.db.dialectType),
101
+ ]
102
+ )
103
+ .where('active', '=', 1)
104
+ .orderBy(['updated_at asc'])
105
+ .limit(limit),
106
+ app.db.dialectType
107
+ ).as('products'),
108
+
109
+ jsonArrayFrom(
110
+ eb
111
+ .selectFrom('discounts')
112
+ .select(resource_to_props.discounts)
113
+ .select(
114
+ eb => [
115
+ with_tags(eb, eb.ref('discounts.id'), app.db.dialectType),
116
+ with_media(eb, eb.ref('discounts.id'), app.db.dialectType),
117
+ ]
118
+ )
119
+ .where('active', '=', 1)
120
+ .orderBy(['updated_at asc'])
121
+ .limit(limit),
122
+ app.db.dialectType
123
+ ).as('discounts'),
124
+
125
+ jsonArrayFrom(
126
+ eb
127
+ .selectFrom('shipping_methods')
128
+ .select(resource_to_props.shipping_methods)
129
+ .select(
130
+ eb => [
131
+ with_tags(eb, eb.ref('shipping_methods.id'), app.db.dialectType),
132
+ with_media(eb, eb.ref('shipping_methods.id'), app.db.dialectType),
133
+ ]
134
+ )
135
+ .where('active', '=', 1)
136
+ .orderBy(['updated_at asc'])
137
+ .limit(limit),
138
+ app.db.dialectType
139
+ ).as('shipping_methods'),
140
+
141
+ jsonArrayFrom(
142
+ eb
143
+ .selectFrom('posts')
144
+ .select(resource_to_props.posts)
145
+ .select(
146
+ eb => [
147
+ with_tags(eb, eb.ref('posts.id'), app.db.dialectType),
148
+ with_media(eb, eb.ref('posts.id'), app.db.dialectType),
149
+ ]
150
+ )
151
+ .where('active', '=', 1)
152
+ .orderBy(['updated_at asc'])
153
+ .limit(limit),
154
+ app.db.dialectType
155
+ ).as('posts'),
156
+
157
+ stringArrayFrom(
158
+ eb.selectFrom('products')
159
+ .innerJoin(
160
+ 'products_to_collections',
161
+ 'products_to_collections.entity_id',
162
+ 'products.id'
163
+ )
164
+ .innerJoin(
165
+ 'entity_to_tags_projections',
166
+ 'entity_to_tags_projections.entity_id',
167
+ 'products.id'
168
+ )
169
+ .select('entity_to_tags_projections.value as tag')
170
+ .groupBy('tag'),
171
+ app.db.dialectType
172
+ ).as('all_products_tags')
173
+ ]
174
+ )
175
+ .executeTakeFirst();
176
+
177
+ console.log(JSON.stringify(items, null, 2))
178
+ }
179
+
180
+ async function test2() {
181
+ const app = await create_app();
182
+ const client = app.db.client;
183
+ const items = await client.selectNoFrom(
184
+ eb => Object
185
+ .entries(resource_to_props)
186
+ .map(
187
+ /**
188
+ * @param {[keyof Database, (keyof Database[keyof Database])[]]} params
189
+ */
190
+ ([table_name, props]) => {
191
+ // console.log(table_name, props)
192
+ // props
193
+ return jsonArrayFrom(
194
+ eb
195
+ .selectFrom(table_name)
196
+ .select(props)
197
+ .orderBy(['updated_at asc'])
198
+ .limit(0),
199
+ app.db.dialectType
200
+ ).as(table_name)
201
+ }
202
+ )
203
+ )
204
+ .executeTakeFirst();
205
+
206
+ console.log(JSON.stringify(items, null, 2))
207
+ }
208
+
209
+ test();
210
+
@@ -1,17 +1,15 @@
1
- import { AttributeType, AuthUserType, Role, TagType,
1
+ import {
2
+ AttributeType, AuthUserType, Role, TagType,
2
3
  CollectionType, ProductType, ShippingMethodType,
3
4
  VariantOption, PostType, CustomerType,
4
5
  VariantOptionSelection, OrderData, StorefrontType,
5
- AddressType, ImageType,
6
- OrderContact,
7
- LineItem,
8
- OrderStatus, DiscountType,
9
- PricingData,
10
- ValidationEntry,
6
+ AddressType, ImageType, OrderContact,
7
+ LineItem, OrderStatus, DiscountType,
8
+ PricingData, ValidationEntry,
11
9
  OrderPaymentGatewayData, NotificationType,
12
- NotificationAction,
13
- DiscountInfo,
14
- DiscountApplicationEnum} from '@storecraft/core/api'
10
+ NotificationAction, DiscountInfo,
11
+ DiscountApplicationEnum
12
+ } from '@storecraft/core/api'
15
13
  import {
16
14
  ColumnType,
17
15
  Generated,
@@ -61,48 +59,104 @@ export interface entity_to_value {
61
59
  }
62
60
 
63
61
  export interface entity_to_media extends entity_to_value {}
64
- export interface entity_to_tags_projections extends entity_to_value {}
62
+
63
+ /**
64
+ * **Here**:
65
+ * - (`entity_id`, `entity_handle`) = (entity id, entity handle) of the resource for example (product id, product handle)
66
+ * - (`value`) = search term , for example 'game', 'shoes'
67
+ * - (`context`) = the resource name `tags` / `collections` / `products` / `posts` / `discounts` / `shipping` / `storefronts` / `notifications` / 'auth_users' etc...)
68
+ *
69
+ * **Note**:
70
+ * - `entity_id` ALWAYS IDENTIFIES THE ENTITY
71
+ * - `entity_handle` DOES NOT IDENTIFY (imagine a product and collection with same handle)
72
+ * - `entity_handle` + `context` ALWAYS IDENTIFIES AN ENTITY
73
+ */
65
74
  export interface entity_to_search_terms extends entity_to_value {}
66
75
 
76
+ /**
77
+ * Here:
78
+ * - (`entity_id`, `entity_handle`) = (entity id, entity handle) of the resource for example (product id, product handle)
79
+ * - (`value`) = search term , for example 'game', 'shoes'
80
+ * - (`context`) = the resource name `tags` / `collections` / `products` / `posts` / `discounts` / `shipping` / `storefronts` / `notifications` / 'auth_users' etc...)
81
+ *
82
+ * Note:
83
+ * - `entity_id` ALWAYS IDENTIFIES AN ENTITY
84
+ * - `entity_handle` DOES NOT IDENTIFY AN ENTITY (imagine a product and collection with same handle)
85
+ * - `entity_handle` + `context` ALWAYS IDENTIFIES AN ENTITY
86
+ */
87
+ export interface entity_to_tags_projections extends entity_to_value {}
88
+
67
89
  /**
68
90
  * here:
69
- * - entity_id, entity_handle = product id, product handle
70
- * - value, reporter = collection id, collection handle
91
+ * - (`entity_id`, `entity_handle`) = (product id, product handle)
92
+ * - (`value`, `reporter`) = (collection id, collection handle)
93
+ * - (`context`) = NULL
94
+ *
95
+ * NOTE:
96
+ * - `entity_id` ALWAYS IDENTIFIES AN ENTITY
97
+ * - `entity_handle` ALWAYS IDENTIFIES AN ENTITY
98
+ * - `value` ALWAYS IDENTIFIES AN ENTITY
99
+ * - `reporter` ALWAYS IDENTIFIES AN ENTITY
71
100
  */
72
101
  export interface products_to_collections extends entity_to_value {}
73
102
 
74
103
  /**
75
104
  * here:
76
- * - entity_id, entity_handle = product id, product handle
77
- * - value, reporter = discount id, discount handle
105
+ * - (`entity_id`, `entity_handle`) = (product id, product handle)
106
+ * - (`value`, `reporter`) = (discount id, discount handle)
107
+ *
108
+ * NOTE:
109
+ * - `entity_id` ALWAYS IDENTIFIES AN ENTITY
110
+ * - `entity_handle` ALWAYS IDENTIFIES AN ENTITY
111
+ * - `value` ALWAYS IDENTIFIES AN ENTITY
112
+ * - `reporter` ALWAYS IDENTIFIES AN ENTITY
78
113
  */
79
114
  export interface products_to_discounts extends entity_to_value {}
80
115
 
81
116
  /**
82
117
  * here:
83
- * - (entity_id, entity_handle) = (parent product id, parent product handle)
84
- * - (value, reporter) = (variant product id, variant product handle)
118
+ * - (`entity_id`, `entity_handle`) = (parent product id, parent product handle)
119
+ * - (`value`, `reporter`) = (variant product id, variant product handle)
120
+ *
121
+ * NOTE:
122
+ * - `entity_id` ALWAYS IDENTIFIES AN ENTITY
123
+ * - `entity_handle` ALWAYS IDENTIFIES AN ENTITY
124
+ * - `value` ALWAYS IDENTIFIES AN ENTITY
125
+ * - `reporter` ALWAYS IDENTIFIES AN ENTITY
85
126
  */
86
127
  export interface products_to_variants extends entity_to_value {}
87
128
 
88
129
  /**
89
130
  * here:
90
- * - (entity_id, entity_handle) = (parent product id, parent product handle)
91
- * - (value, reporter) = (related product id, related product handle)
131
+ * - (`entity_id`, `entity_handle`) = (parent product id, parent product handle)
132
+ * - (`value`, `reporter`) = (related product id, related product handle)
133
+ *
134
+ * NOTE:
135
+ * - `entity_id` ALWAYS IDENTIFIES AN ENTITY
136
+ * - `entity_handle` ALWAYS IDENTIFIES AN ENTITY
137
+ * - `value` ALWAYS IDENTIFIES AN ENTITY
138
+ * - `reporter` ALWAYS IDENTIFIES AN ENTITY
92
139
  */
93
140
  export interface products_to_related_products extends entity_to_value {}
94
141
 
95
142
  /**
96
143
  * storefronts to products/collections/posts/discounts/shipping
97
144
  * here:
98
- * - entity_id, entity_handle = storefront id, storefront handle
99
- * - value, reporter = other entity id, other entity handle, i.e(product_id, product_handle)
100
- * - context = `products` / `collections` / `posts` / `discounts` / `shipping`
145
+ * - (entity_id, entity_handle) = (storefront id, storefront handle)
146
+ * - (value, reporter) = (other entity id, other entity handle), i.e(product_id, product_handle)
147
+ * - (context) = `products` / `collections` / `posts` / `discounts` / `shipping`
101
148
  *
102
149
  * This will probably be a small table hence everything is recorded in the same table.
103
150
  * Usually, a user will have:
104
151
  * - small number of storefronts
105
152
  * - small number of attached products/collections/posts/discounts/shipping per storefront
153
+ *
154
+ * NOTE:
155
+ * - `entity_id` ALWAYS IDENTIFIES AN ENTITY
156
+ * - `entity_handle` ALWAYS IDENTIFIES AN ENTITY
157
+ * - `value` ALWAYS IDENTIFIES AN ENTITY
158
+ * - `reporter` DOES NOT IDENTIFY AN ENTITY (because you can have a product and a collection with the same handle for example)
159
+ * - `reporter` + `context` ALWAYS IDENTIFIES AN ENTITY (in case you need to delete)
106
160
  */
107
161
  export interface storefronts_to_other extends entity_to_value {}
108
162