@retailcrm/embed-ui-v1-contexts 0.5.2 → 0.5.3

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/dist/host.cjs CHANGED
@@ -81,26 +81,89 @@ const createContextAccessor = (accessors, onError = null) => {
81
81
  }
82
82
  };
83
83
  };
84
+ const createCustomContextAccessor = (accessors, queryDictionary = null, onError = null) => {
85
+ const _accessors = accessors.reduce((all, a) => {
86
+ all[a.schema.entity] = a;
87
+ return all;
88
+ }, {});
89
+ const guard = (entity) => {
90
+ if (!(entity in _accessors)) {
91
+ throw new LogicalError(`No custom context for entity ${String(entity)}`);
92
+ }
93
+ };
94
+ return {
95
+ getCustomSchema(entity, onReject = null) {
96
+ return run(() => {
97
+ guard(entity);
98
+ return _accessors[entity].schema;
99
+ }, onReject, onError) ?? null;
100
+ },
101
+ async getCustomDictionary(code, parameters, onReject) {
102
+ return await runAsync(() => {
103
+ if (!queryDictionary) {
104
+ throw new LogicalError("No dictionary source provided");
105
+ }
106
+ return queryDictionary(code, parameters);
107
+ }, onReject, onError) ?? [];
108
+ },
109
+ getCustomField(entity, code, onReject = null) {
110
+ return run(() => {
111
+ guard(entity);
112
+ return _accessors[entity].get(code);
113
+ }, onReject, onError) ?? null;
114
+ },
115
+ setCustomField(entity, code, value, onReject = null) {
116
+ return run(() => {
117
+ guard(entity);
118
+ _accessors[entity].set(code, value);
119
+ }, onReject, onError);
120
+ },
121
+ onCustomFieldChange(entity, code, handler, onReject = null) {
122
+ return run(() => {
123
+ guard(entity);
124
+ return _accessors[entity].onChange(code, handler);
125
+ }, onReject, onError);
126
+ }
127
+ };
128
+ };
84
129
  function run(fn, onReject = null, onError = null) {
85
130
  rpc.retain(onReject);
86
131
  try {
87
132
  return fn();
88
133
  } catch (e) {
89
- if (onReject) {
90
- onReject(e instanceof HostError ? e.rejection : { message: String(e) });
134
+ handle(e, onReject, onError);
135
+ if (!onError) {
136
+ throw e;
91
137
  }
92
- if (onError) {
93
- onError(e);
94
- return;
138
+ } finally {
139
+ rpc.release(onReject);
140
+ }
141
+ }
142
+ async function runAsync(fn, onReject = null, onError = null) {
143
+ rpc.retain(onReject);
144
+ try {
145
+ return await fn();
146
+ } catch (e) {
147
+ handle(e, onReject, onError);
148
+ if (!onError) {
149
+ throw e;
95
150
  }
96
- throw e;
97
151
  } finally {
98
152
  rpc.release(onReject);
99
153
  }
100
154
  }
155
+ function handle(e, onReject = null, onError = null) {
156
+ if (onReject) {
157
+ onReject(e instanceof HostError ? e.rejection : { message: String(e) });
158
+ }
159
+ if (onError) {
160
+ onError(e);
161
+ }
162
+ }
101
163
  exports.HostError = HostError;
102
164
  exports.LogicalError = LogicalError;
103
165
  exports.RuntimeError = RuntimeError;
104
166
  exports.createContextAccessor = createContextAccessor;
167
+ exports.createCustomContextAccessor = createCustomContextAccessor;
105
168
  exports.createGetter = createGetter;
106
169
  exports.createSetter = createSetter;
package/dist/host.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ContextAccessor, ContextSchema, ContextSchemaMap, FieldAccessor, FieldGetters, FieldSetters, LogicalRejection, Rejection, RuntimeRejection } from '@retailcrm/embed-ui-v1-types/context';
1
+ import { ContextAccessor, ContextSchema, ContextSchemaList, CustomContextAccessor, CustomDictionary, CustomDictionaryFilter, CustomFieldAccessor, FieldAccessor, FieldGetters, FieldSetters, LogicalRejection, Rejection, RuntimeRejection } from '@retailcrm/embed-ui-v1-types/context';
2
2
  import { Maybe } from '@retailcrm/embed-ui-v1-types/scaffolding';
3
3
  export declare class HostError extends Error {
4
4
  constructor(message: string, previous?: Error | undefined);
@@ -12,5 +12,6 @@ export declare class RuntimeError extends HostError {
12
12
  }
13
13
  export declare const createGetter: <S extends ContextSchema>(id: string, getters: FieldGetters<S>) => FieldAccessor<S>["get"];
14
14
  export declare const createSetter: <S extends ContextSchema>(id: string, setters: FieldSetters<S>) => FieldAccessor<S>["set"];
15
- export declare const createContextAccessor: <M extends ContextSchemaMap>(accessors: { [K in keyof M]: FieldAccessor<M[K]>; }, onError?: Maybe<ErrorHandler>) => ContextAccessor<M>;
15
+ export declare const createContextAccessor: <M extends ContextSchemaList>(accessors: { [K in keyof M]: FieldAccessor<M[K]>; }, onError?: Maybe<ErrorHandler>) => ContextAccessor<M>;
16
+ export declare const createCustomContextAccessor: (accessors: CustomFieldAccessor[], queryDictionary?: Maybe<(code: string, filter?: CustomDictionaryFilter) => Promise<CustomDictionary>>, onError?: Maybe<ErrorHandler>) => CustomContextAccessor;
16
17
  export type ErrorHandler = (e: unknown) => void;
package/dist/host.js CHANGED
@@ -79,28 +79,91 @@ const createContextAccessor = (accessors, onError = null) => {
79
79
  }
80
80
  };
81
81
  };
