-
Notifications
You must be signed in to change notification settings - Fork 119
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
v2.0.0 checklist #137
Comments
General thoughts... Currently this API feels more complex than it is. I think this is partly from the use of inherited/extended interfaces, and a tendency toward "elegance" over "transparency" Many functions take a single argument, that is While elegant, in order for a user to know what they need to pass into this fn, they need to look at 3 different doc pages. Whereas, we could make the useage of many of these method more transparent if we simply passed in more, well-named, arguments, instead of collapsing the args into an ISomethingRequestOptions. As @tomwayson noted - perhaps some of this could be addressed by using TypeScrip union types or overloading, and may not require a 2.x release |
@dbouwman I feel like you are conflating up a problem with the docs vs a problem with the library. We all argued endlessly about multiple function params vs a single object params in #73 and #78 and ultimately we all agreed that the single object param was probably the best way to go because it is to difficult to figure out what should be broken out into a standalone param and what should be moved into an optional request options object. Managing a single options object requires a lot less work on our part and allows for non-breaking changes more easily then hardcoding lots of options in distinct params. If we need to solve the issue of having to look at several doc pages:
That is an issue with the doc build that I can work to integrate |
@patrickarlt I can say that from using the library, the current approach is cumbersome, regardless of the doc structure. I totally agree that this is a hard problem to solve, given the API we are abstracting, and all the use-cases we are trying to support... so maybe the single-object-params solution is the best we can do... it's just been O_o when working with this api. If ya'll have been using it on other projects, and feel it works as-is, then we can shelf this. |
@dbouwman I haven't contributed in awhile but while digging through the docs today I think one area we could really improve is how we use the I originally intended However it seems like It would be simpler for users if export interface IQueryFeaturesRequestOptions extends IRequestOptions {
where?: string;
// ect...
} Then in // start with any user defined params from the options.params literal.
const params = options.params;
// sometimes options will override params.
params.where = options.where || '1=1'; This means that that With changes like this im place we can do the following:
I like this approach where we don't wrap every param. Instead lets just mention:
This means that:
@dbouwman we used this library a whole bunch in the Vector Tile Style Editor and I generally wasn't too annoyed when I had to click though 1 page to get tot he key options for the request. But when I had to click through twice (method > options > params) it got annoying. I also think we should also do what @jgravois suggested and short required params to the top and inherited params to the bottom and look into some way to sort popular params to the top. |
Before we go to 2.0 I'd like to at least discuss consolidating all the "portal" packages - items, groups, sharing, users, etc, are all so intertwined that even if you start out only using 1 or 2 of them, you'll eventually end up using most of them. I think it'd be a lot simpler if those were all in @esri/arcgis-rest-portal (or -sharing, or whatever). Now that modern apps can treeshake our packages (#490), we can stress less about bundling too much code. |
Now that tree shaking is working I think package consolidation makes a lot of sense for a 2.0.0. If you work with 1 method you are probally going to work with a bunch of methods. Having I also think we could consolidate I would also like to refactor the commits that @jgravois made in a50da42 and see if I can get |
Combining everything into |
I also think it makes sense to cut a 2.0.0 soon with all these changes since we realized we should have anyway because of #506. |
I'm also going to propose that we make |
I'm less inclined to do that just b/c I think so few people need -admin, but I don't feel strongly that they should be kept separate. |
I agree, but I could see another way to do it would be to put |
I'd either leave if we're consolidating so much into |
@jgravois I feel like feature services should still be their own package since I think lots of people will use them independent of the portal packages and conceptually they related to a very different API area. |
I've been thinking about it quite a bit and i'm starting to think that it makes more sense to put the two methods we have for administering feature service endpoints into
|
that's a-okay with me. |
If you all are thinking about 2.0, I have some work that you may or not be interested in. Several months ago I did some experimenting with TS generics and the geometry types of this lib. Now it's been a while, so I will have to refresh my memory, but the idea was to make the types more extensible. Below is a snippet from the write-up I did here. I went into much more detail in that thread. // Create a polygon.
const examplePolygon: IPolygon = {
rings: [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]],
spatialReference: {
wkid: 3857
}
}
// Adding the polygon to a feature set with generic guarding.
// The third generic specifies that the feature set should contain point features.
const exampleFeatureSetWithGuard: IFeatureSet<any, any, IPoint> = {
// Error: Type 'esriGeometryPolygon' cannot be assigned to type 'esriGeometryPoint'.
// This is due to a conditional type that assigns the correct geometryType string to the
// specified feature set geometry generic.
geometryType: 'esriGeometryPolygon',
spatialReference: {
wkid: 3857
},
fields: [],
features: [
{
// Error: Type IPolygon cannot be assigned to type IPoint.
geometry: examplePolygon,
attributes: {}
}
]
} Being several months old it isn't really inline with the current state of this project, but if it's something you are interested in I could work on incorporating it. |
I have to be honest I'm not a super big fan on forcing the pattern in #137 (comment) // v1.x, not great
getItem(id: string, requestOptions?: IRequestOptions) {};
// v2.x, better:
getItem(idOrOptions: string | IGetItemOptions) {}; I think the pattern we laid out works just fine. @tomwayson proposal would basically require us to make a custom options interface for every method. I do agree that the proposed I'm still going to push for something like #78 (comment) like this: interface IGeocodeRequestOptions extends IRequestOptions {
address?: string;
outSr?: number;
// ect...
}
/**
* Used to merge an array of options objects
*/
function mergeOptions<T>(...options) : T {
let finalOptions = {};
let o;
while(o = options.shift()) {
finalOptions = {...finalOptions, ...o}
}
return finalOptions as T;
};
/**
* Higher order functions that returns a function where the first param will be
* passed as `key` and the remaining params are options objects for `mergeOptions`
*/
function mergeOptionsWith<T>(key: keyof T) {
return function (helper: any, ...options: T[]) {
if(helper && typeof helper === "object") {
return mergeOptions<T>(...options, helper);
}
return mergeOptions<T>(...options, {[key]: helper});
}
}
/**
* functions now use overloads to handle their different possible implimenations
* but all the logic around the special first param is handled by mergeOptionsWith
*/
function geocode(address: string, options?: IGeocodeRequestOptions): void
function geocode(options: IGeocodeRequestOptions): void
function geocode(address: string|IGeocodeRequestOptions, options: IGeocodeRequestOptions = {}): void {
const defaultOptions: IGeocodeRequestOptions = {
outSr: 4326
};
options = mergeOptionsWith<IGeocodeRequestOptions>("address")(address, defaultOptions, options, );
console.log(options);
}
geocode('221B Baker St, London');
// { outSr: 4326, address: '221B Baker St, London' }
geocode('221B Baker St, London', {
outSr: 102100
});
// { outSr: 102100, address: '221B Baker St, London' }
geocode({
address: "221B Baker St",
outSr: 102100
});
// { outSr: 102100, address: '221B Baker St' } This pattern is definitely more implementation work BUT still allows for really clean signatures doesn't force functional programming patterns on users and we can abstract most of the implimentation work out to the |
thanks for chiming in @itrew! to be honest, besides shuffling some code around, this is one area where we haven't put in much work because frankly my own team isn't juggling a lot of code that manages geometries or feature sets in production. our goal is to ship a if he's interested, i'd be curious to get @JeffJacobson's take on your missive as well. |
I didn't think of that. I still think in JS, not TS. Looking at the 2.0.0. branch I see 8 fns w/ an optional second argument of type My main concern is consistency in the API, and the 2 arg fns in For example, when I was writing But then there are a few other places where we have a fn that takes only one argument that can be either a
Each of those has sort of special conditions why the 2 arg format won't work as well. We could definitely use that format for The single arg that's either a string or a options hash seems to be the one format that can work for every "fetch" fn we have. |
@tomwayson allowing const queryStates = withOptions({
url: "..."
}, queryFeatures);
queryStates() // 1=1
queryStates("STATE_NAME = 'Alaska'") works great. But this is what it would look like with const queryStates = withOptions({
url: "..."
}, queryFeatures);
queryStates()
queryStates({query: "STATE_NAME = 'Alaska'"}) I would always prefer a pattern where we pass a
|
@patrickarlt I'll admit that Now that we've merged all the portal packages and aligned the signatures for I'll drop the whole notion of allowing |
I'm really happy w/ the new structure of the packages. I think we've got everything in it's right place. I do wonder a bit about the package names (sorry). I doubt @patrickarlt wants to revisit the great ontology debates that went into the section headings of https://developers.arcgis.com/rest/, but I have these concerns:
As far as the If any name changes result from this comment, I'll make the changes myself to atone for even bringing this up. |
True.
Also true.
Disagree since the only methods are specific for feature services
I would prefer |
I would also like to loop back on our discussions in #515. After working with the new types exported from I think would rather do the following:
This lets users:
This feels way better then: import { addItem } from @esri/arcgis-rest-portal;
import { IItemAdd } from @esri/arcgis-rest-request; I'll volunteer for this since I feel strongly about it if everyone else agrees. |
@jgravois a minor nit for 2.0.0. We should standardize our exported constants like
|
I can get behind:
pat is right about the methods in there now, but besides being concise, @tomwayson is being forward looking
as for given that the official doc punts to "Users, Groups and Items", i don't think there is a hope in hell of us landing on something more elegant, especially since we are prepending everything with to put it another way,
|
Agree, and I feel better about Search for "arcgis portal" - the results are all about enterprise. 😞 However, if you search for "arcgis portal rest" or "arcgis portal api - at least the first result goes to the Developers REST docs, and the rest are interleaved between that and enterprise API docs. To me that means the concept of a "portal (REST) API" means items, groups, and users and applies to both Online or enterprise. I can live w/ that. |
I don't know that our users will know or care that "nothing related to that type is happening". Another idea is to leave them in Regardless of where the types end up (I don't really care if they stay in |
I'm fine with what @jgravois proposed in #137 (comment). I think I'm going to handle a |
So @JeffJacobson and I briefly discussed the idea of splitting the types in order to differentiate between the // common - field that belongs to a feature set
interface IField {
name: string;
type: FieldType;
alias?: string;
length?: number;
}
// webmap spec
export interface IField {
name: string;
type: FieldType;
alias?: string;
domain?: IDomain;
editable?: boolean;
exactMatch?: boolean;
length?: number;
nullable?: boolean;
defaultValue?: any;
} I see two ways of splitting them if that's something you're interested in.
//arcgis-rest-types/src/index.ts
import * as Webmap from './webmap';
export * from './feature';
// other top level exports
export { Webmap }; import { Webmap } from '@esri/arcgis-rest-types';
let fields: Webmap.IField[]; or 2) Any webmap specific type/interface is prepended with a 'WM' (e.g. Thoughts? I can spare some time in the morning getting something going but I'm not sure when you guys plan to pull the trigger on 2.0. |
option #1 sounds good to me
unless there's an actual conflict between the two, we can also just be lazy and (only) document the webmap version even though we're not doing anything with webmaps here yet. |
What was the idea behind re-exporting the types when used in a package when the package is already a dependency? I'm running into a little bit of a road block in trying to implement that when re-exporting interfaces under a Going off of option 1 above, import { Webmap } from "@esri/arcgis-rest-types";
let definition: Webmap.IFeatureLayerDefinition; If the idea is to re-export used types, how would we export |
i think its the other way around. since import { IFeature, queryFeatures } from "@esri/arcgis-rest-feature-layer"; instead of import { queryFeatures } from "@esri/arcgis-rest-feature-layer";
import { IFeature } from "@esri/arcgis-rest-types"; which doesn't preclude TS developers from importing from does that make sense? |
@itrew The reason for having a import { IUser } from "@esri/arcgis-rest-auth";
import { IUser } from "@esri/arcgis-rest-portal";
import { IUser } from "@esri/arcgis-rest-types"; However in order to avoid having duplicate definitions of types we centralize them in In your example:
//IFeatureLayerDefinition.ts
export interface IFeatureLayerDefinition {
//....
}; //IWebmap.ts
import {IFeatureLayerDefinition} from `./IFeatureLayerDefinition`;
export interface IWebMap {
//... do something with IFeatureLayerDefinition
}; // @esri/arcgis-restypes index.ts
export * from `./IFeatureLayerDefinition`;
export * from `./IWebmap`; When you need to use // some file somewhere
import { IFeatureLayerDefinition } from `@esri/arcgis-rest-types`;
// use IFeatureLayerDefinition // index.ts
export { IFeatureLayerDefinition } from `@esri/arcgis-rest-types`; // re-export so users of `feature-service` can use IFeatureLayerDefinition with explictly requiring `-types` |
all v2.0.0 work items are complete. i'm happy to continue the TypeScript discussion here or in a new issue. |
@jgravois I've been chewing on this for a couple of days and I am still on the fence. It makes sense to me, but on one hand I just don't really see the benefit. Does it add anything other than removing a few import statements and one On the other hand, I will say having every type encapsulated in each library that uses it is pretty elegant when only using one or two packages. It's ultimately your call though, just my 2 cents. @patrickarlt I totally understand the reasoning behind managing your types in a single package. It would be a nightmare trying to maintain the same types in each package that uses them. As for my example, I wasn't implying that import { IFeatureSet, Webmap } from "@esri/arcgis-rest-types";
// In this case, IFeatureSet !== Webmap.IFeatureSet as they are slightly different shapes. So back to my original question. If you still plan to re-export used types and if we used a submodule |
nope. thats about it.
we're not manipulating any interfaces or types from that said, you can also explicitly depend on, and import directly from,
we don't have any need to import/re-export |
I didn't mean to imply you were, I more meant it would be nice to have those re-exported modules documented as well (at least that it's coming from
This was just an option try and keep things clean, not really a requirement of mine. Right now, this library has types from both specs intermingled (e.g. IField is from the webmap spec, IFeatureSet is from common). Now probably 95% of the time that won't be an issue, so it probably won't be an issue and can wait. Down the road though I'd be happy to assist with any typings. |
I agree. I think having organized webmap typings would be awesome. |
a list of breaking changes that we could group together. feedback welcome.
drop the 'r' fromarcgis-rest-geocoder
?drop the 's' fromarcgis-rest-items
?drop the 's' fromarcgis-rest-groups
?renamearcgis-rest-common-types
toarcgis-rest-common
and move the growing collection of shared utility methods inrequest
(that aren't actually needed byrequest
) there along with the typesgetAsyncStuff
>fetchAsyncStuff
? (and useget
for getters instead)consolidatefeature-service
andfeature-service-admin
start bundlingcommon
inside upstream packages?keep documenting the structure, but stop declaring a named interface, for unique responses.serviceInfo()
fromarcgis-rest-geocoder
esriGeometryType
>GeometryType
esriUnits
>Units
esriFieldTypes
>FieldTypes
folder
>folderId
inIItemCrudRequestOptions
(see createItemInFolder expects a folderId, not a folder name #236)IOauth2Options
(to beIOAuth2Options
)"adds"/"updates"/"deletes"
in feature service crud functions with consistent use of"features"
appendCustomParams()
re-export from feature-service (see ensure 'first class citizen' geocode request parameters are passed through #365)getItemResources()
(for consistency)IGeocodeParams
re-export IGeocodeParams (until v2.0.0) #468serializeGroup()
fix: deprecate serializeGroup() and simplify SerializeItem() #475IItemResourceAddRequestOptions
items
,groups
,sharing
andusers
packages into@esri/arcgis-rest-portal
IParamBuilder
/SearchQueryBuilder
common-types
totypes
and rexport typings, make it a real dep, and rexport interfaces and types from dependent packagesgetPortalUrl()
andgetPortal()
fromrequest
toportal
getUserUrl()
fromauth
toportal
appendCustomParams
withOptions()
andsetDefaultOptions()
torequest
searchItems()
andsearchGroups()
consistent Inconsistent signatures... #104generateToken(optionsOrParams)
>generateToken(options)
(see: fix(:bug:): support use in Node.js with custom fetch (and no global) #264 (comment))geocoder
>geocoding
@tomwaysonfeature-service
>feature-layer
(@tomwayson)feature-service-admin
>service-admin
(@tomwayson)types
in upstream packages (@patrickarlt)ICustomeRequestOptions
>ICustomOptions
@tomwaysonThe text was updated successfully, but these errors were encountered: