1"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([["802"],{367(e,n,i){i.r(n),i.d(n,{metadata:()=>r,default:()=>a,frontMatter:()=>l,contentTitle:()=>t,toc:()=>c,assets:()=>o});var r=JSON.parse('{"id":"api-reference/Collection Files","title":"Collection Files","description":"In Stoker, you define \\"collections\\" for your data. In other systems, these might be referred to as \\"tables\\", \\"objects\\" or \\"entities\\".","source":"@site/docs/api-reference/Collection Files.md","sourceDirName":"api-reference","slug":"/api-reference/Collection Files","permalink":"/docs/api-reference/Collection Files","draft":false,"unlisted":false,"tags":[],"version":"current","sidebarPosition":3,"frontMatter":{"sidebar_position":3},"sidebar":"tutorialSidebar","previous":{"title":"Global Config File","permalink":"/docs/api-reference/Global Config File"},"next":{"title":"Application State","permalink":"/docs/api-reference/Application State"}}'),s=i(1085),d=i(1184);let l={sidebar_position:3},t,o={},c=[{value:"General Collection Config",id:"general-collection-config",level:2},{value:"labels",id:"labels",level:3},{value:"recordTitleField",id:"recordtitlefield",level:3},{value:"auth",id:"auth",level:3},{value:"singleton",id:"singleton",level:3},{value:"parentCollection",id:"parentcollection",level:3},{value:"softDelete",id:"softdelete",level:3},{value:"fullTextSearch",id:"fulltextsearch",level:3},{value:"relationLists",id:"relationlists",level:3},{value:"enableWriteLog",id:"enablewritelog",level:3},{value:"preserveWriteLog",id:"preservewritelog",level:3},{value:"indexExemption",id:"indexexemption",level:3},{value:"roleSystemFields",id:"rolesystemfields",level:3},{value:"fieldAccessGroups",id:"fieldaccessgroups",level:3},{value:"Group keys",id:"group-keys",level:4},{value:"FieldAccessCondition",id:"fieldaccesscondition",level:4},{value:"allowSchemalessFields",id:"allowschemalessfields",level:3},{value:"ttl",id:"ttl",level:3},{value:"skipRulesValidation",id:"skiprulesvalidation",level:3},{value:"seedOrder",id:"seedorder",level:3},{value:"queries",id:"queries",level:3},{value:"custom.autoCorrectUnique",id:"customautocorrectunique",level:3},{value:"custom.disableOfflineCreate",id:"customdisableofflinecreate",level:3},{value:"custom.disableOfflineUpdate",id:"customdisableofflineupdate",level:3},{value:"custom.disableOfflineDelete",id:"customdisableofflinedelete",level:3},{value:"Collection Access Config",id:"collection-access-config",level:2},{value:"serverReadOnly",id:"serverreadonly",level:3},{value:"serverWriteOnly",id:"serverwriteonly",level:3},{value:"operations",id:"operations",level:3},{value:"auth",id:"auth-1",level:3},{value:"Access Control Policies",id:"access-control-policies",level:3},{value:"attributeRestrictions",id:"attributerestrictions",level:3},{value:"Record Owner Restriction",id:"record-owner-restriction",level:4},{value:"Record User Restriction",id:"record-user-restriction",level:4},{value:"Record Property Restriction",id:"record-property-restriction",level:4},{value:"entityRestrictions.restrictions",id:"entityrestrictionsrestrictions",level:3},{value:"Individual Entity Restriction",id:"individual-entity-restriction",level:4},{value:"Parent Entity Restriction",id:"parent-entity-restriction",level:4},{value:"Parent Property Entity Restriction",id:"parent-property-entity-restriction",level:4},{value:"entityRestrictions.assignable",id:"entityrestrictionsassignable",level:3},{value:"entityRestrictions.parentFilters",id:"entityrestrictionsparentfilters",level:3},{value:"Individual Entity Parent Filters",id:"individual-entity-parent-filters",level:4},{value:"Parent Entity Parent Filters",id:"parent-entity-parent-filters",level:4},{value:"Parent Property Entity Parent Filters",id:"parent-property-entity-parent-filters",level:4},{value:"customSecurityRules",id:"customsecurityrules",level:3},{value:"customStorageRules",id:"customstoragerules",level:3},{value:"permissionWriteRestrictions",id:"permissionwriterestrictions",level:3},{value:"files",id:"files",level:3},{value:"custom.serverAccess",id:"customserveraccess",level:3},{value:"Preload Cache Config",id:"preload-cache-config",level:2},{value:"roles",id:"roles",level:3},{value:"relationCollections",id:"relationcollections",level:3},{value:"range",id:"range",level:3},{value:"constraints",id:"constraints",level:3},{value:"Collection Fields Config",id:"collection-fields-config",level:2},{value:"General Field Properties",id:"general-field-properties",level:3},{value:"name",id:"name",level:4},{value:"access",id:"access",level:4},{value:"restrictCreate",id:"restrictcreate",level:4},{value:"restrictUpdate",id:"restrictupdate",level:4},{value:"required",id:"required",level:4},{value:"nullable",id:"nullable",level:4},{value:"sorting",id:"sorting",level:4},{value:"skipRulesValidation",id:"skiprulesvalidation-1",level:4},{value:"description",id:"description",level:4},{value:"singleFieldExemption",id:"singlefieldexemption",level:4},{value:"saveToAuthToken",id:"savetoauthtoken",level:4},{value:"custom.initialValue",id:"custominitialvalue",level:4},{value:"custom.serverAccess",id:"customserveraccess-1",level:4},{value:"String Field Properties",id:"string-field-properties",level:3},{value:"type",id:"type",level:4},{value:"values",id:"values",level:4},{value:"unique",id:"unique",level:4},{value:"length",id:"length",level:4},{value:"minlength",id:"minlength",level:4},{value:"maxlength",id:"maxlength",level:4}
1,{value:"pattern",id:"pattern",level:4},{value:"email",id:"email",level:4},{value:"url",id:"url",level:4},{value:"emoji",id:"emoji",level:4},{value:"uuid",id:"uuid",level:4},{value:"ip",id:"ip",level:4},{value:"Number Field Properties",id:"number-field-properties",level:3},{value:"type",id:"type-1",level:4},{value:"values",id:"values-1",level:4},{value:"unique",id:"unique-1",level:4},{value:"autoIncrement",id:"autoincrement",level:4},{value:"decimal",id:"decimal",level:4},{value:"min",id:"min",level:4},{value:"max",id:"max",level:4},{value:"Timestamp Field Properties",id:"timestamp-field-properties",level:3},{value:"type",id:"type-2",level:4},{value:"values",id:"values-2",level:4},{value:"min",id:"min-1",level:4},{value:"max",id:"max-1",level:4},{value:"Array Field Properties",id:"array-field-properties",level:3},{value:"type",id:"type-3",level:4},{value:"values",id:"values-3",level:4},{value:"length",id:"length-1",level:4},{value:"minlength",id:"minlength-1",level:4},{value:"maxlength",id:"maxlength-1",level:4},{value:"Map Field Properties",id:"map-field-properties",level:3},{value:"type",id:"type-4",level:4},{value:"Relational Field Properties",id:"relational-field-properties",level:3},{value:"type",id:"type-5",level:4},{value:"collection",id:"collection",level:4},{value:"includeFields",id:"includefields",level:4},{value:"titleField",id:"titlefield",level:4},{value:"dependencyFields",id:"dependencyfields",level:4},{value:"twoWay",id:"twoway",level:4},{value:"preserve",id:"preserve",level:4},{value:"writeAny",id:"writeany",level:4},{value:"constraints",id:"constraints-1",level:4},{value:"enforceHierarchy",id:"enforcehierarchy",level:4},{value:"length",id:"length-2",level:4},{value:"min",id:"min-2",level:4},{value:"max",id:"max-2",level:4},{value:"Computed Field Properties",id:"computed-field-properties",level:3},{value:"type",id:"type-6",level:4},{value:"formula",id:"formula",level:4},{value:"Field Admin UI Properties",id:"field-admin-ui-properties",level:3},{value:"label",id:"label",level:4},{value:"listLabel",id:"listlabel",level:4},{value:"icon",id:"icon",level:4},{value:"condition",id:"condition",level:4},{value:"hidden",id:"hidden",level:4},{value:"readOnly",id:"readonly",level:4},{value:"sort",id:"sort",level:4},{value:"column",id:"column",level:4},{value:"description",id:"description-1",level:4},{value:"live",id:"live",level:4},{value:"switch",id:"switch",level:4},{value:"textarea",id:"textarea",level:4},{value:"richText",id:"richtext",level:4},{value:"italic",id:"italic",level:4},{value:"currency",id:"currency",level:4},{value:"radio",id:"radio",level:4},{value:"buttonGroup",id:"buttongroup",level:4},{value:"slider",id:"slider",level:4},{value:"time",id:"time",level:4},{value:"month",id:"month",level:4},{value:"location",id:"location",level:4},{value:"image",id:"image",level:4},{value:"badge",id:"badge",level:4},{value:"tags",id:"tags",level:4},{value:"asRichText",id:"asrichtext",level:4},{value:"noExport",id:"noexport",level:4},{value:"exportSeparator",id:"exportseparator",level:4},{value:"modifyDisplayValue",id:"modifydisplayvalue",level:4},{value:"skipFormRequiredValidation",id:"skipformrequiredvalidation",level:4},{value:"overrideFormRequiredValidation",id:"overrideformrequiredvalidation",level:4},{value:"filterValues",id:"filtervalues",level:4},{value:"filterResults",id:"filterresults",level:4},{value:"modifyResultTitle",id:"modifyresulttitle",level:4},{value:"customListView",id:"customlistview",level:4},{value:"queryFullRecord",id:"queryfullrecord",level:4},{value:"Field Hooks",id:"field-hooks",level:3},{value:"AI Chat Config",id:"ai-chat-config",level:2},{value:"Admin UI Config",id:"admin-ui-config",level:2},{value:"hidden",id:"hidden-1",level:3},{value:"navbarPosition",id:"navbarposition",level:3},{value:"titles",id:"titles",level:3},{value:"icon",id:"icon-1",level:3},{value:"itemsPerPage",id:"itemsperpage",level:3},{value:"statusField",id:"statusfield",level:3},{value:"breadcrumbs",id:"breadcrumbs",level:3},{value:"duplicate",id:"duplicate",level:3},{value:"convert",id:"convert",level:3},{value:"defaultView",id:"defaultview",level:3},{value:"defaultRoute",id:"defaultroute",level:3},{value:"defaultSort",id:"defaultsort",level:3},{value:"secondarySort",id:"secondarysort",level:3},{value:"searchOptions",id:"searchoptions",level:3},{value:"rangeSelectorValues",id:"rangeselectorvalues",level:3},{value:"defaultRangeSelector",id:"defaultrangeselector",level:3},{value:"disableRangeSelector",id:"disablerangeselector",level:3},{value:"restrictExport",id:"restrictexport",level:3},{value:"customListActions",id:"customlistactions",level:3},{value:"addRecordButtonOverride",id:"addrecordbuttonoverride",level:3},{value:"hideCreate",id:"hidecreate",level:3},{value:"disableUpdate",id:"disableupdate",level:3},{value:"onFormOpen",id:"onformopen",level:3},{value:"onChange",id:"onchange",level:3},{value:"live",id:"live-1",level:3},{value:"meta",id:"meta",level:3},{value:"rowHighlight",id:"rowhighlight",level:3},{value:"formButtons",id:"formbuttons",level:3},{value:"formUpload",id:"formupload",level:3},{value:"formImages",id:"formimages",level:3},{value:"formLists",id:"formlists",level:3},{value:"fileOptions",id:"fileoptions",level:3},{value:"customFields",id:"customfields",level:3},{value:"customRecordPages",id:"customrecordpages",level:3},{value:"retriever",id:"retriever",level:3},{value:"list",id:"list",level:3},{value:"cards",id:"cards",level:3},{value:"images",id:"images",level:3},{value:"map",id:"map",level:3},{value:"calendar",id:"calendar",level:3},{value:"filters",id:"filters",level:3},{value:"titleCount",id:"titlecount",level:3},{value:"metrics",id:"metrics",level:3},{value:"Collection Hooks",id:"collection-hooks",level:2},{value:"preOperation",id:"preoperation",level:3},{value:"preRead",id:"preread",level:3},{value:"preValidate",id:"prevalidate",level:3},{value:"preWrite",id:"prewrite",level:3},{value:"preDuplicate",id:"preduplicate",level:3},{value:"postOperation",id:"postoperation",level:3},{value:"postRead",id:"postread",level:3},{value:"postWrite",id:"postwrite",level:3},{value:"postWriteError",id:"postwriteerror",level:3},{value:"preFileAdd",id:"prefileadd",level:3},{value:"preFileUpdate",id:"prefileupdate",level:3},{value:"postFileAdd",id:"postfileadd",level:3},{value:"postFileAddError",id:"postfileadderror",level:3},{value:"postFileUpdate",id:"postfileupdate",level:3},{value:"setEmbedding",id:"setembedding",level:3}];function h(e){let n={a:"a",admonition:"admonition",code:"code",h2:"h2",h3:"h3",h4:"h4",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,d.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.p,{children:'In Stoker, you define "collections" for your data. In other systems, these might be referred to as "tables", "objects" or "entities".'}),"\n",(0,s.jsx)(n.p,{children:"Each collection represents a collection in Cloud Firestore, and a page in the Admin UI."}),"\n",(0,s.jsxs)(n.p,{children:["Each collection in stoker has its own file in ",(0,s.jsx)(n.code,{children:"src/collections"}),"."]}),"\n",(0,s.jsx)(n.p,{children:"The following are the options for each collection file."}),"\n",(0,s.jsxs)(n.p,{children:["You can use ",(0,s.jsx)(n.a,{href:"/docs/api-reference/Application%20State",children:"application state"})," and the ",(0,s.jsx)(n.a,{href:"/docs/api-reference/Web%20SDK",children:"Web"})," and ",(0,s.jsx)(n.a,{href:"/docs/api-reference/Node%20SDK",children:"Node"})," SDKs in your config files."]}),"\n",(0,s.jsx)(n.h2,{id:"general-collection-config",children:"General Collection Config"}),"\n",(0,s.jsx)(n.h3,{id:"labels",children:"labels"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"{ collection: string, record: string }"})}),"\n",(0,s.jsxs)(n.p,{children:["These are the names for the collection. Collection names must start with a capital letter and contain only letters, digits, and underscores. You can provide a user-friendly set of labels in ",(0,s.jsx)(n.a,{href:"#titles",children:(0,s.jsx)(n.code,{children:"admin.titles"})}),"."]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"collection"}),': The name for the collection, i.e. "Clients".']}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"record"}),': The name for a record in the collection, i.e. "Client".']}),"\n",(0,s.jsx)(n.h3,{id:"recordtitlefield",children:"recordTitleField"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"string"})}),"\n",(0,s.jsx)(n.p,{children:'The field in the collection that will be used as the record\'s title i.e. "Name".'}),"\n",(0,s.jsx)(n.h3,{id:"auth",children:"auth"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"}),' if this collection is a "users" collection.']}),"\n",(0,s.jsx)(n.p,{children:"When set to true, records in the collection can be assigned access credentials."}),"\n",(0,s.jsx)(n.h3,{id:"singleton",children:"singleton"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"}),' if the collection will only have one record / page i.e. "Settings"']}),"\n",(0,s.jsxs)(n.p,{children:["When set to ",(0,s.jsx)(n.code,{children:"true"}),", no list page will be shown in the Admin UI. The user will be sent straight to the form page for the single record
1in the collection."]}),"\n",(0,s.jsx)(n.h3,{id:"parentcollection",children:"parentCollection"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"string"})}),"\n",(0,s.jsx)(n.p,{children:"This is an advanced option. Set to the collection's parent collection if this collection will be a subcollection in Firestore. Subcollections are not currently supported in the Admin UI."}),"\n",(0,s.jsx)(n.h3,{id:"softdelete",children:"softDelete"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"{ archivedField: string, timestampField: string, retentionPeriod: number }"})}),"\n",(0,s.jsx)(n.p,{children:"Enable soft-delete for this collection."}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"archivedField"}),": Provide the name of a Boolean field. This field will be set to ",(0,s.jsx)(n.code,{children:"true"})," when the record is soft-deleted."]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"timestampField"}),": Provide the name of a Timestamp field. This field will be set to the current time when the record is soft-deleted."]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"retentionPeriod"}),": The number of days after which soft-deleted records will be permananently deleted."]}),"\n",(0,s.jsx)(n.h3,{id:"fulltextsearch",children:"fullTextSearch"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"string[]"})}),"\n",(0,s.jsx)(n.p,{children:"An array of field names. These fields will be searchable."}),"\n",(0,s.jsxs)(n.p,{children:["For collections without ",(0,s.jsx)(n.code,{children:"preloadCache"})," or ",(0,s.jsx)(n.code,{children:"serverReadOnly"})," set to ",(0,s.jsx)(n.code,{children:"true"}),", you will need to set up ",(0,s.jsx)(n.a,{href:"https://www.algolia.com/",children:"Algolia"}),". Steps:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Create an Algolia application for your app."}),"\n",(0,s.jsx)(n.li,{children:"Create an index for the collection in your Algolia app, using the exact collection name."}),"\n",(0,s.jsxs)(n.li,{children:['Set searchable attributes for your index (in the "Configuration" section of the index). These attributes should match the fields provided to ',(0,s.jsx)(n.code,{children:"fullTextSearch"}),"."]}),"\n",(0,s.jsxs)(n.li,{children:['Set facets for your index (in the "Configuration" section of the index). The ',(0,s.jsx)(n.code,{children:"tenant_id"})," attribute is required. You will also need to add any fields that your app uses to query records in the collection. This usually means:","\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:['The "Archived" field if your collection has ',(0,s.jsx)(n.a,{href:"#softdelete",children:"soft delete"})," enabled."]}),"\n",(0,s.jsxs)(n.li,{children:["All fields used for ",(0,s.jsx)(n.a,{href:"#filters",children:"filters"}),' in the admin app. So, if your app filters the collection by "Status", add a "Status" attribute to the facets section. For ManyToOne / ManyToMany relation filters, append "_Array" to the end of the field name (for example, "Project_Array"). For OneToOne / OneToMany relation filters, use "Field_Single.id" (for example, "Project_Single.id").']}),"\n",(0,s.jsxs)(n.li,{children:["All fields used for ",(0,s.jsx)(n.a,{href:"Collection%20Files#attributerestrictions",children:"attribute restrictions"}),"."]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"relationlists",children:"relationLists"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'{\n collection: string\n field: string\n roles?: string[]\n loadAll?: boolean\n constraints?: [string, "==" | "in", unknown][]\n showMetrics?: boolean\n showFilters?: string[]\n}[]\n'})}),"\n",(0,s.jsx)(n.p,{children:'Define "child lists" that will appear in the Admin UI for records in this collection.'}),"\n",(0,s.jsx)(n.p,{children:'For example, a "Clients" collection might have lists of related "Sites", "Quotes" and "Invoices" on its record page.'}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"collection"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The collection for the relation list."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"field"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The field in the current collection that relates to the collection above."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"roles"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string[]"})}),(0,s.jsx)(n.td,{children:"The roles that can see this relation list."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"loadAll"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"boolean"})}),(0,s.jsxs)(n.td,{children:["When ",(0,s.jsx)(n.a,{href:"#range",children:(0,s.jsx)(n.code,{children:"preloadCache.range"})})," is enabled, setting this option will ignore the range restrictions and load all records available for the relation list."]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"constraints"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:'[string, "==" | "in", unknown][]'})}),(0,s.jsx)(n.td,{children:"Firestore constraints to apply to the relation list query."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"showMetrics"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"boolean"})}),(0,s.jsxs)(n.td,{children:["Show ",(0,s.jsx)(n.a,{href:"#metrics",children:"metrics"})," above the list in the Admin UI."]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"showFilters"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string[]"})}),(0,s.jsxs)(n.td,{children:["A list of ",(0,s.jsx)(n.a,{href:"#filters",children:"filters"})," to show in the LHS sidebar when the relation list is active."]})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"enablewritelog",children:"enableWriteLog"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," to enable the write log for this collection. Every write to a record will be logged in Firestore, creating a history that can be used for data recovery and audit purposes."]}),"\n",(0,s.jsx)(n.h3,{id:"preservewritelog",children:"preserveWriteLog"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," to preserve write log entries for deleted records. If this option is not enabled, all write log entries for a record will be deleted on record delete."]}),"\n",(0,s.jsx)(n.h3,{id:"indexexemption",children:"indexExemption"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," to exempt this collection from Firestore indexing. Indexes that are reqiuried for your app to function will be re-added automatically. This feature improves performance and reduces costs by minimizing the number of indexes in your database. However, it may prevent you from running queries outside of the standard queries used by your app, for example when performing manual back end data operations."]}),"\n",(0,s.jsx)(n.h3,{id:"rolesystemfields",children:"roleSystemFields"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"{ field: string, roles?: string[] }[]"})}),"\n",(0,s.jsx)(n.p,{children:"By default, the following system fields are not accessible to your app's users:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"Created_At"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"Saved_At"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"Created_By"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"Last_Write_At"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"Last_Save_At"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"Last_Write_By"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"Last_Write_App"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"Last_Write_Connection_Status"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"Last_Write_Version"})}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"You can make them accessible by defining an array of objects containing the name of a field and the roles that can access that field."}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"Saved_At"})," and ",(0,s.jsx)(n.code,{children:"Last_Save_At"})," are safely generated on the server. ",(0,s.jsx)(n.code,{children:"Created_At"})," and ",(0,s.jsx)(n.code,{children:"Last_Write_At"})," are set on the client, and may not be reliable. The purpose of these fields is to log when offline writes occurred."]})}),"\n",(0,s.jsx)(n.h3,{id:"fieldaccessgroups",children:"fieldAccessGroups"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"Record<string, FieldAccessCondition>"})}),"\n",(0,s.jsxs)(n.p,{children:["Named groups of fields that are only visible when a condition is met for the current user. Use this when a static role list on ",(0,s.jsx)(n.a,{href:"#access",children:(0,s.jsx)(n.code,{children:"field.access"})})," is not enough \u2014 for example when visibility depends on auth token claims."]}),"\n",(0,s.jsxs)(n.p,{children:["Define the groups on the collection, then reference them from fields with ",(0,s.jsx)(n.code,{children:'access: { group: "Group-Key" }'}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'fieldAccessGroups: {\n "Private-Notes": {\
1n applicableRoles: ["Admin", "User"],\n match: "any",\n roles: ["Admin"],\n claims: { Private_Notes_Access: true },\n },\n},\nfields: [\n {\n name: "Private_Field",\n type: "Map",\n access: { group: "Private-Notes" },\n },\n]\n'})}),"\n",(0,s.jsxs)(n.p,{children:["In this example, Admins always see the field, and standard Users see it only when their auth token has ",(0,s.jsx)(n.code,{children:"Private_Notes_Access: true"}),"."]}),"\n",(0,s.jsx)(n.h4,{id:"group-keys",children:"Group keys"}),"\n",(0,s.jsxs)(n.p,{children:["Keys must start with a letter and contain only letters, digits, and hyphens. They must not end with a hyphen followed by digits (for example ",(0,s.jsx)(n.code,{children:"Notes-1"}),")."]}),"\n",(0,s.jsx)(n.h4,{id:"fieldaccesscondition",children:"FieldAccessCondition"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"applicableRoles"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string[]"})}),(0,s.jsxs)(n.td,{children:[(0,s.jsx)(n.strong,{children:"Required."})," Roles that this group can ever apply to. The user's role must be in this list or access is denied."]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"match"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:'"any" | "all"'})}),(0,s.jsxs)(n.td,{children:["How to combine the checks below. Required when more than one of ",(0,s.jsx)(n.code,{children:"collectionAuth"}),", ",(0,s.jsx)(n.code,{children:"roles"}),", ",(0,s.jsx)(n.code,{children:"claims"}),", or ",(0,s.jsx)(n.code,{children:"restrictions"})," is set."]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"collectionAuth"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"boolean"})}),(0,s.jsxs)(n.td,{children:["Require the user's collection ",(0,s.jsx)(n.code,{children:"auth"})," permission to equal this value."]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"roles"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string[]"})}),(0,s.jsx)(n.td,{children:"The user's role must be one of these."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"claims"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"Record<string, scalar | scalar[]>"})}),(0,s.jsx)(n.td,{children:"Auth token claim checks. A scalar requires equality; an array requires the claim to be one of the listed values. All entries must match."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"restrictions"})}),(0,s.jsx)(n.td,{children:"object"}),(0,s.jsx)(n.td,{children:"Required state of this collection's permission restrictions for the current user. All provided entries must match. See below."})]})]})]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"restrictions"})," may include:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"recordOwner"}),": collection Record Owner restriction must equal this"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"recordUser"}),": collection Record User restriction must equal this"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"recordProperty"}),": collection Record Property restriction must equal this"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"restrictEntities"}),": collection entity restrictions must equal this"]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["At least one of ",(0,s.jsx)(n.code,{children:"collectionAuth"}),", ",(0,s.jsx)(n.code,{children:"roles"}),", ",(0,s.jsx)(n.code,{children:"claims"}),", or ",(0,s.jsx)(n.code,{children:"restrictions"})," is required."]}),"\n",(0,s.jsx)(n.h3,{id:"allowschemalessfields",children:"allowSchemalessFields"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," to allow fields that are not defined in the schema to be written to records in the collection."]}),"\n",(0,s.jsx)(n.h3,{id:"ttl",children:"ttl"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"string"})}),"\n",(0,s.jsxs)(n.p,{children:["The name of a ",(0,s.jsx)(n.a,{href:"#timestamp-field-properties",children:"Timestamp field"}
1)," containing an automatic deletion date for the record i.e. ",(0,s.jsx)(n.code,{children:'"Expires_At"'})]}),"\n",(0,s.jsx)(n.admonition,{type:"warning",children:(0,s.jsx)(n.p,{children:"Denormalized data is not currently deleted by TTL policies, making it unsuitable for deleting data for privacy purposes."})}),"\n",(0,s.jsx)(n.h3,{id:"skiprulesvalidation",children:"skipRulesValidation"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean"})}),"\n",(0,s.jsx)(n.p,{children:"Collections with many fields may hit the limit of 1000 expressions per request allowed by Firestore Security Rules. If you see this error you can set this property to true to skip rule validation of writes. Validation will be run post-write in a Cloud Function, and you will get an email if invalid data is submitted."}),"\n",(0,s.jsxs)(n.p,{children:["This option can be ",(0,s.jsx)(n.a,{href:"#skiprulesvalidation-1",children:"set at the field-level"})," if you only want to skip rule validation for a subset of fields."]}),"\n",(0,s.jsxs)(n.p,{children:["Alternatively, you can set ",(0,s.jsx)(n.a,{href:"#serverwriteonly",children:(0,s.jsx)(n.code,{children:"access.serverWriteOnly"})})," to true. This will force all writes to go through the server, validating all fields. The downside of this method is that your app will not be able to perform offline writes."]}),"\n",(0,s.jsx)(n.h3,{id:"seedorder",children:"seedOrder"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"number"})}),"\n",(0,s.jsxs)(n.p,{children:["The priority of this collection when seeding test data using ",(0,s.jsx)(n.a,{href:"/docs/api-reference/CLI#data-commands",children:(0,s.jsx)(n.code,{children:"stoker seed-data"})}),"."]}),"\n",(0,s.jsx)(n.h3,{id:"queries",children:"queries"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"{\n field: string\n range?: boolean\n standalone?: boolean\n roles?: string[]\n}[]\n"})}),"\n",(0,s.jsx)(n.admonition,{type:"warning",children:(0,s.jsx)(n.p,{children:"This is an advanced option that is only relevant if you are using Stoker as a headless CMS. This option is handled automatically by the Admin UI."})}),"\n",(0,s.jsx)(n.p,{children:"Define fields that will be indexed for querying."}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"field"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The name of the field."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"range"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"boolean"})}),(0,s.jsxs)(n.td,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," if the field is a Timestamp field."]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"standalone"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"boolean"})}),(0,s.jsxs)(n.td,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," if the field needs to be indexed independently of sorting."]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"roles"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string[]"})}),(0,s.jsx)(n.td,{children:"The user roles that will run the query."})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"customautocorrectunique",children:"custom.autoCorrectUnique"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean | (() => boolean | Promise<boolean>)"})}),"\n",(0,s.jsxs)(n.p,{children:["Return ",(0,s.jsx)(n.code,{children:"true"})," to automatically rename duplicate records rather than throwing an error."]}),"\n",(0,s.jsxs)(n.p,{children:["Only relevant when ",(0,s.jsx)(n.a,{href:"#serverwriteonly",children:(0,s.jsx)(n.code,{children:"access.serverWriteOnly"})})," is falsy."]}),"\n",(0,s.jsx)(n.h3,{id:"customdisableofflinecreate",children:"custom.disableOfflineCreate"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean | (() => boolean | Promise<boolean>)"})}),"\n",(0,s.jsxs)(n.p,{children:["Return ",(0,s.jsx)(n.code,{children:"true"})," to disable adding new records while offline."]}),"\n",(0,s.jsx)(n.h3,{id:"customdisableofflineupdate",children:"custom.disableOfflineUpdate"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean | (() => boolean | Promise<boolean>)"})}),"\n",(0,s.jsxs)(n.p,{children:["Return ",(0,s.jsx)(n.code,{children:"true"})," to disable updating records while offline."]}),"\n",(0,s.jsx)(n.h3,{id:"customdisableofflinedelete",children:"custom.disableOfflineDelete"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean | (() => boolean | Promise<boolean>)"})}),"\n",(0,s.jsxs)(n.p,{children:["Return ",(0,s.jsx)(n.code,{children:"true"})," to disable deleting records while offline."]}),"\n",(0,s.jsx)(n.h2,{id:"collection-access-config",children:"Collection Access Config"}),"\n",(0,s.jsxs)(n.p,{children:["Access config is specified using the ",(0,s.jsx)(n.code,{children:"access"})," property."]}),"\n",(0,s.jsx)(n.h3,{id:"serverreadonly",children:"serverReadOnly"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"string[]"})}),"\n",(0,s.jsxs)(n.p,{children:["An array of roles that must read data via the server. This allows more granular access control (specified in ",(0,s.jsx)(n.code,{children:"serverAccess"})," at the ",(0,s.jsx)(n.a,{href:"#customserveraccess",children:"collection"})," or ",(0,s.jsx)(n.a,{href:"#customserveraccess-1",children:"field"})," level)."]}),"\n",(0,s.jsx)(n.admonition,{type:"warning",children:(0,s.jsx)(n.p,{children:"This option slows performance and removes offline and realtime capabilities, so it is not recommended unless you absolutely need more granular access control."})}),"\n",(0,s.jsx)(n.h3,{id:"serverwriteonly",children:"serverWriteOnly"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," to force writes through the server."]}),"\n",(0,s.jsxs)(n.p,{children:["This option is required if your collection has fields that perform ",(0,s.jsx)(n.a,{href:"#twoway",children:"two-way relation writes"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["This option is automatically enabled for collections with ",(0,s.jsx)(n.code,{children:"auth"})," set to ",(0,s.jsx)(n.code,{children:"true"}),"."]}),"\n",(0,s.jsx)(n.admonition,{type:"warning",children:(0,s.jsx)(n.p,{children:"The above scenarios aside, we do not recommend enabling this option as it slows performance and removes offline write capabilities."})}),"\n",(0,s.jsx)(n.p,{children:"On the other hand, this option can greatly reduce the amount of Firestore Security Rules used by the collection. If you are exceeding the 256KB rules limit, you might consider enabling this option for collections that don't require offline writes."}),"\n",(0,s.jsx)(n.h3,{id:"operations",children:"operations"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"{\n assignable?: boolean | string[]\n read?: string[]\n create?: string[]\n update?: string[]\n delete?: string[]\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"Define which roles can perform which CRUD operations for the collection."}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"assignable"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"boolean | string[]"})}),(0,s.jsxs)(n.td,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," or an array of user roles to allow disabling of access in the user's profile."]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"read"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string[]"})}),(0,s.jsx)(n.td,{children:"Roles that can read records in the collection."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"create"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string[]"})}),(0,s.jsx)(n.td,{children:"Roles that can create records in the collection."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"update"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string[]"})}),(0,s.jsx)(n.td,{children:"Roles that can update records in the collection."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"delete"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string[]"})}),(0,s.jsx)(n.td,{children:"Roles that can delete records in the collection."})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"auth-1",children:"auth"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"{ roles: string[] assignable?: string[] }"})}),"\n",(0,s.jsxs)(n.p,{children:["Only relevant when ",(0,s.jsx)(n.code,{children:"auth"})," is set to true in the root collection config (above)."]}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"roles"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string[]"})}),(0,s.jsxs)(n.td,{children:["Roles that can be granted the ability to assign access credentials for this collection. Roles listed here but not in ",(0,s.jsx)(n.code,{children:"assignable"})," are granted auth access automatically."]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"assignable"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string[]"})}),(0,s.jsxs)(n.td,{children:["Optional subset of ",(0,s.jsx)(n.code,{children:"roles"})," for which auth can be enabled or disabled per user."]})]})]})]}),"\n",(0,s.jsxs)(n.p,{children:["If ",(0,s.jsx)(n.code,{children:"assignable"})," is omitted, every role in ",(0,s.jsx)(n.code,{children:"roles"})," is granted auth automatically."]}),"\n",(0,s.jsx)(n.h3,{id:"access-control-policies",children:"Access Control Policies"}),"\n",(0,s.jsx)(n.p,{children:"Stoker allows you to set complex access control policies using simple c
1onfig."}),"\n",(0,s.jsx)(n.p,{children:"This config comes in the form of:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:'"Attribute restrictions", which restrict a user\'s access to records with certain attributes.'}),"\n",(0,s.jsx)(n.li,{children:'"Entity restrictions", where you explicitly define which specific records or groups of records can be accessed by a user. This assignment is done in the user\'s profile (by an Admin).'}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"attributerestrictions",children:"attributeRestrictions"}),"\n",(0,s.jsx)(n.p,{children:"Provide an array of the following attribute restrictions:"}),"\n",(0,s.jsx)(n.h4,{id:"record-owner-restriction",children:"Record Owner Restriction"}),"\n",(0,s.jsx)(n.p,{children:"Users will only be able to access records that they created themselves."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'type RecordOwnerRestriction = {\n type: "Record_Owner"\n roles: { role: string, assignable?: boolean }[]\n operations?: ("Read" | "Create" | "Update" | "Delete")[]\n}\n'})}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"roles"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"{ role: string, assignable?: boolean }[]"})}),(0,s.jsxs)(n.td,{children:["The roles that this restriction applies to. If ",(0,s.jsx)(n.code,{children:"assignable"})," is ",(0,s.jsx)(n.code,{children:"true"}),", this restriction can be removed for individual users."]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"operations"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:'("Read" | "Create" | "Update" | "Delete")[]'})}),(0,s.jsx)(n.td,{children:"If provided, the restriction only applies to the listed operations."})]})]})]}),"\n",(0,s.jsx)(n.h4,{id:"record-user-restriction",children:"Record User Restriction"}),"\n",(0,s.jsx)(n.p,{children:"Users will only be able to access records that they have been assigned to."}),"\n",(0,s.jsx)(n.p,{children:'For example, if the collection has an "Assigned To" field, the user will only be able to access the record if they have been added to that field.'}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'type RecordUserRestriction = {\n type: "Record_User"\n roles: { role: string, assignable?: boolean }[]\n collectionField: string\n operations?: ("Read" | "Create" | "Update" | "Delete")[]\n}\n'})}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"collectionField"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The field used to assign access. Must be a relational field linked to a users collection."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"roles"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"{ role: string, assignable?: boolean }[]"})}),(0,s.jsxs)(n.td,{children:["The roles that this restriction applies to. If ",(0,s.jsx)(n.code,{children:"assignable"})," is ",(0,s.jsx)(n.code,{children:"true"}),", this restriction can be removed for individual users."]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"operations"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:'("Read" | "Create" | "Update" | "Delete")[]'})}),(0,s.jsx)(n.td,{children:"If provided, the restriction only applies to the listed operations."})]})]})]}),"\n",(0,s.jsx)(n.h4,{id:"record-property-restriction",children:"Record Property Restriction"}),"\n",(0,s.jsx)(n.p,{children:"Users will only be able to access records that have specified values for a selected field."}),"\n",(0,s.jsx)(n.p,{children:'For example, if the collection has a "Status" field, you can ensure that a user role only sees "Not Started" and "In Progress" records, but not "Completed" or "Archived" records.'}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'type RecordPropertyRestriction = {\n type: "Record_Property"\n roles: { role: string, assignable?: boolean, values: string[] }[]\n propertyField: string\n operations?: ("Read" | "Create" | "Upd
1ate" | "Delete")[]\n}\n'})}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"propertyField"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsxs)(n.td,{children:["Must be a String field with ",(0,s.jsx)(n.code,{children:"values"})," set."]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"roles"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"{ role: string, assignable?: boolean, values: string[] }[]"})}),(0,s.jsxs)(n.td,{children:["The roles this restriction applies to, and which property values they can access. If ",(0,s.jsx)(n.code,{children:"assignable"})," is ",(0,s.jsx)(n.code,{children:"true"}),", this restriction can be removed for individual users."]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"operations"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:'("Read" | "Create" | "Update" | "Delete")[]'})}),(0,s.jsx)(n.td,{children:"If provided, the restriction only applies to the listed operations."})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"entityrestrictionsrestrictions",children:"entityRestrictions.restrictions"}),"\n",(0,s.jsx)(n.p,{children:"Provide an array of the following entity restrictions:"}),"\n",(0,s.jsx)(n.h4,{id:"individual-entity-restriction",children:"Individual Entity Restriction"}),"\n",(0,s.jsx)(n.p,{children:"Assign individual records to a user in their profile."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'type IndividualEntityRestriction = {\n type: "Individual"\n roles: { role: string }[]\n singleQuery?: number\n}\n'})}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"roles"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"{ role: string }[]"})}),(0,s.jsx)(n.td,{children:"The roles that this restriction applies to."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"singleQuery"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"number"})}),(0,s.jsx)(n.td,{children:"Advanced. Force read operations to get all records in a single API call."})]})]})]}),"\n",(0,s.jsx)(n.h4,{id:"parent-entity-restriction",children:"Parent Entity Restriction"}),"\n",(0,s.jsx)(n.p,{children:'Assign all records for a parent record to a user in their profile. For example, you might assign a user "All Sites for Company X" or "All Tasks for Project X".'}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'type ParentEntityRestriction = {\n type: "Parent"\n roles: { role: string }[]\n collectionField: string\n singleQuery?: number\n}\n'})}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"collectionField"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The field that parent records can be selected from. Must be a relational field."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"roles"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"{ role: string }[]"})}),(0,s.jsx)(n.td,{children:"The roles that this restriction applies to."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"singleQuery"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"number"})}),(0,s.jsx)(n.td,{children:"Advanced. Force read operations to get all records in a single API call."})]})]})]}),"\n",(0,s.jsx)(n.h4,{id:"parent-property-entity-restriction",children:"Parent Property Entity Restriction"}),"\n",(0,s.jsx)(n.p,{children:'Assign all records for a parent record to a user in their profile, by attribute. For example, you might assign a user "All Sites for Company X in State NY" or "All Tasks for Project X assigned to Team B".'}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'type ParentPropertyEntityRestriction = {\n type: "Parent_Property"\n roles: { role: string }[]\n collectionField: string\n propertyField: string\n}\n'})}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"collectionField"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The field that parent records can be selected from. Must be a relational field."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"propertyField"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The field that defines the attribute."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"roles"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"{ role: string }[]"})}),(0,s.jsx)(n.td,{children:"The roles that this restriction applies to."})]})]})]}),"\n",(0,s.jsx)(n.admonition,{type:"warning",children:(0,s.jsxs)(n.p,{children:["Soft-deleting assigned re
1cords does not block access to them. See ",(0,s.jsx)(n.a,{href:"#softdelete",children:(0,s.jsx)(n.code,{children:"softDelete"})}),"."]})}),"\n",(0,s.jsx)(n.h3,{id:"entityrestrictionsassignable",children:"entityRestrictions.assignable"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"string[]"})}),"\n",(0,s.jsx)(n.p,{children:"An array of user roles for which entity restrictions can be disabled for individual users."}),"\n",(0,s.jsx)(n.h3,{id:"entityrestrictionsparentfilters",children:"entityRestrictions.parentFilters"}),"\n",(0,s.jsx)(n.p,{children:"This section lets you apply entity restrictions from a parent collection onto a child collection."}),"\n",(0,s.jsx)(n.p,{children:'For example, you could assign a user access to "All Jobs on Sites for Company X in State NY" or "All Subtasks on Tasks for Project X assigned to Team B".'}),"\n",(0,s.jsx)(n.h4,{id:"individual-entity-parent-filters",children:"Individual Entity Parent Filters"}),"\n",(0,s.jsx)(n.p,{children:"Assign an individual entity restriction from a parent collection."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'type IndividualEntityParentFilter = {\n type: "Individual"\n collectionField: string\n roles: { role: string }[]\n}\n'})}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"collectionField"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The relational field that links to the collection that the individual entity restriction is on."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"roles"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"{ role: string }[]"})}),(0,s.jsx)(n.td,{children:"The roles that this parent filter applies to."})]})]})]}),"\n",(0,s.jsx)(n.h4,{id:"parent-entity-parent-filters",children:"Parent Entity Parent Filters"}),"\n",(0,s.jsx)(n.p,{children:"Assign a parent entity restriction from a parent collection."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'type ParentEntityParentFilter = {\n type: "Parent"\n collectionField: string\n parentCollectionField: string\n roles: { role: string }[]\n}\n'})}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"collectionField"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The relational field that links to the collection that the individual entity restriction is on."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"parentCollectionField"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The relational field that matches the parent entity restriction's collection field."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"roles"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"{ role: string }[]"})}),(0,s.jsx)(n.td,{children:"The roles that this parent filter applies to."})]})]})]}),"\n",(0,s.jsx)(n.h4,{id:"parent-property-entity-parent-filters",children:"Parent Property Entity Parent Filters"}),"\n",(0,s.jsx)(n.p,{children:"Assign a parent property entity restriction from a parent collection."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'type ParentPropertyEntityParentFilter = {\n type: "Parent_Property"\n collectionField: string\n parentCollectionField: string\n parentPropertyField: string\n roles: { role: string }[]\n}\n'})}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"collectionField"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The relational field that links to the collection that the individual entity restriction is on."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"parentCollectionField"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The relational field that matches the parent entity restriction's collection field."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"parentPropertyField"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The field that matches the parent entity restriction's property field."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"roles"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"{ role: string }[]"})}),(0,s.jsx)(n.td,{children:"The roles that this parent filter applies to."})]})]})]}),"\n",(0,s.jsx)(n.admonition,{type:"warning",children:(0,s.jsxs)(n.p,{children:["When parent filters are used, the collection and property fields used in entity restrictions and parent filters should have updates disabled using ",(0,s.jsx)(n.a,{href:"#restrictupdate",children:(0,s.jsx)(n.code,{children:"restrictUpdate"})}),". This is because you will likely be denormalizing data for these fields."]})}),"\n",(0,s.jsx)(n.h3,{id:"customsecurityrules",children:"customSecurityRules"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," if you want to write custom Firestore Security Rules for the collection."]}),"\n",(0,s.jsxs)(n.p,{children:["Custom security rules can be added at ",(0,s.jsx)(n.code,{children:"firebase-rules/firestore.custom.rules"})]}),"\n",(0,s.jsx)(n.p,{children:"You'll probably want to copy the generated security rules for modification before enabling this option."}),"\n",(0,s.jsx)(n.h3,{id:"customstoragerules",children:"customStorageRules"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," if you want to write custom Firestore Storage Rules for the collection."]}),"\n",(0,s.jsx)(n.p,{children:"You'll probably want to copy the generated security rules for modification before enabling this option."}),"\n",(0,s.jsx)(n.h3,{id:"permissionwriterestrictions",children:"permissionWriteRestrictions"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"permissionWriteRestriction[]"})}),"\n",(0,s.jsx)(n.p,{children:"Stoker lets you restrict which permissions a user role can assign to other user roles. For example, imagine a scenario where you want:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:'"Manager" users to be able to create any users with any permissions'}),"\n",(0,s.jsx)(n.li,{children:'"Supervisors" to be able to create "Staff Member" users only. Additionally, Supervisors can only grant access to 3 selected collections and must apply certain attribute and entity restrictions on those collections.'}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"This allows a flexible yet secure hierarchy of access assignment."}),"\n",(0,s.jsx)(n.p,{children:"Your config should look like this:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'type permissionWriteRestriction = {\n userRole: string\n recordRole: string\n collections: {\n collection: string\n operations: ("Read" | "Create" | "Upd
1ate" | "Delete")[]\n attributeRestrictions?: ("Record_Owner", "Record_User", "Record_Property")[]\n restrictEntities?: boolean\n auth?: boolean\n }[]\n}\n'})}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"userRole"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The user role you are applying restrictions to."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"recordRole"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"A role that the user above can assign access to."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"collections"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"object[]"})}),(0,s.jsx)(n.td,{children:"Define which operations and restrictions are applied for each collection."})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"files",children:"files"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"AccessFiles"})}),"\n",(0,s.jsx)(n.p,{children:"You can define access rules for file uploads."}),"\n",(0,s.jsx)(n.p,{children:"Your config should look like this:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"type AccessFiles = {\n assignment?: {\n [role: string]: {\n optional?: {\n read?: string[]\n update?: string[]\n delete?: string[]\n }\n required?: {\n read?: string[]\n update?: string[]\n delete?: string[]\n }\n }\n }\n metadata?: {\n [key: string]: string\n }\n customMetadata?: {\n [key: string]: string\n }\n}\n"})}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"assignment"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"object"})}),(0,s.jsx)(n.td,{children:"Define the user roles that the user must assign access to for each file."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"metadata"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"Record<string, string>"})}),(0,s.jsx)(n.td,{children:"Enforce Firebase Storage metadata constraints."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"customMetadata"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"Record<string, string>"})}),(0,s.jsx)(n.td,{children:"Enforce custom metadata constraints."})]})]})]}),"\n",(0,s.jsx)(n.p,{children:"For example:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'{\n assignment: {\n Client: {\n required: {\n read: ["Manager", "Supervisor"],\n update: ["Manager", "Supervisor"],\n delete: ["Manager"],\n }\n }\n },\n metadata: {\n size: " <= (5 * 1024 * 1024)",\n contentType: \' in ["application/octet-stream"]\',\n },\n customMetadata: {\n collection: \' == "Work_Orders"\',\n },\n}\n'})}),"\n",(0,s.jsx)(n.h3,{id:"customserveraccess",children:"custom.serverAccess"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"{\n read?: (permissions: StokerPermissions, user: UserRecord, record?: StokerRecord) => boolean | Promise<boolean>\n create?: (permissions: StokerPermissions, user: UserRecord, record: StokerRecord) => boolean | Promise<boolean>\n update?: (permissions: StokerPermissions, user: UserRecord, record: StokerRecord, originalRecord?: StokerRecord) => boolean | Promise<boolean>\n delete?: (permissions: StokerPermissions, user: UserRecord, record: StokerRecord) => boolean | Promise<boolean>\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"Define additional access control using code on the server."}),"\n",(0,s.jsxs)(n.p,{children:["Only relevant if ",(0,s.jsx)(n.a,{href:"#serverwriteonly",children:(0,s.jsx)(n.code,{children:"access.serverWriteOnly"})})," is set to ",(0,s.jsx)(n.code,{children:"true"}),"."]}),"\n",(0,s.jsx)(n.p,{children:"Return a boolean indicating whether or not the access check passed."}),"\n",(0,s.jsx)(n.p,{children:"This code is not sent to the client."}),"\n",(0,s.jsx)(n.h2,{id:"preload-cache-config",children:"Preload Cache Config"}),"\n",(0,s.jsx)(n.p,{children:"You can preload data for your collections on app startup. Preloaded data is cached and is available for the lifetime of the session."}),"\n",(0,s.jsx)(n.p,{children:"Since Stoker is primarily designed for enterprise applications, long sessions are common. It is also common for users to download the app onto the desktop as a PWA."}),"\n",(0,s.jsx)(n.p,{children:"Because long sessions are common, we encourage preloading data on app startup. This results in a snappy application that works offline thanks to cached data."}),"\n",(0,s.jsx)(n.admonition,{type:"tip",children:(0,s.jsx)(n.p,{children:"We absolutely recommend using the preload cache for time series data. This will result in huge performance gains, more available features and fewer indexes used. You don't have to preload huge amounts of data if you don't want to."})}),"\n",(0,s.jsxs)(n.p,{children:["To enable preloading of data, use the ",(0,s.jsx)(n.code,{children:"preloadCache"})," property."]}),"\n",(0,s.jsxs)(n.p,{children:["For examples of this config, see the default Inbox and Outbox collections in your project (created by ",(0,s.jsx)(n.code,{children:"stoker init"}),")."]}),"\n",(0,s.jsx)(n.h3,{id:"roles",children:"roles"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"string[]"})}),"\n",(0,s.jsx)(n.p,{children:"An array of user roles that will use the preload cache."}),"\n",(0,s.jsx)(n.h3,{id:"relationcollections",children:"relationCollections"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean | (() => boolean | Promise<boolean>)"})}),"\n",(0,s.jsx)(n.p,{children:"Whether to wait for related collections to load before signalling to the app that the collection is loaded."}),"\n",(0,s.jsx)(n.h3,{id:"range",children:"range"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"PreloadCacheRange"})}),"\n",(0,s.jsx)(n.p,{children:"Use this option to preload a range of time-series data. The user will be able to update the preloaded range using a date picker in the Admin UI."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'type PreloadCacheRange = {\n fields: string[]\n ranges?: [string, string][]\n labels?: string[]\n start: "Today" | "Week" | "Month" | "Year" | Date | number\n startOffsetDays?: number\n startOffsetHours?: number\n end?: Date | number\n endOffsetDays?: number\n endOffsetHours?: number\n selector?: "range" | "week" | "month" | ("range" | "week" | "month")[]\n}\n'})}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"fields"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string[]"})}),(0,s.jsx)(n.td,{children:"Timestamp fields the user can preload by."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"ranges"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"[string, string][]"})}),(0,s.jsxs)(n.td,{children:["Ranges of fields to preload, for example ",(0,s.jsx)(n.code,{children:'["Start", "End"]'}),". Fields must also be listed in ",(0,s.jsx)(n.code,{children:"fields"}),"."]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"labels"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string[]"})}),(0,s.jsx)(n.td,{children:"Human-readable labels for the fields listed above."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"start"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:'"Today" | "Week" | "Month" | "Year" | Date | number'})}),(0,s.jsx)(n.td,{children:"The default start date for the preloaded range."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"startOffsetDays"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"number"})}),(0,s.jsx)(n.td,{children:"Offset the default start date by this many days."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"startOffsetHours"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"number"})}),(0,s.jsx)(n.td,{children:"Offset the default start date by this many hours."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"end"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"Date | number"})}),(0,s.jsx)(n.td,{children:"The default end date for the preloaded range."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"endOffsetDays"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"number"})}),(0,s.jsx)(n.td,{children:"Offset the default end date by this many days."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"endOffsetHours"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"number"})}),(0,s.jsx)(n.td,{children:"Offset the default end date by this many hours."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"selector"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:'"range" | "week" | "month" | ("range" | "week" | "month")[]'})}),(0,s.jsx)(n.td,{children:"Which selectors to show in the range picker."})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"constraints",children:"constraints"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"[string, WhereFilterOp, unknown][] | (() => [string, WhereFilterOp, unknown][] | Promise<[string, WhereFilterOp, unknown][]>)"})}),"\n",(0,s.jsx)(n.p,{children:"Additional Firestore constraints to apply to the preload cache. This is an advanced option."}),"\n",(0,s.jsx)(n.h2,{id:"collection-fields-config",children:"Collection Fields Config"}),"\n",(0,s.jsxs)(n.p,{children:["Fields are specified using the ",(0,s.jsx)(n.code,{children:"fields"})," property."]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"fields"})," must be an array of field objects. These objects are outlined below."]}),"\n",(0,s.jsx)(n.h3,{id:"general-field-properties",children:"General Field Properties"}),"\n",(0,s.jsx)(n.p,{children:"These properties can be set on any field type."}),"\n",(0,s.jsx)(n.h4,{id:"name",children:"name"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"string"})}),"\n",(0,s.jsxs)(n.p,{children:["The name of the field. It must not have spaces (use underscores). You can set a human-readable name in ",(0,s.jsx)(n.a,{href:"#label",children:(0,s.jsx)(n.code,{children:"admin.label"})}),"."]}),"\n",(0,s.jsx)(n.h4,{id:"access",children:"access"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"string[] | { group: string }"})}),"\n",(0,s.jsx)(n.p,{children:"Controls which users can access the field."}),"\n",(0,s.jsx)(n.p,{children:"Provide an array of user roles for static access:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'access: ["Administrator", "Management"]\n'})}),"\n",(0,s.jsxs)(n.p,{children:["Or reference a ",(0,s.jsx)(n.a,{href:"#fieldaccessgroups",children:"field access group"})," for conditional access:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'access: { group: "Ground-Notes" }\n'})}),"\n",(0,s.jsx)(n.admonition,{type:"danger",children:(0,s.jsx)(n.p,{children:"Omitting the access property altogether allows access by ALL roles."})}),"\n",(0,s.jsx)(n.h4,{id:"restrictcreate",children:"restrictCreate"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean | string[] | FieldAccessCondition"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"}
1)," to prevent this field from being included when the record is created."]}),"\n",(0,s.jsxs)(n.p,{children:["Alternatively, provide an array of user roles that CAN provide the field when creating a record, or provide a ",(0,s.jsx)(n.a,{href:"#fieldaccesscondition",children:(0,s.jsx)(n.code,{children:"FieldAccessCondition"})})," for even more granular access control."]}),"\n",(0,s.jsx)(n.h4,{id:"restrictupdate",children:"restrictUpdate"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean | string[] | FieldAccessCondition"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," to prevent this field from being changed when the record is updated."]}),"\n",(0,s.jsxs)(n.p,{children:["Alternatively, provide an array of user roles that CAN change the field when updating a record, or provide a ",(0,s.jsx)(n.a,{href:"#fieldaccesscondition",children:(0,s.jsx)(n.code,{children:"FieldAccessCondition"})})," for even more granular access control."]}),"\n",(0,s.jsx)(n.h4,{id:"required",children:"required"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," if the field is a required field."]}),"\n",(0,s.jsx)(n.h4,{id:"nullable",children:"nullable"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," if the field is a nullable field."]}),"\n",(0,s.jsx)(n.h4,{id:"sorting",children:"sorting"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:'boolean | { direction?: "asc" | "desc" roles?: string[] }'})}),"\n",(0,s.jsxs)(n.p,{children:["Specifies that this field will be used for sorting. Not required if all user roles have ",(0,s.jsx)(n.code,{children:"preloadCache"})," or ",(0,s.jsx)(n.code,{children:"access.serverReadOnly"})," enabled. In these cases, sorting is automatic."]}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"}),' to sort by "asc" and "desc" for all user roles, or provide the more granular config above.']}),"\n",(0,s.jsx)(n.admonition,{type:"warning",children:(0,s.jsxs)(n.p,{children:["Specifying a sorting direction for a field in a collection without ",(0,s.jsx)(n.code,{children:"preloadCache"})," or ",(0,s.jsx)(n.code,{children:"serverReadOnly"})," set to ",(0,s.jsx)(n.code,{children:"true"}),' will result in the "Previous" page button being disabled. The "Back to start" button will still work.']})}),"\n",(0,s.jsx)(n.h4,{id:"skiprulesvalidation-1",children:"skipRulesValidation"}),"\n",(0,s.jsx)(n.p,{children:"Skip Firestore Security Rules validation for this field. This can help to keep the size of the security ruleset down. When this option is enabled, validation will be performed post-write in a Cloud Function. You will receive an email if invalid data has been submitted."}),"\n",(0,s.jsx)(n.h4,{id:"description",children:"description"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"string | (() => string | Promise<string>)"})}),"\n",(0,s.jsxs)(n.p,{children:["A description for the field for LLMs. Only relevant if ",(0,s.jsx)(n.a,{href:"#ai-chat-config",children:(0,s.jsx)(n.code,{children:"ai"})})," is configured."]}),"\n",(0,s.jsx)(n.h4,{id:"singlefieldexemption",children:"singleFieldExemption"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'{\n queryScope: "COLLECTION" | "COLLECTION_GROUP"\n order?: "ASCENDING" | "DESCENDING"\n arrayConfig?: "CONTAINS" | "CONTAINS_ANY"\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:"Place a single field exemption on the field in Firestore."}),"\n",(0,s.jsxs)(n.p,{children:["If you have set ",(0,s.jsx)(n.code,{children:"indexExemption"})," at the collection level, this option will re-enable indexing for the field."]}),"\n",(0,s.jsxs)(n.p,{children:["You should consider exempting ",(0,s.jsx)(n.a,{href:"/docs/advanced-google-cloud/Limitations#write-rate-limitations",children:"incrementally increasing monotonic fields"}),", large String fields, Map fields and Array fields."]}),"\n",(0,s.jsx)(n.h4,{id:"savetoauthtoken",children:"saveToAuthToken"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean"})}),"\n",(0,s.jsxs)(n.p,{children:["In auth collections, set to ",(0,s.jsx)(n.code,{children:"true"})," to add the field to the linked user's auth token."]}),"\n",(0,s.jsx)(n.admonition,{type:"warning",children:(0,s.jsxs)(n.p,{children:["Be sure to control access to auth token fields using ",(0,s.jsx)(n.a,{href:"#restrictcreate",children:(0,s.jsx)(n.code,{children:"restrictCreate"})})," and ",(0,s.jsx)(n.a,{href:"#restrictupdate",children:(0,s.jsx)(n.code,{children:"restrictUpdate"})})]})}),"\n",(0,s.jsx)(n.h4,{id:"custominitialvalue",children:"custom.initialValue"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"unknown | ((record?: StokerRecord) => unknown | Promise<unknown>)"})}),"\n",(0,s.jsx)(n.p,{children:"Calculate an initial value for this field when creating the record."}),"\n",(0,s.jsx)(n.h4,{id:"customserveraccess-1",children:"custom.serverAccess"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"{\n read?: (permissions: StokerPermissions, user: UserRecord, record?: StokerRecord) => boolean | Promise<boolean>\n create?: (permissions: StokerPermissions, user: UserRecord, record: StokerRecord) => boolean | Promise<boolean>\n update?: (permissions: StokerPermissions, user: UserRecord, record: StokerRecord, originalRecord?: StokerRecord) => boolean | Promise<boolean>\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"Define additional access control using code on the server."}),"\n",(0,s.jsxs)(n.p,{children:["Only relevant if ",(0,s.jsx)(n.code,{children:"access.serverWriteOnly"})," is set to ",(0,s.jsx)(n.code,{children:"true"}),"."]}),"\n",(0,s.jsx)(n.p,{children:"Return a boolean indicating whether or not the access check passed."}),"\n",(0,s.jsx)(n.p,{children:"This code is not sent to the client."}),"\n",(0,s.jsx)(n.h3,{id:"string-field-properties",children:"String Field Properties"}),"\n",(0,s.jsx)(n.h4,{id:"type",children:"type"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:'"String"'})}),"\n",(0,s.jsx)(n.h4,{id:"values",children:"values"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"string[]"})}),"\n",(0,s.jsx)(n.p,{children:"An optional list of values. This will result in a dropdown list being shown in the Admin UI."}),"\n",(0,s.jsx)(n.h4,{id:"unique",children:"unique"}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," to make the field a unique field. Be sparing with this option unless ",(0,s.jsx)(n.code,{children:"access.serverWriteOnly"})," is set to true."]}),"\n",(0,s.jsx)(n.p,{children:"Case is ignored when determining whether or not a value is a duplicate."}),"\n",(0,s.jsx)(n.admonition,{type:"tip",children:(0,s.jsxs)(n.p,{children:["You can auto-rename duplicate values in the Web SDK using ",(0,s.jsx)(n.a,{href:"#customautocorrectunique",children:(0,s.jsx)(n.code,{children:"custom.autoCorrectUnique"})}),'. Note that this will append an identifier i.e. "DUPLICATE-1" to the end of the field value. Your field validation strategy will need to account for this identifier, or writes will be rejected.']})}),"\n",(0,s.jsx)(n.admonition,{type:"warning",children:(0,s.jsxs)(n.p,{children:["Unique field values are NOT freed up when a re
1cord is ",(0,s.jsx)(n.a,{href:"#softdelete",children:"soft-deleted"}),"."]})}),"\n",(0,s.jsx)(n.h4,{id:"length",children:"length"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"number"})}),"\n",(0,s.jsx)(n.p,{children:"Specify a fixed length for the field."}),"\n",(0,s.jsx)(n.h4,{id:"minlength",children:"minlength"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"number"})}),"\n",(0,s.jsx)(n.p,{children:"Specify a minimum length for the field."}),"\n",(0,s.jsx)(n.h4,{id:"maxlength",children:"maxlength"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"number"})}),"\n",(0,s.jsx)(n.p,{children:"Specify a maximum length for the field."}),"\n",(0,s.jsx)(n.h4,{id:"pattern",children:"pattern"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"string"})}),"\n",(0,s.jsx)(n.p,{children:"Specify a regex pattern for the field."}),"\n",(0,s.jsx)(n.h4,{id:"email",children:"email"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," if the field is an email address"]}),"\n",(0,s.jsx)(n.admonition,{type:"warning",children:(0,s.jsxs)(n.p,{children:["Emails are currently only validated server-side if you have ",(0,s.jsx)(n.code,{children:"access.serverWriteOnly"})," set to true. You can alternatively use ",(0,s.jsx)(n.code,{children:"pattern"})," above, which is always validated server-side."]})}),"\n",(0,s.jsx)(n.h4,{id:"url",children:"url"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," if the field is a url"]}),"\n",(0,s.jsx)(n.admonition,{type:"warning",children:(0,s.jsxs)(n.p,{children:["URLs are currently only validated server-side if you have ",(0,s.jsx)(n.code,{children:"access.serverWriteOnly"})," set to true. You can alternatively use ",(0,s.jsx)(n.code,{children:"pattern"})," above, which is always validated server-side."]})}),"\n",(0,s.jsx)(n.h4,{id:"emoji",children:"emoji"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," if the field is an emoji"]}),"\n",(0,s.jsx)(n.admonition,{type:"warning",children:(0,s.jsxs)(n.p,{children:["Emojis are currently only validated server-side if you have ",(0,s.jsx)(n.code,{children:"access.serverWriteOnly"})," set to true. You can alternatively use ",(0,s.jsx)(n.code,{children:"pattern"})," above, which is always validated server-side."]})}),"\n",(0,s.jsx)(n.h4,{id:"uuid",children:"uuid"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," if the field is a UUID"]}),"\n",(0,s.jsx)(n.admonition,{type:"warning",children:(0,s.jsxs)(n.p,{children:["UUIDs are currently only validated server-side if you have ",(0,s.jsx)(n.code,{children:"access.serverWriteOnly"})," set to true. You can alternatively use ",(0,s.jsx)(n.code,{children:"pattern"})," above, which is always validated server-side."]})}),"\n",(0,s.jsx)(n.h4,{id:"ip",children:"ip"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," if the field is an IP address"]}),"\n",(0,s.jsx)(n.admonition,{type:"warning",children:(0,s.jsxs)(n.p,{children:["IP addresses are currently only validated server-side if you have ",(0,s.jsx)(n.code,{children:"access.serverWriteOnly"})," set to true. You can alternatively use ",(0,s.jsx)(n.code,{children:"pattern"})," above, which is always validated server-side."]})}),"\n",(0,s.jsx)(n.h3,{id:"number-field-properties",children:"Number Field Properties"}),"\n",(0,s.jsx)(n.h4,{id:"type-1",children:"type"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:'"Number"'})}),"\n",(0,s.jsx)(n.h4,{id:"values-1",children:"values"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"number[]"})}),"\n",(0,s.jsx)(n.p,{children:"An optional list of values. This will result in a dropdown list being shown in the Admin UI."}),"\n",(0,s.jsx)(n.h4,{id:"unique-1",children:"unique"}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," to make the field a unique field. Be sparing with this option unless ",(0,s.jsx)(n.code,{children:"access.serverWriteOnly"})," is set to true."]}),"\n",(0,s.jsx)(n.admonition,{type:"warning",children:(0,s.jsxs)(n.p,{children:["Unique field values are NOT freed up when a re
1cord is ",(0,s.jsx)(n.a,{href:"#softdelete",children:"soft-deleted"}),"."]})}),"\n",(0,s.jsx)(n.h4,{id:"autoincrement",children:"autoIncrement"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," to make the field an auto-incremented number."]}),"\n",(0,s.jsx)(n.admonition,{type:"info",children:(0,s.jsx)(n.p,{children:"Auto-incremented numbers are written by a Cloud Function after the record has been saved to the server. This means auto-incremented numbers made in offline mode won't appear until the user has reconnected."})}),"\n",(0,s.jsx)(n.h4,{id:"decimal",children:"decimal"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"number"})}),"\n",(0,s.jsx)(n.p,{children:"Set the maximum number of decimal places for this field."}),"\n",(0,s.jsx)(n.h4,{id:"min",children:"min"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"number"})}),"\n",(0,s.jsx)(n.p,{children:"Set the minimum number value for this field."}),"\n",(0,s.jsx)(n.h4,{id:"max",children:"max"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"number"})}),"\n",(0,s.jsx)(n.p,{children:"Set the maximum number value for this field."}),"\n",(0,s.jsx)(n.h3,{id:"timestamp-field-properties",children:"Timestamp Field Properties"}),"\n",(0,s.jsx)(n.h4,{id:"type-2",children:"type"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:'"Timestamp"'})}),"\n",(0,s.jsx)(n.h4,{id:"values-2",children:"values"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"number[]"})}),"\n",(0,s.jsx)(n.p,{children:"An optional list of dates in milliseconds format. This will result in a dropdown list being shown in the Admin UI."}),"\n",(0,s.jsx)(n.h4,{id:"min-1",children:"min"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"number"})}),"\n",(0,s.jsx)(n.p,{children:"Set the minimum milliseconds value for this field."}),"\n",(0,s.jsx)(n.h4,{id:"max-1",children:"max"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"number"})}),"\n",(0,s.jsx)(n.p,{children:"Set the maximum milliseconds value for this field."}),"\n",(0,s.jsx)(n.h3,{id:"array-field-properties",children:"Array Field Properties"}),"\n",(0,s.jsx)(n.h4,{id:"type-3",children:"type"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:'"Array"'})}),"\n",(0,s.jsx)(n.h4,{id:"values-3",children:"values"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"string[]"})}),"\n",(0,s.jsx)(n.p,{children:"A list of values. A dropdown selector will be shown in the Admin UI."}),"\n",(0,s.jsx)(n.h4,{id:"length-1",children:"length"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"number"})}),"\n",(0,s.jsx)(n.p,{children:"Set the exact required length for this field."}),"\n",(0,s.jsx)(n.h4,{id:"minlength-1",children:"minlength"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"number"})}),"\n",(0,s.jsx)(n.p,{children:"Set the minimum length for this field."}),"\n",(0,s.jsx)(n.h4,{id:"maxlength-1",children:"maxlength"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"number"})}),"\n",(0,s.jsx)(n.p,{children:"Set the maximum length for this field."}),"\n",(0,s.jsx)(n.h3,{id:"map-field-properties",children:"Map Field Properties"}),"\n",(0,s.jsx)(n.h4,{id:"type-4",children:"type"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:'"Map"'})}),"\n",(0,s.jsx)(n.h3,{id:"relational-field-properties",children:"Relational Field Properties"}),"\n",(0,s.jsxs)(n.admonition,{type:"warning",children:[(0,s.jsx)(n.p,{children:"Stoker records are limited to Firestore's per-document limits:"}),(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"1MB max document size"}),"\n",(0,s.jsx)(n.li,{children:"40,000 max index entries"}),"\n"]}),(0,s.jsx)(n.p,{children:"This limits the maximum number of relations you can have per document."})]}),"\n",(0,s.jsx)(n.h4,{id:"type-5",children:"type"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:'"OneToOne" | "OneToMany" | "ManyToOne" | "ManyToMany"'})}),"\n",(0,s.jsx)(n.h4,{id:"collection",children:"collection"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"string"})}),"\n",(0,s.jsx)(n.p,{children:"The collection for the relational field."}),"\n",(0,s.jsx)(n.h4,{id:"includefields",children:"includeFields"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"string[]"})}),"\n",(0,s.jsx)(n.p,{children:"Save fields from the related record to the target record. Field values will be denormalized and will automatically update when the source record
1is updated."}),"\n",(0,s.jsx)(n.p,{children:"This architecture avoids the N+1 problem and gives you a snappy, offline-friendly app. The downside is that potentially large back-end write operations are required when values change. However, these write operations are automatically managed by highly scalable Cloud Functions in Stoker."}),"\n",(0,s.jsxs)(n.p,{children:['For examples of this config, see the "Sender" and "Recipients" fields in the default Inbox collection in your project (created by ',(0,s.jsx)(n.code,{children:"stoker init"}),")"]}),"\n",(0,s.jsx)(n.admonition,{type:"info",children:(0,s.jsx)(n.p,{children:"Updates to include fields are written by a Cloud Function after the source record has been updated on the server. This means include field updates made in offline mode won't appear until the user has reconnected."})}),"\n",(0,s.jsx)(n.h4,{id:"titlefield",children:"titleField"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"string"})}),"\n",(0,s.jsxs)(n.p,{children:["Choose one of the ",(0,s.jsx)(n.code,{children:"includeFields"})," values above to act as the title field for the related record."]}),"\n",(0,s.jsxs)(n.p,{children:['For examples of this config, see the "Sender" and "Recipients" fields in the default Inbox collection in your project (created by ',(0,s.jsx)(n.code,{children:"stoker init"}),")"]}),"\n",(0,s.jsx)(n.h4,{id:"dependencyfields",children:"dependencyFields"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"{ field: string, roles: StokerRole[] }[]"})}),"\n",(0,s.jsx)(n.p,{children:'Grant users access to the specified field in the related collection. For example, you could assign a user access to only the "ID" and "Name" fields in the related collection. This lets users select values from a dropdown without giving them full access to the related collection.'}),"\n",(0,s.jsxs)(n.p,{children:['For an example of this config, see the "Recipient" field in the default Outbox collection in your project (created by ',(0,s.jsx)(n.code,{children:"stoker init"}),")"]}),"\n",(0,s.jsx)(n.admonition,{type:"info",children:(0,s.jsxs)(n.p,{children:["Only records allowed by the user role's ",(0,s.jsx)(n.a,{href:"#access-control-policies",children:"access policy"})," for the related collection will be accessible."]})}),"\n",(0,s.jsx)(n.h4,{id:"twoway",children:"twoWay"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"string"})}),"\n",(0,s.jsx)(n.p,{children:"Enable a two-way relation."}),"\n",(0,s.jsx)(n.p,{children:"Provide the name of a relational field in the target collection to link with. The following link types are valid:"}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"OneToOne"})," > ",(0,s.jsx)(n.code,{children:"OneToOne"})]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"OneToMany"})," > ",(0,s.jsx)(n.code,{children:"ManyToOne"})]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"ManyToOne"})," > ",(0,s.jsx)(n.code,{children:"OneToMany"})]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"ManyToMany"})," > ",(0,s.jsx)(n.code,{children:"ManyToMany"})]}),"\n",(0,s.jsx)(n.admonition,{type:"warning",children:(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"access.serverWriteOnly"})," must be enabled for this feature to work, which will result in the loss of offline write capabilities."]})}),"\n",(0,s.jsxs)(n.admonition,{type:"warning",children:[(0,s.jsx)(n.p,{children:"For two-way writes to work, the user must have write access to both the source record and the target record. Ensure that the user will have:"}),(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Sufficient access to the target record's collection"}),"\n",(0,s.jsxs)(n.li,{children:["Sufficient access to the target record (ensure that ",(0,s.jsx)(n.a,{href:"#access-control-policies",children:"Access control policies"})," won't block access to the target record)"]}),"\n",(0,s.jsxs)(n.li,{children:["Sufficient access to the target field (",(0,s.jsx)(n.a,{href:"#access",children:(0,s.jsx)(n.code,{children:"field.access"})}),")"]}),"\n"]})]}),"\n",(0,s.jsx)(n.h4,{id:"preserve",children:"preserve"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," to preserve relation data when the related record is deleted."]}),"\n",(0,s.jsx)(n.admonition,{type:"warning",children:(0,s.jsx)(n.p,{children:"This may have privacy implications"})}),"\n",(0,s.jsx)(n.h4,{id:"writeany",children:"writeAny"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," to allow users to write any value to the relational field. By default, users can only write values for records that they have access to."]}),"\n",(0,s.jsx)(n.admonition,{type:"warning",children:(0,s.jsx)(n.p,{children:"This may have security implications"})}),"\n",(0,s.jsx)(n.h4,{id:"constraints-1",children:"constraints"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:'[string, "==" | "in", unknown][]'})}),"\n",(0,s.jsxs)(n.p,{children:["Set ",(0,s.jsx)(n.a,{href:"https://firebase.google.com/docs/firestore/query-data/queries#simple_queries",children:"Firestore where() query"})," constraints that will be applied when retrieving records for the dropdown selector."]}),"\n",(0,s.jsx)(n.h4,{id:"enforcehierarchy",children:"enforceHierarchy"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"{ field: string, recordLinkField: string }"})}),"\n",(0,s.jsx)(n.p,{children:'Enforce the relational integrity of the field. For example, ensure that the record\'s "Site" is actually related to the record\'s "Company".'}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"field"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"Another relational field in the collection that is above the current field in the relational hierarchy."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"recordLinkField"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The field in the related collection above that links to the same collection as the current field."})]})]})]}),"\n",(0,s.jsx)(n.h4,{id:"length-2",children:"length"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"number"})}),"\n",(0,s.jsx)(n.p,{children:"Set the exact number of relations required for this field."}),"\n",(0,s.jsx)(n.h4,{id:"min-2",children:"min"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"number"})}),"\n",(0,s.jsx)(n.p,{children:"Set the minimum number of relations for this field."}),"\n",(0,s.jsx)(n.h4,{id:"max-2",children:"max"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"number"})}),"\n",(0,s.jsx)(n.p,{children:"Set the maximum number of relations for this field."}),"\n",(0,s.jsx)(n.admonition,{type:"info",children:(0,s.jsx)(n.p,{children:"When a record is deleted from Stoker, its relation values in other records are deleted. This happens in a Cloud Function, so deletions made in offline mode won't be visible until the user has reconnected."})}),"\n",(0,s.jsx)(n.h3,{id:"computed-field-properties",children:"Computed Field Properties"}),"\n",(0,s.jsx)(n.h4,{id:"type-6",children:"type"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:'"Computed"'})}),"\n",(0,s.jsx)(n.h4,{id:"formula",children:"formula"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"(record: StokerRecord, retrieverData?: any) => string | number | Promise<string | number>"})}),"\n",(0,s.jsx)(n.p,{children:"Calculates the value for the field."}),"\n",(0,s.jsxs)(n.p,{children:["When using ",(0,s.jsx)(n.code,{children:"getSome"})," or ",(0,s.jsx)(n.code,{children:"subscribeMany"}),", ",(0,s.jsx)(n.code,{children:"retrieverData"})," will return the data provided by the collection's ",(0,s.jsx)(n.a,{href:"#retriever",children:(0,s.jsx)(n.code,{children:"retriever"})})," function. This lets you load data sets for your computed field formulas ",(0,s.jsx)(n.strong,{children:"once per query"}),"."]}),"\n",(0,s.jsx)(n.h3,{id:"field-admin-ui-properties",children:"Field Admin UI Properties"}),"\n",(0,s.jsxs)(n.p,{children:["Admin UI config is specified using the ",(0,s.jsx)(n.code,{children:"admin"})," property."]}),"\n",(0,s.jsx)(n.h4,{id:"label",children:"label"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"string | (() => string)"})}),"\n",(0,s.jsxs)(n.p,{children:["A human-readable name for the field. Only necessary if ",(0,s.jsx)(n.code,{children:"name"})," is not human-readable."]}),"\n",(0,s.jsx)(n.h4,{id:"listlabel",children:"listLabel"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"string | (() => string)"})}),"\n",(0,s.jsx)(n.p,{children:"Override the field name shown in the list view."}),"\n",(0,s.jsx)(n.h4,{id:"icon",children:"icon"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"{ component: React.FC, className?: string } | (() => { component: React.FC, className?: string } | Promise<{ component: React.FC, className?: string } >)"})}),"\n",(0,s.jsxs)(n.p,{children:["An icon that will be shown for the field on the form page. Provide additional Tailwind classes using ",(0,s.jsx)(n.code,{children:"className"}),"."]}),"\n",(0,s.jsx)(n.h4,{id:"condition",children:"condition"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'{\n list?: boolean | ((parentCollection?: CollectionSchema, parentRecord?: StokerRecord) => boolean)\n form?: boolean | ((operation?: "create" | "update", record?: StokerRecord, isExport?: boolean) => boolean)\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:"Show or hide the field in the list view and on the form page."}),"\n",(0,s.jsx)(n.p,{children:"The list method receives the parent collection and parent record when shown on a relation list page."}),"\n",(0,s.jsx)(n.p,{children:"The form method receives isExport as true during CSV export operations."}),"\n",(0,s.jsx)(n.h4,{id:"hidden",children:"hidden"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:'"sm" | "md" | "lg" | "xl" | "2xl" | ((record?: StokerRecord) => "sm" | "md" | "lg" | "xl" | "2xl")'})}),"\n",(0,s.jsx)(n.p,{children:"The screen size at which the field should be hidden from the list view. This is useful for responsiveness."}),"\n",(0,s.jsx)(n.h4,{id:"readonly",children:"readOnly"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:'boolean | ((operation?: "create" | "update", record?: StokerRecord) => boolean)'})}),"\n",(0,s.jsxs)(n.p,{children:["Return ",(0,s.jsx)(n.code,{children:"true"})," to set this as a read-only field in the Admin UI"]}),"\n",(0,s.jsx)(n.h4,{id:"sort",children:"sort"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"(record?: StokerRecord) => unknown"})}),"\n",(0,s.jsx)(n.p,{children:"The returned value will be used for sorting in the list view."}),"\n",(0,s.jsx)(n.h4,{id:"column",children:"column"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean | number | (() => boolean | number)"})}),"\n",(0,s.jsx)(n.p,{children:"Set the position of the field in the list and the form. Defaults to the position of the field in the fields array, so this option may not be necessary."}),"\n",(0,s.jsx)(n.h4,{id:"description-1",children:"description"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"{\n message: string | ((record?: StokerRecord) => string | Promise<string>)\n condition?: boolean | ((record?: StokerRecord) => boolean | Promise<boolean>)\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"Display a conditional description message under the field in the Admin UI."}),"\n",(0,s.jsx)(n.h4,{id:"live",children:"live"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," to have the field live-update on the form page when the record
1is updated remotely."]}),"\n",(0,s.jsx)(n.h4,{id:"switch",children:"switch"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean | (() => boolean | Promise<boolean>)"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," on a Boolean field to make it a switch"]}),"\n",(0,s.jsx)(n.h4,{id:"textarea",children:"textarea"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean | (() => boolean | Promise<boolean>)"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," on a String field to make it a textarea"]}),"\n",(0,s.jsx)(n.h4,{id:"richtext",children:"richText"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean | (() => boolean | Promise<boolean>)"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," on a Map field to make it a rich text field"]}),"\n",(0,s.jsx)(n.h4,{id:"italic",children:"italic"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean | ((record?: StokerRecord) => boolean)"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," on a String field to make it italic"]}),"\n",(0,s.jsx)(n.h4,{id:"currency",children:"currency"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"string | ((record?: StokerRecord) => string)"})}),"\n",(0,s.jsx)(n.p,{children:"Set to a currency symbol on a String field to make it a currency"}),"\n",(0,s.jsx)(n.h4,{id:"radio",children:"radio"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean | (() => boolean | Promise<boolean>)"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," on a String field with ",(0,s.jsx)(n.code,{children:"values"})," to make it a radio group"]}),"\n",(0,s.jsx)(n.h4,{id:"buttongroup",children:"buttonGroup"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean | (() => boolean | Promise<boolean>)"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," on a String field with ",(0,s.jsx)(n.code,{children:"values"})," to make it a button group"]}),"\n",(0,s.jsx)(n.h4,{id:"slider",children:"slider"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean | (() => boolean | Promise<boolean>)"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," on a Number field to make it a slider"]}),"\n",(0,s.jsx)(n.h4,{id:"time",children:"time"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean | (() => boolean)"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," on a String field to make it a time field."]}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," on a Timestamp field to make it a datetime field."]}),"\n",(0,s.jsx)(n.h4,{id:"month",children:"month"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean | (() => boolean | Promise<boolean>)"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," on a Timestamp field to make it a month picker."]}),"\n",(0,s.jsx)(n.h4,{id:"location",children:"location"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"{ center: { lat: number, lng: number }, zoom: number }"})}),"\n",(0,s.jsx)(n.p,{children:"Set on an Array field to make it a coordinates field."}),"\n",(0,s.jsx)(n.p,{children:"Provide starting location coordinates and zoom."}),"\n",(0,s.jsx)(n.h4,{id:"image",children:"image"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean | (() => boolean)"})}),"\n",(0,s.jsx)(n.p,{children:"Set on a String field to make it an image field. Images can be uploaded, or selected from files uploaded to the record."}),"\n",(0,s.jsx)(n.h4,{id:"badge",children:"badge"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean | string | ((record?: StokerRecord) => boolean | string)"})}),"\n",(0,s.jsx)(n.p,{children:"Set on a String field to make it a badge. Return a Tailwind class specifying the color for the badge."}),"\n",(0,s.jsx)(n.h4,{id:"tags",children:"tags"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"string[] | (() => string[])"})}),"\n",(0,s.jsx)(n.p,{children:"For Array fields. Provide an array of Tailwind classes to have values appear as colored badges. The order of the Tailwind classes array must be the same as the field's values array."}),"\n",(0,s.jsx)(n.h4,{id:"asrichtext",children:"asRichText"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean | (() => boolean)"})}),"\n",(0,s.jsxs)(n.p,{children:["Display a ",(0,s.jsx)(n.a,{href:"#computed-field-properties",children:"Computed"})," field value as rich text"]}),"\n",(0,s.jsx)(n.h4,{id:"noexport",children:"noExport"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean | (() => boolean)"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," to exclude this field from CSV export"]}),"\n",(0,s.jsx)(n.h4,{id:"exportseparator",children:"exportSeparator"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"string | (() => string)"})}),"\n",(0,s.jsxs)(n.p,{children:["Set on an Array or relational field to specify the separator to be used between items in CSV export. Defaults to ",(0,s.jsx)(n.code,{children:'", "'}),"."]}),"\n",(0,s.jsx)(n.h4,{id:"modifydisplayvalue",children:"modifyDisplayValue"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:'(record?: StokerRecord, context?: "card" | "form" | "list") => unknown'})}),"\n",(0,s.jsx)(n.p,{children:"Modify the displayed value for the field, for example when shown in the list view or when read-only on a form."}),"\n",(0,s.jsx)(n.h4,{id:"skipformrequiredvalidation",children:"skipFormRequiredValidation"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean | (() => boolean)"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," to skip required validation on the form page. This is useful if the required field value will be set after the form has been submitted, for example in ",(0,s.jsx)(n.a,{href:"#custominitialvalue",children:(0,s.jsx)(n.code,{children:"custom.initialValue"})})," or a hook."]}),"\n",(0,s.jsx)(n.h4,{id:"overrideformrequiredvalidation",children:"overrideFormRequiredValidation"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:'(operation: "create" | "update", record?: StokerRecord, originalRecord?: StokerRecord) => boolean'})}),"\n",(0,s.jsxs)(n.p,{children:['Conditionally set a field to "required". This overrides ',(0,s.jsx)(n.code,{children:"required"})," above, however ",(0,s.jsx)(n.code,{children:"required"})," above will still be enforced on the server if present."]}),"\n",(0,s.jsx)(n.h4,{id:"filtervalues",children:"filterValues"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"(value: string | number, parentCollection: CollectionSchema, parentRecord?: StokerRecord) => boolean"})}),"\n",(0,s.jsxs)(n.p,{children:["Filter the options in the dropdown for String or Number fields with ",(0,s.jsx)(n.code,{children:"values"})," set."]}),"\n",(0,s.jsx)(n.h4,{id:"filterresults",children:"filterResults"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"(result: StokerRecord, parentCollection: CollectionSchema, parentRecord?: StokerRecord) => boolean"})}),"\n",(0,s.jsxs)(n.p,{children:["Filter the options in the dropdown for relational fields. Only works when ",(0,s.jsx)(n.code,{children:"preloadCache"})," is enabled for the user's role."]}),"\n",(0,s.jsx)(n.h4,{id:"modifyresulttitle",children:"modifyResultTitle"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"(\n record: StokerRecord,\n parentCollection: CollectionSchema,\n parentRecord?: StokerRecord,\n) => string\n"})}),"\n",(0,s.jsx)(n.p,{children:"Modify the results in the dropdown for relational fields."}),"\n",(0,s.jsx)(n.h4,{id:"customlistview",children:"customListView"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"(\n record?: StokerRecord,\n parentCollection?: CollectionSchema,\n parentRecord?: StokerRecord,\n) => {\n component: React.FC\n props?: Record<string, unknown>\n receiveClick?: boolean\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Return a custom component to be used in the list view. Set ",(0,s.jsx)(n.code,{children:"receiveClick"})," to ",(0,s.jsx)(n.code,{children:"true"})," to have the component receive the click and override the default behaviour of navigating to the record page."]}),"\n",(0,s.jsx)(n.h4,{id:"queryfullrecord",children:"queryFullRecord"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean | (() => boolean)"})}),"\n",(0,s.jsxs)(n.p,{children:["When using ",(0,s.jsx)(n.a,{href:"#includefields",children:"include fields"})," on a relation field, return true to force the full relation record to be loaded (on the form page only)."]}),"\n",(0,s.jsx)(n.h3,{id:"field-hooks",children:"Field Hooks"}),"\n",(0,s.jsxs)(n.p,{children:["Fields can have any of the hooks defined in ",(0,s.jsx)(n.a,{href:"#collection-hooks",children:"Collection Hooks"}),". Add them to the field's ",(0,s.jsx)(n.code,{children:"custom"})," property."]}),"\n",(0,s.jsxs)(n.admonition,{type:"danger",children:[(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Server code protection:"})," During the build process, server-only code blocks are automatically stripped from the client bundle:"]}),(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:'if(sdk === "node")'})," blocks are removed"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"else"})," and ",(0,s.jsx)(n.code,{children:"else if"})," blocks following ",(0,s.jsx)(n.code,{children:'if (sdk === "web")'})," guards are removed"]}),"\n",(0,s.jsxs)(n.li,{children:["Code in ",(0,s.jsx)(n.code,{children:"src/node"})," is stripped to export names only"]}),"\n"]}),(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Important:"})," Code outside of these guard blocks WILL be sent to the client and will run on the client."]}),(0,s.jsxs)(n.p,{children:["For highly sensitive server operations, consider using ",(0,s.jsx)(n.a,{href:"Other%20Project%20Files",children:"custom cloud functions"})," instead."]})]}),"\n",(0,s.jsx)(n.h2,{id:"ai-chat-config",children:"AI Chat Config"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"AI Chat",src:i(122).A+"",width:"2924",height:"1736"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"{\n embedding?: boolean\n chat?: {\n name: string\n defaultQueryLimit?: number\n roles: string[]\n }\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"You can enable an AI chat bot for the collection. The chat bot uses Retrieval Augmented Generation (RAG) to converse with the user about the data in the collection."}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"embedding"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"boolean"})}),(0,s.jsxs)(n.td,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," to save embeddings for records in this collection. This only works if the ",(0,s.jsx)(n.a,{href:"#setembedding",children:(0,s.jsx)(n.code,{children:"custom.setEmbedding"})})," hook has been enabled."]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"chat.name"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The name for the chat bot."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"chat.defaultQueryLimit"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"number"})}),(0,s.jsx)(n.td,{children:"The number of records the LLM should retrieve for context."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"chat.roles"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string[]"})}),(0,s.jsx)(n.td,{children:"The roles that can view the chat bot."})]})]})]}),"\n",(0,s.jsx)(n.admonition,{type:"danger",children:(0,s.jsx)(n.p,{children:"Only assign AI chat access to roles that have access to ALL fields used to calculate embeddings!"})}),"\n",(0,s.jsx)(n.h2,{id:"admin-ui-config",children:"Admin UI Config"}),"\n",(0,s.jsxs)(n.p,{children:["Admin UI config is specified using the ",(0,s.jsx)(n.code,{children:"admin"})," property."]}),"\n",(0,s.jsx)(n.h3,{id:"hidden-1",children:"hidden"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean | (() => boolean | Promise<boolean>)"})}),"\n",(0,s.jsxs)(n.p,{children:["Return ",(0,s.jsx)(n.code,{children:"true"})," to hide the collection in the Admin UI."]}),"\n",(0,s.jsx)(n.h3,{id:"navbarposition",children:"navbarPosition"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"number | (() => number)"})}),"\n",(0,s.jsx)(n.p,{children:"The collection's position in the navbar."}),"\n",(0,s.jsx)(n.h3,{id:"titles",children:"titles"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'{ collection: string, record: string }
1 | \n(\n (\n context?: "permissions" | "search-all" | "relation-list", \n parentCollection?: CollectionSchema,\n parentRecord?: StokerRecord,\n ) => { collection: string; record: string } | Promise<{ collection: string; record: string }>\n)\n'})}),"\n",(0,s.jsxs)(n.p,{children:["Human-readable labels for the collection. Only necessary if the root ",(0,s.jsx)(n.code,{children:"labels"})," are not human-readable."]}),"\n",(0,s.jsx)(n.h3,{id:"icon-1",children:"icon"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"React.FC | (() => React.FC | Promise<React.FC>)"})}),"\n",(0,s.jsxs)(n.p,{children:["An icon component for the collection. We recommend using ",(0,s.jsx)(n.a,{href:"https://lucide.dev/icons/",children:"Lucide"})," icons, which are bundled with Stoker."]}),"\n",(0,s.jsx)(n.h3,{id:"itemsperpage",children:"itemsPerPage"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"number | (() => number | Promise<number>)"})}),"\n",(0,s.jsx)(n.p,{children:"The number of items to show per page."}),"\n",(0,s.jsx)(n.h3,{id:"statusfield",children:"statusField"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"{ field: string, active?: unknown[], archived?: unknown[] }"})}),"\n",(0,s.jsx)(n.p,{children:'Define a field that will be used to sort records into "Active" and "Archived" lists.'}),"\n",(0,s.jsxs)(n.p,{children:["For example: ",(0,s.jsx)(n.code,{children:'{ field: "Status", active: ["Not Started", "In Progress"], archived: ["Completed"]}'})]}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"field"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The field that defines the active / archived status."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"active"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"unknown[]"})}),(0,s.jsx)(n.td,{children:"Values considered active."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"archived"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"unknown[]"})}),(0,s.jsx)(n.td,{children:"Values considered archived."})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"breadcrumbs",children:"breadcrumbs"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"string[] | (() => string[] | Promise<string[]>)"})}),"\n",(0,s.jsx)(n.p,{children:"Return an array of relational field names that will be used to show breadcrumbs at the top of the record page."}),"\n",(0,s.jsx)(n.h3,{id:"duplicate",children:"duplicate"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean | (() => boolean | Promise<boolean>)"})}),"\n",(0,s.jsxs)(n.p,{children:["Return ",(0,s.jsx)(n.code,{children:"true"})," to show the ",(0,s.jsx)(n.code,{children:"Duplicate"})," button on the form page."]}),"\n",(0,s.jsx)(n.h3,{id:"convert",children:"convert"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"type Convert = {\n collection: string\n convert: (record: StokerRecord) => Partial<StokerRecord> | Promise<Partial<StokerRecord>>\n roles?: string[]\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"Convert[] | (() => Convert[] | Promise<Convert[]>)"})}),"\n",(0,s.jsx)(n.p,{children:"Converting a record creates a new record in the target collection and keeps the original."}),"\n",(0,s.jsx)(n.p,{children:'Return an array of objects defining which collections records can be converted to. A "Convert" button will be shown on the form page.'}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"collection"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The collection to convert records to."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"convert"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"(record: StokerRecord) => Partial<StokerRecord> | Promise<Partial<StokerRecord>>"})}),(0,s.jsx)(n.td,{children:"A function that modifies the record before conversion."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"roles"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string[]"})}),(0,s.jsx)(n.td,{children:"The roles that can perform this conversion."})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"defaultview",children:"defaultView"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:'"list" | "cards" | "images" | "map" | "calendar" | (() => "list" | "cards" | "images" | "map" | "calendar")'})}),"\n",(0,s.jsx)(n.p,{children:"The default view for the collection."}),"\n",(0,s.jsx)(n.h3,{id:"defaultroute",children:"defaultRoute"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"string | (() => string)"})}),"\n",(0,s.jsxs)(n.p,{children:['The default route for the record page. Can be "edit", "files", a relation list collection name or a ',(0,s.jsx)(n.a,{href:"#customrecordpages",children:"custom form page url"}),"."]}),"\n",(0,s.jsx)(n.h3,{id:"defaultsort",children:"defaultSort"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'{ field: string, direction?: "asc" | "desc" }
1 | \n(\n () => { field: string, direction?: "asc" | "desc" } |\n Promise<{ field: string, direction?: "asc" | "desc" }>\n)\n'})}),"\n",(0,s.jsx)(n.p,{children:"The default field to sort the list by."}),"\n",(0,s.jsx)(n.h3,{id:"secondarysort",children:"secondarySort"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'{ field: string, direction?: "asc" | "desc" } | \n(\n () => { field: string, direction?: "asc" | "desc" } |\n Promise<{ field: string, direction?: "asc" | "desc" }>\n)\n'})}),"\n",(0,s.jsxs)(n.p,{children:["The secondary field to sort the list by. Only works for collections with ",(0,s.jsx)(n.code,{children:"preloadCache"})," or ",(0,s.jsx)(n.code,{children:"access.serverReadOnly"})," enabled."]}),"\n",(0,s.jsx)(n.h3,{id:"searchoptions",children:"searchOptions"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"SearchOptions | (() => SearchOptions)"})}),"\n",(0,s.jsxs)(n.p,{children:["Full text search options. For roles with the ",(0,s.jsx)(n.a,{href:"#preload-cache-config",children:"preload cache"})," or ",(0,s.jsx)(n.code,{children:"access.serverReadOnly"})," enabled, provide ",(0,s.jsx)(n.a,{href:"https://lucaong.github.io/minisearch/types/MiniSearch.SearchOptions.html",children:"MiniSeach settings"}),". For other roles, provide ",(0,s.jsx)(n.code,{children:"{ hitsPerPage?: number }"})," to specify the maximum number of results to retrieve from Algolia."]}),"\n",(0,s.jsx)(n.h3,{id:"rangeselectorvalues",children:"rangeSelectorValues"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:'"range" | "week" | "month" | ("range" | "week" | "month")[] | (() => "range" | "week" | "month" | ("range" | "week" | "month")[])'})}),"\n",(0,s.jsx)(n.p,{children:"The date range selector options to be shown to the user."}),"\n",(0,s.jsxs)(n.p,{children:["Only relevant when ",(0,s.jsx)(n.a,{href:"#range",children:(0,s.jsx)(n.code,{children:"preloadCache.range"})})," is present."]}),"\n",(0,s.jsx)(n.h3,{id:"defaultrangeselector",children:"defaultRangeSelector"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:'"range" | "week" | "month" | (() => "range" | "week" | "month")'})}),"\n",(0,s.jsx)(n.p,{children:"The default date range selector to be shown to the user."}),"\n",(0,s.jsxs)(n.p,{children:["Only relevant when ",(0,s.jsx)(n.a,{href:"#range",children:(0,s.jsx)(n.code,{children:"preloadCache.range"})})," is present or a ",(0,s.jsx)(n.a,{href:"#filters",children:"range filter"})," has been applied."]}),"\n",(0,s.jsx)(n.h3,{id:"disablerangeselector",children:"disableRangeSelector"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean | (() => boolean)"})}),"\n",(0,s.jsx)(n.p,{children:"Disable the date range selector for the user."}),"\n",(0,s.jsx)(n.h3,{id:"restrictexport",children:"restrictExport"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"string[] | (() => string[] | Promise<string[]>)"})}),"\n",(0,s.jsx)(n.p,{children:"Restrict CSV export to the defined roles."}),"\n",(0,s.jsx)(n.h3,{id:"customlistactions",children:"customListActions"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"type CustomListAction = {\n title: string\n icon?: React.FC<{ className?: string }>\n action: () => void | Promise<void>\n condition?: () => boolean\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"CustomListAction[] | (() => CustomListAction[] | Promise<CustomListAction[]>)"})}),"\n",(0,s.jsx)(n.p,{children:"Provide custom actions that will be shown in a dropdown menu on the list page."}),"\n",(0,s.jsx)(n.h3,{id:"addrecordbuttonoverride",children:"addRecordButtonOverride"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"(record?: StokerRecord) => void | Promise<void>"})}),"\n",(0,s.jsx)(n.p,{children:"Provide a function that will override the default behaviour of opening the add record form."}),"\n",(0,s.jsx)(n.h3,{id:"hidecreate",children:"hideCreate"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean | ((relationList?: string) => boolean | Promise<boolean>)"})}),"\n",(0,s.jsx)(n.p,{children:"Hide the add record button."}),"\n",(0,s.jsx)(n.h3,{id:"disableupdate",children:"disableUpdate"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:'boolean | ((operation: "create" | "update", record: StokerRecord) => boolean | Promise<boolean>)'})}),"\n",(0,s.jsx)(n.p,{children:"Disable the edit record form."}),"\n",(0,s.jsx)(n.admonition,{type:"warning",children:(0,s.jsxs)(n.p,{children:["This option only disables editing client side. Do not use it if you need to securely block record updates. Use ",(0,s.jsx)(n.a,{href:"#restrictupdate",children:(0,s.jsx)(n.code,{children:"restrictUpdate"})})," or ",(0,s.jsx)(n.a,{href:"#operations",children:(0,s.jsx)(n.code,{children:"operations"})})," instead."]})}),"\n",(0,s.jsx)(n.h3,{id:"onformopen",children:"onFormOpen"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'(\n operation: "create" | "update",\n record: StokerRecord,\n parentCollection?: string,\n parentRecord?: StokerRecord\n) => void | Promise<void>\n'})}),"\n",(0,s.jsx)(n.p,{children:"A hook that fires when the record form is opened."}),"\n",(0,s.jsx)(n.p,{children:'When the "create" form is opened from within another record\'s relation list, the parent collection and parent record are provided.'}),"\n",(0,s.jsx)(n.h3,{id:"onchange",children:"onChange"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'(\n operation: "create" | "update",\n record: StokerRecord,\n previous: StokerRecord | undefined,\n) => Partial<StokerRecord> | void | Promise<Partial<StokerRecord> | void>\n'})}),"\n",(0,s.jsx)(n.p,{children:"A hook that fires whenever the form is updated. Optionally return an object with field updates."}),"\n",(0,s.jsx)(n.h3,{id:"live-1",children:"live"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean | (() => boolean | Promise<boolean>)"})}),"\n",(0,s.jsxs)(n.p,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"}
1)," to have the form page live-update when the record is updated remotely. This can also be configured at the ",(0,s.jsx)(n.a,{href:"#live",children:"field-level"}),"."]}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsx)(n.p,{children:"Live forms reduce the risk of conflicts and are great for collaboration. However, on a live form remote updates will overwrite local changes. This introduces a risk of data loss. Choose wisely!"})}),"\n",(0,s.jsx)(n.h3,{id:"meta",children:"meta"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"{ title?: string, description?: string } |\n(() => { title?: string, description?: string } | Promise<{ title?: string, description?: string }>)\n"})}),"\n",(0,s.jsx)(n.p,{children:"Define a custom meta title and description for the collection's pages."}),"\n",(0,s.jsx)(n.h3,{id:"rowhighlight",children:"rowHighlight"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"type RowHighlight = {\n condition: (record: StokerRecord) => boolean\n className: string\n roles?: string[]\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"RowHighlight[] | (() => RowHighlight[])"})}),"\n",(0,s.jsx)(n.p,{children:"Highlight rows in the list view."}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"condition"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"(record: StokerRecord) => boolean"})}),(0,s.jsxs)(n.td,{children:["Return ",(0,s.jsx)(n.code,{children:"true"})," to highlight the row for the given record."]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"className"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The Tailwind classes to apply to the highlighted row."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"roles"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string[]"})}),(0,s.jsx)(n.td,{children:"The user roles to highlight rows for."})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"formbuttons",children:"formButtons"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'type FormButton = {\n title: string\n icon?: React.FC<{ className?: string }>\n variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link"\n action: (\n operation: "create" | "update" | "update-many",\n formValues: StokerRecord,\n originalRecord?: StokerRecord,\n ) => void | Promise<void>\n condition?: boolean | ((operation: "create" | "update" | "update-many", record?: StokerRecord) => boolean)\n setIsLoading?: (isLoading: boolean) => void\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"FormButton[] | (() => FormButton[] | Promise<FormButton[]>)"})}),"\n",(0,s.jsx)(n.p,{children:"Show custom buttons at the bottom of the edit record form."}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"title"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The title text for the custom button."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"icon"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"React.FC<{ className?: string }>"})}),(0,s.jsx)(n.td,{children:"The icon shown on the button."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"variant"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:'"default" | "destructive" | "outline" | "secondary" | "ghost" | "link"'})}),(0,s.jsx)(n.td,{children:"The style of the button."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"action"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"(...) => void | Promise<void>"})}),(0,s.jsx)(n.td,{children:"The function that fires when the button is clicked."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"condition"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"boolean | ((operation: ..., record?: StokerRecord) => boolean)"})}),(0,s.jsx)(n.td,{children:"Show or hide the button."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"setIsLoading"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"(isLoading: boolean) => void"})}),(0,s.jsx)(n.td,{children:"A loading callback that will be called when the button is pressed."})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"formupload",children:"formUpload"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean | (() => boolean | Promise<boolean>)"})}),"\n",(0,s.jsx)(n.p,{children:"Show a file upload button on the add record form."}),"\n",(0,s.jsx)(n.h3,{id:"formimages",children:"formImages"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean | (() => boolean | Promise<boolean>)"})}),"\n",(0,s.jsx)(n.p,{children:"Show an image carousel at the top of the edit record form. All image files uploaded to the record will be displayed."}),"\n",(0,s.jsx)(n.h3,{id:"formlists",children:"formLists"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'type FormList {\n collection: string\n fields: string[]\n sortField?: string\n sortDirection?: "asc" | "desc"\n label?: string\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"FormList[] | (() => FormList[] | Promise<FormList[]>)"})}),"\n",(0,s.jsxs)(n.p,{children:["Show ",(0,s.jsx)(n.a,{href:"#relationlists",children:"relation lists"})," directly on the edit record form page."]}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"collection"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The collection to show the relation list for."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"fields"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string[]"})}),(0,s.jsx)(n.td,{children:"Which columns to show in the list."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"sortField"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The field to sort records by."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"sortDirection"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:'"asc" | "desc"'})}),(0,s.jsx)(n.td,{children:"The direction to sort records by."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"label"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The title for the relation list."})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"fileoptions",children:"fileOptions"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"type FileOptions {\n maxImageWidth?: number\n thumbnails: boolean\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"FileOptions[] | (() => FileOptions[] | Promise<FileOptions[]>)"})}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"maxImageWidth"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"number"})}),(0,s.jsx)(n.td,{children:"The maximum width for uploaded image files. Images above this size will be downscaled."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"thumbnails"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"boolean"})}),(0,s.jsxs)(n.td,{children:["Set to ",(0,s.jsx)(n.code,{children:"true"})," to show thumbnails for images in the files list."]})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"customfields",children:"customFields"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'type CustomField = {\n position?: number | ((record?: StokerRecord) => number)\n component?: React.FC\n props?: Record<string, unknown>\n condition?: (operation: "create" | "update" | "update-many", record?: StokerRecord) => boolean\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"CustomField[] | (() => CustomField[] | Promise<CustomField[]>)"})}),"\n",(0,s.jsx)(n.p,{children:"Create custom form field components."}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"position"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"number | ((record?: StokerRecord) => number)"})}),(0,s.jsx)(n.td,{children:"The position of the custom component in the form."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"component"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"React.FC"})}),(0,s.jsx)(n.td,{children:"The React component."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"props"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"Record<string, unknown>"})}),(0,s.jsx)(n.td,{children:"Props to pass to the React component."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"condition"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:'(operation: "create" | "update" | "update-many", record?: StokerRecord) => boolean'})}),(0,s.jsx)(n.td,{children:"Show or hide the custom component."})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"customrecordpages",children:"customRecordPages"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"type CustomRecordPage = {\n title: string\n icon?: React.FC<{ className?: string }>\n url: string\n component: React.FC<{\n record: StokerRecord | undefined\n collection: CollectionSchema\n components: any\n hooks: any\n utils: any\n }>\n props?: Record<string, unknown>\n condition?: (record: StokerRecord | undefined) => boolean\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"CustomRecordPage[] | (() => CustomRecordPage[] | Promise<CustomRecordPage[]>)"})}),"\n",(0,s.jsx)(n.p,{children:"Create custom pages for the collection."}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"title"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The title for the custom page in the sidebar."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"icon"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"React.FC<{ className?: string }>"})}),(0,s.jsx)(n.td,{children:"The icon shown in the sidebar."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"url"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The URL segment that the page will load on."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"component"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"React.FC<...>"})}),(0,s.jsx)(n.td,{children:"The custom component."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"props"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"Record<string, unknown>"})}),(0,s.jsx)(n.td,{children:"Props to pass to the custom component."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"condition"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"(record: StokerRecord | undefined) => boolean"})}),(0,s.jsx)(n.td,{children:"Show or hide the custom page."})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"retriever",children:"retriever"}
1),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"() => any | Promise<any>"})}),"\n",(0,s.jsxs)(n.p,{children:["Use this method to load data for use in your ",(0,s.jsx)(n.a,{href:"#computed-field-properties",children:"computed fields"}),"."]}),"\n",(0,s.jsx)(n.h3,{id:"list",children:"list"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"List",src:i(9231).A+"",width:"2444",height:"1588"})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"List",src:i(4319).A+"",width:"2442",height:"1586"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"{ roles?: string[], title?: string } | (() => { title?: string } | Promise<{ title?: string }>)\n"})}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"title"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsxs)(n.td,{children:["Customise the title for the list tab. Defaults to ",(0,s.jsx)(n.code,{children:'"List"'}),"."]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"roles"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string[]"})}),(0,s.jsx)(n.td,{children:"Limit which user roles can view the list."})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"cards",children:"cards"}),"\n",(0,s.jsx)(n.p,{children:"Show a board view with drag and drop and infinite scroll."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Board",src:i(5917).A+"",width:"2440",height:"1550"})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Board",src:i(1685).A+"",width:"2452",height:"1584"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'type CardsConfig = {\n title?: string\n roles?: string[]\n statusField?: string\n excludeValues?: string[] | number[]\n headerField: string\n maxHeaderLines?: 1 | 2\n sections: {\n title?: string\n fields: string[]\n blocks?: boolean\n large?: boolean\n maxSectionLines?: 1 | 2 | 3 | 4\n collapse?: "sm" | "md" | "lg" | "xl" | "2xl" | ((record?: StokerRecord) => "sm" | "md" | "lg" | "xl" | "2xl")\n }[]\n footerField?: string\n maxFooterLines?: 1 | 2\n cardClass?: string\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"CardsConfig | (() => CardsConfig | Promise<CardsConfig>)"})}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"title"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsxs)(n.td,{children:["Customise the title for the board tab. Defaults to ",(0,s.jsx)(n.code,{children:'"Board"'}),"."]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"roles"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string[]"})}),(0,s.jsx)(n.td,{children:"Limit which user roles can view the board."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"statusField"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsxs)(n.td,{children:["The field that defines the board columns. Must be a String or Number field with ",(0,s.jsx)(n.code,{children:"values"}),", or a Boolean field. Not required if ",(0,s.jsx)(n.a,{href:"#statusfield",children:(0,s.jsx)(n.code,{children:"admin.statusField"})})," has already been set."]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"excludeValues"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string[] | number[]"})}),(0,s.jsx)(n.td,{children:"Exclude status values from the board."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"headerField"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The header field shown on cards."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"maxHeaderLines"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"1 | 2"})}),(0,s.jsx)(n.td,{children:"The number of lines for the header field text."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"sections"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"object[]"})}),(0,s.jsx)(n.td,{children:"Sections to display on cards."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"footerField"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The footer field shown on cards."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"maxFooterLines"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"1 | 2"})}),(0,s.jsx)(n.td,{children:"The number of lines for the footer field text."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"cardClass"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"Tailwind classes to apply to the card component."})]})]})]}),"\n",(0,s.jsxs)(n.p,{children:["For ",(0,s.jsx)(n.code,{children:"sections"}),", each entry supports:"]}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"title"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The title for the section."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"fields"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string[]"})}),(0,s.jsx)(n.td,{children:"The fields to display in the section."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"blocks"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"boolean"})}),(0,s.jsx)(n.td,{children:"Show multiple columns of fields, rather than listing fields down the card vertically."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"large"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"boolean"})}),(0,s.jsx)(n.td,{children:"Show a large field value."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"maxSectionLine
1s"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"1 | 2 | 3 | 4"})}),(0,s.jsx)(n.td,{children:"The number of lines for field text."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"collapse"})}),(0,s.jsx)(n.td,{children:"responsive size or callback"}),(0,s.jsxs)(n.td,{children:["Only relevant when ",(0,s.jsx)(n.code,{children:"blocks"})," is set to ",(0,s.jsx)(n.code,{children:"true"}),". Hide the outermost block at this screen size. Helps with responsiveness."]})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"images",children:"images"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'type ImagesConfig = {\n title?: string\n roles?: string[]\n imageField: string\n size: "sm" | "md" | "lg" | "xl"\n maxHeaderLines?: 1 | 2\n customComponent?: {\n component: React.FC<{\n record: StokerRecord | undefined\n parentRecord?: StokerRecord\n collection: CollectionSchema\n parentCollection?: CollectionSchema\n components: any\n hooks: any\n utils: any\n }>\n height: number\n condition?: (parentCollection?: CollectionSchema, parentRecord?: StokerRecord, isAssigning?: boolean) => boolean\n }\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"ImagesConfig | (() => ImagesConfig | Promise<ImagesConfig>)"})}),"\n",(0,s.jsx)(n.p,{children:"Show a list of image cards with infinite scroll."}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"title"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsxs)(n.td,{children:["Customise the title for the images tab. Defaults to ",(0,s.jsx)(n.code,{children:'"Pics"'}),"."]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"roles"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string[]"})}),(0,s.jsx)(n.td,{children:"Limit which user roles can view the images page."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"imageField"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The field that contains the image URL for the record. Must be a String field."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"size"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:'"sm" | "md" | "lg" | "xl"'})}),(0,s.jsx)(n.td,{children:"The image size."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"maxHeaderLines"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"1 | 2"})}),(0,s.jsx)(n.td,{children:"The number of lines for the header field text."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"customComponent"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"object"})}),(0,s.jsx)(n.td,{children:"An optional custom component shown above each image."})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"map",children:"map"}),"\n",(0,s.jsx)(n.p,{children:"Show a map view."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Map",src:i(2748).A+"",width:"2456",height:"1588"})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Map",src:i(8623).A+"",width:"2454",height:"1590"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"type MapConfig = {\n title?: string\n roles?: string[]\n coordinatesField?: string\n addressField?: string\n center: {\n lat: number\n lng: number\n }\n zoom: number\n noLocation?: {\n title: string\n }\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"MapConfig | (() => MapConfig | Promise<MapConfig>)"})}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"title"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsxs)(n.td,{children:["Customise the title for the map tab. Defaults to ",(0,s.jsx)(n.code,{children:'"Map"'}),"."]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"roles"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string[]"})}),(0,s.jsx)(n.td,{children:"Limit which user roles can view the map page."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"coordinatesField"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The field containing coordinates. Must be an Array field."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"addressField"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"Alternatively, provide a String field containing an address."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"center"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"{ lat: number, lng: number }"})}),(0,s.jsx)(n.td,{children:"The starting coordinates for the map."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"zoom"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"number"})}),(0,s.jsx)(n.td,{children:"The starting zoom value for the map."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"noLocation"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"{ title: string }"})}),(0,s.jsx)(n.td,{children:"Show a column of records without coordinates or an address. Records can be dragged onto the map (only when coordinatesField is provided)."})]})]})]}),"\n",(0,s.jsx)(n.h3,{id:"calendar",children:"calendar"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"type CalendarConfig = {\n title?: string\n roles?: string[]\n startField: string\n endField?: string\n additionalFields?: string[]\n allDayField?: string\n eventTitle?: (record: StokerRecord) => string\n color?: string | ((record: StokerRecord) => string)\n filterRecords?: (record: StokerRecord) => boolean\n fullCalendarLarge?: CalendarOptions\n fullCalendarSmall?: CalendarOptions\n resourceField?: string\n resourceTitleField?: string\n unscheduled?: {\n title: string\n roles?: string[]\n }\n additionalCollections: string[]\n dataStart?: { days: number } | { weeks: number } | { months: number } | { years: number }\n dataEnd?: { days: number } | { weeks: number } | { months: number } | { years: number }\n dataStartOffset?: { days: number } | { weeks: number } | { months: number } | { years: number }\n dataEndOffset?: { days: number } | { weeks: number } | { months: number } | { years: number }\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"CalendarConfig | (() => CalendarConfig | Promise<CalendarConfig>)"})}),"\n",(0,s.jsx)(n.p,{children:"Show a calendar view."}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"title"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsxs)(n.td,{children:["Customise the title for the calendar tab. Defaults to ",(0,s.jsx)(n.code,{children:'"Calendar"'}),"."]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"roles"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string[]"})}),(0,s.jsx)(n.td,{children:"Limit which user roles can view the calendar page."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"startField"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"Timestamp field specifying the start date."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"endField"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"Timestamp field specifying the end date. If omitted, the start date is used."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"additionalFields"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string[]"})}),(0,s.jsx)(n.td,{children:"Additional Timestamp fields to include as all-day events."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"allDayField"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"Boolean field indicating whether records are all-day."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"eventTitle"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"(record: StokerRecord) => string"})}),(0,s.jsx)(n.td,{children:"A custom title for the provided record's event on the calendar."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"color"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string | ((record: StokerRecord) => string)"})}),(0,s.jsx)(n.td,{children:"The color for the provided record's event on the calendar."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"filterRecords"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"(record: StokerRecord) => boolean"})}),(0,s.jsx)(n.td,{children:"An optional function that determines whether a record should be displayed on the calendar. This filter only runs in the client, so it should not be used for access control purposes."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"fullCalendarLarge"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"CalendarOptions"})}),(0,s.jsxs)(n.td,{children:[(0,s.jsx)(n.a,{href:"https://fullcalendar.io/docs",children:"Fullcalendar"})," options for desktop screen sizes."]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"fullCalendarSmall"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"CalendarOptions"})}),(0,s.jsxs)(n.td,{children:[(0,s.jsx)(n.a,{href:"https://fullcalendar.io/docs",children:"Fullcalendar"})," options for mobile screen sizes."]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"resourceField"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:'Relational field specifying a parent resource. Used for Fullcalendar features that require "resources".'})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"resourceTitleField"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsxs)(n.td,{children:["Field in the ",(0,s.jsx)(n.code,{children:"resourceField"})," collection that acts as the resource title."]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"unscheduled"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"object"})}),(0,s.jsxs)(n.td,{children:["Show a column of unscheduled records. Only relevant if ",(0,s.jsx)(n.a,{href:"#range",children:(0,s.jsx)(n.code,{children:"preloadCache.range"})})," is present for the user's role. Records can be dragged onto the calendar."]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"additionalCollections"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string[]"})}),(0,s.jsx)(n.td,{children:"Additional collections to show on the calendar. Additional collections will only work when the preload cache is enabled for the user's role for the given collection."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"dataStart"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"object"})}),(0,s.jsx)(n.td,{children:"How far into the past to load records for."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"dataEnd"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"object"})}),(0,s.jsx)(n.td,{children:"How far into the future to load records for."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"dataStartOffset"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"object"})}),(0,s.jsx)(n.td,{children:"Threshold at which more past records will be loaded."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"dataEndOffset"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"object"})}),(0,s.jsx)(n.td,{children:"Threshold at which more future records will be loaded."})]})]})]}),"\n",(0,s.jsx)(n.admonition,{type:"tip",children:(0,s.jsxs)(n.p,{children:["When both the ",(0,s.jsx)(n.a,{href:"#preload-cache-config",children:"preload cache"})," range and the calendar are enabled, the system will calculate the maximum possible date range from the start and end dates for each. It will then load data for the calculated date range. You may want to keep the initial date ranges for each small, in order to reduce the amount of data initially loaded."]})}),"\n",(0,s.jsxs)(n.admonition,{type:"note",children:[(0,s.jsxs)(n.p,{children:["These limitations apply if you are NOT using the ",(0,s.jsx)(n.a,{href:"#preload-cache-config",children:"preload cache"}),":"]}),(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Records will appear on the calendar based on their start date only."}),"\n",(0,s.jsx)(n.li,{children:"The calendar start date field must be a required field."}),"\n",(0,s.jsx)(n.li,{children:"If you use a range filter, its f
1ield must match the calendar start date field."}),"\n"]})]}),"\n",(0,s.jsx)(n.admonition,{type:"warning",children:(0,s.jsxs)(n.p,{children:["The calendar requires a ",(0,s.jsx)(n.a,{href:"https://fullcalendar.io/license",children:"Fullcalendar"})," license"]})}),"\n",(0,s.jsx)(n.h3,{id:"filters",children:"filters"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'type SelectFilter = {\n type: "select"\n field: string\n title?: string | (() => string)\n roles?: string[]\n style?: "select" | "radio" | "buttons"\n titles?: (\n value: string,\n relationCollection?: CollectionSchema,\n relationParent?: StokerRecord,\n isAssigning?: boolean,\n ) => string\n filterValues?: (\n value: boolean | string | number | undefined,\n relationCollection?: CollectionSchema,\n relationParent?: StokerRecord,\n isAssigning?: boolean,\n ) => boolean\n defaultValue?: string | number | ((\n parentCollection: CollectionSchema,\n parentRecord?: StokerRecord,\n isAssigning?: boolean,\n ) => string | number)\n condition?: (\n parentCollection?: CollectionSchema,\n parentRecord?: StokerRecord,\n isAssigning?: boolean,\n ) => boolean\n}\n\ntype RelationFilter = {\n type: "relation"\n field: string\n title?: string | (() => string)\n roles?: string[]\n constraints?: [string, "==" | "in", unknown][]\n}\n\ntype RangeFilter = {\n type: "range"\n field: string\n selector?:\n | "range"\n | "week"\n | "month"\n | ("range" | "week" | "month")[]\n | (() => "range" | "week" | "month" | ("range" | "week" | "month")[])\n startOffsetDays?: number\n startOffsetHours?: number\n endOffsetDays?: number\n endOffsetHours?: number\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"Filter[]"})}),"\n",(0,s.jsx)(n.p,{children:"Return an array of filter objects (specified above). Filters will appear in the right-hand-side filter drawer on the list page."}),"\n",(0,s.jsxs)(n.p,{children:['"Select filters" allow you to filter the list by a field with ',(0,s.jsx)(n.code,{children:"values"})," set."]}),"\n",(0,s.jsxs)(n.p,{children:['"Relation filters" allow you to filter by a related field. The "constraints" option lets you filter the list of related values using a ',(0,s.jsx)(n.a,{href:"https://firebase.google.com/docs/firestore/query-data/queries#simple_queries",children:"Firestore where()"})," query."]}),"\n",(0,s.jsxs)(n.p,{children:['"Range filters" allow you to filter by a Timestamp field. See ',(0,s.jsx)(n.a,{href:"#range",children:(0,s.jsx)(n.code,{children:"preloadCache.range"})})," for more information on what the options do."]}),"\n",(0,s.jsx)(n.h3,{id:"titlecount",children:"titleCount"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"boolean | (() => boolean | Promise<boolean>)"})}),"\n",(0,s.jsx)(n.p,{children:"Display a counter in the title bar showing the number of items in the list."}),"\n",(0,s.jsx)(n.h3,{id:"metrics",children:"metrics"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'type Metric = {\n type: "sum" | "average" | "count" | "custom"\n field?: string\n title?: string\n roles?: string[]\n decimal?: number\n prefix?: string\n suffix?: string\n textSize?: "text-xs" | "text-sm" | "text-base" | "text-lg" | "text-xl" | "text-2xl" | "text-3xl"\n compact?: boolean\n formula?: (records: StokerRecord[]) => number | string\n}\n\ntype Chart = {\n type: "area"\n dateField: string\n metricField1?: string\n metricField2?: string\n title?: string\n roles?: string[]\n defaultRange: "90d" | "30d" | "7d"\n currency?: string | (() => string)\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"(Metric | Chart)[] | (() => (Metric | Chart)[] | Promise<(Metric | Chart)[]>)"})}),"\n",(0,s.jsx)(n.p,{children:"Show metrics (numerical counters) and a chart at the top of the list page."}),"\n",(0,s.jsx)(n.p,{children:"We recommend 1-2 metrics and a chart."}),"\n",(0,s.jsx)(n.p,{children:'For "custom" metrics, use the formula method to calculate the value to display.'}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"Metric"})," supports:"]}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"type"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:'"sum" | "average" | "count" | "custom"'})}),(0,s.jsx)(n.td,{children:"The metric type."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"field"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsxs)(n.td,{children:["The field to aggregate. Not required for ",(0,s.jsx)(n.code,{children:"count"})," or ",(0,s.jsx)(n.code,{children:"custom"}),"."]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"title"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The title shown above the metric."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"roles"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string[]"})}),(0,s.jsx)(n.td,{children:"Limit which user roles can view the metric."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"decimal"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"number"})}),(0,s.jsx)(n.td,{children:"Maximum decimal places to display."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"prefix"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"Prefix text, for example a currency symbol."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"suffix"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"Suffix text, for example units."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"textSize"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:'"text-xs" | "text-sm" | "text-base" | "text-lg" | "text-xl" | "text-2xl" | "text-3xl"'})}),(0,s.jsx)(n.td,{children:"Tailwind text size for the metric value."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"compact"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"boolean"})}),(0,s.jsx)(n.td,{children:"Compact the metric vertically. The metric will be displayed as a single-line card showing the title and value together."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"formula"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"(records: StokerRecord[]) => number | string"})}),(0,s.jsx)(n.td,{children:"Custom metric calculation."})]})]})]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"Chart"})," supports:"]}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"type"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:'"area"'})}),(0,s.jsx)(n.td,{children:"The chart type."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"dateField"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"The date field used to group points."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"metricField1"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"First metric field."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"metricField2"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"Optional second metric field."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"title"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"Title shown above the chart."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"roles"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string[]"})}),(0,s.jsx)(n.td,{children:"Limit which user roles can view the chart."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"defaultRange"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:'"90d" | "30d" | "7d"'})}),(0,s.jsx)(n.td,{children:"Default chart date range."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"currency"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string | (() => string)"})}),(0,s.jsx)(n.td,{children:"Currency symbol to display."})]})]})]}),"\n",(0,s.jsx)(n.h2,{id:"collection-hooks",children:"Collection Hooks"}),"\n",(0,s.jsxs)(n.admonition,{type:"info",children:[(0,s.jsx)(n.p,{children:"When importing modules that are web-only or server-only, be sure to:"}),(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["Install them using ",(0,s.jsx)(n.a,{href:"Other%20Project%20Files",children:(0,s.jsx)(n.code,{children:"external.packages.json"})})]}),"\n",(0,s.jsxs)(n.li,{children:["Guard their use by wrapping code in:","\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'if(sdk === "web") { \n ...your web code goes here...\n}\nif(sdk === "node") { \n ...your server code goes here...\n}\n'})}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["Import them dynamically i.e. ",(0,s.jsx)(n.code,{children:'await import("firebase/firestore")'})]}),"\n"]}),(0,s.jsx)(n.p,{children:"This lets you write web and server code in the same file."}),(0,s.jsxs)(n.p,{children:["You can also write your own modules in the ",(0,s.jsx)(n.code,{children:"src/web"})," and ",(0,s.jsx)(n.code,{children:"src/node"})," folders and import modules statically there. In this case, you can only use one import per line i.e. ",(0,s.jsx)(n.code,{children:'import { getAuth } from "firebase/auth"'})]})]}),"\n",(0,s.jsxs)(n.admonition,{type:"danger",children:[(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Server code protection:"})," During the build process, server-only code blocks are automatically stripped from the client bundle:"]}),(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:'if(sdk === "node")'})," blocks are removed"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"else"})," and ",(0,s.jsx)(n.code,{children:"else if"})," blocks following ",(0,s.jsx)(n.code,{children:'if (sdk === "web")'})," guards are removed"]}),"\n",(0,s.jsxs)(n.li,{children:["Code in ",(0,s.jsx)(n.code,{children:"src/node"})," is stripped to export names only"]}),"\n"]}),(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Important:"})," Code outside of these guard blocks WILL be sent to the client and will run on the client."]}),(0,s.jsxs)(n.p,{children:["For highly sensitive server operations, consider using ",(0,s.jsx)(n.a,{href:"Other%20Project%20Files",children:"custom cloud functions"})," instead."]})]}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsx)(n.p,{children:'Auto-incremented numbers will be "Pending" in hooks.'})}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.p,{children:["In the Web SDK, postWrite, postError and postOperation hooks will not fire if the window is closed before the operation completes. Consider using a ",(0,s.jsx)(n.a,{href:"https://firebase.google.com/docs/functions/firestore-events",children:"cloud trigger function"})," instead."]})}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Hooks will not run on retries for offline writes and ",(0,s.jsx)(n.a,{href:"#customautocorrectunique",children:"unique field auto-correction"}),"."]}),"\n"]})}),"\n",(0,s.jsx)(n.h3,{id:"preoperation",children:"preOperation"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'({\n operation: "read" | "create" | "update" | "delete"\n data?: StokerRecord\n recordId?: string\n context?: any\n batch?: WriteBatch\n originalRecord?: StokerRecord\n}) => boolean | void | Promise<boolean | void>\n'})}),"\n",(0,s.jsx)(n.p,{children:"Fires before a read or write operation."}),"\n",(0,s.jsxs)(n.p,{children:["Return ",(0,s.jsx)(n.code,{children:"false"})," to cancel the operation."]}),"\n",(0,s.jsx)(n.h3,{id:"preread",children:"preRead"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"({\n context: any\n refs: unknown[]\n multiple?: boolean\n listener?: boolean\n}) => void | Promise<void>\n"})}),"\n",(0,s.jsx)(n.p,{children:"Fires before a read operation."}),"\n",(0,s.jsx)(n.h3,{id:"prevalidate",children:"preValidate"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'({\n operation: "create" | "update"\n data: StokerRecord\n context: any\n batch?: WriteBatch\n originalRecord?: StokerRecord\n}) => { valid: boolean; message?: string } | Promise<{ valid: boolean; message?: string }>\n'})}),"\n",(0,s.jsx)(n.p,{children:"Fires at write validation time. This is where you can define custom validation logic."}),"\n",(0,s.jsx)(n.p,{children:"Return an object with a boolean indicating whether validation passed, and a message to display to the user if val
1idation has failed."}),"\n",(0,s.jsxs)(n.p,{children:["When ",(0,s.jsx)(n.a,{href:"#serverwriteonly",children:"serverWriteOnly"})," is set to true, this hook will fire in the Web SDK before the write is handed off to the Node SDK. This lets you run web code such as warning dialogs before the write is sent to the server. Be sure to guard web code in ",(0,s.jsx)(n.a,{href:"/docs/api-reference/Application%20State#sdk",children:(0,s.jsx)(n.code,{children:'if(sdk === "web")'})})," to ensure it doesn't run on the server."]}),"\n",(0,s.jsx)(n.h3,{id:"prewrite",children:"preWrite"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'({\n operation: "create" | "update" | "delete"\n data: StokerRecord\n recordId: string\n context: any\n batch?: WriteBatch\n originalRecord?: StokerRecord\n}) => boolean | void | Promise<boolean | void>\n'})}),"\n",(0,s.jsx)(n.p,{children:"Fires before a write operation."}),"\n",(0,s.jsxs)(n.p,{children:["Return ",(0,s.jsx)(n.code,{children:"false"})," to cancel the operation."]}),"\n",(0,s.jsx)(n.h3,{id:"preduplicate",children:"preDuplicate"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"({ data: Partial<StokerRecord> }) => boolean | void | Promise<boolean | void>\n"})}),"\n",(0,s.jsx)(n.p,{children:"Fires before a duplicate operation in the Admin UI."}),"\n",(0,s.jsxs)(n.p,{children:["Return ",(0,s.jsx)(n.code,{children:"false"})," to cancel the operation."]}),"\n",(0,s.jsx)(n.h3,{id:"postoperation",children:"postOperation"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'({\n operation: "read" | "create" | "update" | "delete"\n data?: StokerRecord\n recordId?: string\n context?: any\n retry?: boolean\n originalRecord?: StokerRecord\n}) => void | Promise<void>\n'})}),"\n",(0,s.jsx)(n.p,{children:"Fires after a read or write operation."}),"\n",(0,s.jsx)(n.h3,{id:"postread",children:"postRead"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"({\n context: any\n refs: unknown[]\n record: StokerRecord | undefined\n listener?: boolean\n}) => void | Promise<void>\n"})}),"\n",(0,s.jsx)(n.p,{children:"Fires after a read operation."}),"\n",(0,s.jsx)(n.h3,{id:"postwrite",children:"postWrite"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'({\n operation: "create" | "update" | "delete"\n data: StokerRecord\n recordId: string\n context: any\n retry?: boolean\n originalRecord?: StokerRecord\n}) => void | Promise<void>\n'})}),"\n",(0,s.jsx)(n.p,{children:"Fires after a write operation."}),"\n",(0,s.jsx)(n.h3,{id:"postwriteerror",children:"postWriteError"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'({\n operation: "create" | "update" | "delete"\n data: StokerRecord\n recordId: string\n context: any\n error: unknown\n batch?: WriteBatch\n retry?: boolean\n retries?: number\n originalRecord?: StokerRecord\n}) => void | Promise<void>\n'})}),"\n",(0,s.jsx)(n.p,{children:"Fires when a write operation encounters an error."}),"\n",(0,s.jsx)(n.p,{children:"This hook may fire multiple times per write, so be sure to write idempotent code."}),"\n",(0,s.jsx)(n.h3,{id:"prefileadd",children:"preFileAdd"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"({\n record: StokerRecord\n fullPath: string\n filename: string\n permissions: FilePermissions\n}) => boolean | void | Promise<boolean | void>\n"})}),"\n",(0,s.jsx)(n.p,{children:"Fires before a file is uploaded."}),"\n",(0,s.jsxs)(n.p,{children:["Return ",(0,s.jsx)(n.code,{children:"false"})," to cancel the operation."]}),"\n",(0,s.jsx)(n.h3,{id:"prefileupdate",children:"preFileUpdate"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'({\n record: StokerRecord\n update:\n { type: "rename"; oldPath: string; newPath: string } |\n {\n type: "permissions"\n path: string\n originalPermissions: FilePermissions\n permissions: FilePermissions\n }\n}) => boolean | void | Promise<boolean | void>\n'})}),"\n",(0,s.jsx)(n.p,{children:"Fires before a file is updated."}),"\n",(0,s.jsxs)(n.p,{children:["Return ",(0,s.jsx)(n.code,{children:"false"})," to cancel the operation."]}),"\n",(0,s.jsx)(n.h3,{id:"postfileadd",children:"postFileAdd"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"({\n record: StokerRecord\n fullPath: string\n filename: string\n permissions: FilePermissions\n}) => void | Promise<void>\n"})}),"\n",(0,s.jsx)(n.p,{children:"Fires after a file is uploaded."}),"\n",(0,s.jsx)(n.h3,{id:"postfileadderror",children:"postFileAddError"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"({\n record: StokerRecord\n fullPath: string\n filename: string\n permissions: FilePermissions\n error: unknown\n}) => void | Promise<void>\n"})}),"\n",(0,s.jsx)(n.p,{children:"Fires when a file upload fails."}),"\n",(0,s.jsx)(n.h3,{id:"postfileupdate",children:"postFileUpdate"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'({\n record: StokerRecord\n update:\n { type: "rename"; oldPath: string; newPath: string } |\n {\n type: "permissions"\n path: string\n originalPermissions: FilePermissions\n permissions: FilePermissions\n }\n}) => void | Promise<void>\n'})}),"\n",(0,s.jsx)(n.p,{children:"Fires after a file is updated."}),"\n",(0,s.jsx)(n.h3,{id:"setembedding",children:"setEmbedding"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"({ record: StokerRecord }) => string | Promise<string>"})}),"\n",(0,s.jsx)(n.p,{children:"Calculate an embedding value for the record."}),"\n",(0,s.jsx)(n.admonition,{type:"danger",children:(0,s.jsxs)(n.p,{children:["Only assign ",(0,s.jsx)(n.a,{href:"#ai-chat-config",children:"AI chat access"})," to roles that have access to ALL fields used to calculate embeddings!"]})})]})}function a(e={}){let{wrapper:n}={...(0,d.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(h,{...e})}):h(e)}},122(e,n,i){i.d(n,{A:()=>r});let r=i.p+"assets/images/ai-2c8d4bbfb87c1d7971ced98c34c0f01e.png"},5917(e,n,i){i.d(n,{A:()=>r});let r=i.p+"assets/images/board-dark-94d0800e219831747eba51104ce1ac35.png"},1685(e,n,i){i.d(n,{A:()=>r});let r=i.p+"assets/images/board-light-26670e18cf3e85ff64cf8360988e6ed2.png"},9231(e,n,i){i.d(n,{A:()=>r});let r=i.p+"assets/images/list-dark-a0e917baa408e37e52b228d87c0a8239.png"},4319(e,n,i){i.d(n,{A:()=>r});let r=i.p+"assets/images/list-light-57830a1ce5c566f2b396e6edcc0064bf.png"},2748(e,n,i){i.d(n,{A:()=>r});let r=i.p+"assets/images/map-dark-c124d7546a544a6051c707337f152b32.png"},8623(e,n,i){i.d(n,{A:()=>r});let r=i.p+"assets/images/map-light-9a4f42745310d1e352e397e92d5981fd.png"},1184(e,n,i){i.d(n,{R:()=>l,x:()=>t});var r=i(4041);let s={},d=r.createContext(s);function l(e){let n=r.useContext(d);return r.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function t(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:l(e.components),r.createElement(d.Provider,{value:n},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.