82
+ const createCustomContextAccessor = (accessors, queryDictionary = null, onError = null) => {
83
+ const _accessors = accessors.reduce((all, a) => {
84
+ all[a.schema.entity] = a;
85
+ return all;
86
+ }, {});
87
+ const guard = (entity) => {
88
+ if (!(entity in _accessors)) {
89
+ throw new LogicalError(`No custom context for entity ${String(entity)}`);
90
+ }
91
+ };
92
+ return {
93
+ getCustomSchema(entity, onReject = null) {
94
+ return run(() => {
95
+ guard(entity);
96
+ return _accessors[entity].schema;
97
+ }, onReject, onError) ?? null;
98
+ },
99
+ async getCustomDictionary(code, parameters, onReject) {
100
+ return await runAsync(() => {
101
+ if (!queryDictionary) {
102
+ throw new LogicalError("No dictionary source provided");
103
+ }
104
+ return queryDictionary(code, parameters);
105
+ }, onReject, onError) ?? [];
106
+ },
107
+ getCustomField(entity, code, onReject = null) {
108
+ return run(() => {
109
+ guard(entity);
110
+ return _accessors[entity].get(code);
111
+ }, onReject, onError) ?? null;
112
+ },
113
+ setCustomField(entity, code, value, onReject = null) {
114
+ return run(() => {
115
+ guard(entity);
116
+ _accessors[entity].set(code, value);
117
+ }, onReject, onError);
118
+ },
119
+ onCustomFieldChange(entity, code, handler, onReject = null) {
120
+ return run(() => {
121
+ guard(entity);
122
+ return _accessors[entity].onChange(code, handler);
123
+ }, onReject, onError);
124
+ }
125
+ };
126
+ };
82
127
  function run(fn, onReject = null, onError = null) {
83
128
  retain(onReject);
84
129
  try {
85
130
  return fn();
86
131
  } catch (e) {
87
- if (onReject) {
88
- onReject(e instanceof HostError ? e.rejection : { message: String(e) });
132
+ handle(e, onReject, onError);
133
+ if (!onError) {
134
+ throw e;
89
135
  }
90
- if (onError) {
91
- onError(e);
92
- return;
136
+ } finally {
137
+ release(onReject);
138
+ }
139
+ }
140
+ async function runAsync(fn, onReject = null, onError = null) {
141
+ retain(onReject);
142
+ try {
143
+ return await fn();
144
+ } catch (e) {
145
+ handle(e, onReject, onError);
146
+ if (!onError) {
147
+ throw e;
93
148
  }
94
- throw e;
95
149
  } finally {
96
150
  release(onReject);
97
151
  }
98
152
  }
153
+ function handle(e, onReject = null, onError = null) {
154
+ if (onReject) {
155
+ onReject(e instanceof HostError ? e.rejection : { message: String(e) });
156
+ }
157
+ if (onError) {
158
+ onError(e);
159
+ }
160
+ }
99
161
  export {
100
162
  HostError,
101
163
  LogicalError,
102
164
  RuntimeError,
103
165
  createContextAccessor,
166
+ createCustomContextAccessor,
104
167
  createGetter,
105
168
  createSetter
106
169
  };
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const pinia = require("pinia");
4
+ const CustomContextAccessorKey = Symbol("CustomContextAccessor");
5
+ const injectAccessor = (endpoint) => {
6
+ return (context) => {
7
+ context.store[CustomContextAccessorKey] = endpoint.call;
8
+ };
9
+ };
10
+ const isTypeOf = (field, type) => {
11
+ return field.kind === type;
12
+ };
13
+ const defineContext = (entity) => pinia.defineStore(`custom/${entity}`, {
14
+ state() {
15
+ return {
16
+ schema: null,
17
+ values: {}
18
+ };
19
+ },
20
+ getters: {
21
+ entity: () => entity
22
+ },
23
+ actions: {
24
+ async initialize(onReject = null) {
25
+ const accessor = this[CustomContextAccessorKey];
26
+ const schema = await accessor.getCustomSchema(entity, onReject);
27
+ if (!schema) {
28
+ return null;
29
+ }
30
+ this.schema = schema;
31
+ this.values = schema.fields.reduce((state, field) => {
32
+ state[field.code] = Array.isArray(field.initial) ? [...field.initial] : field.initial;
33
+ return state;
34
+ }, {});
35
+ schema.fields.forEach((field) => {
36
+ accessor.onCustomFieldChange(entity, field.code, (value) => {
37
+ this.values[field.code] = value;
38
+ });
39
+ });
40
+ return schema;
41
+ },
42
+ set(code, value, onReject = null) {
43
+ if (!this.schema) {
44
+ throw new Error(`[crm:embed:remote] Custom context for entity=${entity} is not initialized`);
45
+ }
46
+ const field = this.schema.fields.find((f) => f.code === code);
47
+ if (!field) {
48
+ throw new Error(`[crm:embed:remote] Custom context for entity=${entity} does not contain field with code ${code}`);
49
+ }
50
+ guardReadonly(entity, field);
51
+ guardType(entity, field, value);
52
+ this[CustomContextAccessorKey].setCustomField(entity, code, value, onReject);
53
+ }
54
+ }
55
+ });
56
+ const useContext = (entity) => defineContext(entity)();
57
+ function guardReadonly(entity, field) {
58
+ if (field.readonly) {
59
+ throw new Error(`[crm:embed:remote] Field with code ${field.code} is not writable according to schema for entity=${entity}`);
60
+ }
61
+ }
62
+ function guardType(entity, field, value) {
63
+ if (!accepts(field, value)) {
64
+ throw new Error(`[crm:embed:remote] Invalid value for field kind ${field.kind} with code ${field.code} in entity=${entity}`);
65
+ }
66
+ }
67
+ function accepts(field, value) {
68
+ switch (field.kind) {
69
+ case "boolean":
70
+ return typeof value === "boolean";
71
+ case "date":
72
+ return typeof value === "string" || value === null;
73
+ case "datetime":
74
+ return typeof value === "string" || value === null;
75
+ case "dictionary":
76
+ return typeof value === "string" || value === null;
77
+ case "multiselect_dictionary":
78
+ return Array.isArray(value) && (value.length === 0 || value.every((v) => typeof v === "string"));
79
+ case "email":
80
+ return typeof value === "string" || value === null;
81
+ case "integer":
82
+ return typeof value === "number" && Number.isInteger(value) || value === null;
83
+ case "numeric":
84
+ return typeof value === "number" || value === null;
85
+ case "string":
86
+ case "text":
87
+ return typeof value === "string" || value === null;
88
+ default:
89
+ return false;
90
+ }
91
+ }
92
+ const useDictionary = pinia.defineStore("@retailcrm/embed-ui/_dictionary", {
93
+ actions: {
94
+ async query(code, parameters = {}) {
95
+ const accessor = this[CustomContextAccessorKey];
96
+ return new Promise((resolve, reject) => {
97
+ let rejection = null;
98
+ accessor.getCustomDictionary(code, parameters, (r) => rejection = r).then((dictionary) => {
99
+ if (!rejection) {
100
+ resolve(dictionary);
101
+ } else {
102
+ reject(rejection);
103
+ }
104
+ });
105
+ });
106
+ }
107
+ }
108
+ });
109
+ exports.CustomContextAccessorKey = CustomContextAccessorKey;
110
+ exports.defineContext = defineContext;
111
+ exports.injectAccessor = injectAccessor;
112
+ exports.isTypeOf = isTypeOf;
113
+ exports.useContext = useContext;
114
+ exports.useDictionary = useDictionary;
@@ -0,0 +1,38 @@
1
+ import { CustomField, CustomFieldKind, CustomFieldType, CustomContext, CustomContextAccessor, CustomContextSchema, CustomDictionary, RejectionHandler } from '@retailcrm/embed-ui-v1-types/context';
2
+ import { Endpoint } from '@remote-ui/rpc';
3
+ import { Maybe } from '@retailcrm/embed-ui-v1-types/scaffolding';
4
+ import { PiniaPluginContext, StoreDefinition, Store } from 'pinia';
5
+ export declare const CustomContextAccessorKey: unique symbol;
6
+ declare module 'pinia' {
7
+ interface PiniaCustomProperties {
8
+ [CustomContextAccessorKey]: Endpoint<CustomContextAccessor>['call'];
9
+ }
10
+ }
11
+ export declare const injectAccessor: (endpoint: Endpoint<CustomContextAccessor>) => (context: PiniaPluginContext) => void;
12
+ export declare const isTypeOf: <T extends CustomFieldKind>(field: CustomField, type: T) => field is CustomField<T>;
13
+ export declare const defineContext: <T extends string>(entity: T) => StoreDefinition<`custom/${T}`, {
14
+ schema: CustomContextSchema | null;
15
+ values: CustomContext;
16
+ }, {
17
+ entity: () => T;
18
+ }, {
19
+ initialize(onReject?: Maybe<RejectionHandler>): Promise<CustomContextSchema | null>;
20
+ set(code: string, value: CustomFieldType, onReject?: Maybe<RejectionHandler>): void;
21
+ }>;
22
+ export declare const useContext: <T extends string>(entity: T) => Store<`custom/${T}`, {
23
+ schema: CustomContextSchema | null;
24
+ values: CustomContext;
25
+ }, {
26
+ entity: () => T;
27
+ }, {
28
+ initialize(onReject?: Maybe<RejectionHandler>): Promise<CustomContextSchema | null>;
29
+ set(code: string, value: CustomFieldType, onReject?: Maybe<RejectionHandler>): void;
30
+ }>;
31
+ export type CustomContextStore<T extends string> = ReturnType<CustomContextStoreDefinition<T>>;
32
+ export type CustomContextStoreDefinition<T extends string> = ReturnType<typeof defineContext<T>>;
33
+ export declare const useDictionary: StoreDefinition<"@retailcrm/embed-ui/_dictionary", {}, {}, {
34
+ query(code: string, parameters?: {
35
+ after?: string;
36
+ first?: number;
37
+ }): Promise<CustomDictionary>;
38
+ }>;
@@ -0,0 +1,114 @@
1
+ import { defineStore } from "pinia";
2
+ const CustomContextAccessorKey = Symbol("CustomContextAccessor");
3
+ const injectAccessor = (endpoint) => {
4
+ return (context) => {
5
+ context.store[CustomContextAccessorKey] = endpoint.call;
6
+ };
7
+ };
8
+ const isTypeOf = (field, type) => {
9
+ return field.kind === type;
10
+ };
11
+ const defineContext = (entity) => defineStore(`custom/${entity}`, {
12
+ state() {
13
+ return {
14
+ schema: null,
15
+ values: {}
16
+ };
17
+ },
18
+ getters: {
19
+ entity: () => entity
20
+ },
21
+ actions: {
22
+ async initialize(onReject = null) {
23
+ const accessor = this[CustomContextAccessorKey];
24
+ const schema = await accessor.getCustomSchema(entity, onReject);
25
+ if (!schema) {
26
+ return null;
27
+ }
28
+ this.schema = schema;
29
+ this.values = schema.fields.reduce((state, field) => {
30
+ state[field.code] = Array.isArray(field.initial) ? [...field.initial] : field.initial;
31
+ return state;
32
+ }, {});
33
+ schema.fields.forEach((field) => {
34
+ accessor.onCustomFieldChange(entity, field.code, (value) => {
35
+ this.values[field.code] = value;
36
+ });
37
+ });
38
+ return schema;
39
+ },
40
+ set(code, value, onReject = null) {
41
+ if (!this.schema) {
42
+ throw new Error(`[crm:embed:remote] Custom context for entity=${entity} is not initialized`);
43
+ }
44
+ const field = this.schema.fields.find((f) => f.code === code);
45
+ if (!field) {
46
+ throw new Error(`[crm:embed:remote] Custom context for entity=${entity} does not contain field with code ${code}`);
47
+ }
48
+ guardReadonly(entity, field);
49
+ guardType(entity, field, value);
50
+ this[CustomContextAccessorKey].setCustomField(entity, code, value, onReject);
51
+ }
52
+ }
53
+ });
54
+ const useContext = (entity) => defineContext(entity)();
55
+ function guardReadonly(entity, field) {
56
+ if (field.readonly) {
57
+ throw new Error(`[crm:embed:remote] Field with code ${field.code} is not writable according to schema for entity=${entity}`);
58
+ }
59
+ }
60
+ function guardType(entity, field, value) {
61
+ if (!accepts(field, value)) {
62
+ throw new Error(`[crm:embed:remote] Invalid value for field kind ${field.kind} with code ${field.code} in entity=${entity}`);
63
+ }
64
+ }
65
+ function accepts(field, value) {
66
+ switch (field.kind) {
67
+ case "boolean":
68
+ return typeof value === "boolean";
69
+ case "date":
70
+ return typeof value === "string" || value === null;
71
+ case "datetime":
72
+ return typeof value === "string" || value === null;
73
+ case "dictionary":
74
+ return typeof value === "string" || value === null;
75
+ case "multiselect_dictionary":
76
+ return Array.isArray(value) && (value.length === 0 || value.every((v) => typeof v === "string"));
77
+ case "email":
78
+ return typeof value === "string" || value === null;
79
+ case "integer":
80
+ return typeof value === "number" && Number.isInteger(value) || value === null;
81
+ case "numeric":
82
+ return typeof value === "number" || value === null;
83
+ case "string":
84
+ case "text":
85
+ return typeof value === "string" || value === null;
86
+ default:
87
+ return false;
88
+ }
89
+ }
90
+ const useDictionary = defineStore("@retailcrm/embed-ui/_dictionary", {
91
+ actions: {
92
+ async query(code, parameters = {}) {
93
+ const accessor = this[CustomContextAccessorKey];
94
+ return new Promise((resolve, reject) => {
95
+ let rejection = null;
96
+ accessor.getCustomDictionary(code, parameters, (r) => rejection = r).then((dictionary) => {
97
+ if (!rejection) {
98
+ resolve(dictionary);
99
+ } else {
100
+ reject(rejection);
101
+ }
102
+ });
103
+ });
104
+ }
105
+ }
106
+ });
107
+ export {
108
+ CustomContextAccessorKey,
109
+ defineContext,
110
+ injectAccessor,
111
+ isTypeOf,
112
+ useContext,
113
+ useDictionary
114
+ };
package/dist/remote.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Context, ContextAccessor, ContextSchema, ContextSchemaMap, RejectionHandler, TypeOf, Writable } from '@retailcrm/embed-ui-v1-types/context';
1
+ import { Context, ContextAccessor, ContextSchema, ContextSchemaList, RejectionHandler, TypeOf, Writable } from '@retailcrm/embed-ui-v1-types/context';
2
2
  import { Endpoint } from '@remote-ui/rpc';
3
3
  import { Maybe } from '@retailcrm/embed-ui-v1-types/scaffolding';
4
4
  import { PiniaPluginContext, StoreDefinition } from 'pinia';
@@ -7,7 +7,7 @@ declare module 'pinia' {
7
7
  endpoint: Endpoint<ContextAccessor>;
8
8
  }
9
9
  }
