#attach extra information to field definitions

25 messages · Page 1 of 1 (latest)

clear quartz
#

Anyone knows a good idea to attach extra info to field definitions?

The goal is to be able to read that information through a plugin.

E.g. something like:

{
            name: 'textArea',
            type: 'textarea',
            extraInfo: {
              someKey: "val"
            }
},

So I can do stuff like:
Find all fields of a CollectionConfig which have value XX for key XY

clear quartz
hollow dome
clear quartz
#

Really looking forward to it getting merged!

hollow dome
#

yes. I need it for my payload-swagger plugin to properly document custom endpoint, but thought it wouldn't hurt to also add it to the other levels in the config.

#

for instance for adding example data

clear quartz
hollow dome
#

I'd like for the developer of an endpoint to be able to document it. I can already derive the endpoint, method and route parameters, but I cannot e.g. generate a proper description, supported query parameters, returntype. And you, what did you intend to use this for?

clear quartz
# hollow dome I'd like for the developer of an endpoint to be able to document it. I can alrea...

That's interesting! Makes sense since endpoints don't have a field like "description".

I'd like to use it for my new https://github.com/AlessioGr/payload-plugin-ai plugin. Currently using hooks to mark fields where "embeddings" should be generated for a field after it's been saved. Apart from being ugly, I also want to let users specify additional options. E.g. should the new embeddings field be hidden or not?

So something like

custom: {
   ...genEmbeddings({hidden: false}),
}

instead of just

hooks: {beforeChange: [genEmbeddings]}

Or for the future, allow users to run GPT prompts for fields. E.g.

custom: {
   ...runPrompt({
when: 'update',
prompt: """
Summarize this text:
%fieldvalue%
""",
outputField: '%fieldname%_summary'
}),
}
#

So, would be quite a useful feature to me 😄

hollow dome
#

Nice one 🙂

#

I see that using this custom field would be nice, but since you are already looping through all the fields, you can already just add a field and then just remove it in your plugin. Also, did you know that since your hook is a function, you can allready add properties to it and the joy validation does not fail on it or strip them?

clear quartz
# hollow dome I see that using this custom field would be nice, but since you are already loop...

did you know that since your hook is a function, you can allready add properties to it and the joy validation does not fail on it or strip them?
Hmm could you make an example? I experimented with it a lot. Only solution I found was returning the data I wanna specify from the hook. But that way I need to call the hook of every single field which is not optimal

you can already just add a field and then just remove it in your plugin
Wouldn't that have typescript and/or joy complain?

hollow dome
#

I think this would be the way to go for you:

type EmbeddingsHook = FieldHook & Required<Options>;
type AllowedField = TextField; // assuming not all fields support this

export const genEmbeddings = ({ hidden = false }: Options = {}): EmbeddingsHook => {
  const hook = (args => {
    return args.value;
  }) as EmbeddingsHook;
  // So here we add the options to the hook. You'll be able to read them in your plugin, yup ignores them
  hook.hidden = hidden;

  return hook;
};

// Users of your plugin can use the below function as a wrapper where they create the field.
// The typesystem just sees a field that is returned from the function and expects a function, so you're good there
export const createEmbeddingsField = (field: AllowedField & { options: Options }): Field => {
  const { options, ...payloadField } = field;
  return {
    ...payloadField,
    hooks: {
      ...field.hooks,
      beforeChange: [...(field.hooks?.beforeChange || []), genEmbeddings(options)],
    },
  };
};

Of course if you have the custom field you don't need to use the trick of adding the options to the function. But then only users with a Payload version higher than the one where that get's merged can use your plugin.

#

Alternatively you can just cleanup the config in your plugin and take the approach below:

You could do this, but that's not ideal, but users of you plugin wouldn't be able to do use multiple libraries with config extensions at the same time.

import { CollectionConfig as PayloadCollectionConfig, Field as PayloadField } from 'payload/types';

export type Field = PayloadField & { description: string };

export type CollectionConfig = Omit<PayloadCollectionConfig, 'fields'> & { fields: Field[] };

You can solve this by passing the config types as generic params, but it's a bit ugly

import { CollectionConfig as PayloadCollectionConfig, Field as PayloadField } from 'payload/types';

export type Field<BaseField extends PayloadField = PayloadField> = BaseField & { description: string };

export type CollectionConfig<BaseCollection extends PayloadCollectionConfig = PayloadCollectionConfig> = Omit<BaseCollection, 'fields'> & { fields: Field[] };
#

Hope this makes sense

clear quartz
#

Thank you!!! Those are very creative solutions! And people say programmers aren't creative... The wrapper for creating a function is certainly an interesting solution.

Honestly I'm not sure what to prefer. The first one with the wrapper for the field itself might be a bit annoying for the user. Especially when eventually there would be multiple plugins doing that. Similar to the if-condition hell (if(){if(){if(){ } } })

Last one would be nicer - but then I'd rather wait for your PR to be approved. In the end, it would have the same effect

hollow dome
#

Don't understand what you think would cause an if-condition hell. You could just use it like createEmbeddingsField(createDocumentedField(createFancyField({ type: 'text', name: 'bla' })))

clear quartz
#

Yeah that's what I mean - nested functions like these. I think those would make it look quite confusing/complex, especially when there are multiple functions and plugins like in your example.

Also not sure if there's any benefit compared to your custom field

hollow dome
#

It's just a basic decorator pattern. Don't think that's confusing. Maybe naming can be better. Like withEmbeddings(withDocumentation(withFancyStuff({ type: 'text', name: 'bla' })))

#

It can just be used like this: withEmbeddings(withDocumentation(withFancyStuff({ type: 'text', name: 'bla', hidden: true, description: 'bla bla', fancy: 'something' })))

#

And I don't think this is instead of the custom field. When that field is available, you can still apply this pattern, but then instead of putting the hidden property on the function, you remap it to the custom object.

clear quartz