Coder Social home page Coder Social logo

beezwax / fmrest-ruby Goto Github PK

View Code? Open in Web Editor NEW
19.0 11.0 10.0 597 KB

FileMaker Data API client for Ruby with ActiveRecord-like ORM features

License: MIT License

Ruby 99.96% Shell 0.04%
filemaker filemaker-data-api filemaker-api ruby-client ruby-gem

fmrest-ruby's Introduction

fmrest-ruby

Gem Version CI Yard Docs Powered by Beezwax

A Ruby client for FileMaker's Data API with ActiveRecord-ish ORM features.

While pretty feature-rich, fmrest-ruby doesn't yet support 100% of FileMaker 19's Data API features. See the implementation completeness table to check if a feature you need is natively supported by the gem.

Need Ruby or FileMaker consulting? Contact us at Beezwax.net

Gems

The fmrest gem is a wrapper for these gems:

  • fmrest-spyke, providing an ActiveRecord-like ORM library built on top of fmrest-core and Spyke.
  • fmrest-core, providing the core Faraday connection builder, session management, and other core utilities.
  • fmrest-rails, providing Rails integration.

In addition, the optional fmrest-cloud gem adds support for FileMaker Cloud. See the main document on connecting to FileMaker Cloud.

Installation

In your Gemfile:

gem 'fmrest'

# Optional: if your files are hosted on FileMaker Cloud
gem 'fmrest-cloud'

If you're using Rails you can now run:

rails generate fmrest:config

Simple example

# A Layout model connecting to the "Honeybees Web" FileMaker layout
class Honeybee < FmRest::Layout("Honeybees Web")
  # Connection settings
  self.fmrest_config = {
    host:     "…",
    database: "…",
    username: "…",
    password: "…"
  }

  # Mapped attributes
  attributes name: "Bee Name", age: "Bee Age", created_on: "Created On"

  # Portal associations
  has_portal :tasks

  # File containers
  container :photo, field_name: "Bee Photo"

  # Scopes
  scope :can_legally_fly, -> { query(age: ">18") }

  # Client-side validations
  validates :name, presence: true

  # Callbacks
  before_save :set_created_on

  private

  def set_created_on
    self.created_on = Date.today
  end
end

# Find a record by id
bee = Honeybee.find(9)

bee.name = "Hutch"

# Add a new record to portal
bee.tasks.build(urgency: "Today")

bee.save

In case you don't want the ORM features (i.e. you only need authentication and JSON parsing, and are comfortable writing the API requests manually without the ORM overhead) you can use the Faraday connection provided by fmrest-core. See the main document on using the base connection for more.

Connection settings

The minimum required connection settings are :host, :database, :username and :password, but fmrest-ruby has many other options you can pass when setting up a connection (see full list below).

:ssl and :proxy are forwarded to the underlying Faraday connection. You can use this to, for instance, disable SSL verification:

{
  host: "…",
  
  ssl:  { verify: false },
  proxy: "http://user:[email protected]:4321"
}

You can also pass a :log option for basic request logging, see the section on Logging below.

Full list of available options

Option Description Format Default
:host Hostname with optional port, e.g. example.com:9000 String None
:database The name of the database to connect to String None
:username A Data API-ready account String None
:password Your password String None
:account_name Alias of :username String None
:ssl SSL options to be forwarded to Faraday Faraday SSL options hash None
:proxy Proxy URI, e.g. http://user:[email protected]:4321 String None
:log Log JSON responses to STDOUT Boolean false
:log_level Which log level to log into Values accepted by Logger#level= :debug
:coerce_dates See section on date fields Boolean | :hybrid | :full false
:date_format Date parsing format String (FM date format) "MM/dd/yyyy"
:timestamp_format Timestmap parsing format String (FM date format) "MM/dd/yyyy HH:mm:ss"
:time_format Time parsing format String (FM date format) "HH:mm:ss"
:timezone The timezone for the FM server :local | :utc | nil nil
:autologin Whether to automatically start Data API sessions Boolean true
:token Used to manually provide a session token (e.g. if :autologin is false) String None
:fmid_token Claris ID token (only needed if manually obtaining the token) String None
:cloud Specifies whether the host is using FileMaker Cloud :auto | Boolean :auto
:cognito_client_id Overwrites the hardcoded FileMaker Cloud Cognito Client ID String None
:cognito_pool_id Overwrites the hardcoded FileMaker Cloud Cognito Pool ID String None
:aws_region Overwrites the hardcoded FileMaker Cloud AWS Region String None