10
- export declare const injectEndpoint: <M extends ContextSchemaMap, A extends ContextAccessor<M> = ContextAccessor<M>>(endpoint: Endpoint<A>) => (context: PiniaPluginContext) => void;
10
+ export declare const injectEndpoint: <M extends ContextSchemaList, A extends ContextAccessor<M> = ContextAccessor<M>>(endpoint: Endpoint<A>) => (context: PiniaPluginContext) => void;
11
11
  export declare const defineContext: <Id extends string, S extends ContextSchema>(id: Id, schema: S) => StoreDefinition<Id, Context<S>, {
12
12
  schema: () => S;
13
13
  }, {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@retailcrm/embed-ui-v1-contexts",
3
3
  "description": "Reactive contexts for RetailCRM JS API",
4
4
  "type": "module",
5
- "version": "0.5.2",
5
+ "version": "0.5.3",
6
6
  "license": "MIT",
7
7
  "author": "RetailDriverLLC <integration@retailcrm.ru>",
8
8
  "repository": "git@github.com:retailcrm/embed-ui.git",
@@ -50,6 +50,12 @@
50
50
  "require": "./dist/remote/settings.cjs",
51
51
  "default": "./dist/remote/settings.js"
52
52
  },
53
+ "./remote/custom": {
54
+ "types": "./dist/remote/custom.d.ts",
55
+ "import": "./dist/remote/custom.js",
56
+ "require": "./dist/remote/custom.cjs",
57
+ "default": "./dist/remote/custom.js"
58
+ },
53
59
  "./*": "./*"
54
60
  },
55
61
  "typesVersions": {
@@ -74,6 +80,9 @@
74
80
  ],
75
81
  "remote/settings": [
76
82
  "./dist/remote/settings.d.ts"
83
+ ],
84
+ "remote/custom": [
85
+ "./dist/remote/custom.d.ts"
77
86
  ]
78
87
  }
79
88
  },
@@ -93,10 +102,10 @@
93
102
  "pinia": "^2.2"
94
103
  },
95
104
  "dependencies": {
96
- "@retailcrm/embed-ui-v1-types": "^0.5.2"
105
+ "@retailcrm/embed-ui-v1-types": "^0.5.3"
97
106
  },
98
107
  "devDependencies": {
99
- "@retailcrm/embed-ui-v1-testing": "^0.5.2",
108
+ "@retailcrm/embed-ui-v1-testing": "^0.5.3",
100
109
  "tsx": "^4.19.2",
101
110
  "typescript": "^5.6.3",
102
111
  "vite": "^5.4.11",