Quick Start
Background
jsonapi-resources-anchor
enables a minimally invasive way to generate a type schema for your jsonapi-resources
API.
- TypeScript Schema Generator
- Type Inference via the underlying ActiveRecord model of the resource
- Type Annotation
Installation
gem 'jsonapi-resources-anchor'
bundle install
Generate a schema
Note that the usage of Types::String
without the Anchor::
prefix is
enabled via
module Types
include Anchor::Types
end
Resources
class ApplicationResource < JSONAPI::Resource
abstract
include Anchor::SchemaSerializable
end
class ExhaustiveResource < ApplicationResource
class AssertedObject < Types::Object
property :a, Types::Literal.new("a")
property "b-dash", Types::Literal.new(1)
property :c, Types::Maybe.new(Types::String)
property :d_optional, Types::Maybe.new(Types::String), optional: true
end
attribute :asserted_string, Types::String, description: "My asserted string."
attribute :asserted_number, Types::Integer
attribute :asserted_boolean, Types::Boolean
attribute :asserted_null, Types::Null
attribute :asserted_unknown, Types::Unknown
attribute :asserted_object, AssertedObject
attribute :asserted_maybe_object, Types::Maybe.new(AssertedObject)
attribute :asserted_array_record, Types::Array.new(Types::Record.new(Types::Integer))
attribute :asserted_union, Types::Union.new([Types::String, Types::Float])
attribute :with_description, Types::String, description: "This is a provided description."
attribute :inferred_unknown
attribute :uuid
attribute :string
attribute :maybe_string
attribute :text
attribute :integer
attribute :float
attribute :decimal
attribute :datetime
attribute :timestamp
attribute :time
attribute :date
attribute :boolean
attribute :array_string
attribute :maybe_array_string
attribute :json
attribute :jsonb
attribute :daterange
attribute :enum
attribute :virtual_upcased_string
attribute :loljk
attribute :delegated_maybe_string, delegate: :maybe_string
attribute :model_overridden
attribute :resource_overridden
attribute :with_comment
class LinkSchema < Anchor::Types::Object
property :self, Anchor::Types::String
property :some_url, Anchor::Types::String
end
anchor_links_schema LinkSchema
class MetaSchema < Anchor::Types::Object
property :some_count, Anchor::Types::Integer
property :extra_stuff, Anchor::Types::String
end
anchor_meta_schema MetaSchema
def asserted_string = "asserted_string"
def asserted_number = 1
def asserted_boolean = true
def asserted_null = nil
def asserted_unknown = nil
def asserted_object = { a: "a", "b-dash" => 1, c: nil }
def asserted_maybe_object = nil
def asserted_array_record = [{ key: 1 }]
def asserted_union = 2
def inferred_unknown = nil
def resource_overridden = "resource_overridden"
def with_description = "with_description"
end
module Anchor
configure do |c|
c.field_case = :camel_without_inflection
c.use_active_record_comment = true
c.use_active_record_validations = true
c.infer_nullable_relationships_as_optional = true
c.ar_column_to_type = lambda { |column|
return Types::Literal.new("never") if column.name == "loljk"
Types::Inference::ActiveRecord::SQL.default_ar_column_to_type(column)
}
c.empty_relationship_type = -> { Anchor::Types::Object }
end
end
JSONAPI.configure do |c|
c.json_key_format = :camelized_key
end
Schemas
class Schema < Anchor::Schema
resource CommentResource
resource UserResource
resource PostResource
resource ExhaustiveResource
enum UserRoleEnum
end
namespace :jsonapi do
desc "Generate JSONAPI::Resource Anchor schema"
task generate: :environment do
puts "Generating JSONAPI::Resource Anchor schema..."
content = Anchor::TypeScript::SchemaGenerator(
register: Schema.register,
context: {},
include_all_fields: true,
exclude_fields: nil,
).call
path = Rails.root.join("schema.ts")
File.open(path, "w") { |f| f.write(content) }
puts "✅ #{File.basename(path)}"
end
end
rails jsonapi:generate
type Maybe<T> = T | null;
export enum UserRole {
Admin = "admin",
ContentCreator = "content_creator",
External = "external",
Guest = "guest",
System = "system",
}
export type Comment = {
id: number;
type: "comments";
text: string;
createdAt: string;
updatedAt: string;
relationships: {
/** Author of the comment. */
user: User;
deletedBy?: User;
commentable?: Post;
};
};
export type User = {
id: number;
type: "users";
name: string;
role: UserRole;
relationships: {
comments: Array<Comment>;
posts: Array<Post>;
};
};
export type Post = {
id: number;
type: "posts";
description: string;
relationships: {
user: User;
comments: Array<Comment>;
participants: Array<User>;
};
};
export type Exhaustive = {
id: number;
type: "exhaustives";
/** My asserted string. */
assertedString: string;
assertedNumber: number;
assertedBoolean: boolean;
assertedNull: null;
assertedUnknown: unknown;
assertedObject: {
a: "a";
"b-dash": 1;
c: Maybe<string>;
d_optional?: Maybe<string>;
};
assertedMaybeObject: Maybe<{
a: "a";
"b-dash": 1;
c: Maybe<string>;
d_optional?: Maybe<string>;
}>;
assertedArrayRecord: Array<Record<string, number>>;
assertedUnion: string | number;
/** This is a provided description. */
withDescription: string;
inferredUnknown: unknown;
uuid: string;
string: string;
maybeString: string;
text: string;
integer: number;
float: number;
decimal: string;
datetime: string;
timestamp: string;
time: string;
date: string;
boolean: boolean;
arrayString: Array<string>;
maybeArrayString: Maybe<Array<string>>;
json: Record<string, unknown>;
jsonb: Record<string, unknown>;
daterange: unknown;
enum: unknown;
virtualUpcasedString: Maybe<string>;
loljk: "never";
delegatedMaybeString: string;
modelOverridden: unknown;
resourceOverridden: unknown;
/** This is a comment. */
withComment: Maybe<string>;
relationships: {};
meta: {
some_count: number;
extra_stuff: string;
};
links: {
self: string;
some_url: string;
};
};