Coder Social home page Coder Social logo

spraypaint.js's People

Contributors

bilouw avatar calebuharrison avatar cdidier80 avatar darronz avatar dishwasha avatar e-pavlica avatar eldub avatar gstark avatar jannishuebl avatar japestrale avatar jimmy89 avatar leipeleon avatar mkessler avatar mqchau avatar nobitagit avatar reizar avatar richmolj avatar sherbrow avatar wadetandy avatar wkstar avatar yoann-hellopret avatar zooip avatar zpencerq avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  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

spraypaint.js's Issues

Bulk creating

How can use this library to bulk create?
There is nested updating, but I didn't see anything about bulk create (with nested attributes).

422 Validation Errors

Hi,

We are using Spraypaint with another JSONAPI compliant backend. The backend returns validation errors as 400's, while Spraypaint explicitly looks for 422's. The result is an "invalid json" exception being thrown, when in fact it's not. This also means I am cut off short when trying to get access to the errors payload.

Can you make the 422 validation status code configurable? or at least refactor the code to spit out the payload so I can handle it myself?

Thanks

Create Model Instance with Raw HTTP Request

With some edge-cases, I need to do some manual requests, since I can't use Spraypaint for arbitrary endpoints.

Those requests return a Graphiti Response (JSON:API Spec) with associations. How can I transform this request into a Model Instance?

Best regards

create record and associate it to another record in one request

Hi,

Let's say i want to create a Post and associate it to an already created Comment. (in my example, Comment belongs_to Post and Post has_one Comment)
This is what i'm doing:

let post = new Post({ comment: new Comment})
post.comment.id = commentIdToAssociate
post.save({ with: 'comment.id' })

This code will create a new comment associated to the post with method: "create".
I don't want to create this comment, i want to associate it to my post (method: "update")

I want this kind of body:

"relationships": {
	"comment": {
		"data": {
			"id": "64520319-03be-4c29-b86b-199c1038ba23",
			"type": " comments",
			"method": "update"
		}
	},
}

How can i do that ?

Thanks.

State Syncing : EventBus.dispatch is not a function

Hi, thanks for your great work with Spraypaint & Graphiti !

I can't use the state sync. When I add ApplicationRecord.sync = true

I got EventBus.dispatch is not a function

It seems that we should call EventBus.default.dispatch to make it work

I tried with v0.10.8 and 0.10.9

Angular: AoT compile error with Model decorator

Using Angular 8.2.14 and spraypaint 0.10.14, when compiling with AOT enabled, this error is shown:

ERROR in spraypaint/lib-esm/decorators.ts(14,15): Error during template compile of 'Model'

Only initialized variables and constants can be referenced in decorators because the value of this variable is needed by the template compiler.

I'm not really sure how to proceed with this issue.

[Question] Custom routes and actions

I was wondering how to implement fetching the currently active user.
Normally, I would just call /api/v1/user. How can I achieve this behaviour with spraypaint?

Besides, a related question is concerning actions (which are indicated in the route). For example I would like a post by performing a get request to /api/v1/posts/1/like. Is there a possibility to add something to the route in a find request?

Thanks in advance!

Logic issue in validation-error-builder

I believe the code here: https://github.com/graphiti-api/spraypaint.js/blob/master/src/util/validation-error-builder.ts#L71 is lacking an additional presence check of the r.temp_id and.or meta["temp-id"] as are done in many other places in the code.

For a relationship where there are no temporary ids on any elements, this causes the first element to be returned always.

Also, it seems there is a type issue with ids. For all the records I fetch via spraypaint the object's id is a string. However, the id coming back in the validation errors is an integer. And thus the === won’t match.

I have made a branch of employee-directory which demonstrates this issue: https://github.com/gstark/employee_directory/tree/demonstrate-validation-error-builder-bug

To run:

bundle && rails db:setup && rails server

In a separate shell:

cd js

yarn install

node demo.js

Sideposting verbs do not seem to comply with JSONAPI specification

Hi,

When attempting to persist relationships, the addition of the HTTP verb in the method field of the relationship's data payload may not be strictly JSONAPI-compliant.

As per the specification:

A resource object MUST contain at least the following top-level members:

id
type
Exception: The id member is not required when the resource object originates at the client and represents a new resource to be created on the server.

In addition, a resource object MAY contain any of these top-level members:

attributes: an attributes object representing some of the resource’s data.
relationships: a relationships object describing relationships between the resource and other JSON:API resources.
links: a links object containing links related to the resource.
meta: a meta object containing non-standard meta-information about a resource that can not be represented as an attribute or relationship.

This additional field provokes errors in some server-side implementations, as a non-valid data representation.

Would there be a way to deal with sideposting in another fashion? Or allow the developer to choose whether or not to enable this "Graphiti-only" behaviour?

.save doesn't serialize attributes? : (RESOLVED - TypeScript compatibility issue)

Hey everyone,

(see solution at end of post, or read the entire thread)

