UNPKG

wikibase-edit

Version:

Edit Wikibase from NodeJS

184 lines (160 loc) 7.05 kB
import { omit } from 'lodash-es' import { isEntityId, type EntityType, type Item, type Lexeme, type MediaInfo, type Property, datatypes, type Claims, type Statements } from 'wikibase-sdk' import { newError } from '../error.js' import { getEntityClaims } from '../get_entity.js' import { arrayIncludes, forceArray, objectEntries } from '../utils.js' import { formatClaims, formatSitelinks, formatTermsObject } from './format.js' import { isIdAliasPattern, resolveIdAlias } from './id_alias.js' import type { CreateEntityResponse } from './create.js' import type { Reconciliation } from './validate_reconciliation_object.js' import type { PropertiesDatatypes } from '../properties/fetch_properties_datatypes.js' import type { AbsoluteUrl, BaseRevId } from '../types/common.js' import type { SerializedConfig } from '../types/config.js' import type { RawEditableEntity, RawEditableItem, RawEditableLexeme, RawEditableMediaInfo, RawEditableProperty, SimplifiedEditableClaims, SimplifiedEditableEntity } from '../types/edit_entity.js' interface EditEntityParamsBase { clear?: boolean create?: boolean reconciliation?: Reconciliation summary?: string baserevid?: BaseRevId } export type EditEntityRawModeParams = EditEntityParamsBase & Partial<RawEditableEntity> & { rawMode: true } export type EditEntitySimplifiedModeParams = EditEntityParamsBase & Partial<SimplifiedEditableEntity> export type EditEntityParams = EditEntityRawModeParams | EditEntitySimplifiedModeParams const editableTypes = [ // 'form', 'item', 'lexeme', 'mediainfo', 'property', // 'sense', ] as const interface WbeditentityDataBase <T extends RawEditableEntity> { type: T['type'] new?: T['type'] clear?: boolean id?: T['id'] data: Partial<T> } type WbeditentityItemData = WbeditentityDataBase<RawEditableItem> type WbeditentityPropertyData = WbeditentityDataBase<RawEditableProperty> type WbeditentityLexemeData = WbeditentityDataBase<RawEditableLexeme> type WbeditentityMediaInfoData = WbeditentityDataBase<RawEditableMediaInfo> type WbeditentityData = WbeditentityItemData | WbeditentityPropertyData | WbeditentityLexemeData | WbeditentityMediaInfoData export async function editEntity (inputParams: EditEntitySimplifiedModeParams, properties: PropertiesDatatypes, instance: AbsoluteUrl, config: SerializedConfig) { return _editEntity(inputParams, properties, instance, config) } export async function _rawEditEntity (inputParams: EditEntityRawModeParams, properties: PropertiesDatatypes, instance: AbsoluteUrl, config: SerializedConfig) { return _editEntity(inputParams, properties, instance, config) } async function _editEntity (inputParams: EditEntitySimplifiedModeParams | EditEntityRawModeParams, properties: PropertiesDatatypes, instance: AbsoluteUrl, config: SerializedConfig) { validateParameters(inputParams) let { id } = inputParams const { create, type = 'item', clear, reconciliation } = inputParams const datatype = 'datatype' in inputParams ? inputParams.datatype : undefined const rawMode = 'rawMode' in inputParams ? inputParams.rawMode : false if (!arrayIncludes(editableTypes, type)) { throw newError('invalid entity type', { type }) } let params: Partial<WbeditentityData> = { type, data: {} } let existingClaims: Claims | Statements if (create) { if (type === 'property') { if (!datatype) throw newError('missing property datatype', { datatype }) if (!datatypesSet.has(datatype)) { throw newError('invalid property datatype', { datatype, knownDatatypes: datatypes }) } params = { ...params, new: 'property', data: { datatype, }, } as WbeditentityPropertyData } else { if (datatype) { throw newError("an item can't have a datatype", { datatype }) } params.new = 'item' } } else if (isEntityId(id) || isIdAliasPattern(id)) { if (isIdAliasPattern(id)) { id = await resolveIdAlias(id, instance) } params.id = id // @ts-expect-error if (hasReconciliationSettings(reconciliation, inputParams.claims || inputParams.statements)) { existingClaims = await getEntityClaims(id, config) } } else { throw newError('invalid entity id', { id }) } for (const [ attribute, types ] of objectEntries(attributesPerEntityType)) { if (arrayIncludes(types, params.type)) { if (attribute in inputParams && inputParams[attribute] != null) { const inputParam = inputParams[attribute] if (rawMode) { params.data[attribute] = inputParam } else { if (attribute === 'claims' || attribute === 'statements') { params.data[attribute] = formatClaims(inputParam, properties, instance, reconciliation, existingClaims) } else { params.data[attribute] = simplifiedInputFormatters[attribute](inputParam) } } } } } if (clear === true) params.clear = true if (!clear && Object.keys(params.data).length === 0) { throw newError('no data was passed', { id }) } return { action: 'wbeditentity', data: { ...omit(params, 'type', 'data'), // Stringify as it will be passed as form data data: JSON.stringify(params.data), }, } } // TODO: add support for lemmas, forms and senses const attributesPerEntityType = { aliases: [ 'item', 'property' ], claims: [ 'item', 'property', 'lexeme' ], descriptions: [ 'item', 'property', 'mediainfo' ], labels: [ 'item', 'property', 'mediainfo' ], statements: [ 'mediainfo' ], sitelinks: [ 'item' ], } satisfies Partial<Record<keyof Item | keyof Property | keyof Lexeme | keyof MediaInfo, EntityType[]>> type HasSimplifiedInputFormatters = Exclude<keyof typeof attributesPerEntityType, 'claims' | 'statements'> // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type const simplifiedInputFormatters: Record<HasSimplifiedInputFormatters, Function> = { aliases: formatTermsObject.bind(null, 'alias'), descriptions: formatTermsObject.bind(null, 'description'), labels: formatTermsObject.bind(null, 'label'), sitelinks: formatSitelinks, } const datatypesSet = new Set(datatypes) const allowedParameters = new Set([ 'id', 'create', 'type', 'datatype', 'clear', 'rawMode', 'summary', 'baserevid', 'labels', 'aliases', 'descriptions', 'claims', 'statements', 'sitelinks', 'reconciliation', 'origin', ]) function validateParameters (params: EditEntityParams) { for (const parameter in params) { if (!allowedParameters.has(parameter)) { throw newError(`invalid parameter: ${parameter}`, 400, { parameter, allowedParameters, params }) } } } function hasReconciliationSettings (reconciliation: Reconciliation, claims: SimplifiedEditableClaims) { if (reconciliation != null) return true for (const property in claims) { for (const claim of forceArray(claims[property])) { if (claim.reconciliation != null) return true } } return false } export type EditEntityResponse = CreateEntityResponse