Default connection settings

If you're only connecting to a single FM database you can configure it globally through FmRest.default_connection_settings=. E.g.:

FmRest.default_connection_settings = {
  host:     "…",
  database: "…",
  username: "…",
  password: "…"
}

These settings will be used by default by FmRest::Layout models whenever you don't set fmrest_config= explicitly, as well as by FmRest::V1.build_connection in case you're setting up your Faraday connection manually.

Session token store

fmrest-ruby includes a number of options for storing session tokens:

  • Memory
  • ActiveRecord
  • Redis
  • Moneta

See the main document on token stores for detailed info on how to set up each store.

Date fields and timezones

fmrest-ruby has automatic detection and coercion of date fields to and from Ruby date/time objects. Basic timezone support is also provided.

See the main document on date fields for more info.

ActiveRecord-like ORM (fmrest-spyke)

Spyke is an ActiveRecord-like gem for building REST ORM models. fmrest-ruby uses it to build its ORM features, bundled in the fmrest-spyke gem (already included if you're using the fmrest gem).

To create a model you can inherit directly from FmRest::Layout (itself a subclass of Spyke::Base).

class Honeybee < FmRest::Layout
end

All of Spyke's basic ORM operations work as expected:

bee = Honeybee.new

bee.name = "Hutch"
bee.save # POST request (creates new record)

bee.name = "ハッチ"
bee.save # PATCH request (updates existing record)

bee.reload # GET request

bee.destroy # DELETE request

bee = Honeybee.find(9) # GET request

It's recommended that you read Spyke's documentation for more information on these basic features. If you've used ActiveRecord or similar ORM libraries you'll find it quite familiar.

Notice that FmRest::Layout is aliased as FmRest::Spyke::Base. Previous versions of fmrest-ruby only provided the latter version, so if you're already using FmRest::Spyke::Base there's no need to rename your classes to FmRest::Layout, both will continue to work interchangeably.

In addition, FmRest::Layout extends Spyke::Base with the following features:

FmRest::Layout.fmrest_config=

This allows you to set Data API connection settings specific to your model class:

class Honeybee < FmRest::Layout
  self.fmrest_config = {
    host:     "…",
    database: "…",
    username: "…",
    password: "…"
  }
end

These settings are class-inheritable, so you could create a base class that does the initial connection setup and then inherit from it in models using that same connection. E.g.:

class ApplicationFmLayout < FmRest::Layout
  self.fmrest_config = { host: "…", database: "…",  }
end

class Honeybee < ApplicationFmLayout
  # This model will use the same connection as ApplicationFmLayout
end

If fmrest_config is not set, your model will try to use FmRest.default_connection_settings instead.

Connection settings overlays

There may be cases where you want to use a different set of connection settings depending on context. For example, if you want to use username and password provided by the user in a web application. Since .fmrest_config is set at the class level, changing the username/password for the model in one context would also change it in all other contexts, leading to security issues.

To solve this scenario, fmrest-ruby provides a way of defining thread-local, reversible connection settings overlays through .fmrest_config_overlay=.

See the main document on connection setting overlays for details on how it works.

FmRest::Layout.layout

Use layout to set the layout name for your model.

class Honeybee < FmRest::Layout
  layout "Honeybees Web"
end

Alternatively, if you're inheriting from FmRest::Layout directly you can set the layout name in the class definition line:

class Honeybee < FmRest::Layout("Honeybees Web")

Note that you only need to manually set the layout name if the name of the class and the name of the layout differ, otherwise fmrest-ruby will just use the name of the class.

FmRest::Layout.request_auth_token

Requests a Data API session token using the connection settings in fmrest_config and returns it if successful, otherwise returns false.

You normally don't need to use this method as fmrest-ruby will automatically request and store session tokens for you (provided that :autologin is true in the connection settings, which it is by default).

FmRest::Layout.logout

Use .logout to log out from the database session (you may call it on any model that uses the database session you want to log out from).

Honeybee.logout

Mapped FmRest::Layout.attributes

Spyke allows you to define your model's attributes using attributes, however sometimes FileMaker's field names aren't very Ruby-ORM-friendly, especially since they may sometimes contain spaces and other special characters, so fmrest-ruby extends attributes' functionality to allow you to map Ruby-friendly attribute names to FileMaker field names. E.g.:

class Honeybee < FmRest::Layout
  attributes first_name: "First Name", last_name: "Last Name"
end

You can then simply use the pretty attribute names whenever working with your model and they will get mapped to their FileMaker fields:

bee = Honeybee.find(1)

bee.first_name # => "Princess"
bee.last_name  # => "Buzz"

bee.first_name = "Queen"

bee.attributes # => { "First Name": "Queen", "Last Name": "Buzz" }

FmRest::Layout.has_portal

You can define portal associations on your model wth has_portal, as such:

class Honeybee < FmRest::Layout
  has_portal :flowers
end

class Flower < FmRest::Layout
  attributes :color, :species
end

See the main document on portal associations for details.

Dirty attributes

fmrest-ruby includes support for ActiveModel's Dirty mixin out of the box, providing methods like:

bee = Honeybee.new

bee.changed? # => false

bee.name = "Maya"

bee.changed? # => true

bee.name_changed? # => true

fmrest-ruby uses the Dirty functionality to only send changed attributes back to the server on save.

You can read more about ActiveModel's Dirty in Rails Guides.

Query API

Since Spyke is API-agnostic it only provides a wide-purpose .where method for passing arbitrary parameters to the REST backend. fmrest-ruby however is well aware of its backend API, so it extends Spkye models with a bunch of useful querying methods: .query, .match, .omit, .limit, .offset, .sort, .portal, .script, etc.

See the main document on querying for detailed information on the query API methods.

Finding records in batches

Sometimes you want to iterate over a very large number of records to do some processing, but requesting them all at once would result in one huge request to the Data API, and loading too many records in memory all at once.

To mitigate this problem you can use .find_in_batches and .find_each.

See the main document on finding in batches for detailed information on how those work.

Container fields

You can define container fields on your model class with container:

class Honeybee < FmRest::Layout
  container :photo, field_name: "Beehive Photo ID"
end

See the main document on container fields for details on how to use it.

Script execution

The FM Data API allows running scripts as part of many types of requests, and fmrest-spyke provides mechanisms for all of them.

See the main document on script execution for details.

Setting global field values

You can call .set_globals on any FmRest::Layout model to set global field values on the database that model is configured for.

See the main document on setting global field values for details.

Rescuable mixin

Sometimes you may want to handle Data API errors at the model level. For such cases fmrest-ruby provides an off-by-default mixin called Rescuable that provides convenience macros for that. If you've used Ruby on Rails you may be familiar with its syntax from controllers. E.g.

class BeeBase < FmRest::Layout
  include FmRest::Spyke::Model::Rescuable

  rescue_from FmRest::APIError::SystemError, with: :notify_admin_of_system_error

  def self.notify_admin_of_system_error(e)
    # Shoot an email to the FM admin...
  end
end

Since Rescuable uses ActiveSupport::Rescuable internally, you may want to check Rails' documentation too for details on how it works.

One caveat of using rescue_from is that it always catches exceptions at the class level, so if you pass a method name to with: that method has to be a class method. Also note that this will only catch exceptions raised during an API call to the Data API server (in other words, only on actions that perform an HTTP request).

Optional modId

The Data API provides an optional modId value that gets set on a record every time you fetch or update it. This value then gets included in the API request when you save the record, and FileMaker compares it against the current value, preventing the update in case of a mismatch.

This safety feature is enabled by default in fmrest-spyke models, but you can disable it using the inheritable ignore_mod_id flag on your model classes or instances. E.g.

class Bee < FmRest::Layout
  # This disables modId for all instances and subclasses
  self.ignore_mod_id = true
end

# Or set it on instances:
bee = Bee.new
bee.ignore_mod_id # => true (set in class)
bee.ignore_mod_id = false # (affects only this instance)

You can also set it directly on FmRest::Layout if you want to disable it for your entire app:

FmRest::Layout.ignore_mod_id = true

Logging

If using fmrest-spyke with Rails then pretty log output will be set up for you automatically by Spyke (see their README).

You can also enable simple Faraday logging of raw requests (useful for debugging) by passing log: true in the options hash for either FmRest.default_connection_settings= or your models' fmrest_config=, e.g.:

FmRest.default_connection_settings = {
  host: "…",
  
  log:  true
}

# Or in your model
class LoggyBee < FmRest::Layout
  self.fmrest_config = {
    host: "…",
    
    log:  true
  }
end

You can also pass log_level to connection settings to change the severity of log output (defaults to :debug).

By default fmrest-ruby logs to STDOUT or to Rails' logger object if available. You can change this by providing your own logger object to FmRest.logger=:

FmRest.logger = Logger.new("fmrest.log")

If you need to set up more complex logging for your models you can use the faraday block inside your class to inject your own logger middleware into the Faraday connection, e.g.:

class LoggyBee < FmRest::Layout
  faraday do |conn|
    conn.response :logger, MyApp.logger, bodies: true
  end
end

Gotchas

Read about unexpected scenarios in the gotchas doc.

API implementation completeness table

FM Data API reference: https://help.claris.com/en/data-api-guide/

FM 19 Data API feature Supported by basic connection Supported by FmRest::Layout
Log in using HTTP Basic Auth Yes Yes
Log in using OAuth No No
Log in to an external data source No No
Log in using Claris ID account (FileMaker Cloud) Yes Yes
Log out Yes Yes
Get product information Manual* No
Get database names Manual* No
Get script names Manual* No
Get layout names Manual* No
Get layout metadata Manual* No
Create a record Manual* Yes
Edit a record Manual* Yes
Duplicate a record Manual* No
Delete a record Manual* Yes
Edit portal records Manual* Yes
Get a single record Manual* Yes
Get a range of records Manual* Yes
Get container data Manual* Yes
Upload container data Manual* Yes
Perform a find request Manual* Yes
Set global field values Manual* Yes
Run a script Manual* Yes
Run a script with another request Manual* Yes

* You can manually supply the URL and JSON to a FmRest connection.

Supported Ruby and Rails versions

The latest fmrest-ruby is tested against Ruby 3.0 through 3.3, and Rails (ActiveSupport) 6.1 through 7.1.

Gem development

After checking out the repo, run bin/setup to install dependencies. Then, run bundle exec rspec to run the specs. You can also run bin/console for an interactive prompt that will allow you to experiment (it will auto-load all fixtures in spec/fixtures).

To install all gems onto your local machine, run bundle exec rake all:install. To release a new version, update the version number in lib/fmrest/version.rb, and then run bundle exec rake all:release, which will create a git tag for the version, push git commits and tags, and push the .gem files to rubygems.org.

Disclaimer

This project is not sponsored by or otherwise affiliated with Claris International Inc., an Apple Inc. subsidiary. FileMaker is a trademark of Claris International Inc., registered in the U.S. and other countries.

fmrest-ruby's People

Contributors

capripot avatar emptyflask avatar hkly avatar marquete avatar pilaf avatar turino avatar woller avatar wout avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fmrest-ruby's Issues

Only mapped attributes should have accessors, the original field names should not

Currently if you define a mapped attribute, the original FileMaker attribute name will get a writer method due to how Spyke works (uses method_missing).

E.g.

class Foo < FmRest::Spyke::Base
  attributes foo: "FooBar"
end

f = Foo.new

f.FooBar = "oopsie!" # this will work, but it shouldn't!
f.foo # => "oopsie!"

Only the mapped version should work (e.g. foo= in the above example).

Handle HTTP Data API errors

Currently we only handle 500 errors with FileMaker server error codes.

The Data API has a set of standard HTTP 4xx errors that it can respond with that we're currently not accounting for, but we should. These should probably raise exceptions with their own namespaces, maybe FmRest::HTTPError (as opposed to FmRest::APIError). More discussion may be needed for settling on the right naming scheme though.

See: https://fmhelp.filemaker.com/docs/17/en/dataapi/#error-responses

Support OAuth Data API authentication

limit(1).find_some and find_one not attaching sort params to GET

I'm attempting to retrieve the latest "Term" from our FileMaker database by sorting the records by the term's end date descending and then retrieving the first record.

Doing a limit of 2 results in the sort working and the most recent two records being returned.

FileMaker::Term.sort(:fm_end_date!).limit(2).find_some

GET https://<server>/fmi/data/v1/databases/<dbname>/layouts/<layoutname>/records?_limit=2&_sort=%5B%7B%22fieldName%22%3A%22%E7%B5%82%E4%BA%86%E6%97%A5%22%2C%22sortOrder
%22%3A%22descend%22%7D%5D

But calling a limit of 1 and find_some or simply calling find_one does not attach the sort params although they exist in the Spyke Relation.

FileMaker::Term.sort(:fm_end_date!).limit(1).find_some

<FmRest::Spyke::Relation:0x00007f866817e238 @klass=FileMaker::Term, @options={:uri=>"layouts/dls_terms/records(/:id)"}, @params={}, @should_fallback=false, @limit_value=1, @query_params=[], @portal_params=[], @sort_params=[{:fieldName=>
"終了日", :sortOrder=>"descend"}]>

GET https://<server>/fmi/data/v1/databases/<dbname>/layouts/<layoutname>/records?_limit=1

I have not been able to find a place in the source code where limit(1) or find_one would be ignoring the sort params.

Autoloading of token store class

Currently you need to manually require the token store class and assign it to FmRest.token_store= as the Ruby class object.

E.g.:

require "fmrest"
require "fmrest/token_store/redis"

FmRest.token_store = FmRest::TokenStore::Redis

While we want to still allow the above example to work, it would be good if we could set the token store with a symbol and autoload it if it's one of the default token stores included with the gem. E.g.:

require "fmrest"

FmRest.token_store = :redis # this would automatically require fmrest/token_store/redis.rb and set the proper class

Add an interface for running FM scripts

Currently running FM scripts is supported when saving a record (see FmRest::Spyke::Model::Orm#perform_save_persistence), but not for other actions (delete, search and list). We need to add an interface to allow calling scripts in all those cases, as well as a method for executing scripts directly, which can be accomplished by running a list request with limit 1 (see how this nodejs library deals with it for inspiration).

This may need discussion about the final Ruby interface.

Remove Spyke dependency

Instead of having Spyke as a dependency that we patch on top of, copy the relevant bits of Spyke into this gem and modify it as needed. This should give us a lot more flexibility going forward.

Things we may want to do once we break free of Spyke:

  • Rename Model#id to Model#record_id
  • Alias Model.query as Model.where

Note: this will require adding specs for all of Spyke's features, which we currently don't have as we trust that Spyke tests those itself.

Error thrown for count where api responds with no records matching

I imagine this bubbles back to FileMaker's API choices, but if I want to query something and find the count, I can never get a count of zero because an error is thrown telling me there are no records.

FmRest::APIError::ParameterError (FileMaker Data API responded with error 401: No records match the request)

I don't want to check for this error in every place that I just want a 0 returned.

What do you think about the count method catching this error to return 0 because this is expected behavior in the application space?

Error in dependencies

Thanks for a great gem! Found a little mistake in the dependencies.

The dependencies claim that any version of ActiveRecord is supported, however it uses features of ActiveSupport that do not exist before 5.2.3, e.g. this snippet from lib/fmrest/spyke/model/associations.rb

class_attribute :portal_options, instance_accessor: false,
                                           instance_predicate: false,
                                           default:           {}.freeze

Sending any options to the class_attribute method is not supported before 5.2.3.

Support for namespaced model names

Currently when using has_portal and referencing a model in the same namespace you need to also pass :class_name and :portal_key manually as the gem can't figure out the Ruby namespaces. E.g.:

module App
  class Family < FmRest::Spyke::Base
    has_portal :member, class_name: "App::Member", portal_key: "Family_Member"
  end
end

Let's also look at how ActiveRecord's relations deal with this... maybe we don't want to default :class_name to same namespace after all 🤔

We probably do want to strip the namespace for the default :portal_key though.

A similar thing happens with the default layout, which currently defaults to the class name, but includes the namespace. E.g.:

module App
  class Family < FmRest::Spyke::Base
  end
end

App::Family.layout # => "App::Family"

We probably want it to default to the class name with namespace removed, e.g. just "Family" in the above example.

Provide a way to request no portals

Currently FmRest::Spyke::Relation doesn't provide an easy way to specify you want no portals.

Some ideas for the API for this:

Model.portal(nil) # could work as the first call, but multiple calls to `.portal` currently add to the list of portals... how to deal with that?

Model.portal([]) # ugly?
Model.clear_portals # also ugly?

Note: the Data API needs portal=“[]” for this to work.

Better errors when configs are missing

Currently if configs are missing you don't get very useful errors, would be good to have a FmRest::ConfigError exception detailing what's wrong or something like that perhaps.

Allow configs with indifferent access

Currently FmRest's configs require symbol keys. Since these configs are often loaded from YAML files though it would be good if we could support indifferent access (e.g. strings OR symbols for keys).

E.g., currently this fails:

# fmrest_config.yml
host: foo.com
database: Database
FmRest.config = YAML.load_file("fmrest_config.yml")

When trying to connect with the above example the gem will complain that it can't find :host in the config hash, etc.

Note that YAML does support symbol keys (just prefix : to each key), but that's kinda ugly and not great to require it.

BTW, we don't want to use Rails' HashWithIndifferentAccess.

Query API enhancement: change default OR behavior to AND

Currently calling Model.query(…).query(…) does an OR query of both query call arguments, which is the opposite of what ActiveRecord does and actually prevents composable scopes.

We need to change this behavior to AND, but keeping the ability to perform OR queries if needed.

Set Rails’ ActionDispatch FmRest exceptions handling

See: https://makandracards.com/makandra/41790-how-rails-chooses-error-pages-404-500-for-exceptions

Rails' ActionDispatch will by default handle ActiveRecord::RecordNotFound and other exceptions appropriately and deliver a 404.

It is possible to extend this ActionDispatch behavior with more known exceptions (see above link), so it would be good if the gem could have a railstie script where to set this up by default. We want to add support for FmRest::APIError::RecordMissingError especially, mapping it to a 404 error, but other errors may be mappable as well.

Broken on Rails 7.1

I'm running into a few errors after upgrading to Rails 7.1:

NoMethodError: undefined method `attribute_method_matchers' for FmRest::Spyke::Base:Class

          self.attribute_method_matchers.shift
              ^^^^^^^^^^^^^^^^^^^^^^^^^^
Did you mean?  attribute_method_patterns
               attribute_method_patterns?
               attribute_method_patterns=
               attribute_method_affix
from lib/ruby/gems/3.1.0/gems/fmrest-spyke-0.24.0/lib/fmrest/spyke/model/attributes.rb:25:in `block in <module:Attributes>'

attribute_method_matchers was changed to attribute_method_patterns here

NameError: undefined local variable or method `mapped_attributes' for Filemaker::Events:Class

            self.mapped_attributes = mapped_attributes.merge(from => to.to_s).freeze
                                     ^^^^^^^^^^^^^^^^^
from lib/ruby/gems/3.1.0/gems/fmrest-spyke-0.24.0/lib/fmrest/spyke/model/attributes.rb:107:in `_fmrest_define_attribute'

as well as a TypeError: superclass mismatch for class Foo in my classes that subclass FmRest::Layout('whatever')

I haven't had a chance to look into these other two issues yet...

Implement CLI

Providing a CLI would be very useful for quick testing and other uses outside of Ruby projects.

String.match? call forces Ruby 2.4.x+

The String.match? function was added to Ruby in 2.4.x; this function is invoked once in fmrest: https://github.com/beezwax/fmrest-ruby/blob/master/lib/fmrest/v1/token_session.rb#L74. Since String.match? throws an error on older Ruby versions, this prevents compatibility with 2.3.x and before.

Replacing host.match?(/\Ahttps?:\/\//) with an equivalent function (e.g. host =~ /\Ahttps?:\/\//) would allow for fmrest to be used with older versions of ruby. AFAICT, the advantage of .match? is that it doesn't override the $~ variable, which isn't relevant here.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.