I'm starting a new project in VueJS, coming from an Ember project, and both will be using the same JSON:API backend.
I looked around for libraries to Help with the JSON:API in the Vue project and landed on Spraypaint after a bit of search, and it seems a perfect match, but I'm having an issue that I can't simple figure out.

This is my base model:

import { Model, SpraypaintBase } from 'spraypaint';
import envConfig from '@/configEnv.js';

@Model()
export class ApplicationRecord extends SpraypaintBase {
  static baseUrl = envConfig.url;
  static apiNamespace = '/api';
}

And this is my model

import { Model, Attr } from 'spraypaint';
import { ApplicationRecord } from './BaseAssemblyModel';

@Model()
export class Client extends ApplicationRecord {
  static jsonapiType = 'project-clients';

  @Attr() name: string | null;
}

I can query just fine, but I have an issue with saving (in this case, updating an existing model, but the same happens when creating a new one)

When I ask to await client.save() the json that is sent to the backend doesn't include the name property defined in the model.
If I inspect the data however, it's there:
image

However, the serialized JSON doesn't have it. And apparently, from what I can investigate... it's because of this:
image
The attributes property returns empty all the time.

Digging more, we find this:
image

The typedAttributes always returns empty :(. Further digging, and It seems the model._attributes is never set, I've put debugger statements, live debugging, but don't find a place where _attributes is set and it's always empty.

It seems such a basic thing, that I'm not sure what I'm doing wrong... any ideas? Thanks :)


Solution: The compilerOptions of tsconfig.json needed to be changed, we had target: esnext and needed to change to target:es2017. Thanks @bilouw for the help

model.isDirty() reactivity for VueJS

I'd like to be able to use the model.isDirty() method within VueJS computed values for immediate DOM updates when a model becomes dirty. I believe that this currently does not work because of the way VueJS observes referenced objects within a computed method for property access on first run and subsequently assigns getter/setter methods with which it can determine that a change has been made and therefore trigger a regeneration of the computed value.

My first thought was to touch/read all model attribute methods within isDirty(), but because of the way it (rightfully) short-circuits checking all objects when the first dirty object is found, it wouldn't trigger reactivity in all places. Perhaps a method on the models that could be called in tandem within the computed value?

Or perhaps a better implementation if my need is valid for this project?

Here is a sample of what I'm currently thinking of doing.

HTML:

  div.alert(v-if="assetChanged") You have unsaved data!

VueJS component:

computed:
  assetChanged: ->
    if @.asset
      relationships = ['relation1', 'relation2': 'child-relation']
      @.asset.establishReactivity(relationships)
      @.asset.isDirty(relationships)

Same jsonapiType for 2 resources in two different namespaces throws Uncaught Error

I have 2 resources in different namespaces with the same base url. When I try and set the jsonapiType for both of these to the same string - my_type in my models, I receive:

