@equinor/apollo-utils 0.1.0

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/.eslintrc.cjs ADDED
@@ -0,0 +1,4 @@
1
+ module.exports = {
2
+ root: true,
3
+ extends: ['custom'],
4
+ }
@@ -0,0 +1,14 @@
1
+ $ tsup src/index.ts --format esm,cjs --dts --external react
2
+ CLI Building entry: src/index.ts
3
+ CLI Using tsconfig: tsconfig.json
4
+ CLI tsup v6.6.3
5
+ CLI Target: es6
6
+ ESM Build start
7
+ CJS Build start
8
+ CJS dist/index.js 3.75 KB
9
+ CJS ⚡️ Build success in 65ms
10
+ ESM dist/index.mjs 2.62 KB
11
+ ESM ⚡️ Build success in 68ms
12
+ DTS Build start
13
+ DTS ⚡️ Build success in 3380ms
14
+ DTS dist/index.d.ts 1.86 KB
package/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ # @equinor/apollo-utils
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 8abf3eb: Initial version of Apollo Utils with jotai-form
8
+ - 8abf3eb: Create apollo-utils package and jotai form example app
@@ -0,0 +1,44 @@
1
+ import { PrimitiveAtom } from 'jotai';
2
+ import { AtomFamily } from 'jotai/vanilla/utils/atomFamily';
3
+ import { z } from 'zod';
4
+
5
+ type ValidationErrorMap<T> = Map<keyof T, {
6
+ message: string;
7
+ code: string;
8
+ }>;
9
+
10
+ declare function createValidator<S extends z.ZodTypeAny>(schema: S): {
11
+ validate: <E extends z.TypeOf<S>>(entity: E) => ValidationErrorMap<E> | undefined;
12
+ };
13
+
14
+ type FormState<T> = {
15
+ status: 'editing' | 'pending';
16
+ values: T;
17
+ errors?: ValidationErrorMap<T>;
18
+ isValid?: boolean;
19
+ };
20
+ type FormFamilyParam = {
21
+ id: number | string;
22
+ } & Record<string, unknown>;
23
+
24
+ declare function createFormFamily<E extends Record<string, unknown>>(): AtomFamily<FormFamilyParam, PrimitiveAtom<FormState<E> | undefined> & {
25
+ init: FormState<E> | undefined;
26
+ }>;
27
+ type FormFamily<T> = AtomFamily<FormFamilyParam, PrimitiveAtom<FormState<T> | undefined>>;
28
+ type FormValidator = ReturnType<typeof createValidator>;
29
+ type UseFormFamilyUtilsOptions = {
30
+ validator?: FormValidator;
31
+ };
32
+ declare function useFormFamilyUtils<T>(family: FormFamily<T>, options?: UseFormFamilyUtilsOptions): {
33
+ useFormStateAtom: (param: FormFamilyParam) => [FormState<T> | undefined, (args_0: FormState<T> | ((prev: FormState<T> | undefined) => FormState<T> | undefined) | undefined) => void];
34
+ useFormState: (param: FormFamilyParam) => FormState<T> | undefined;
35
+ useUpdateFormMutation: (param: FormFamilyParam) => (update: Partial<T>) => void;
36
+ useFormMutations(param: FormFamilyParam): {
37
+ update: (update: Partial<T>) => void;
38
+ initializeForm: (entity: T) => void;
39
+ resetForm: () => void;
40
+ };
41
+ };
42
+ declare function useFormFamilyMutation<T>(family: FormFamily<T>, param: FormFamilyParam, validator?: FormValidator): (update: Partial<T>) => void;
43
+
44
+ export { FormFamilyParam, FormState, ValidationErrorMap, createFormFamily, createValidator, useFormFamilyMutation, useFormFamilyUtils };
package/dist/index.js ADDED
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
8
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
+ var __spreadValues = (a, b) => {
10
+ for (var prop in b || (b = {}))
11
+ if (__hasOwnProp.call(b, prop))
12
+ __defNormalProp(a, prop, b[prop]);
13
+ if (__getOwnPropSymbols)
14
+ for (var prop of __getOwnPropSymbols(b)) {
15
+ if (__propIsEnum.call(b, prop))
16
+ __defNormalProp(a, prop, b[prop]);
17
+ }
18
+ return a;
19
+ };
20
+ var __export = (target, all) => {
21
+ for (var name in all)
22
+ __defProp(target, name, { get: all[name], enumerable: true });
23
+ };
24
+ var __copyProps = (to, from, except, desc) => {
25
+ if (from && typeof from === "object" || typeof from === "function") {
26
+ for (let key of __getOwnPropNames(from))
27
+ if (!__hasOwnProp.call(to, key) && key !== except)
28
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
29
+ }
30
+ return to;
31
+ };
32
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
33
+
34
+ // src/index.ts
35
+ var src_exports = {};
36
+ __export(src_exports, {
37
+ createFormFamily: () => createFormFamily,
38
+ createValidator: () => createValidator,
39
+ useFormFamilyMutation: () => useFormFamilyMutation,
40
+ useFormFamilyUtils: () => useFormFamilyUtils
41
+ });
42
+ module.exports = __toCommonJS(src_exports);
43
+
44
+ // src/jotai-form/formUtils.ts
45
+ var import_jotai = require("jotai");
46
+ var import_utils = require("jotai/utils");
47
+ function createFormFamily() {
48
+ return (0, import_utils.atomFamily)(
49
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
50
+ (_param) => (0, import_jotai.atom)(void 0),
51
+ (a, b) => a.id === b.id
52
+ );
53
+ }
54
+ function useFormFamilyUtils(family, options = {}) {
55
+ return {
56
+ useFormStateAtom: (param) => (0, import_jotai.useAtom)(family(param)),
57
+ useFormState: (param) => (0, import_jotai.useAtomValue)(family(param)),
58
+ useUpdateFormMutation: (param) => {
59
+ return useFormFamilyMutation(family, param, options.validator);
60
+ },
61
+ useFormMutations(param) {
62
+ const mutateAtom = (0, import_jotai.useSetAtom)(family(param));
63
+ return {
64
+ update: useFormFamilyMutation(family, param, options.validator),
65
+ initializeForm: (entity) => mutateAtom({
66
+ status: "editing",
67
+ values: entity,
68
+ isValid: true
69
+ }),
70
+ resetForm: () => mutateAtom(void 0)
71
+ };
72
+ }
73
+ };
74
+ }
75
+ function useFormFamilyMutation(family, param, validator) {
76
+ const mutate = (0, import_jotai.useSetAtom)(family(param));
77
+ return (update) => {
78
+ return mutate((previous) => {
79
+ if (!previous)
80
+ return;
81
+ const updatedValues = __spreadValues(__spreadValues({}, previous.values), update);
82
+ const errors = validator == null ? void 0 : validator.validate(updatedValues);
83
+ return {
84
+ status: "editing",
85
+ values: updatedValues,
86
+ errors,
87
+ isValid: !errors
88
+ };
89
+ });
90
+ };
91
+ }
92
+
93
+ // src/zod-validation/utils.ts
94
+ function createValidator(schema) {
95
+ return {
96
+ validate: (entity) => {
97
+ const validation = schema.safeParse(entity);
98
+ if (validation.success)
99
+ return void 0;
100
+ return new Map(
101
+ validation.error.errors.map((error) => [
102
+ error.path[0],
103
+ { message: error.message, code: error.code }
104
+ ])
105
+ );
106
+ }
107
+ };
108
+ }
109
+ // Annotate the CommonJS export names for ESM import in node:
110
+ 0 && (module.exports = {
111
+ createFormFamily,
112
+ createValidator,
113
+ useFormFamilyMutation,
114
+ useFormFamilyUtils
115
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,88 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
3
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
4
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
5
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
+ var __spreadValues = (a, b) => {
7
+ for (var prop in b || (b = {}))
8
+ if (__hasOwnProp.call(b, prop))
9
+ __defNormalProp(a, prop, b[prop]);
10
+ if (__getOwnPropSymbols)
11
+ for (var prop of __getOwnPropSymbols(b)) {
12
+ if (__propIsEnum.call(b, prop))
13
+ __defNormalProp(a, prop, b[prop]);
14
+ }
15
+ return a;
16
+ };
17
+
18
+ // src/jotai-form/formUtils.ts
19
+ import { atom, useAtom, useAtomValue, useSetAtom } from "jotai";
20
+ import { atomFamily } from "jotai/utils";
21
+ function createFormFamily() {
22
+ return atomFamily(
23
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
24
+ (_param) => atom(void 0),
25
+ (a, b) => a.id === b.id
26
+ );
27
+ }
28
+ function useFormFamilyUtils(family, options = {}) {
29
+ return {
30
+ useFormStateAtom: (param) => useAtom(family(param)),
31
+ useFormState: (param) => useAtomValue(family(param)),
32
+ useUpdateFormMutation: (param) => {
33
+ return useFormFamilyMutation(family, param, options.validator);
34
+ },
35
+ useFormMutations(param) {
36
+ const mutateAtom = useSetAtom(family(param));
37
+ return {
38
+ update: useFormFamilyMutation(family, param, options.validator),
39
+ initializeForm: (entity) => mutateAtom({
40
+ status: "editing",
41
+ values: entity,
42
+ isValid: true
43
+ }),
44
+ resetForm: () => mutateAtom(void 0)
45
+ };
46
+ }
47
+ };
48
+ }
49
+ function useFormFamilyMutation(family, param, validator) {
50
+ const mutate = useSetAtom(family(param));
51
+ return (update) => {
52
+ return mutate((previous) => {
53
+ if (!previous)
54
+ return;
55
+ const updatedValues = __spreadValues(__spreadValues({}, previous.values), update);
56
+ const errors = validator == null ? void 0 : validator.validate(updatedValues);
57
+ return {
58
+ status: "editing",
59
+ values: updatedValues,
60
+ errors,
61
+ isValid: !errors
62
+ };
63
+ });
64
+ };
65
+ }
66
+
67
+ // src/zod-validation/utils.ts
68
+ function createValidator(schema) {
69
+ return {
70
+ validate: (entity) => {
71
+ const validation = schema.safeParse(entity);
72
+ if (validation.success)
73
+ return void 0;
74
+ return new Map(
75
+ validation.error.errors.map((error) => [
76
+ error.path[0],
77
+ { message: error.message, code: error.code }
78
+ ])
79
+ );
80
+ }
81
+ };
82
+ }
83
+ export {
84
+ createFormFamily,
85
+ createValidator,
86
+ useFormFamilyMutation,
87
+ useFormFamilyUtils
88
+ };
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@equinor/apollo-utils",
3
+ "main": "src/index.ts",
4
+ "types": "src/index.ts",
5
+ "version": "0.1.0",
6
+ "license": "MIT",
7
+ "scripts": {
8
+ "build": "tsup src/index.ts --format esm,cjs --dts --external react",
9
+ "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist",
10
+ "dev": "tsup src/index.ts --format esm,cjs --watch --dts --external react",
11
+ "lint": "TIMING=1 eslint . --fix",
12
+ "type-check": "tsc --noEmit",
13
+ "test": "vitest --run",
14
+ "test:watch": "vitest --watch"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "dependencies": {
20
+ "jotai": "^2.0.1",
21
+ "tsup": "^6.6.3",
22
+ "typescript": "^4.9.5",
23
+ "zod": "^3.20.6"
24
+ },
25
+ "devDependencies": {
26
+ "@testing-library/react": "^13.4.0",
27
+ "@testing-library/react-hooks": "^8.0.1",
28
+ "@vitest/ui": "^0.28.5",
29
+ "eslint": "^8.34.0",
30
+ "eslint-config-custom": "*",
31
+ "jsdom": "^21.1.0",
32
+ "react": "^18.2.0",
33
+ "react-dom": "^18.2.0",
34
+ "vitest": "^0.28.5"
35
+ }
36
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './jotai-form'
2
+ export * from './zod-validation'
@@ -0,0 +1,36 @@
1
+ import { renderHook } from '@testing-library/react-hooks'
2
+ import { useAtomValue } from 'jotai'
3
+ import { describe, expect, it } from 'vitest'
4
+ import { examplePerson, Person } from '../test-data/person'
5
+ import { createFormFamily, useFormFamilyUtils } from './formUtils'
6
+
7
+ describe('Test createFormFamily', () => {
8
+ it('should create a family', () => {
9
+ const family = createFormFamily<Person>()
10
+ expect(family).toBeTruthy()
11
+ })
12
+ it('should initialize atom as undefined', () => {
13
+ const family = createFormFamily<Person>()
14
+ const { result } = renderHook(() => useAtomValue(family(examplePerson)))
15
+ expect(result.current).toBeUndefined()
16
+ })
17
+ })
18
+
19
+ function initFormUtils() {
20
+ const family = createFormFamily<Person>()
21
+ return useFormFamilyUtils(family)
22
+ }
23
+
24
+ describe('Test useFormFamilyUtils', () => {
25
+ it('should initialize form as valid with values', () => {
26
+ const utils = initFormUtils()
27
+ renderHook(() => utils.useFormMutations(examplePerson).initializeForm(examplePerson))
28
+ const { result } = renderHook(() => utils.useFormState(examplePerson))
29
+ expect(result.current?.isValid).toBeTruthy()
30
+ expect(result.current).toStrictEqual({
31
+ status: 'editing',
32
+ values: examplePerson,
33
+ isValid: true,
34
+ })
35
+ })
36
+ })
@@ -0,0 +1,73 @@
1
+ import { atom, PrimitiveAtom, useAtom, useAtomValue, useSetAtom } from 'jotai'
2
+ import { atomFamily } from 'jotai/utils'
3
+ import { type AtomFamily } from 'jotai/vanilla/utils/atomFamily'
4
+ import { createValidator } from '../zod-validation'
5
+ import { FormFamilyParam, FormState } from './types'
6
+
7
+ export function createFormFamily<E extends Record<string, unknown>>() {
8
+ return atomFamily(
9
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
10
+ (_param: FormFamilyParam) => atom<FormState<E> | undefined>(undefined),
11
+ (a, b) => a.id === b.id
12
+ )
13
+ }
14
+
15
+ type FormFamily<T> = AtomFamily<FormFamilyParam, PrimitiveAtom<FormState<T> | undefined>>
16
+ type FormValidator = ReturnType<typeof createValidator>
17
+
18
+ type UseFormFamilyUtilsOptions = {
19
+ validator?: FormValidator
20
+ }
21
+
22
+ export function useFormFamilyUtils<T>(
23
+ family: FormFamily<T>,
24
+ options: UseFormFamilyUtilsOptions = {}
25
+ ) {
26
+ return {
27
+ useFormStateAtom: (param: FormFamilyParam) => useAtom(family(param)),
28
+ useFormState: (param: FormFamilyParam) => useAtomValue(family(param)),
29
+ useUpdateFormMutation: (param: FormFamilyParam) => {
30
+ return useFormFamilyMutation(family, param, options.validator)
31
+ },
32
+ useFormMutations(param: FormFamilyParam) {
33
+ const mutateAtom = useSetAtom(family(param))
34
+
35
+ return {
36
+ update: useFormFamilyMutation(family, param, options.validator),
37
+ initializeForm: (entity: T) =>
38
+ mutateAtom({
39
+ status: 'editing',
40
+ values: entity,
41
+ isValid: true,
42
+ }),
43
+ resetForm: () => mutateAtom(undefined),
44
+ }
45
+ },
46
+ }
47
+ }
48
+
49
+ export function useFormFamilyMutation<T>(
50
+ family: FormFamily<T>,
51
+ param: FormFamilyParam,
52
+ validator?: FormValidator
53
+ ) {
54
+ const mutate = useSetAtom(family(param))
55
+ return (update: Partial<T>) => {
56
+ return mutate((previous) => {
57
+ if (!previous) return
58
+
59
+ const updatedValues = {
60
+ ...previous.values,
61
+ ...update,
62
+ }
63
+ const errors = validator?.validate(updatedValues)
64
+
65
+ return {
66
+ status: 'editing',
67
+ values: updatedValues,
68
+ errors,
69
+ isValid: !errors,
70
+ }
71
+ })
72
+ }
73
+ }
@@ -0,0 +1,2 @@
1
+ export * from './formUtils'
2
+ export * from './types'
@@ -0,0 +1,12 @@
1
+ import { ValidationErrorMap } from '../zod-validation'
2
+
3
+ export type FormState<T> = {
4
+ status: 'editing' | 'pending'
5
+ values: T
6
+ errors?: ValidationErrorMap<T>
7
+ isValid?: boolean
8
+ }
9
+
10
+ export type FormFamilyParam = {
11
+ id: number | string
12
+ } & Record<string, unknown>
@@ -0,0 +1,15 @@
1
+ import { z } from 'zod'
2
+
3
+ export const personSchema = z.object({
4
+ id: z.number(),
5
+ name: z.string().min(1),
6
+ age: z.number().min(0).max(150),
7
+ })
8
+
9
+ export type Person = z.infer<typeof personSchema>
10
+
11
+ export const examplePerson: Person = {
12
+ id: 0,
13
+ name: 'Apollo',
14
+ age: 25,
15
+ }
@@ -0,0 +1,2 @@
1
+ export * from './types'
2
+ export * from './utils'
@@ -0,0 +1 @@
1
+ export type ValidationErrorMap<T> = Map<keyof T, { message: string; code: string }>
@@ -0,0 +1,17 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { examplePerson, personSchema } from '../test-data/person'
3
+ import { createValidator } from './utils'
4
+
5
+ const validator = createValidator(personSchema)
6
+
7
+ describe('Test validator creation', () => {
8
+ it('should return undefined on valid data', () => {
9
+ const errors = validator.validate(examplePerson)
10
+ expect(errors).toBeUndefined()
11
+ })
12
+ it('should return errors on invalid name and age', () => {
13
+ const errors = validator.validate({ ...examplePerson, name: '', age: 200 })
14
+ expect(errors?.has('name')).toBeTruthy()
15
+ expect(errors?.has('age')).toBeTruthy()
16
+ })
17
+ })
@@ -0,0 +1,17 @@
1
+ import { z } from 'zod'
2
+ import { ValidationErrorMap } from './types'
3
+
4
+ export function createValidator<S extends z.ZodTypeAny>(schema: S) {
5
+ return {
6
+ validate: <E extends z.infer<typeof schema>>(entity: E) => {
7
+ const validation = schema.safeParse(entity)
8
+ if (validation.success) return undefined
9
+ return new Map(
10
+ validation.error.errors.map((error) => [
11
+ error.path[0] as keyof E,
12
+ { message: error.message, code: error.code },
13
+ ])
14
+ ) as ValidationErrorMap<E>
15
+ },
16
+ }
17
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "tsconfig/vite.json",
3
+ "include": ["src", "setupTest.ts"],
4
+ "compilerOptions": { "types": ["vitest/importMeta"] }
5
+ }
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from 'vitest/config'
2
+
3
+ export default defineConfig({
4
+ define: {
5
+ 'import.meta.vitest': 'undefined',
6
+ },
7
+ test: {
8
+ includeSource: ['src/**/*.ts'],
9
+ environment: 'jsdom',
10
+ },
11
+ })