Unverified Commit cb5b5cb5 authored by Eugen Rochko's avatar Eugen Rochko Committed by GitHub
Browse files

Slightly reduce RAM usage (#7301)

* No need to re-require sidekiq plugins, they are required via Gemfile

* Add derailed_benchmarks tool, no need to require TTY gems in Gemfile

* Replace ruby-oembed with FetchOEmbedService

Reduce startup by 45382 allocated objects

* Remove preloaded JSON-LD in favour of caching HTTP responses

Reduce boot RAM by about 6 MiB

* Fix tests

* Fix test suite by stubbing out JSON-LD contexts
parent 71a7cea7
......@@ -54,7 +54,7 @@ gem 'httplog', '~> 1.0'
gem 'idn-ruby', require: 'idn'
gem 'kaminari', '~> 1.1'
gem 'link_header', '~> 0.0'
gem 'mime-types', '~> 3.1'
gem 'mime-types', '~> 3.1', require: 'mime/types/columnar'
gem 'nokogiri', '~> 1.8'
gem 'nsa', '~> 0.2'
gem 'oj', '~> 3.5'
......@@ -70,7 +70,6 @@ gem 'rails-settings-cached', '~> 0.6'
gem 'redis', '~> 4.0', require: ['redis', 'redis/connection/hiredis']
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
gem 'rqrcode', '~> 0.10'
gem 'ruby-oembed', '~> 0.12', require: 'oembed'
gem 'ruby-progressbar', '~> 1.4'
gem 'sanitize', '~> 4.6'
gem 'sidekiq', '~> 5.1'
......@@ -82,14 +81,14 @@ gem 'simple_form', '~> 4.0'
gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie'
gem 'stoplight', '~> 2.1.3'
gem 'strong_migrations', '~> 0.2'
gem 'tty-command', '~> 0.8'
gem 'tty-prompt', '~> 0.16'
gem 'tty-command', '~> 0.8', require: false
gem 'tty-prompt', '~> 0.16', require: false
gem 'twitter-text', '~> 1.14'
gem 'tzinfo-data', '~> 1.2018'
gem 'webpacker', '~> 3.4'
gem 'webpush'
gem 'json-ld-preloaded', '~> 2.2'
gem 'json-ld', '~> 2.2'
gem 'rdf-normalize', '~> 0.3'
group :development, :test do
......@@ -135,6 +134,9 @@ group :development do
gem 'capistrano-rails', '~> 1.3'
gem 'capistrano-rbenv', '~> 2.1'
gem 'capistrano-yarn', '~> 2.0'
gem 'derailed_benchmarks'
gem 'stackprof'
end
group :production do
......
......@@ -75,6 +75,7 @@ GEM
aws-sigv4 (~> 1.0)
aws-sigv4 (1.0.2)
bcrypt (3.1.11)
benchmark-ips (2.7.2)
better_errors (2.4.0)
coderay (>= 1.0.0)
erubi (>= 1.0.0)
......@@ -138,6 +139,14 @@ GEM
css_parser (1.6.0)
addressable
debug_inspector (0.0.3)
derailed_benchmarks (1.3.4)
benchmark-ips (~> 2)
get_process_mem (~> 0)
heapy (~> 0)
memory_profiler (~> 0)
rack (>= 1)
rake (> 10, < 13)
thor (~> 0.19)
devise (4.4.3)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
......@@ -206,6 +215,7 @@ GEM
fuubar (2.3.1)
rspec-core (~> 3.0)
ruby-progressbar (~> 1.4)
get_process_mem (0.2.1)
globalid (0.4.1)
activesupport (>= 4.2.0)
goldfinger (2.1.0)
......@@ -226,6 +236,7 @@ GEM
concurrent-ruby (~> 1.0)
hashdiff (0.3.7)
hashie (3.5.7)
heapy (0.1.3)
highline (1.7.10)
hiredis (0.6.1)
hitimes (1.2.6)
......@@ -264,10 +275,6 @@ GEM
json-ld (2.2.1)
multi_json (~> 1.12)
rdf (>= 2.2.8, < 4.0)
json-ld-preloaded (2.2.3)
json-ld (>= 2.2, < 4.0)
multi_json (~> 1.12)
rdf (>= 2.2, < 4.0)
jsonapi-renderer (0.2.0)
jwt (2.1.0)
kaminari (1.1.1)
......@@ -502,7 +509,6 @@ GEM
rainbow (>= 2.2.2, < 4.0)
ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1)
ruby-oembed (0.12.0)
ruby-progressbar (1.9.0)
ruby-saml (1.7.2)
nokogiri (>= 1.5.10)
......@@ -557,6 +563,7 @@ GEM
sshkit (1.16.0)
net-scp (>= 1.1.2)
net-ssh (>= 2.8.0)
stackprof (0.2.11)
statsd-ruby (1.2.1)
stoplight (2.1.3)
streamio-ffmpeg (3.0.2)
......@@ -645,6 +652,7 @@ DEPENDENCIES
chewy (~> 5.0)
cld3 (~> 3.2.0)
climate_control (~> 0.2)
derailed_benchmarks
devise (~> 4.4)
devise-two-factor (~> 3.0)
devise_pam_authenticatable2 (~> 9.1)
......@@ -668,7 +676,7 @@ DEPENDENCIES
i18n-tasks (~> 0.9)
idn-ruby
iso-639
json-ld-preloaded (~> 2.2)
json-ld (~> 2.2)
kaminari (~> 1.1)
letter_opener (~> 1.4)
letter_opener_web (~> 1.3)
......@@ -714,7 +722,6 @@ DEPENDENCIES
rspec-retry (~> 0.5)
rspec-sidekiq (~> 3.0)
rubocop (~> 0.55)
ruby-oembed (~> 0.12)
ruby-progressbar (~> 1.4)
sanitize (~> 4.6)
scss_lint (~> 0.57)
......@@ -726,6 +733,7 @@ DEPENDENCIES
simple_form (~> 4.0)
simplecov (~> 0.16)
sprockets-rails (~> 3.2)
stackprof
stoplight (~> 2.1.3)
streamio-ffmpeg (~> 3.0)
strong_migrations (~> 0.2)
......
......@@ -9,9 +9,12 @@ class Api::Web::EmbedsController < Api::Web::BaseController
status = StatusFinder.new(params[:url]).status
render json: status, serializer: OEmbedSerializer, width: 400
rescue ActiveRecord::RecordNotFound
oembed = OEmbed::Providers.get(params[:url])
render json: Oj.dump(oembed.fields)
rescue OEmbed::NotFound
render json: {}, status: :not_found
oembed = FetchOEmbedService.new.call(params[:url])
if oembed
render json: oembed
else
render json: {}, status: :not_found
end
end
end
# frozen_string_literal: true
require 'sidekiq-bulk'
class Settings::FollowerDomainsController < ApplicationController
layout 'admin'
......
......@@ -48,7 +48,7 @@ module JsonLdHelper
end
def canonicalize(json)
graph = RDF::Graph.new << JSON::LD::API.toRdf(json)
graph = RDF::Graph.new << JSON::LD::API.toRdf(json, documentLoader: method(:load_jsonld_context))
graph.dump(:normalize)
end
......@@ -90,4 +90,19 @@ module JsonLdHelper
request.add_headers('Accept' => 'application/activity+json, application/ld+json')
request
end
def load_jsonld_context(url, _options = {}, &_block)
json = Rails.cache.fetch("jsonld:context:#{url}", expires_in: 30.days, raw: true) do
request = Request.new(:get, url)
request.add_headers('Accept' => 'application/ld+json')
request.perform do |res|
raise JSON::LD::JsonLdError::LoadingDocumentFailed unless res.code == 200 && res.mime_type == 'application/ld+json'
res.body_with_limit
end
end
doc = JSON::LD::API::RemoteDocument.new(url, json)
block_given? ? yield(doc) : doc
end
end
# frozen_string_literal: true
class ProviderDiscovery < OEmbed::ProviderDiscovery
class << self
def get(url, **options)
provider = discover_provider(url, options)
options.delete(:html)
provider.get(url, options)
end
def discover_provider(url, **options)
format = options[:format]
html = if options[:html]
Nokogiri::HTML(options[:html])
else
Request.new(:get, url).perform do |res|
raise OEmbed::NotFound, url if res.code != 200 || res.mime_type != 'text/html'
Nokogiri::HTML(res.body_with_limit)
end
end
if format.nil? || format == :json
provider_endpoint ||= html.at_xpath('//link[@type="application/json+oembed"]')&.attribute('href')&.value
format ||= :json if provider_endpoint
end
if format.nil? || format == :xml
provider_endpoint ||= html.at_xpath('//link[@type="text/xml+oembed"]')&.attribute('href')&.value
format ||= :xml if provider_endpoint
end
raise OEmbed::NotFound, url if provider_endpoint.nil?
begin
provider_endpoint = Addressable::URI.parse(provider_endpoint)
provider_endpoint.query = nil
provider_endpoint = provider_endpoint.to_s
rescue Addressable::URI::InvalidURIError
raise OEmbed::NotFound, url
end
OEmbed::Provider.new(provider_endpoint, format)
end
end
end
# frozen_string_literal: true
require 'sidekiq-bulk'
class FanOutOnWriteService < BaseService
# Push a status into home and mentions feeds
# @param [Status] status
......
......@@ -85,42 +85,40 @@ class FetchLinkCardService < BaseService
end
def attempt_oembed
embed = OEmbed::Providers.get(@url, html: @html)
embed = FetchOEmbedService.new.call(@url, html: @html)
return false unless embed.respond_to?(:type)
return false if embed.nil?
@card.type = embed.type
@card.title = embed.respond_to?(:title) ? embed.title : ''
@card.author_name = embed.respond_to?(:author_name) ? embed.author_name : ''
@card.author_url = embed.respond_to?(:author_url) ? embed.author_url : ''
@card.provider_name = embed.respond_to?(:provider_name) ? embed.provider_name : ''
@card.provider_url = embed.respond_to?(:provider_url) ? embed.provider_url : ''
@card.type = embed[:type]
@card.title = embed[:title] || ''
@card.author_name = embed[:author_name] || ''
@card.author_url = embed[:author_url] || ''
@card.provider_name = embed[:provider_name] || ''
@card.provider_url = embed[:provider_url] || ''
@card.width = 0
@card.height = 0
case @card.type
when 'link'
@card.image_remote_url = embed.thumbnail_url if embed.respond_to?(:thumbnail_url)
@card.image_remote_url = embed[:thumbnail_url] if embed[:thumbnail_url].present?
when 'photo'
return false unless embed.respond_to?(:url)
return false if embed[:url].blank?
@card.embed_url = embed.url
@card.image_remote_url = embed.url
@card.width = embed.width.presence || 0
@card.height = embed.height.presence || 0
@card.embed_url = embed[:url]
@card.image_remote_url = embed[:url]
@card.width = embed[:width].presence || 0
@card.height = embed[:height].presence || 0
when 'video'
@card.width = embed.width.presence || 0
@card.height = embed.height.presence || 0
@card.html = Formatter.instance.sanitize(embed.html, Sanitize::Config::MASTODON_OEMBED)
@card.image_remote_url = embed.thumbnail_url if embed.respond_to?(:thumbnail_url)
@card.width = embed[:width].presence || 0
@card.height = embed[:height].presence || 0
@card.html = Formatter.instance.sanitize(embed[:html], Sanitize::Config::MASTODON_OEMBED)
@card.image_remote_url = embed[:thumbnail_url] if embed[:thumbnail_url].present?
when 'rich'
# Most providers rely on <script> tags, which is a no-no
return false
end
@card.save_with_optional_image!
rescue OEmbed::NotFound
false
end
def attempt_opengraph
......
# frozen_string_literal: true
class FetchOEmbedService
attr_reader :url, :options, :format, :endpoint_url
def call(url, options = {})
@url = url
@options = options
discover_endpoint!
fetch!
end
private
def discover_endpoint!
return if html.nil?
@format = @options[:format]
page = Nokogiri::HTML(html)
if @format.nil? || @format == :json
@endpoint_url ||= page.at_xpath('//link[@type="application/json+oembed"]')&.attribute('href')&.value
@format ||= :json if @endpoint_url
end
if @format.nil? || @format == :xml
@endpoint_url ||= page.at_xpath('//link[@type="text/xml+oembed"]')&.attribute('href')&.value
@format ||= :xml if @endpoint_url
end
return if @endpoint_url.blank?
@endpoint_url = Addressable::URI.parse(@endpoint_url).to_s
rescue Addressable::URI::InvalidURIError
@endpoint_url = nil
end
def fetch!
return if @endpoint_url.blank?
body = Request.new(:get, @endpoint_url).perform do |res|
res.code != 200 ? nil : res.body_with_limit
end
validate(parse_for_format(body)) unless body.nil?
rescue Oj::ParseError, Ox::ParseError
nil
end
def parse_for_format(body)
case @format
when :json
Oj.load(body, mode: :strict)&.with_indifferent_access
when :xml
Ox.load(body, mode: :hash_no_attrs)&.with_indifferent_access&.dig(:oembed)
end
end
def validate(oembed)
oembed if oembed[:version] == '1.0' && oembed[:type].present?
end
def html
return @html if defined?(@html)
@html = @options[:html] || Request.new(:get, @url).perform do |res|
res.code != 200 || res.mime_type != 'text/html' ? nil : res.body_with_limit
end
end
end
# frozen_string_literal: true
require 'sidekiq-scheduler'
class Scheduler::BackupCleanupScheduler
include Sidekiq::Worker
......
# frozen_string_literal: true
require 'sidekiq-scheduler'
class Scheduler::DoorkeeperCleanupScheduler
include Sidekiq::Worker
......
# frozen_string_literal: true
require 'sidekiq-scheduler'
class Scheduler::EmailScheduler
include Sidekiq::Worker
......
# frozen_string_literal: true
require 'sidekiq-scheduler'
class Scheduler::FeedCleanupScheduler
include Sidekiq::Worker
......
# frozen_string_literal: true
require 'sidekiq-scheduler'
class Scheduler::IpCleanupScheduler
include Sidekiq::Worker
......
# frozen_string_literal: true
require 'sidekiq-scheduler'
class Scheduler::MediaCleanupScheduler
include Sidekiq::Worker
......
# frozen_string_literal: true
require 'sidekiq-scheduler'
class Scheduler::SubscriptionsCleanupScheduler
include Sidekiq::Worker
......
# frozen_string_literal: true
require 'sidekiq-scheduler'
require 'sidekiq-bulk'
class Scheduler::SubscriptionsScheduler
include Sidekiq::Worker
......
# frozen_string_literal: true
require 'sidekiq-scheduler'
class Scheduler::UserCleanupScheduler
include Sidekiq::Worker
......
# frozen_string_literal: true
require 'sidekiq-bulk'
class SoftBlockDomainFollowersWorker
include Sidekiq::Worker
......
# frozen_string_literal: true
require_relative '../../lib/json_ld/identity'
require_relative '../../lib/json_ld/security'
require_relative '../../lib/json_ld/activitystreams'
# frozen_string_literal: true
require_relative '../../app/lib/provider_discovery'
OEmbed::Providers.register_fallback(ProviderDiscovery)
# -*- encoding: utf-8 -*-
# frozen_string_literal: true
# This file generated automatically from https://www.w3.org/ns/activitystreams
require 'json/ld'
class JSON::LD::Context
add_preloaded("https://www.w3.org/ns/activitystreams") do
new(vocab: "_:", processingMode: "json-ld-1.0", term_definitions: {
"Accept" => TermDefinition.new("Accept", id: "https://www.w3.org/ns/activitystreams#Accept", simple: true),
"Activity" => TermDefinition.new("Activity", id: "https://www.w3.org/ns/activitystreams#Activity", simple: true),
"Add" => TermDefinition.new("Add", id: "https://www.w3.org/ns/activitystreams#Add", simple: true),
"Announce" => TermDefinition.new("Announce", id: "https://www.w3.org/ns/activitystreams#Announce", simple: true),
"Application" => TermDefinition.new("Application", id: "https://www.w3.org/ns/activitystreams#Application", simple: true),
"Arrive" => TermDefinition.new("Arrive", id: "https://www.w3.org/ns/activitystreams#Arrive", simple: true),
"Article" => TermDefinition.new("Article", id: "https://www.w3.org/ns/activitystreams#Article", simple: true),
"Audio" => TermDefinition.new("Audio", id: "https://www.w3.org/ns/activitystreams#Audio", simple: true),
"Block" => TermDefinition.new("Block", id: "https://www.w3.org/ns/activitystreams#Block", simple: true),
"Collection" => TermDefinition.new("Collection", id: "https://www.w3.org/ns/activitystreams#Collection", simple: true),
"CollectionPage" => TermDefinition.new("CollectionPage", id: "https://www.w3.org/ns/activitystreams#CollectionPage", simple: true),
"Create" => TermDefinition.new("Create", id: "https://www.w3.org/ns/activitystreams#Create", simple: true),
"Delete" => TermDefinition.new("Delete", id: "https://www.w3.org/ns/activitystreams#Delete", simple: true),
"Dislike" => TermDefinition.new("Dislike", id: "https://www.w3.org/ns/activitystreams#Dislike", simple: true),
"Document" => TermDefinition.new("Document", id: "https://www.w3.org/ns/activitystreams#Document", simple: true),
"Event" => TermDefinition.new("Event", id: "https://www.w3.org/ns/activitystreams#Event", simple: true),
"Flag" => TermDefinition.new("Flag", id: "https://www.w3.org/ns/activitystreams#Flag", simple: true),
"Follow" => TermDefinition.new("Follow", id: "https://www.w3.org/ns/activitystreams#Follow", simple: true),
"Group" => TermDefinition.new("Group", id: "https://www.w3.org/ns/activitystreams#Group", simple: true),
"Ignore" => TermDefinition.new("Ignore", id: "https://www.w3.org/ns/activitystreams#Ignore", simple: true),
"Image" => TermDefinition.new("Image", id: "https://www.w3.org/ns/activitystreams#Image", simple: true),
"IntransitiveActivity" => TermDefinition.new("IntransitiveActivity", id: "https://www.w3.org/ns/activitystreams#IntransitiveActivity", simple: true),
"Invite" => TermDefinition.new("Invite", id: "https://www.w3.org/ns/activitystreams#Invite", simple: true),
"IsContact" => TermDefinition.new("IsContact", id: "https://www.w3.org/ns/activitystreams#IsContact", simple: true),
"IsFollowedBy" => TermDefinition.new("IsFollowedBy", id: "https://www.w3.org/ns/activitystreams#IsFollowedBy", simple: true),
"IsFollowing" => TermDefinition.new("IsFollowing", id: "https://www.w3.org/ns/activitystreams#IsFollowing", simple: true),
"IsMember" => TermDefinition.new("IsMember", id: "https://www.w3.org/ns/activitystreams#IsMember", simple: true),
"Join" => TermDefinition.new("Join", id: "https://www.w3.org/ns/activitystreams#Join", simple: true),
"Leave" => TermDefinition.new("Leave", id: "https://www.w3.org/ns/activitystreams#Leave", simple: true),
"Like" => TermDefinition.new("Like", id: "https://www.w3.org/ns/activitystreams#Like", simple: true),
"Link" => TermDefinition.new("Link", id: "https://www.w3.org/ns/activitystreams#Link", simple: true),
"Listen" => TermDefinition.new("Listen", id: "https://www.w3.org/ns/activitystreams#Listen", simple: true),
"Mention" => TermDefinition.new("Mention", id: "https://www.w3.org/ns/activitystreams#Mention", simple: true),
"Move" => TermDefinition.new("Move", id: "https://www.w3.org/ns/activitystreams#Move", simple: true),
"Note" => TermDefinition.new("Note", id: "https://www.w3.org/ns/activitystreams#Note", simple: true),
"Object" => TermDefinition.new("Object", id: "https://www.w3.org/ns/activitystreams#Object", simple: true),
"Offer" => TermDefinition.new("Offer", id: "https://www.w3.org/ns/activitystreams#Offer", simple: true),
"OrderedCollection" => TermDefinition.new("OrderedCollection", id: "https://www.w3.org/ns/activitystreams#OrderedCollection", simple: true),
"OrderedCollectionPage" => TermDefinition.new("OrderedCollectionPage", id: "https://www.w3.org/ns/activitystreams#OrderedCollectionPage", simple: true),
"Organization" => TermDefinition.new("Organization", id: "https://www.w3.org/ns/activitystreams#Organization", simple: true),
"Page" => TermDefinition.new("Page", id: "https://www.w3.org/ns/activitystreams#Page", simple: true),
"Person" => TermDefinition.new("Person", id: "https://www.w3.org/ns/activitystreams#Person", simple: true),
"Place" => TermDefinition.new("Place", id: "https://www.w3.org/ns/activitystreams#Place", simple: true),
"Profile" => TermDefinition.new("Profile", id: "https://www.w3.org/ns/activitystreams#Profile", simple: true),
"Question" => TermDefinition.new("Question", id: "https://www.w3.org/ns/activitystreams#Question", simple: true),
"Read" => TermDefinition.new("Read", id: "https://www.w3.org/ns/activitystreams#Read", simple: true),
"Reject" => TermDefinition.new("Reject", id: "https://www.w3.org/ns/activitystreams#Reject", simple: true),
"Relationship" => TermDefinition.new("Relationship", id: "https://www.w3.org/ns/activitystreams#Relationship", simple: true),
"Remove" => TermDefinition.new("Remove", id: "https://www.w3.org/ns/activitystreams#Remove", simple: true),
"Service" => TermDefinition.new("Service", id: "https://www.w3.org/ns/activitystreams#Service", simple: true),
"TentativeAccept" => TermDefinition.new("TentativeAccept", id: "https://www.w3.org/ns/activitystreams#TentativeAccept", simple: true),
"TentativeReject" => TermDefinition.new("TentativeReject", id: "https://www.w3.org/ns/activitystreams#TentativeReject", simple: true),
"Tombstone" => TermDefinition.new("Tombstone", id: "https://www.w3.org/ns/activitystreams#Tombstone", simple: true),
"Travel" => TermDefinition.new("Travel", id: "https://www.w3.org/ns/activitystreams#Travel", simple: true),
"Undo" => TermDefinition.new("Undo", id: "https://www.w3.org/ns/activitystreams#Undo", simple: true),
"Update" => TermDefinition.new("Update", id: "https://www.w3.org/ns/activitystreams#Update", simple: true),
"Video" => TermDefinition.new("Video", id: "https://www.w3.org/ns/activitystreams#Video", simple: true),
"View" => TermDefinition.new("View", id: "https://www.w3.org/ns/activitystreams#View", simple: true),
"accuracy" => TermDefinition.new("accuracy", id: "https://www.w3.org/ns/activitystreams#accuracy", type_mapping: "http://www.w3.org/2001/XMLSchema#float"),
"actor" => TermDefinition.new("actor", id: "https://www.w3.org/ns/activitystreams#actor", type_mapping: "@id"),
"altitude" => TermDefinition.new("altitude", id: "https://www.w3.org/ns/activitystreams#altitude", type_mapping: "http://www.w3.org/2001/XMLSchema#float"),
"anyOf" => TermDefinition.new("anyOf", id: "https://www.w3.org/ns/activitystreams#anyOf", type_mapping: "@id"),
"as" => TermDefinition.new("as", id: "https://www.w3.org/ns/activitystreams#", simple: true, prefix: true),
"attachment" => TermDefinition.new("attachment", id: "https://www.w3.org/ns/activitystreams#attachment", type_mapping: "@id"),
"attributedTo" => TermDefinition.new("attributedTo", id: "https://www.w3.org/ns/activitystreams#attributedTo", type_mapping: "@id"),
"audience" => TermDefinition.new("audience", id: "https://www.w3.org/ns/activitystreams#audience", type_mapping: "@id"),
"bcc" => TermDefinition.new("bcc", id: "https://www.w3.org/ns/activitystreams#bcc", type_mapping: "@id"),
"bto" => TermDefinition.new("bto", id: "https://www.w3.org/ns/activitystreams#bto", type_mapping: "@id"),
"cc" => TermDefinition.new("cc", id: "https://www.w3.org/ns/activitystreams#cc", type_mapping: "@id"),
"closed" => TermDefinition.new("closed", id: "https://www.w3.org/ns/activitystreams#closed", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"),
"content" => TermDefinition.new("content", id: "https://www.w3.org/ns/activitystreams#content", simple: true),
"contentMap" => TermDefinition.new("contentMap", id: "https://www.w3.org/ns/activitystreams#content", container_mapping: "@language"),
"context" => TermDefinition.new("context", id: "https://www.w3.org/ns/activitystreams#context", type_mapping: "@id"),
"current" => TermDefinition.new("current", id: "https://www.w3.org/ns/activitystreams#current", type_mapping: "@id"),
"deleted" => TermDefinition.new("deleted", id: "https://www.w3.org/ns/activitystreams#deleted", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"),
"describes" => TermDefinition.new("describes", id: "https://www.w3.org/ns/activitystreams#describes", type_mapping: "@id"),
"duration" => TermDefinition.new("duration", id: "https://www.w3.org/ns/activitystreams#duration", type_mapping: "http://www.w3.org/2001/XMLSchema#duration"),
"endTime" => TermDefinition.new("endTime", id: "https://www.w3.org/ns/activitystreams#endTime", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"),
"endpoints" => TermDefinition.new("endpoints", id: "https://www.w3.org/ns/activitystreams#endpoints", type_mapping: "@id"),
"first" => TermDefinition.new("first", id: "https://www.w3.org/ns/activitystreams#first", type_mapping: "@id"),
"followers" => TermDefinition.new("followers", id: "https://www.w3.org/ns/activitystreams#followers", type_mapping: "@id"),
"following" => TermDefinition.new("following", id: "https://www.w3.org/ns/activitystreams#following", type_mapping: "@id"),
"formerType" => TermDefinition.new("formerType", id: "https://www.w3.org/ns/activitystreams#formerType", type_mapping: "@id"),
"generator" => TermDefinition.new("generator", id: "https://www.w3.org/ns/activitystreams#generator", type_mapping: "@id"),
"height" => TermDefinition.new("height", id: "https://www.w3.org/ns/activitystreams#height", type_mapping: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger"),
"href" => TermDefinition.new("href", id: "https://www.w3.org/ns/activitystreams#href", type_mapping: "@id"),
"hreflang" => TermDefinition.new("hreflang", id: "https://www.w3.org/ns/activitystreams#hreflang", simple: true),