Uncaught Error: Type "<my_type>" already registered on base class function Subclass() {
                return _super !== null && _super.apply(this, arguments) || this;

I had a little look and I think it's down to JsonapiTypeRegistry class.

Will be happy to submit a PR.

support for links in JSON:API?

Hi, in my own backend that implements JSON:API, I use the links as indicated the documentation of JSON:API like this https://jsonapi.org/format/#document-links . I wonder if you guys plan to support it? I'm thinking of something like the @Attr you already did:

@Model()
class Person extends ApplicationRecord {
  static jsonapiType = "people"

  @Link() self: string
}

If you guys think it's good idea to support it then I can attempt a PR.

server side usage with multiple users

Is it possible to specify a different authorization header per request?

I have a server side api (NestJS) that makes calls to another jsonapi service and I was hoping to use Spraypaint to simplify those requests.

The current implementation seems to assume this is running in a browser and uses static methods to set the authorization header.

Everything works great calling external api except the auth methods are static.

This works but seems very unsafe

Widget.setJWT(token);
const widgets = Widget.per(10).all();
Widget.setJWT("");

Is there another way to handle this scenario with Spraypaint?

Add Typecasting for attributes

The lack of typecasting on attributes is preventing the ability to override getters and setters for attributes when using typescript.

validation-error-builder assigns errors to the wrong list element

Hello you wonderful people,

I found an error in the validation-error-builder in Line 74. The error can be summarised as such:

  1. We have an author with two books added via a hasMany relationship. All three models are already persisted and have an id. book.title must be a non-empty string.
  2. We set the title of the second book in the list to an empty string and save the book via the author (nested write).
  3. The validation error will now be added to the first book in the list, because it has no tempId and neither has the server response. So Line 74 will always evaluate
    r.temp_id === meta["temp-id"]
    to
    undefined === undefined.

I'll submit a PR with a test case right away. :)

keyCasing for server calls

When, say, filtering, we send what the server understands - this means (by default) underscores and not camelCase. But we should support sending camelCase and converting to underscores (and other casings as well - we already have primitives for this)

created_at not coming through to Spraypaint model

Hi There,

'created_at' appears in the api and I can see the raw json but comes through as 'undefined' in the spraypaint model in my angular frontend. Note: we are using the Mongoid adapter which I will include below as well.

How can I debug the spraypaint code to see why the attributes are not coming through to the spraypaint model?

Please advise

# order.rb Model:

class Order
  include Mongoid::Document
  include Mongoid::Timestamps

  field :created_at, type: DateTime

end


# order_resource.rb Resources:

class OrderResource < ApplicationResource
  self.adapter = Graphiti::Adapters::Mongoid

  attribute :created_at, :datetime

end

// Spraypaint angular frontend model:
// OrderResource.ts

@Model()
export class OrderResource extends ApplicationRecord {
  static jsonapiType = "orders"

@Attr() created_at: string

}


# mongoid.rb Adapter

module Graphiti
  module Adapters
    class Mongoid < ::Graphiti::Adapters::Abstract

      def base_scope(model)
        model.all
      end

      def paginate(scope, current_page, per_page)
        scope.page(current_page).per(per_page)
      end

      def resolve(scope)
        scope.to_a
      end

      def order(scope, attribute, direction)
        scope.order(attribute => direction)
      end
    end
  end
end


SpraypaintBase undefined for global instance

I was trying to use the library without modules, by directly loading from cdn, and SpraypaintBase was undefined. As far as I understand, there issue is here:

export const isProd = process.env.NODE_ENV === "production"

since process is not defined. I removed the resulting lines from the generated js file, and now it seems to work ok.

How to preload an instance without making a network request?

Hello,

Suppose I use Spraypaint in a React app that is SSR'd. In responding to a request, the frontend server fetches data & instantiates models using Spraypaint/Graphiti layer and, after generating markup, sends markup and data to the client.

The data is a JSON representation of up-to-date models. How can I instantiate those models on the client without making a network request?

I believe I should use middleware to do this, and some methods from the library that are exposed to us.

This looked relevant: #79

Big apology if this is obvious, I've always struggled with Spraypaint because I come from a node background and not a RoR background.

Polymorhpic fields

How to handle polymorphic fields?
I suggest adding a new association that takes a list of resource classes, and figure out the proper class to be used based on the type.
Input will be tricky though.

Add auto stringify on filters for non primative types

It would be really nice if I could do this:
Resource.where({ createdAt: { lte: new Date() } }).all()

But in order for spraypaint to recognise the filter, I need to stringify it first, otherwise it silently ignores it.
Resource.where({ createdAt: { lte: new Date().toUTCString() } }).all()

Maximum call stack exceeded in unlisten when there are models with bi-directional relationships

I'm really enjoying using Spraypaint (currently in React, and potentially next in React Native).

I am getting an uncaught RangeError: Maximum call stack size exceeded when model instances are trying to unlisten. I have model syncing turned on:

ApplicationRecord.sync = true

I have two models, Course and Group. A course belongs to a group, and a group has many courses.

const Course = ApplicationRecord.extend({
  static: {
    jsonapiType: 'courses',
  },
  attrs: {
    name: attr(),
    group: belongsTo(),
  }
})
const Group = ApplicationRecord.extend({
  static: {
    jsonapiType: 'groups',
  },
  attrs: {
    name: attr(),
    courses: hasMany(),
  }
})

I am loading both from the API:

Group.includes({ courses: 'group' }).find(123)

I get the RangeError: Maximum call stack size exceeded when the .unlisten method is called on all instances. Those models are recursively checking their relationships and are getting stuck in a loop when traversing from Group -> Course -> Group -> Course -> etc.

It hits the exception on model.ts line 548:

Object.keys(this.relationships).forEach(k => {
  let related = this.relationships[k]

  if (related) {
    if (Array.isArray(related)) {  // <-- Exception happens here
      related.forEach(r => r.unlisten())
    } else {
      related.unlisten()
    }
  }
})

Let me know if this is a known issue or you need any other documentation! Thanks.

I'm Running:

Spraypaint v0.10.23
React v16.13.1
Node v14.4.0

How do I use ES6 syntax

I'm confused because the docs have Typescript and ES5 syntax, but I generally write ES6.

I can't figure out how to instantiate my models with ES6. I'd really rather not write TS.

This works:

const ApplicationRecord = SpraypaintBase.extend(
  {
    static: {
      baseUrl: window.location.origin,
      apiNamespace: "/api"
    }
  }
)

const Tag = ApplicationRecord.extend(
  {
    static: {
      jsonapiType: "tags"
    },
    attrs: {
      name: attr(),
    },
    methods: {
      sayHi: function() {
        return `Hi I'm a ${this.name} tag`
      }
    }
  }
)

But I'd prefer to write something like this, thought I'm not sure how to define attributes etc... Please help...

export default class ApplicationRecord extends SpraypaintBase {
  static baseUrl = window.location.origin
  static apiNamespace = "/api"
}

export default class Tag extends ApplicationRecord {
  static jsonapiType = "tags"

 //attributes ... etc

  sayHi() {
    return `Hi I'm a ${this.name} tag`
  }
}

Object-type attributes will always be dirty no matter if they are modified or not

Hi, when I try to update a model, it will send all object attributes to server which is not really ideal since sometimes objects are huge and donot meant to be updated.

Found this line of code here:

} else if (prior !== current) {

Update to JSON.stringify(prior) !== JSON.stringify(current) would solve the problem.

Also, this similar issue here:

if (self[k] !== attrs[k] && !changes[k]) {

Inside self[k] !== attrs[k] && !changes[k], if an attribute is an object, self[k] !== attrs[k] might always return true and !changes[k] will always return false. So, model.ts#L480 will never happen.

How to create a patch request with some meta data

I have been using vue, typescript, jsorm, nodejs,jsonapi, nest.js for the application,

need to issue Patch request to people/:id with some meta data,
but i am not able to find any document to do it, like do I use something like this,

@Model({ jsonapiType: 'people/:id', }) export default class ValidateEmail extends AppAuthRecord { @Attr() id!: string; }
suppose if the user id is 5, which we are fetching from the url of frontend, then how could i append to the new http request and make it a fetch request.

Clarification regarding compatibility

Hi,

I got here by asking for support on the jsorm slack, and was directed to an example application which uses this library.

My concern is that I am using jsorm for its compatibility with jsonapi, coupled with a django backend.

I was hoping it could be made clear either in this repository or in the documentation whether spraypaint will maintain jsonapi compatibility, or if there is a chance graphiti may diverge from jsonapi and take spraypaint with it.

Polymorphic Relationship setup

Say I have a Project model defined, which in the ProjectResource defined on the backend has a relationship to Board defined as such:

polymorphic_has_many :boards, as: :boardable

The BoardResource includes

  attribute :boardable_id, :uuid
  attribute :boardable_type, :string
  polymorphic_belongs_to :boardable do
    group_by(:boardable_type) do
      on(:Client)
      on(:Company)
      on(:Project)
    end
  end

Using Spraypaint on the Frontend, I have the Project and Board models defined as such:

export const Project = ApplicationRecord.extend({
    static: {
        jsonapiType: "projects"
    },
    attrs: {
        ...
        boards: hasMany()
    }
});

export const Board = ApplicationRecord.extend({
    static: {
        jsonapiType: "boards"
    },
    attrs: {
        name: attr(),
        boardable_id: attr(),
        boardable_type: attr()
    }
})

When I query for a project using Project.find(projectId) I'm getting the following error:

Error: "Unknown type "boards""

I've tried including boardable: belongsTo() in the Board model on the frontend, but that doesn't help.

Can someone point me in the right direction?

Thanks in advance!

throws an error when an attribute is named 'kind'

Hi There

We're using spraypaint together with typescript and stumbled across an error when an attribute has the name kind.

this code here

import { Attr, Model, BelongsTo, SpraypaintBase } from "spraypaint";

@Model()
class Person extends SpraypaintBase {
  @Attr() name: string;
}

@Model()
class Dog extends SpraypaintBase {
  @Attr() name: string;
  @Attr() kind: string; // if renamed, things are working properly

  @BelongsTo() person: Person; // if removed, things are working properly
}

leads to the following error

TypeError: context.attributes is undefined
    getter attribute.ts:82
    get attribute.ts:93
    isModernDecoratorDescriptor decorators.ts:55
    fn decorators.ts:318
    __decorate index.ts:5
    <anonymous> index.ts:34
    <anonymous> index.ts:42
    <anonymous> index.ts:43

We figured out that the error only occurs if there is some kind of relationship on the model with the attribute.
See minimal demo here: https://stackblitz.com/edit/typescript-xyevlu?file=index.ts

Does someone know whats the problem here?

Thanks in advance.

[Maybe bug?] Empty array does not show up in nested write

E.g. a Company that has many Employee

company.employees = [];
company.save({ with: 'employees' });

This is the payload which doesn't include the relationships field:

{ "data": { "type": "companies" } }

It seems to be because of this line explicitly setting it to null
https://github.com/graphiti-api/spraypaint.js/blob/master/src/util/write-payload.ts#L115

I just wonder this is intended? (JSON:API spec allows replacement of the entire array: https://jsonapi.org/format/#crud-updating-resource-relationships)

Nested includes don't work for return scopes

const bazScope = Baz.selectExtra(['baz_optional_field']);

const returnScope = Foo.selectExtra(['foo_optional_field']).includes(['foo_sideload']).
  merge({bars: Bar.merge({ baz_sideload: bazScope }).includes(['bar_sideload']});
parent.save({ returnScope });

This will call a PATCH URL like /parents?extra_fields[foos]=foo_optional_field&extra_fields[bazes]=baz_optional_field&include=foo_sideload

As you can see the extra_fields all gets constructed correctly, but the include only has the Foo sideloads and not the nested Bar sideloads.

Allow fetching as POST requests

Given that the URL as passed in as a string to beforeFilters, it's unable to be modified.

The use case for us is that we have an endpoint that we need to use HTTP POSTs for reads due to very long filter strings. beforeFilters allows the HTTP method to be changed to POST, and allows for translating the query string to a POST body, but we're unable to strip the query string from the URL.

I'd imagine the fix for this would be nesting the url and options filter args under an object, which would be a breaking change. I'd be happy to write a PR if it's decided that this would be an acceptable feature.

Another approach might be to check to see if the method has changed from GET to POST/PUT/PATCH and do that translation and query string removal automatically.

Dirtiness checks when saving relations

I have a join table model C that joins models A and B (i.e. on the backend C has two columns a_id and b_id and on the frontend C has two BelongTo's for each of a and b).

If I do the following, saving c does not work.

a = (await A.find(1)).data
b = new B({field: 'value'})
c = new C({a, b})
c.save(with: ["a", "b"])

This fails with the message "A must exist".

The solution is to do c.save(with: ["a.id", "b"]).

IMO this isn't exactly intuitive. I believe it's checking for the dirtiness of a and b to determine if they should be sent up to the server for saving. In this case a isn't dirty and b is dirty (it's new) so only b gets sent up. IMO it should be checking for the dirtiness of c.a and c.b aka the dirtiness of the pointers rather than the things the pointers point to.

Thoughts?

Model to json payload

Is there a way to generate json payload from a model's instance? Right now I have this in my code which seems to work, but curious if an alternative exists

import { WritePayload } from 'spraypaint/lib-esm/util/write-payload';

@Model()
class Model extends SpraypaintBase {
  toJsonPayload = () => {
    return new WritePayload(this).asJSON();
  };
}

[Feature] Concurrency Management (isSaving flag)

I really like the idea of saving a resource as easily as resource.save().
Even cooler would be if the saving (or loading) status would be available on the resource, too. This would allow to just bind resource.isSaving e.g. to a loading indicator without having to create additional variables.

Enable custom http client like Axios

It would be really helpful to allow overwriting or mock the default http client. This is especially useful when the existing app is based on axios with many predefined axios interceptors and also when using server side rendering.

Does spraypaint support incremental loading?

If I define a hasOne relationship like:

const Employee = ApplicationRecord.extend({
  static: {
     jsonapiType: 'employees',
  },
  attrs: {
     name: attr(),
    manager: hasOne(),
  },
});

I want to be able to fetch an employee, and the later, optionally fetch the manger, e.g.

var {e} = await Employee.first()
var {m} = await e.manager()
console.log(m.name)

Spraypaint doesn't seem to support following links that JSON:API returns in the original request. The ability to load sub-records incrementally is one of the great promises of JSON:API, but it seems that in the current implementation I need to fetch all the data in my original request, using include('manager').

Am I missing how to do incremental loading?

Multiple errors per attribute are not supported

Spraypaint maps errors in a JSON:API response to model attributes by looking at the attribute metadata.

{
  "errors": [
    {
      "code": "bad_request",
      "status": "400",
      "title": "Request Error",
      "detail": "...",
      "meta": {
        "attribute": "name",
        "message": "..."
      },
      "source": {
        "pointer": "/data/attributes/name"
      }
    }  
  ]
}

Spraypaint models currently only support one error per attribute, so if a response includes multiple errors for a single attribute only the last one will be exposed in the errors object.

https://github.com/graphiti-api/spraypaint.js/blob/master/src/util/validation-error-builder.ts#L56

To allow for multiple errors per attribute we could do something like below, however it would be a breaking change:

diff --git a/src/util/validation-error-builder.ts b/src/util/validation-error-builder.ts
index 2e4fac5..24db9bd 100644
--- a/src/util/validation-error-builder.ts
+++ b/src/util/validation-error-builder.ts
@@ -53,14 +53,15 @@ export class ValidationErrorBuilder<T extends SpraypaintBase> {
     error: JsonapiError
   ) {
     let attribute = this.model.klass.deserializeKey(meta.attribute)
-    errorsAccumulator[attribute] = {
+    errorsAccumulator[attribute] = errorsAccumulator[attribute] || [];
+    errorsAccumulator[attribute].push({
       title: error.title as string,
       code: error.code as string,
       attribute: meta.attribute,
       message: meta.message,
       fullMessage: attribute === "base" ? meta.message : error.detail,
       rawPayload: error
-    }
+    })
   }
 
   private _processRelationship<R extends SpraypaintBase>(

Support for batch processing on response with status 202?

Hi, I'm looking to follow this JSON:API recommendation for batch processing:

https://jsonapi.org/recommendations/#asynchronous-processing

My use case is that I have a delete that is super slow (many minutes). I would like to put that into the background job, then return the job details in the 202 response like that document says.

I look into our source code and see this

async destroy(): Promise<boolean> {

That means it will ignore the response with status 202.

I realize that there is a difficulty with implementing this feature: the model returned by the response is usually completely different from the model that is deleted. I'm not sure how to look up the appropriate model based on the type in the response.

Maximum call stack size exceeded

Hi,

I have the following error on an empty Nuxt project based on typescript (npx create-nuxt-app . followed by https://typescript.nuxtjs.org/guide/setup.html)

 ERROR  INTERNAL ERROR(undefined,undefined) Maximum call stack size exceeded                                                                                                                                       nuxt:typescript 14:25:58
stack trace:
RangeError: Maximum call stack size exceeded
    at isRelatedTo (/workspaces/app/node_modules/typescript/lib/typescript.js:43632:33)
    at someTypeRelatedToType (/workspaces/app/node_modules/typescript/lib/typescript.js:44028:35)
    at isRelatedTo (/workspaces/app/node_modules/typescript/lib/typescript.js:43752:34)
    at typeRelatedToEachType (/workspaces/app/node_modules/typescript/lib/typescript.js:44013:35)
    at isRelatedTo (/workspaces/app/node_modules/typescript/lib/typescript.js:43730:34)
    at typeRelatedToSomeType (/workspaces/app/node_modules/typescript/lib/typescript.js:43913:35)
    at isRelatedTo (/workspaces/app/node_modules/typescript/lib/typescript.js:43719:34)
    at eachTypeRelatedToType (/workspaces/app/node_modules/typescript/lib/typescript.js:44040:35)
    at isRelatedTo (/workspaces/app/node_modules/typescript/lib/typescript.js:43715:25)
    at isPropertySymbolTypeRelated (/workspaces/app/node_modules/typescript/lib/typescript.js:44717:28)

This error occurs as soon as I use the decorator @Model()
example: at the moment, I only define the ApplicationRecord like this:

import { SpraypaintBase, Model } from 'spraypaint';

@Model()
export default class ApplicationRecord extends SpraypaintBase {
  static baseUrl = 'http://localhost:3000';
  public static apiNamespace = '/api/v1';
}

If I remove the decorator, no error is raised.

for info:

node@d92271203242:/workspaces/app$ yarn dev
yarn run v1.19.1
$ nuxt -H 0.0.0.0 -p 3001

   ╭───────────────────────────────────────────╮
   │                                           │
   │   Nuxt.js v2.10.2                         │
   │   Running in development mode (spa)       │
   │                                           │
   │   Listening on: http://localhost:3001/    │
   │                                           │
   ╰───────────────────────────────────────────╯

ℹ Preparing project for development                                                                                                                                                                                                14:33:39
ℹ Initial build may take a while                                                                                                                                                                                                   14:33:39
✔ Builder initialized                                                                                                                                                                                                              14:33:40
✔ Nuxt files generated                                                                                                                                                                                                             14:33:40
ℹ Starting type checking service...                                                                                                                                                                                nuxt:typescript 14:33:56
ℹ Using 1 worker with 2048MB memory limit                                                                                                                                                                          nuxt:typescript 14:33:56

✔ Client
  Compiled successfully in 20.55s

 ERROR  INTERNAL ERROR(undefined,undefined) Maximum call stack size exceeded                                                                                                                                       nuxt:typescript 14:34:17
stack trace:
RangeError: Maximum call stack size exceeded
    at getPropertyOfObjectType (/workspaces/app/node_modules/typescript/lib/typescript.js:39024:31)
    at isKnownProperty (/workspaces/app/node_modules/typescript/lib/typescript.js:50819:21)
    at isKnownProperty (/workspaces/app/node_modules/typescript/lib/typescript.js:50828:25)
    at hasCommonProperties (/workspaces/app/node_modules/typescript/lib/typescript.js:45147:21)
    at isRelatedTo (/workspaces/app/node_modules/typescript/lib/typescript.js:43692:58)
    at typeRelatedToSomeType (/workspaces/app/node_modules/typescript/lib/typescript.js:43913:35)
    at isRelatedTo (/workspaces/app/node_modules/typescript/lib/typescript.js:43719:34)
    at eachTypeRelatedToType (/workspaces/app/node_modules/typescript/lib/typescript.js:44040:35)
    at isRelatedTo (/workspaces/app/node_modules/typescript/lib/typescript.js:43715:25)
    at isPropertySymbolTypeRelated (/workspaces/app/node_modules/typescript/lib/typescript.js:44717:28)


ℹ Version: typescript 3.6.4                                                                                                                                                                                        nuxt:typescript 14:34:17
ℹ Time: 15674ms                                                                                                                                                                                                    nuxt:typescript 14:34:17
ℹ Waiting for file changes                                                                                                                                                                                                         14:34:17
ℹ Memory usage: 510 MB (RSS: 621 MB)                                                                                                                                                                                               14:34:17

When side-posting, doesn't find relatedObject for deeply nested validation errors

Forgive me if this is an operator error, but I think we're seeing Spraypaint being unable to cope with graphiti produced validation errors on (deeply) nested relationships.

We have a Delivery 1 <> n ContentsVersions 1 <> n Contents relationship which is modelled server-side roughly like so:

# Models:

class Delivery < ApplicationRecord
  has_many :contents_versions
end

class ContentsVersions < ApplicationRecord
  belongs_to :delivery
  has_many :contents

  validates :total_price, numericality: { greater_than: 0 }
end

class Contents < ApplicationRecord
  belongs_to :contents_version
end

# Resources:

class DeliveryResource < ApplicationResource
  attribute :courier_name, :string

  has_many :contents_versions
end

class ContentsVersionResource < ApplicationResource
  attribute :total_price, :big_decimal

  belongs_to :delivery
  has_many :contents
end

class ContentResource < ApplicationResource
  attribute :product_id, :string,
  attribute :qty, :integer

  belongs_to :contents_version
end

Our Spraypaint models correspondingly look something like this:

const Delivery = ApplicationRecord.extend({
  attrs: {
    contents_versions: hasMany(),
    courier_name: attr(),
  },
  static: {
    jsonapiType: 'deliveries',
  },
});

const DeliveriesContentsVersion = ApplicationRecord.extend({
  attrs: {
    contents: hasMany(),
    delivery: belongsTo(),
    delivery_id: attr(),
  },

  static: {
    jsonapiType: 'contents_versions',
  },
});

const DeliveriesContent = ApplicationRecord.extend({
  attrs: {
    contents_version: belongsTo(),
    contents_version_id: attr(),
    product_id: attr(),
    qty: attr(),
  },

  static: {
    jsonapiType: 'contents',
  },
});

When we try to create a Delivery side-posting a ContentsVersion with a Content, but the ContentsVersion fails to validate server-side, we get this error:

TypeError: "relatedObject is undefined"
    _processRelationship validation-error-builder.js:56
    apply validation-error-builder.js:20
    apply validation-error-builder.js:16
    apply validation-error-builder.js:8
    _handleResponse model.js:764

The error gets raised when trying to parse the error related to the Contents#content_version presence validation. Please find a full copy of our version of the _processRelationship function further below.

Our request payload looks like this:

{
    "data": {
        "attributes": {
            "courier_name": "DPD"
        },
        "relationships": {
            "contents_versions": {
                "data": [
                    {
                        "temp-id": "temp-id-12",
                        "method": "create",
                        "type": "contents_versions"
                    }
                ]
            }
        },
        "type": "deliveries"
    },
    "included": [
        {
            "temp-id": "temp-id-12",
            "attributes": {
                "total_price": "0.0"
            },
            "relationships": {
                "contents": {
                    "data": [
                        {
                            "temp-id": "temp-id-13",
                            "method": "create",
                            "type": "contents"
                        }
                    ]
                }
            },
            "type": "contents_versions"
        },
        {
            "temp-id": "temp-id-13",
            "attributes": {
                "product_id": "1",
                "qty": 1
            },
            "type": "contents"
        }
    ]
}

Note that this payload format seems to be correct. When we send data that passes the validation, all resources get created correctly. With invalid data, however, the servers responds as follows:

{
    "errors": [
        {
            "status": "422",
            "code": "unprocessable_entity",
            "source": {
                "pointer": "/data/attributes/total_price"
            },
            "meta": {
                "relationship": {
                    "attribute": "total_price",
                    "temp-id": "temp-id-12",
                    "name": "contents_versions",
                    "code": "zero_price",
                    "type": "contents_versions",
                    "message": "must be larger than 0"
                }
            },
            "title": "Validation Error",
            "detail": "Total price must be larger than 0"
        },
        {
            "status": "422",
            "code": "unprocessable_entity",
            "source": {
                "pointer": "/data/relationships/contents_version"
            },
            "meta": {
                "relationship": {
                    "attribute": "contents_version",
                    "temp-id": "temp-id-13",
                    "name": "contents",
                    "code": "blank",
                    "type": "contents",
                    "message": "must exist"
                }
            },
            "title": "Validation Error",
            "detail": "Contents version must exist"
        }
    ]
}

From this error response, Spraypaint does not seem able to figure out that the second object relates to the deeply nested Delivery => ContentsVersion => Content.

ValidationErrorBuilder.prototype._processRelationship = function (model, meta, err) {
        var relatedObject = model[model.klass.deserializeKey(meta.name)];
        if (Array.isArray(relatedObject)) {
            relatedObject = relatedObject.find(function (r) {
                return r.id === meta.id || r.temp_id === meta["temp-id"];
            });
        }
        if (meta.relationship) {
            this._processRelationship(relatedObject, meta.relationship, err);
        }
        else {
            var relatedAccumulator_1 = {};
            this._processResource(relatedAccumulator_1, meta, err);
            // make sure to assign a new error object, instead of mutating
            // the existing one, otherwise js frameworks with object tracking
            // won't be able to keep up. Validate vue.js when changing this code:
            var newErrs_1 = {};
            Object.keys(relatedObject.errors).forEach(function (key) {
                newErrs_1[key] = relatedObject.errors[key];
            });
            Object.keys(relatedAccumulator_1).forEach(function (key) {
                var error = relatedAccumulator_1[key];
                if (error !== undefined) {
                    newErrs_1[key] = error;
                }
            });
            relatedObject.errors = newErrs_1;
        }
    };

Annotated with payloads:

js

Cyclic dependencies in ES2015

Hey, I've been looking into graphitti and saw that you use classes to define relationships in the JS Client.
I'm currently using https://github.com/ghidoz/angular2-jsonapi with the "jsonapi suite" and they use a similar approach for this. But while using it I've had to upgrade to a new angular version, which also upgraded my "target" in tsconfig to es2015.

Now the issue is that it seems that when you change the target, you can no longer define classes that depend on each other in this way, see: ghidoz/angular2-jsonapi#236 (comment). Meaning if you define Post which hasMany Comments, but Comment also belongsTo Post it would throw a Uncaught ReferenceError: Cannot access '<insert class herr>' before initialization error.

Would this be an issue with spraypaint.js as well?

Relationship keys are not getting converted to camelCase

When defining a model, it appears that only attributes are getting converted to camelCase but relationship keys have to be defined in snake case or else I get the error Unknown type "appointment_requests"

I have an Appointment model that is currently setup like this:

const Appointment = ApplicationRecord.extend({
  static: {
    jsonapiType: "appointments",
  },
  attrs: {
    appointmentRequests: hasMany(),
  },
});

It has a has many relationship to the AppointmentRequests model that looks like this:

const AppointmentRequests = ApplicationRecord.extend({
  static: {
    jsonapiType: "appointmentRequests",
  },
  attrs: {
    appointment: belongsTo(),
    startTime: attr(),
  },
});

However, I currently have to change appointmentRequests to appointment_requests in both models in order to be able to access the related appointment requests from an appointment record. It seems like I should be able to define the relationships in camelCase since other attributes get converted from snake_case to camelCase correctly. Has anyone else seen or run into this issue before?

Throws an error when response status is 204 and body is empty

It looks like Spraypaint can't handle 204 - "No content" codes when POSTing or PATCHing.

In those cases, if the server returns 204 and an empty body Spraypaint tries to parse the body (which is not there) and throws an error, while it should just return the success of the operation.

For reference JSON:API considers 204 to be a valid status both for POST (see here and for PATCH (see here)

Note: If a 204 response is received the client should consider the resource object sent in the request to be accepted by the server, as if the server had returned it back in a 201 response.

Parameter encoding not working as expected.

When sending filtering data through a where clause, the parameters are not being properly encoded:

value: this&that

Browser: Firefox 93.0 (64-bit) Mac OS Big Sur

Screen Shot 2021-10-21 at 11 35 40
Screen Shot 2021-10-21 at 11 35 55

Google Chrome 94.0.4606.81 (Official Build) (x86_64) (Big Sur)

Screen Shot 2021-10-21 at 11 37 44

Screen Shot 2021-10-21 at 11 38 02

How to update or create a new object with relationships

Hi,

It seems like the docs cover how to associate an object with a relation, however the relation seems to be stripped from the payload.

const properties = formData.properties.map((id) => new Property({ id }));
const propertyList = new PropertyList({ name: formData.name, properties: [properties]});

return propertyList.save({ with: 'properties.id' });

Is there something I am missing?

@Model()
class Property extends ApplicationRecord {
  static jsonapiType = 'properties';

  @Attr() name = null;
  @Attr() deletedAt = null;

  @HasOne() address = null;
}

export default Property;

import { Model, Attr, HasMany } from 'jsorm';
import ApplicationRecord from './ApplicationRecord';

@Model()
class PropertyList extends ApplicationRecord {
  static jsonapiType = 'property_lists';

  @Attr() name = null;
  @HasMany() users = null;
  @HasMany() properties = null;
}

export default PropertyList;

Resulting request body for the server:

data: {type: "property_lists", attributes: {name: "123123"}}

Thank you for your time!

Constructor vs. direct persisted relationship assignment differences

Using a simple relationship between a Company and Country, and when the Country is already persisted:

@Model()
export class Country extends ApplicationRecord {
  static jsonapiType = "countries"

  @Attr() name: string
}

@Model()
export class Company extends ApplicationRecord {
  static jsonapiType = "companies"

  @Attr() name: string
  @BelongsTo() country: Country
}
var country = new Country({ id: '123', name: 'USA' });
country.isPersisted = true;

Creation via direct assignment appears to work normally (it establishes a relationship):

var company = new Company({ name: 'ACME' });
company.country = country;
company.save({ with: 'country' });
{"data":{"type":"companies","attributes":{"name":"ACME"},"relationships":{"country":{"data":{"type":"countries","id":"123","method":"update"}}}},"included":[{"type":"countries","id":"123"}]}

While creation via the constructor does not (it does not establish the relationship):

var company = new Company({ name: 'ACME', country: country });
company.save({ with: 'country' });
{"data":{"type":"companies","attributes":{"name":"ACME"}}}

An identifiable difference between the two approaches (just before the save is performed) is that via the constructor company._originalRelationships is populated with information related to the country, while during direct assignment it is empty.

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.