diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 000000000..e69de29bb diff --git a/404.html b/404.html new file mode 100644 index 000000000..07d0ad5b4 --- /dev/null +++ b/404.html @@ -0,0 +1,33 @@ + + +
+ + + + + +A class, generated for each controller-backed type in your data model as <ModelName>ApiClient
and exported from api-clients.g.ts
containing one method for each API endpoint.
Each method on the API client takes in the regular parameters of the method as you would expect, as well as an optional AxiosRequestConfig
parameter at the end that can be used to provide additional configuration for the single request, if needed.
API Callers (typed with the name ApiState
in coalesce-vue
, sometimes also referred to as "loaders" or "invokers") are stateful functions for invoking an API endpoint, created with the $makeCaller
function on an API Client. A summary of features:
Each API Caller is itself a function, so it can be invoked to trigger an API request to the server.
API Callers contain properties about the last request made, including things like wasSuccessful
, isLoading
, result
, and more.
Using setConcurrency(mode)
, you can configure how each individual caller handles what happens when multiple requests are made simultaneously
Some examples:
// Preamble for all the examples below:
+import { PersonApiClient } from '@/api-clients.g';
+const client = new PersonApiClient;
+
A caller that takes no additional parameters:
const caller = client.$makeCaller(
+ "item",
+ c => c.namesStartingWith("A")
+);
+
+await caller();
+console.log(caller.result)
+
A caller that takes custom parameters:
const caller = client.$makeCaller(
+ methods => methods.namesStartingWith,
+ (c, str: string) => c.namesStartingWith(str)
+);
+
+await caller("Rob");
+console.log(caller.result)
+
const caller = client.$makeCaller("item",
+ // The parameter-based version is always required, even if it won't be used.
+ (c, str: string) => c.namesStartingWith(str),
+ // A function which creates a blank instance of the args object.
+ // All props should be initialized (i.e. not undefined) to work with Vue's reactivity.
+ () => ({str: null as string | null, }),
+ // The function that accepts the args object and uses it:
+ (c, args) => c.namesStartingWith(args.str)
+);
+
+caller.args.str = "Su";
+await caller.invokeWithArgs();
+console.log(caller.result)
+
A caller that performs multiple async operations:
const deleteFirstNameStartingWith = client.$makeCaller(
+ "item",
+ async (c, str: string) => {
+ const namesResult = await c.namesStartingWith(str)
+ return await c.deletePersonByName(namesResult.data.object[0])
+ }
+);
+
+await caller("Rob");
+console.log(caller.result)
+
The first parameter, resultType
, can either be one of "item"
or "list"
, indicating whether the method returns a ItemResult
or ListResult
(examples #1 and #3 above). It can also be a function which accepts the set of method metadata for the API Client and which returns the specific method metadata (example #2 above), or it can be a direct reference to a specific method metadata object.
The following state properties can be found on API Caller instances. These properties are useful for binding to in a user interface to display errors, results, or indicators of progress.
API callers have a setConcurrency
method that allows you to customize how they behave when additional invocations are performed when there is already a request pending. There are four options available, with "disallow"
being the default:
"disallow"
The default behavior - simply throws an error for any secondary invocations.
Note
Having "disallow"
as the default prevents the unexpected behavior that can happen in a number of ways with the other modes:
"debounce"
When a secondary invocation is performed, enqueue it after the current pending invocation completes.
If additional invocations are performed while there is already an invocation enqueued and waiting, the already-enqueued invocation is abandoned and replaced by the most recent invocation attempt. The promise of the abandoned invocation will be resolved with undefined
(it is NOT rejected).
"cancel"
When a secondary invocation is performed, cancel the current pending invocation.
',9),ye={href:"https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.httpcontext.requestaborted?view=aspnetcore-3.1",target:"_blank",rel:"noopener noreferrer"},me=s("code",null,"undefined",-1),fe=i('"allow"
When a secondary invocation is performed, always continue normally, sending the request to the server.
The state of the properties on the caller at any time will reflect the most recent response received from the server, which is never guaranteed to correlate with the most recent request made to the server - that is, requests are not guaranteed to complete in the order they were made. In particular, the isLoading
property will be false
after the first response comes back, even if the second response has not yet been received.
WARNING
For the reasons outlined above, it is generally not recommended to use "allow"
unless you fully understand the drawbacks. This mode mirrors the legacy behavior of the Knockout stack for Coalesce.
Response caching on API Callers is a feature that will save API responses to persistent storage (sessionStorage
or localStorage
). The next time a matching request is made, the result
property of the API Caller will be populated with that saved response, allowing for a faster time to interactivity and reduced repaints and shifting of elements as initial data loads after a page navigation. It does not prevent any HTTP requests from being made, and does not affect the Promise
returned from invoke
or invokeWithArgs
.
Common use cases include:
When a cached response is loaded, result
is populated with that response's data, wasSuccessful
and hasResult
are set to true
, and onFulfilled
callbacks are invoked.
export type ResponseCachingConfiguration = {
+ /** Function that will determine the cache key used for a particular request.
+ * Return a falsy value to prevent caching. The default key is the request URL.
+ */
+ key?: (
+ req: AxiosRequestConfig,
+ defaultKey: string
+ ) => string | null | undefined;
+
+ /** The maximum age of a cached response. If null, the entry will not expire. Default 1 hour.
+ *
+ * The smallest of the current configured max age and the max age that was set at the time of the cached response is used. */
+ maxAgeSeconds?: number | null;
+
+ /** The Storage (default \`sessionStorage\`) that will hold cached responses. */
+ storage?: Storage;
+};
+
API Callers have a few other methods available as well:
`,3),ge=s("p",null,[e("Manually cancel the current request. The promise of the cancelled invocation will be resolved with "),s("code",null,"undefined"),e(" (it is NOT rejected). If using concurrency mode "),s("code",null,'"allow"'),e(", only the most recent invocation is cancelled.")],-1),be=s("p",null,[e("Add a callback to the caller to be invoked when a success response is received from the server. If a promise is returned, this promise will be awaited and will delay the setting of the "),s("code",null,"isLoading"),e(" prop to "),s("code",null,"false"),e(" until it completes.")],-1),_e=s("p",null,[e("Add a callback to the caller to be invoked when a failure response is received from the server. If a promise is returned, this promise will be awaited and will delay the setting of the "),s("code",null,"isLoading"),e(" prop to "),s("code",null,"false"),e(" until it completes.")],-1),we=s("p",null,[e("The invoke function is a reference from the caller to itself. In other words, "),s("code",null,"caller.invoke === caller"),e(". This exists to mirror the syntax of the Knockout generated method classes.")],-1),Ae=s("p",null,[e("If called a parameter, that parameter will be used as the args object. Otherwise, "),s("code",null,"caller.args"),e(" will be used.")],-1),Ee=s("p",null,"Only exists if the caller was created with the option of being invoked with an args object as described in the sections above.",-1),ke={href:"https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL",target:"_blank",rel:"noopener noreferrer"},qe=s("code",null,"result",-1),Fe=s("p",null,[e("Accepts a "),s("code",null,"Vue"),e(" instance in order to manage the lifecycle of the URL, since object URLs must be manually released to avoid memory leaks. When the provided Vue component is destroyed, the object URL will be destroyed. If called inside the component template, the Vue instance can be acquired automatically.")],-1),Ie=s("p",null,"Only exists if the caller was created with the option of being invoked with an args object as described in the sections above.",-1);function xe(Te,Pe){const l=c("RouterLink"),r=c("ExternalLinkIcon"),t=c("router-link"),o=c("Prop");return u(),h("div",null,[y,p(" MARKER:summary "),s("p",null,[e("The API client layer, generated as "),m,e(", exports a class for each API controller that was generated for your data model. These classes are stateless and provide one method for each API endpoint. This includes both the standard set of endpoints created for "),n(l,{to:"/modeling/model-types/entities.html"},{default:a(()=>[e("Entity Models")]),_:1}),e(" and "),n(l,{to:"/modeling/model-types/dtos.html"},{default:a(()=>[e("Custom DTOs")]),_:1}),e(", as well as any custom "),n(l,{to:"/modeling/model-components/methods.html"},{default:a(()=>[e("Methods")]),_:1}),e(" on the aforementioned types, as well as any methods on your "),n(l,{to:"/modeling/model-types/services.html"},{default:a(()=>[e("Services")]),_:1}),e(".")]),p(" MARKER:summary-end "),s("p",null,[e("The API clients provided by Coalesce are based on "),s("a",f,[e("axios"),n(r)]),e(". All API clients used a shared axios instance, exported from "),v,e(" as "),C,e(". This instance can be used to configure all HTTP requests made by Coalesce, including things like attaching "),s("a",g,[e("interceptors"),n(r)]),e(" to modify the requests being made, or configuring "),s("a",b,[e("defaults"),n(r)]),e(".")]),s("p",null,[e("As with all the layers, the "),s("a",_,[e("source code of coalesce-vue"),n(r)]),e(" is also a great supplement to this documentation.")]),s("nav",w,[s("ul",null,[s("li",null,[n(t,{to:"#concepts"},{default:a(()=>[e("Concepts")]),_:1}),s("ul",null,[s("li",null,[n(t,{to:"#api-client"},{default:a(()=>[e("API Client")]),_:1})]),s("li",null,[n(t,{to:"#api-callers-api-states"},{default:a(()=>[e("API Callers/API States")]),_:1})])])]),s("li",null,[n(t,{to:"#api-callers"},{default:a(()=>[e("API Callers")]),_:1}),s("ul",null,[s("li",null,[n(t,{to:"#creating-and-invoking-an-api-caller"},{default:a(()=>[e("Creating and Invoking an API Caller")]),_:1})]),s("li",null,[n(t,{to:"#properties"},{default:a(()=>[e("Properties")]),_:1})]),s("li",null,[n(t,{to:"#concurrency-mode"},{default:a(()=>[e("Concurrency Mode")]),_:1})]),s("li",null,[n(t,{to:"#response-caching"},{default:a(()=>[e("Response Caching")]),_:1})]),s("li",null,[n(t,{to:"#other-methods"},{default:a(()=>[e("Other Methods")]),_:1})])])])])]),A,s("p",null,[e("For the methods that correspond to the standard set of CRUD endpoints that Coalesce provides ("),E,e(", "),k,e(", "),q,e(", "),F,e(", "),I,e("), an additional parameter "),x,e(" is available that accepts the set of "),n(l,{to:"/modeling/model-components/data-sources.html#standard-parameters"},{default:a(()=>[e("Standard Parameters")]),_:1}),e(" appropriate for the endpoint.")]),s("p",null,[e("Each method returns a "),T,e(" where "),P,e(" is either "),R,e(", "),S,e(", or "),L,e(", depending on the return type of the API endpoint. "),j,e(" is the "),s("a",B,[e("response object from axios"),n(r)]),e(", containing the "),W,e(" in its "),M,e(" property, as well as other properties like "),O,e(". The returned type "),V,e(" is automatically converted into valid "),n(l,{to:"/stacks/vue/layers/models.html"},{default:a(()=>[e("Model implementations")]),_:1}),e(" for you.")]),N,U,s("p",null,[e("Because they are such an integral part of the overall picture of "),H,e(", they have "),n(l,{to:"/stacks/vue/layers/api-clients.html"},{default:a(()=>[e("their own section below")]),_:1}),e(" where they are explained in much greater detail.")]),$,s("p",null,[e("API Callers can be created so that they have an "),z,e(" object that can be bound to, using "),K,e(" to make a request using those arguments as the API endpoint's parameters. The API Callers created for the "),n(l,{to:"/stacks/vue/layers/viewmodels.html"},{default:a(()=>[e("ViewModel Layer")]),_:1}),e(" are all created this way.")]),G,Q,s("div",X,[J,s("p",null,[e("During typical development, it is unlikely that you'll need to make a custom API Caller - the ones created for you on the generated "),n(l,{to:"/stacks/vue/layers/viewmodels.html"},{default:a(()=>[e("ViewModel Layer")]),_:1}),e(" will usually suffice. However, creating your own can allow for some more advanced functionality.")])]),Y,s("p",null,[e("A caller that has an args object that can be bound to. This is how the generated API Callers in the "),n(l,{to:"/stacks/vue/layers/viewmodels.html"},{default:a(()=>[e("ViewModel Layer")]),_:1}),e(" are created:")]),Z,n(o,{def:"isLoading: boolean",lang:"ts"}),ee,n(o,{def:"wasSuccessful: boolean | null",lang:"ts"}),se,n(o,{def:"message: string | null",lang:"ts"}),ne,n(o,{def:"hasResult: boolean",lang:"ts"}),ae,n(o,{def:"args: {}",lang:"ts"}),oe,le,n(o,{def:"get url(): string",lang:"ts"}),te,re,ie,n(o,{def:"result: T | null",lang:"ts",id:"member-result-item"}),ce,n(o,{def:"validationIssues: ValidationIssue[] | null",lang:"ts"}),s("p",null,[e("Any validation issues returned by the previous request. This is never populated automatically by Coalesce, and is therefore is only used if you have written custom code to populate it in your "),n(l,{to:"/modeling/model-components/behaviors.html"},{default:a(()=>[e("Behaviors")]),_:1}),e(" or "),n(l,{to:"/modeling/model-components/methods.html"},{default:a(()=>[e("Methods")]),_:1}),e(".")]),pe,n(o,{def:"result: ArrayCoalesce also supports a number of the built-in System.ComponentModel.DataAnnotations
attributes and will use these to shape the generated code.
The displayed name and description of a property, as well as the order in which it appears in generated views, can be set via the [Display]
attribute. By default, properties will be displayed in the order in which they are defined in their class.
The displayed name of a property can also be set via the [DisplayName]
attribute.
To create your own behaviors, you simply need to define a class that implements IntelliTect.Coalesce.IBehaviors<T>
. To expose your behaviors to Coalesce, either place it as a nested class of the type T
that your behaviors are for, or annotate it with the [Coalesce]
attribute. Of course, the easiest way to create behaviors that doesn't require you to re-engineer a great deal of logic would be to inherit from IntelliTect.Coalesce.StandardBehaviors<T, TContext>
, and then override only the parts that you need.
public class Case
+{
+ public int CaseId { get; set; }
+ public int OwnerId { get; set; }
+ public bool IsDeleted { get; set; }
+ ...
+}
+
+[Coalesce]
+public class CaseBehaviors : StandardBehaviors<Case, AppDbContext>
+{
+ public CaseBehaviors(CrudContext<AppDbContext> context) : base(context) { }
+
+ public override ItemResult BeforeSave(SaveKind kind, Case oldItem, Case item)
+ {
+ // Allow admins to bypass all validation.
+ if (User.IsInRole("Admin")) return true;
+
+ if (kind == SaveKind.Update && oldItem.OwnerId != item.OwnerId)
+ return "The owner of a case may not be changed";
+
+ // This is a new item, OR its an existing item and the owner isn't being modified.
+ if (item.CreatedById != User.GetUserId())
+ return "You are not the owner of this item.";
+
+ return true;
+ }
+
+ public override ItemResult BeforeDelete(Case item)
+ => User.IsInRole("Manager") ? true : "Unauthorized";
+
+ public override Task ExecuteDeleteAsync(Case item)
+ {
+ // Soft delete the item.
+ item.IsDeleted = true;
+ return Db.SaveChangesAsync();
+ }
+}
+
All behaviors are instantiated using dependency injection and your application's IServiceProvider
. As a result, you can add whatever constructor parameters you desire to your behaviors as long as a value for them can be resolved from your application's services. The single parameter to the StandardBehaviors
is resolved in this way - the CrudContext<TContext>
contains the common set of objects most commonly used, including the DbContext
and the ClaimsPrincipal
representing the current user.
The standard behaviors, IntelliTect.Coalesce.StandardBehaviors<T>
and its EntityFramework-supporting sibling IntelliTect.Coalesce.StandardBehaviors<T, TContext>
, contain a significant number of properties and methods that can be utilized and/or overridden at your leisure.
A data source that, if set, will override the data source that is used to retrieve the target of an delete operation from the database after it has been deleted. If an object is able to be retrieved from this data source, it will be sent back to the client. This allows soft-deleted items to be returned to the client when the user is able to see them. Null by default; override by setting a value in the constructor.
The standard behaviors implementation contains many different methods which can be overridden in your derived class to control functionality.
These methods often call one another, so overriding one method may cause some other method to no longer be called. The hierarchy of method calls, ignoring any logic or conditions contained within, is as follows:
SaveAsync
+ DetermineSaveKindAsync
+ GetDbSet
+ ValidateDto
+ MapIncomingDto
+ BeforeSaveAsync
+ BeforeSave
+ ExecuteSaveAsync
+ AfterSave
+
+DeleteAsync
+ BeforeDeleteAsync
+ BeforeDelete
+ ExecuteDeleteAsync
+ GetDbSet
+ AfterDelete
+
All of the methods outlined above can be overridden. A description of each of the methods is as follows:
`,7),x=e("p",null,[s("Save the given item. This is the main entry point for saving, and takes a DTO as a parameter. This method is responsible for performing mapping to your EF models and ultimately saving to your database. If it is required that you access properties from the incoming DTO in this method, a set of extension methods "),e("code",null,"GetValue"),s(" and "),e("code",null,"GetObject"),s(" are available on the DTO for accessing properties that are mapped 1:1 with your EF models.")],-1),k=e("p",null,"Given the incoming DTO on which Save has been called, examine its properties to determine if the operation is meant to be a create or an update operation. Return this distinction along with the key that was used to make the distinction.",-1),F=e("p",null,"This method is called outside of the standard data source by the base API controller to perform role-based security on saves at the controller level.",-1),j=e("p",null,[s("Returns a "),e("code",null,"DbSetMap the properties of the incoming DTO to the model that will be saved to the database. For a SaveKind.Create
, this will call the MapToNew
method on the DTO and a new instance must be returned (item
will be null). For a SaveKind.Update
, this will call the MapTo
method on the DTO, and the incoming item
must be returned. If more precise control is needed, extension methods on IClassDto<T>
or casting to a known type can be used to get specific values. If all else fails, the DTO can be reflected upon.
You can, of course, create a custom base behaviors class that all your custom implementations inherit from. But, what if you want to override the standard behaviors across your entire application, so that StandardBehaviors<,>
will never be instantiated? You can do that too!
Simply create a class that implements IEntityFrameworkBehaviors<,>
(the StandardBehaviors<,>
already does - feel free to inherit from it), then register it at application startup like so:
public class MyBehaviors<T, TContext> : StandardBehaviors<T, TContext>
+ where T : class
+ where TContext : DbContext
+{
+ public MyBehaviors(CrudContext<TContext> context) : base(context)
+ {
+ }
+
+ ...
+}
+
public void ConfigureServices(IServiceCollection services)
+{
+ services.AddCoalesce(b =>
+ {
+ b.AddContext<AppDbContext>();
+ b.UseDefaultBehaviors(typeof(MyBehaviors<,>));
+ });
+
Your custom behaviors class must have the same generic type parameters - <T, TContext>
. Otherwise, the Microsoft.Extensions.DependencyInjection service provider won't know how to inject it.
<select data-bind="
+ select2Ajax: personId,
+ url: '/api/Person/list',
+ idField: 'personId',
+ textField: 'Name',
+ object: person,
+ allowClear: true
+"></select>
+
Creates a select2 dropdown using the specified url and fields that can be used to select an object from the endpoint specified. Additional complimentary bindings include:
`,4),g=e("p",null,"The Coalesce List API url to call to populate the contents of the dropdown.",-1),v=e("p",null,[s("The name of the field on each item in the results of the AJAX call which contains the ID of the option. The value of this field will be set on the observable specified for the main "),e("code",null,"select2Ajax"),s(" binding.")],-1),C=e("p",null,"The name of the field on each item in the results of the AJAX call which contains the text to be displayed for each option.",-1),_=e("p",null,[s("An observable that holds the full object corresponding to the foreign key property being bound to. If the selected value changes, this will be set to null to avoid representation of incorrect data (unless "),e("code",null,"setObject"),s(" is used - see below).")],-1),x=a("If true, the observable specified by the object
binding will be set to the selected data when an option is chosen in the dropdown. Binding itemViewModel
is required if this binding is set.
Additionally, requests to the API to populate the dropdown will request the entire object, as opposed to only the two fields specified for idField
and textField
like is normally done when this binding is missing or set to false. To override this behavior and continue requesting only specific fields even when setObject
is true, add fields=field1,field2,...
to the query string of the url
binding.
If true, the dropdown will open when tabbed to. Browser support may be incomplete in some versions of IE.
<select multiple="multiple" data-bind="
+ select2AjaxMultiple: people,
+ url: '/api/Person/list',
+ idField: 'personId',
+ textField: 'Name',
+ itemViewModel: ViewModels.PersonCase
+"></select>
+
Creates a select2 multi-select input for choosing objects that participate as the foreign object in a many-to-many relationship with the current object. The primary select2AjaxMultiple
binding takes the collection of items that make up the foreign side of the relationship. This is NOT the collection of the join objects (a.k.a. middle table objects) in the relationship.
Additional complimentary bindings include:
`,5),M=e("p",null,[s("The Coalesce List API url to call to populate the contents of the dropdown. In order to only receive specific fields from the server, add "),e("code",null,"fields=field1,field2,..."),s(" to the query string of the url, ensuring that at least the "),e("code",null,"idField"),s(" and "),e("code",null,"textField"),s(" are included in that collection.")],-1),O=e("p",null,"The name of the field on each item in the results of the AJAX call which contains the ID of the option. The value of this field will be set as the key of the foreign object in the many-to-many relationship.",-1),B=e("p",null,"The name of the field on each item in the results of the AJAX call which contains the text to be displayed for each option.",-1),P=e("p",null,[s("A reference to the class that represents the types in the supplied collection. For example, a many-to-many between "),e("code",null,"Person"),s(" and "),e("code",null,"Case"),s(" objects where "),e("code",null,"Case"),s(" is the object being bound to and "),e("code",null,"Person"),s(" is the type represented by a child collection, the correct value is "),e("code",null,"ViewModels.Person"),s(". This is used when constructing new objects representing the relationship when a new item is selected.")],-1),V=e("p",null,"The number of items to request in each call to the server.",-1),S=e("p",null,[s("A string containing the substring "),e("code",null,"{0}"),s(", which will be replaced with the text value of an option in the dropdown list when the option is displayed.")],-1),N=e("p",null,[s("A string containing the substring "),e("code",null,"{0}"),s(", which will be replaced with the text value of the selected option of the dropdown list.")],-1),K=e("p",null,"If true, a cache-busting querystring parameter will be included in AJAX requests.",-1),Y=e("p",null,[s("Directly maps to select2 option "),e("code",null,"selectOnClose"),s(".")],-1),z=e("p",null,[s("Whether or not to allow the current select to be set to null. Directly maps to select2 option "),e("code",null,"allowClear"),s(".")],-1),L=e("p",null,[s("Placeholder when nothing is selected. Directly maps to select2 option "),e("code",null,"placeholder"),s(".")],-1),W=a(`If true, the dropdown will open when tabbed to. Browser support may be incomplete in some versions of IE.
<select data-bind="
+ select2AjaxText: schoolName,
+ url: '/api/Person/SchoolNames'
+"></select>
+
Creates a select2 dropdown against the specified url where the url returns a collection of string values that are potential selection candidates. The dropdown also allows the user to input any value they choose - the API simply serves suggested values.
`,4),X=a("The url to call to populate the contents of the dropdown. This should be an endpoint that returns one of the following:
string[]
{ list: string[] }
{ object: string[] }
{ list: { [prop: string]: string } }
where the value given to resultField
is a valid property of the returned objects.{ object: { [prop: string]: string } }
where the value given to resultField
is a valid property of the returned objects.The url will also be passed a search
parameter and a page
parameter appended to the query string. The chosen endpoint is responsible for implementing this functionality. Page size is expected to be some fixed value. Implementer should anticipate that the requested page may be out of range.
The cases listed above that accept arrays of objects (as opposed to arrays of strings) require that the resultField
binding is also used. These are designed for obtaining string values from objects obtained from the standard list
endpoint.
If true, the dropdown will open when tabbed to. Browser support may be incomplete in some versions of IE.
<select data-bind="select2: selectedNumber">
+ <option value="1">Option 1</option>
+ <option value="2">Option 2</option>
+</select>
+
If true, the dropdown will open when tabbed to. Browser support may be incomplete in some versions of IE.
<div class="input-group date">
+ <input data-bind="datePicker: birthDate" type="text" class="form-control" id-prefix="datePicker" />
+ <span class="input-group-addon">
+ <span class="fa fa-calendar"></span>
+ </span>
+</div>
+
If true, the datePicker will update the underlying observable on each input change. Otherwise, the observable will only be changed when the datePicker loses focus (on blur
).
<div data-bind="with: product">
+ <input type="text" data-bind="textValue: description, saveImmediately: true" />
+</div>
+
<div data-bind="with: product">
+ <input type="text" data-bind="textValue: description, delaySave: true" />
+</div>
+
<div data-bind="tooltip: tooltipText">Some Element</div>
+<div data-bind="tooltip: {title: note, placement: 'bottom', animation: false}">Some Element</div>
+
<div data-bind="fadeVisible: isVisible">Some Element</div>
+
Similar to the Knockout visible
binding, but uses jQuery fadeIn/fadeOut
calls to perform the transition.
<div data-bind="slideVisible: isVisible">Some Element</div>
+
Similar to the Knockout visible
, but uses jQuery slideIn/slideOut
calls to perform the transition.
<div data-bind="moment: momentObservable"></div>
+<div data-bind="moment: momentObservable, format: 'MM/DD/YYYY hh:mm a'"></div>
+
Controls the text of the element by calling the format
method on a moment object.
<div data-bind="momentFromNow: momentObservable"></div>
+<div data-bind="momentFromNow: momentObservable, shorten: true"></div>
+
Controls the text of the element by calling the fromNow
method on a moment object. If shorten is true, certain phrases will be slightly shortened.
<div class="item">
+ <!-- ko let: { showControls: $data.isEditing() || $parent.editingChildren() } -->
+ <button data-bind="click: $root.editItem, visible: showControls">Edit</button>
+ <span data-bind="text: name"></span>
+ <button data-bind="click: $root.deleteItem, visible: showControls">Delete</button>
+ <!-- /ko -->
+</div>
+
The let binding is a somewhat common construct used in Knockout applications, but isn't part of Knockout itself. It effectively allows the creation of variables in the binding context, allowing complex statements which may be used multiple times to be aliased for both clarity of code and better performance.
These are static properties on IntelliTect.Coalesce.Knockout.Helpers.Knockout
you can assign to somewhere in the app lifecycle startup to change the default markup generated server-side when using @Knockout.*
methods to render Knockout bindings in your .cshtml
files.
The date/time picker properties can be coupled with DateTimeOffset
model properties to display time values localized for the current user's locale. If you want to make the localization static, simply include a script block in your _Layout.cshtml
or in a specific view that sets the default for Moment.js:
<script>
+moment.tz.setDefault("America/Chicago");
+</script>
+
Note
This needs to happen after Moment is loaded, but before the bootstrap-datetimepicker script is loaded.
<!-- Renders regularly as text: -->
+<c-admin-display :model="person" for="firstName" />
+
+<!-- Renders as a link to an item: -->
+<c-admin-display :model="person" for="company" />
+
+<!-- Renders as a link to a list: -->
+<c-admin-display :model="person" for="casesAssigned" />
+
// router.ts or main.ts
+
+// WITHOUT Vuetify A la carte:
+import { CAdminEditorPage } from 'coalesce-vue-vuetify';
+// WITH Vuetify A-la-carte:
+import { CAdminEditorPage } from 'coalesce-vue-vuetify/lib';
+
+const router = new Router({
+ // ...
+ routes: [
+ // ... other routes
+ {
+ path: '/admin/:type/edit/:id?',
+ name: 'coalesce-admin-item',
+ component: CAdminEditorPage,
+ props: true,
+ },
+ ]
+})
+
<c-admin-editor :model="person" />
+
<c-admin-method :model="person" for="setTitle" auto-reload-model />
+
<c-admin-methods :model="person" class="x" auto-reload-model />
+
<c-admin-methods :model="person" auto-reload-model />
+
<c-admin-methods :model="personList" auto-reload-model />
+
// router.ts or main.ts
+
+// WITHOUT Vuetify A la carte:
+import { CAdminTablePage } from 'coalesce-vue-vuetify';
+// WITH Vuetify A-la-carte:
+import { CAdminTablePage } from 'coalesce-vue-vuetify/lib';
+
+const router = new Router({
+ // ...
+ routes: [
+ // ... other routes
+ {
+ path: '/admin/:type',
+ name: 'coalesce-admin-list',
+ component: CAdminTablePage,
+ props: true,
+ },
+ ]
+})
+
<c-admin-table-toolbar :list="personList" />
+
<c-admin-table-toolbar :list="personList" color="pink" :editable.sync="isEditable" />
+
<c-admin-table :list="personList" />
+
<c-datetime-picker :model="person" for="birthDate" />
+
+<c-datetime-picker v-model="standaloneDate" />
+
+<c-datetime-picker
+ v-model="standaloneTime"
+ date-kind="time"
+ date-format="h:mm a"
+/>
+
The format of the date that will be rendered in the component's text field, and the format that will be attempted first when parsing user input in the text field.
Defaults to:
M/d/yyyy h:mm a
if dateKind == 'datetime'
,M/d/yyyy
if dateKind == 'date'
, orh:mm a
if dateKind == 'time'
.Typical usage, providing an object and a property on that object:
<c-display :model="person" for="gender" />
+
<c-display :model="person" for="birthDate" format="M/d/yyyy" />
+
<c-display
+ :value="person.setFirstName.result"
+ :for="person.$metadata.methods.setFirstName.return"
+ element="div"
+/>
+
Displaying a standalone date value without a model or other source of metadata:
<c-display :value="dateProp" format="M/d/yyyy" />
+
Can be provided the value to be displayed in conjunction with the for
prop, as an alternative to the model
prop.
This is an uncommon scenario - it is generally easier to use the for
/model
props together.
default
- Used to display fallback content if the value being displayed is either null
or ""
(empty string).
For properties and other values annotated with [DataTypeAttribute], the following special handling occurs based on the data type:
DataType.MultilineText
: Renders with white-space: pre-wrap
.DataType.Password
: Renders with a show/hide toggle (hidden by default), showing a fixed number of dot characters when hidden.DataType.Url
: Renders as a clickable link.DataType.EmailAddress
: Renders as a clickable mailto
link.DataType.PhoneNumber
: Renders as a clickable tel
link.DataType.ImageUrl
: Renders as an img
element."Color"
: Renders a colored dot next to the value, interpreting the field value as a 7-character HTML hex color code..Typical usage, providing an object and a property on that object:
<c-input :model="person" for="firstName" />
+
<c-input :model="comment" for="content" textarea solo />
+
<c-input
+ :model="person.setFirstName"
+ for="newName" />
+
Or, using a more verbose syntax:
<c-input
+ :model="person.setFirstName.args"
+ for="Person.methods.setFirstName.newName" />
+
<c-input :model="personList.$dataSource" for="startsWith" />
+
Usage with v-model
(this scenario is atypical - the model/for pair of props are used in almost all scenarios):
<c-input v-model="person.firstName" for="Person.firstName" />
+
<c-list-filters :list="list" />
+
<c-list-page-size :list="list" />
+
<c-list-page :list="list" />
+
<c-list-pagination :list="list" />
+
<c-list-range-display :list="list" />
+
Wrap contents of a details/edit page:
<h1>Person Details</h1>
+<c-loader-status
+ :loaders="{
+ 'no-initial-content no-error-content': [person.$load],
+ '': [person.$save, person.$delete],
+ }"
+ #default
+>
+ First Name: {{ person.firstName }}
+ Last Name: {{ person.lastName }}
+ Employer: {{ person.company.name }}
+</c-loader-status>
+
Use c-loader-status
to render a progress bar and any error messages, but don't use it to control content:
<c-loader-status :loaders="{'': [list.$load]}" />
+
Wrap a save/submit button:
<c-loader-status
+ :loaders="{ 'no-loading-content': [person.$save] }"
+>
+ <button> Save </button>
+</c-loader-status>
+
Hides the table before the first load has completed, or if loading the list encountered an error. Don't show the progress bar after we've already loaded the list for the first time (useful for loads that occur without user interaction, e.g. setInterval
):
<c-loader-status
+ :loaders="{
+ 'no-secondary-progress no-initial-content no-error-content': [list.$load]
+ }"
+ #default
+>
+ <table>
+ <tr v-for="item in list.$items"> ... </tr>
+ </table>
+</c-loader-status>
+
The available flags are as follows. All flags default to true
, and may be prefixed with no-
to set the flag to false
instead of true
. Multiple flags may be specified at once by delimiting them with spaces.
Flag | Description |
---|---|
loading-content | Controls whether the default slot is rendered while any API caller is loading (i.e. when caller.isLoading === true ). |
error-content | Controls whether the default slot is rendered while any API Caller is in an error state (i.e. when caller.wasSuccessful === false ). |
initial-content | Controls whether the default slot is rendered while any API Caller has yet to receive a response for the first time (i.e. when caller.wasSuccessful === null ). |
initial-progress | Controls whether the progress indicator is shown when an API Caller is loading for the very first time (i.e. when caller.wasSuccessful === null ). |
secondary-progress | Controls whether the progress indicator is shown when an API Caller is loading any time after its first invocation (i.e. when caller.wasSuccessful !== null ). |
<c-select-many-to-many :model="case" for="caseProducts" />
+
<c-select-many-to-many
+ :model="case"
+ for="caseProducts"
+ dense
+ outlined
+/>
+
<c-select-many-to-many
+ v-model="case.caseProducts"
+ for="Case.caseProducts"
+/>
+
adding
- Fired when a new item has been selected, but before the call to /save
has completed.added
- Fired when the call to /save
has completed after adding a new item.deleting
- Fired when an item has been removed, but before the call to /delete
has completed.deleted
- Fired when the call to /delete
has completed after removing an item.<c-select-string-value
+ :model="person"
+ for="jobTitle"
+ method="getSuggestedJobTitles"
+/>
+
<c-select-string-value
+ v-model="title"
+ label="Job Title"
+ for="Person"
+ method="getSuggestedJobTitles"
+/>
+
class Person
+{
+ public int PersonId { get; set; }
+
+ public string JobTitle { get; set; }
+
+ [Coalesce]
+ public static async Task<ICollection<string>> GetSuggestedJobTitles(AppDbContext db, string search)
+ {
+ return await db.People
+ .Select(p => p.JobTitle)
+ .Distinct()
+ .Where(t => t.StartsWith(search))
+ .OrderBy(t => t)
+ .Take(100)
+ .ToListAsync()
+ }
+}
+
<c-select-values
+ :model="post.setTags.args"
+ for="Post.methods.setTags.params.tagNames"
+/>
+
Binding to a navigation property or foreign key of a model:
<c-select :model="person" for="company" />
+ <!-- OR: -->
+ <c-select :model="person" for="companyId" />
+
Binding an arbitrary primary key value or an arbitrary object:
<!-- Binding a key: -->
+ <c-select for="Person" :key-value.sync="selectedPersonId" />
+
+ <!-- Binding an object: -->
+ <c-select for="Person" :object-value.sync="selectedPerson" />
+ <c-select for="Person" v-model="selectedPerson" />
+
Examples of other props:
<c-select
+ for="Person"
+ v-model="selectedPerson"
+ :clearable="false"
+ preselect-first
+ :params="{ pageSize: 42, filter: { isActive: true } }"
+ :create="createMethods"
+ dense
+ outlined
+ color="pink"
+/>
+<!-- \`createMethods\` is defined in the docs of \`create\` below -->
+
For example:
createMethods = {
+ getLabel(search: string, items: Person[]) {
+ const searchLower = search.toLowerCase();
+ if (items.some(a => a.name?.toLowerCase().indexOf(searchLower) == 0)) {
+ return false;
+ }
+ return search;
+ },
+ async getItem(search: string, label: string) {
+ const client = new PersonApiClient();
+ return (await client.addPersonByName(label)).data.object!;
+ }
+}
+
<c-table :list="list" />
+
A more complex example using more of the available options:
<c-table
+ :list="list"
+ :props="['firstName', 'lastName']"
+ :extra-headers="['Actions']"
+>
+ <template #item.append="{item}">
+ <td>
+ <v-btn
+ title="Edit"
+ text icon
+ :to="{name: 'edit-person', params: { id: item.$primaryKey }}"
+ >
+ <i class="fa fa-edit"></i>
+ </v-btn>
+ </td>
+ </template>
+</c-table>
+
public class Person
+{
+ public int PersonId { get; set; }
+
+ [ClientValidation(IsRequired = true, AllowSave = true)]
+ public string FirstName { get; set; }
+
+ [ClientValidation(IsRequired = true, AllowSave = false, MinLength = 1, MaxLength = 100)]
+ public string LastName { get; set; }
+}
+
If set to true
, any client validation errors on the property will not prevent saving on the client. This includes all client-side validation, including null-checking for required foreign keys and other validations that are implicit. This also includes other explicit validation from System.ComponentModel.DataAnnotations
annotations.
Instead, validation errors will be treated only as warnings, and will be available through the warnings: KnockoutValidationErrors
property on the TypeScript ViewModel.
Note
Use AllowSave = true
to allow partially complete data to still be saved, protecting your user from data loss upon navigation while still hinting to them that they are not done filling out data.
In Coalesce, all configuration of the code generation is done in a JSON file. This file is typically named coalesce.json
and is typically placed in the solution root.
When the code generation is run by invoking dotnet coalesce
, Coalesce will try to find a configuration file via the following means:
dotnet coalesce C:/Projects/MyProject/config.json
coalesce.json
coalesce.json
is found. If such a file is never found, an error will be thrown.A full example of a coalesce.json
file, along with an explanation of each property, is as follows:
{
+ "webProject": {
+ // Required: Path to the csproj of the web project. Path is relative to location of this coalesce.json file.
+ "projectFile": "src/Coalesce.Web/Coalesce.Web.csproj",
+
+ // Optional: Framework to use when evaluating & building dependencies.
+ // Not needed if your project only specifies a single framework - only required for multi-targeting projects.
+ "framework": "netcoreapp2.0",
+
+ // Optional: Build configuration to use when evaluating & building dependencies.
+ // Defaults to "Debug".
+ "configuration": "Debug",
+
+ // Optional: Override the namespace prefix for generated C# code.
+ // Defaults to MSBuild's \`$(RootNamespace)\` for the project.
+ "rootNamespace": "MyCompany.Coalesce.Web",
+ },
+
+ "dataProject": {
+ // Required: Path to the csproj of the data project. Path is relative to location of this coalesce.json file.
+ "projectFile": "src/Coalesce.Domain/Coalesce.Domain.csproj",
+
+ // Optional: Framework to use when evaluating & building dependencies.
+ // Not needed if your project only specifies a single framework - only required for multi-targeting projects.
+ "framework": "netstandard2.0",
+
+ // Optional: Build configuration to use when evaluating & building dependencies.
+ // Defaults to "Release".
+ "configuration": "Debug",
+ },
+
+ // The name of the root generator to use. Defaults to "Knockout".
+ // Available values are "Vue" and "Knockout".
+ "rootGenerator": "Vue",
+
+ // If set, specifies a list of whitelisted root type names that will restrict
+ // which types Coalesce will use for code generation.
+ // Root types are those that must be annotated with [Coalesce].
+ // Useful if want to segment a single data project into multiple web projects,
+ // or into different areas/directories within a single web project.
+ "rootTypesWhitelist": [
+ "MyDbContext", "MyCustomDto"
+ ],
+
+ "generatorConfig": {
+ // A set of objects keyed by generator name.
+ // Generator names may optionally be qualified by their full namespace.
+ // All generators are listed when running 'dotnet coalesce' with '--verbosity debug'.
+ // For example, "Views" or "IntelliTect.Coalesce.CodeGeneration.Knockout.Generators.Views".
+ "GeneratorName": {
+ // Optional: true if the generator should be disabled.
+ "disabled": true,
+ // Optional: Configures a path relative to the default output path for the generator
+ // where that generator's output should be placed instead.
+ "targetDirectory": "../DifferentFolder"
+ },
+ // Indentation for generated C# is configurable by type (API controllers, DTO classes and regular View controllers)
+ // It defaults to 4 spaces
+ "ApiController": {
+ "indentationSize": 2
+ },
+ "ClassDto": {
+ "indentationSize": 2
+ },
+ "ViewController" : {
+ "indentationSize": 2
+ }
+ }
+}
+
There are a couple of extra options which are only available as CLI parameters to dotnet coalesce
. These options do not affect the behavior of the code generation - only the behavior of the CLI itself.
--debug
When this flag is specified when running dotnet coalesce
, Coalesce will wait up to 60 seconds for a debugger to be attached to its process before starting code generation.
-v|--verbosity <level>
Set the verbosity of the output. Options are trace
, debug
, information
, warning
, error
, critical
, and none
.
public class Person
+{
+ public int PersonId { get; set; }
+ public string LastName { get; set; }
+
+ public string PictureHash { get; set; }
+
+ [Coalesce]
+ [ControllerAction(Method = HttpMethod.Get)]
+ public static long PersonCount(AppDbContext db, string lastNameStartsWith = "")
+ {
+ return db.People.Count(f => f.LastName.StartsWith(lastNameStartsWith));
+ }
+
+ [Coalesce]
+ [ControllerAction(HttpMethod.Get, VaryByProperty = nameof(PictureHash))]
+ public IFile GetPicture(AppDbContext db)
+ {
+ return new IntelliTect.Coalesce.Models.File(db.PersonPictures
+ .Where(x => x.PersonId == this.PersonId)
+ .Select(x => x.Content)
+ )
+ {
+ ContentType = "image/jpg",
+ };
+ }
+}
+
The HTTP method to use on the generated API Controller.
Enum values are:
HttpMethod.Post
Use the POST method.HttpMethod.Get
Use the GET method.HttpMethod.Put
Use the PUT method.HttpMethod.Delete
Use the DELETE method.HttpMethod.Patch
Use the PATCH method.[Controller(ApiRouted = false, ApiControllerSuffix = "Gen", ApiActionsProtected = true)]
+public class Person
+{
+ public int PersonId { get; set; }
+
+ ...
+}
+
Determines whether or not a [Route]
annotation will be placed on the generated API controller. Set to false
to prevent emission of the [Route]
attribute.
Use cases include:
If true, actions on the generated API controller will have an access modifier of protected
instead of public
.
In order to consume the generated API controller, you must inherit from the generated controller and override each desired generated action method via hiding (i.e. use public new ...
, not public override ...
).
Note
If you inherit from the generated API controllers and override their methods without setting ApiActionsProtected = true
, all non-overridden actions from the generated controller will still be exposed as normal.
By default an API and View controller are both created. This allows for suppressing the creation of either or both of these.
[CreateController(view: false, api: true)]
+public class Person
+{
+ public int PersonId { get; set; }
+
+ ...
+}
+
By default, each of your models that Coalesce exposes will expose the standard data source (IntelliTect.Coalesce.StandardDataSource<T, TContext>
). This data source provides all the standard functionality one would expect - paging, sorting, searching, filtering, and so on. Each of these component pieces is implemented in one or more virtual methods, making the StandardDataSource
a great place to start from when implementing your own data source. To suppress this behavior of always exposing the raw StandardDataSource
, create your own custom data source and annotate it with [DefaultDataSource]
.
To implement your own custom data source, you simply need to define a class that implements IntelliTect.Coalesce.IDataSource<T>
. To expose your data source to Coalesce, either place it as a nested class of the type T
that you data source serves, or annotate it with the [Coalesce]
attribute. Of course, the easiest way to create a data source that doesn't require you to re-engineer a great deal of logic would be to inherit from IntelliTect.Coalesce.StandardDataSource<T, TContext>
, and then override only the parts that you need.
public class Person
+{
+ [DefaultDataSource]
+ public class IncludeFamily : StandardDataSource<Person, AppDbContext>
+ {
+ public IncludeFamily(CrudContext<AppDbContext> context) : base(context) { }
+
+ public override IQueryable<Person> GetQuery(IDataSourceParameters parameters)
+ => Db.People
+ .Where(f => User.IsInRole("Admin") || f.CreatedById == User.GetUserId())
+ .Include(f => f.Parents).ThenInclude(s => s.Parents)
+ .Include(f => f.Cousins).ThenInclude(s => s.Parents);
+ }
+}
+
+[Coalesce]
+public class NamesStartingWithA : StandardDataSource<Person, AppDbContext>
+{
+ public NamesStartingWithA(CrudContext<AppDbContext> context) : base(context) { }
+
+ public override IQueryable<Person> GetQuery(IDataSourceParameters parameters)
+ => Db.People.Include(f => f.Siblings).Where(f => f.FirstName.StartsWith("A"));
+}
+
WARNING
If you create a custom data source that has custom logic for securing your data, be aware that the default implementation of StandardDataSource
(or your custom default implementation - see below) is still exposed unless you annotate one of your custom data sources with [DefaultDataSource]
. Doing so will replace the default data source with the annotated class for your type T
.
All data sources are instantiated using dependency injection and your application's IServiceProvider
. As a result, you can add whatever constructor parameters you desire to your data sources as long as a value for them can be resolved from your application's services. The single parameter to the StandardDataSource
is resolved in this way - the CrudContext<TContext>
contains the common set of objects most commonly used, including the DbContext
and the ClaimsPrincipal
representing the current user.
All methods on IDataSource<T>
take a parameter that contains all the client-specified parameters for things paging, searching, sorting, and filtering information. Almost all virtual methods on StandardDataSource
are also passed the relevant set of parameters.
On any data source that you create, you may add additional properties annotated with [Coalesce]
that will then be exposed as parameters to the client. These property parameters are currently restricted to primitives (numeric types, strings) and dates (DateTime, DateTimeOffset). Property parameter primitives may be expanded to allow for more types in the future.
[Coalesce]
+public class NamesStartingWith : StandardDataSource<Person, AppDbContext>
+{
+ public NamesStartingWith(CrudContext<AppDbContext> context) : base(context) { }
+
+ [Coalesce]
+ public string StartsWith { get; set; }
+
+ public override IQueryable<Person> GetQuery(IDataSourceParameters parameters)
+ => Db.People.Include(f => f.Siblings)
+ .Where(f => string.IsNullOrWhitespace(StartsWith) ? true : f.FirstName.StartsWith(StartsWith));
+}
+
The maximum page size that will be served. By default, client-specified page sizes will be clamped to this value. Override by setting a value in the constructor.
The standard data source contains 19 different methods which can be overridden in your derived class to control its behavior.
These methods often call one another, so overriding one method may cause some other method to no longer be called. The hierarchy of method calls, ignoring any logic or conditions contained within, is as follows:
GetMappedItemAsync
+ GetItemAsync
+ GetQueryAsync
+ GetQuery
+ GetIncludeTree
+ TransformResults
+
+GetMappedListAsync
+ GetListAsync
+ GetQueryAsync
+ GetQuery
+ ApplyListFiltering
+ ApplyListPropertyFilters
+ ApplyListPropertyFilter
+ ApplyListSearchTerm
+ GetListTotalCountAsync
+ ApplyListSorting
+ ApplyListClientSpecifiedSorting
+ ApplyListDefaultSorting
+ ApplyListPaging
+ GetIncludeTree
+ TrimListFields
+ TransformResults
+
+GetCountAsync
+ GetQueryAsync
+ GetQuery
+ ApplyListFiltering
+ ApplyListPropertyFilters
+ ApplyListPropertyFilter
+ ApplyListSearchTerm
+ GetListTotalCountAsync
+
All of the methods outlined above can be overridden. A description of each of the non-interface inner methods is as follows:
`,7),Z=r("The method is the one that you will most commonly be override in order to implement custom query logic. The default implementation of GetQueryAsync simply calls GetQuery - be aware of this in cases of complex overrides/inheritance. From this method, you could:
.Include()
and .ThenInclude()
..IncludedSeparately()
and .ThenIncluded()
.Performs trimming of the fields of the result set based on the parameters given to the data source. Can be overridden to forcibly disable this, override the behavior to always trim specific fields, or any other functionality desired.
You can, of course, create a custom base data source that all your custom implementations inherit from. But, what if you want to override the standard data source across your entire application, so that StandardDataSource<,>
will never be instantiated? You can do that too!
Simply create a class that implements IEntityFrameworkDataSource<,>
(the StandardDataSource<,>
already does - feel free to inherit from it), then register it at application startup like so:
public class MyDataSource<T, TContext> : StandardDataSource<T, TContext>
+ where T : class
+ where TContext : DbContext
+{
+ public MyDataSource(CrudContext<TContext> context) : base(context)
+ {
+ }
+
+ ...
+}
+
public void ConfigureServices(IServiceCollection services)
+{
+ services.AddCoalesce(b =>
+ {
+ b.AddContext<AppDbContext>();
+ b.UseDefaultDataSource(typeof(MyDataSource<,>));
+ });
+}
+
Your custom data source must have the same generic type parameters - <T, TContext>
. Otherwise, the Microsoft.Extensions.DependencyInjection service provider won't know how to inject it.
Specifies whether a DateTime type will have a date and a time, or only a date.
public class Person
+{
+ public int PersonId { get; set; }
+
+ [DateType(DateTypeAttribute.DateTypes.DateOnly)]
+ public DateTimeOffset? BirthDate { get; set; }
+}
+
public class Person
+{
+ public int PersonId { get; set; }
+
+ public int DepartmentId { get; set; }
+
+ [DefaultOrderBy(FieldOrder = 0, FieldName = nameof(Department.Order))]
+ public Department Department { get; set; }
+
+ [DefaultOrderBy(FieldOrder = 1)]
+ public string LastName { get; set; }
+}
+
public class Person
+{
+ public int PersonId { get; set; }
+
+ public int DepartmentId { get; set; }
+
+ [DefaultOrderBy(FieldOrder = 0, FieldName = nameof(Department.Order))]
+ public Department Department { get; set; }
+
+ [DefaultOrderBy(FieldOrder = 1)]
+ public string LastName { get; set; }
+}
+
Server code:
public class Person
+{
+ // Don't include CreatedBy when editing - will be included for all other views
+ [DtoExcludes("Editor")]
+ public AppUser CreatedBy { get; set; }
+
+ // Only include the Person's Department when \`includes == "details"\` on the TypeScript ViewModel.
+ [DtoIncludes("details")]
+ public Department Department { get; set; }
+
+ // LastName will be included in all views
+ public string LastName { get; set; }
+}
+
+public class Department
+{
+ [DtoIncludes("details")]
+ public ICollection<Person> People { get; set; }
+}
+
Client code:
`,4),F=s("div",{class:"language-typescript line-numbers-mode","data-ext":"ts"},[s("pre",{class:"shiki",style:{"background-color":"#1E1E1E"}},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"import"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}}," } "),s("span",{style:{color:"#C586C0"}},"from"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#CE9178"}},"'@/viewmodels.g'")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"const"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#4FC1FF"}},"personList"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#DCDCAA"}},"PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"$includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"Editor"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"await"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"$load"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#6A9955"}},"// Objects in personList.$items will not contain CreatedBy nor Department objects.")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"const"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#4FC1FF"}},"personList2"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#DCDCAA"}},"PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList2"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"$includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"details"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"await"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"$load"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#6A9955"}},"// Objects in personList2.items will be allowed to contain both CreatedBy and Department objects. ")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#6A9955"}},"// Department will be allowed to include its other Person objects.")]),e(` +`),s("span",{class:"line"})])]),s("div",{class:"line-numbers","aria-hidden":"true"},[s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"})])],-1),L=s("div",{class:"language-typescript line-numbers-mode","data-ext":"ts"},[s("pre",{class:"shiki",style:{"background-color":"#1E1E1E"}},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#9CDCFE"}},"ListViewModels"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"PersonList"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"Editor"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"load"),s("span",{style:{color:"#D4D4D4"}},"(() "),s("span",{style:{color:"#569CD6"}},"=>"),s("span",{style:{color:"#D4D4D4"}}," {")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#6A9955"}},"// objects in personList.items will not contain CreatedBy nor Department objects.")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#D4D4D4"}},"});")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#9CDCFE"}},"personList2"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#9CDCFE"}},"ListViewModels"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"PersonList"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList2"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"details"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList2"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"load"),s("span",{style:{color:"#D4D4D4"}},"(() "),s("span",{style:{color:"#569CD6"}},"=>"),s("span",{style:{color:"#D4D4D4"}}," {")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#6A9955"}},"// objects in personList2.items will be allowed to contain both CreatedBy and Department objects. Department will be allowed to include its other Person objects.")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#D4D4D4"}},"});")]),e(` +`),s("span",{class:"line"})])]),s("div",{class:"line-numbers","aria-hidden":"true"},[s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"})])],-1),x=s("h2",{id:"properties",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#properties","aria-hidden":"true"},"#"),e(" Properties")],-1),B=s("code",null,"includes",-1),P=t("For DtoIncludes
, this will be the values of includes
for which this property will be allowed to be serialized and sent to the client.
For DtoExcludes
, this will be the values of includes
for which this property will not be serialized and sent to the client.
Next, ensure that one property is annotated with [Key]
so that Coalesce can know the primary key of your DTO in order to perform database lookups and keep track of your object uniquely in the client-side TypeScript.
Now, populate the required MapTo
and MapFrom
methods with code for mapping from and to your DTO, respectively (the methods are named with respect to the underlying entity, not the DTO). Most properties probably map one-to-one in both directions, but you probably created a DTO because you wanted some sort of custom mapping - say, mapping a collection on your entity with a comma-delimited string on the DTO. This is also the place to perform any user-based, role-based, property-level security. You can access the current user on the IMappingContext
object.
[Coalesce]
+public class CaseDto : IClassDto<Case>
+{
+ [Key]
+ public int CaseId { get; set; }
+
+ public string Title { get; set; }
+
+ [Read]
+ public string AssignedToName { get; set; }
+
+ public void MapTo(Case obj, IMappingContext context)
+ {
+ obj.Title = Title;
+ }
+
+ public void MapFrom(Case obj, IMappingContext context = null, IncludeTree tree = null)
+ {
+ CaseId = obj.CaseKey;
+ Title = obj.Title;
+ if (obj.AssignedTo != null)
+ {
+ AssignedToName = obj.AssignedTo.Name;
+ }
+ }
+}
+
using IntelliTect.Coalesce.Mapping;
+
+[Coalesce]
+public class CaseDto : IClassDto<Case>
+{
+ public int ProductId { get; set; }
+ public Product Product { get; set; }
+ ...
+
+ public void MapFrom(Case obj, IMappingContext context = null, IncludeTree tree = null)
+ {
+ ProductId = obj.ProductId;
+
+ if (tree == null || tree[nameof(this.Product)] != null)
+ Product = Mapper.MapToDto<Product, ProductDtoGen>(obj.Product, context, tree?[nameof(this.Product)]
+ ...
+ }
+}
+
As a nested class of the DTO. The relationship between your data source or behaviors and your DTO will be picked up automatically.
[Coalesce]
+public class CaseDto : IClassDto<Case>
+{
+ [Key]
+ public int CaseId { get; set; }
+
+ public string Title { get; set; }
+
+ ...
+
+ public class MyCaseDtoSource : StandardDataSource<Case, AppDbContext>
+ {
+ ...
+ }
+}
+
With a [DeclaredFor]
attribute that references the DTO type:
[Coalesce]
+public class CaseDto : IClassDto<Case>
+{
+ [Key]
+ public int CaseId { get; set; }
+
+ public string Title { get; set; }
+
+ ...
+}
+
+[Coalesce, DeclaredFor(typeof(CaseDto))]
+public class MyCaseDtoSource : StandardDataSource<Case, AppDbContext>
+{
+ ...
+}
+
[Coalesce, DeclaredFor(typeof(CaseDto))]
+public class CaseDtoSource : ProjectedDtoDataSource<Case, CaseDto, AppDbContext>
+{
+ public CaseDtoSource(CrudContext<AppDbContext> context) : base(context) { }
+
+ public override IQueryable<CaseDto> ApplyProjection(IQueryable<Case> query, IDataSourceParameters parameters)
+ {
+ return query.Select(c => new CaseDto
+ {
+ CaseId = c.CaseKey,
+ Title = c.Title,
+ AssignedToName = c.AssignedTo == null ? null : c.AssignedTo.Name
+ });
+ }
+}
+
Once you've got a solid data model in place, its time to start customizing the way that Coalesce will read your data, as well as the way that it will handle your data when processing creates, updates, and deletes.
To define data sources and behaviors for Standalone Entities, it is recommended you inherit from StandardDataSource<T>
and StandardBehaviors<T>
, respectively. For example:
[Coalesce, StandaloneEntity]
+public class StandaloneExample
+{
+ public int Id { get; set; }
+
+ [Search(SearchMethod = SearchAttribute.SearchMethods.Contains), ListText]
+ public string Name { get; set; } = "";
+
+ [DefaultOrderBy]
+ public DateTimeOffset Date { get; set; }
+
+ private static int nextId = 0;
+ private static ConcurrentDictionary<int, StandaloneExample> backingStore = new ConcurrentDictionary<int, StandaloneExample>();
+
+ public class DefaultSource : StandardDataSource<StandaloneExample>
+ {
+ public DefaultSource(CrudContext context) : base(context) { }
+
+ public override Task<IQueryable<StandaloneExample>> GetQueryAsync(IDataSourceParameters parameters)
+ => Task.FromResult(backingStore.Values.AsQueryable());
+ }
+
+ public class Behaviors : StandardBehaviors<StandaloneExample>
+ {
+ public Behaviors(CrudContext context) : base(context) { }
+
+ public override Task ExecuteDeleteAsync(StandaloneExample item)
+ {
+ backingStore.TryRemove(item.Id, out _);
+ return Task.CompletedTask;
+ }
+
+ public override Task ExecuteSaveAsync(SaveKind kind, StandaloneExample? oldItem, StandaloneExample item)
+ {
+ if (kind == SaveKind.Create)
+ {
+ item.Id = Interlocked.Increment(ref nextId);
+ backingStore.TryAdd(item.Id, item);
+ }
+ else
+ {
+ backingStore.TryRemove(item.Id, out _);
+ backingStore.TryAdd(item.Id, item);
+ }
+ return Task.CompletedTask;
+ }
+ }
+}
+
The above example is admittedly contrived, as it is unlikely that you would be using an in-memory collection as a data persistence mechanism. A more likely real-world scenario would be to inject an interface to some other data store. Data Source and Behavior classes are instantiated using your application's service provider, so any registered service can be injected.
`,3);function k(T,I){const t=p("ExternalLinkIcon"),o=p("router-link"),l=p("RouterLink");return D(),i("div",null,[y,a("p",null,[s("Models are the core business objects of your application - they serve as the fundamental representation of data in your application. The design of your models is very important. In "),a("a",u,[s("Entity Framework Core"),n(t)]),s(" (EF), data models are just Plain Old CLR Objects (POCOs).")]),a("nav",C,[a("ul",null,[a("li",null,[n(o,{to:"#building-a-data-model"},{default:e(()=>[s("Building a Data Model")]),_:1}),a("ul",null,[a("li",null,[n(o,{to:"#properties"},{default:e(()=>[s("Properties")]),_:1})]),a("li",null,[n(o,{to:"#attributes"},{default:e(()=>[s("Attributes")]),_:1})]),a("li",null,[n(o,{to:"#methods"},{default:e(()=>[s("Methods")]),_:1})])])]),a("li",null,[n(o,{to:"#customizing-crud-operations"},{default:e(()=>[s("Customizing CRUD Operations")]),_:1}),a("ul",null,[a("li",null,[n(o,{to:"#data-sources"},{default:e(()=>[s("Data Sources")]),_:1})]),a("li",null,[n(o,{to:"#behaviors"},{default:e(()=>[s("Behaviors")]),_:1})])])]),a("li",null,[n(o,{to:"#standalone-non-ef-entities"},{default:e(()=>[s("Standalone (non-EF) Entities")]),_:1})])])]),m,a("p",null,[s("To start building your data model that Coalesce will generate code for, follow the best practices for "),a("a",h,[s("EF Core"),n(t)]),s(". Guidance on this topic is available in abundance in the "),a("a",v,[s("Entity Framework Core documentation"),n(t)]),s(".")]),b,E,f,a("p",null,[s("Read "),n(l,{to:"/modeling/model-components/properties.html"},{default:e(()=>[s("Properties")]),_:1}),s(" for an outline of the different types of properties that you may place on your models and the code that Coalesce will generate for each of them.")]),g,_,a("p",null,[s("Read "),n(l,{to:"/modeling/model-components/attributes.html"},{default:e(()=>[s("Attributes")]),_:1}),s(" to learn more.")]),F,a("p",null,[s("You can place both static and interface methods on your model classes. Any public methods annotated with "),n(l,{to:"/modeling/model-components/attributes/coalesce.html"},{default:e(()=>[s("[Coalesce]")]),_:1}),s(" will have a generated API endpoint and corresponding generated TypeScript members for calling this API endpoint. Read "),n(l,{to:"/modeling/model-components/methods.html"},{default:e(()=>[s("Methods")]),_:1}),s(" to learn more.")]),B,a("p",null,[s("The method by which you can control what data the users of your application can access through Coalesce's generated APIs is by creating custom data sources. These are classes that allow complete control over the way that data is retrieved from your database and provided to clients. Read "),n(l,{to:"/modeling/model-components/data-sources.html"},{default:e(()=>[s("Data Sources")]),_:1}),s(" to learn more.")]),S,a("p",null,[s("Behaviors in Coalesce are to mutating data as data sources are to reading data. Defining a behaviors class for a model allows complete control over the way that Coalesce will create, update, and delete your application's data in response to requests made through its generated API. Read "),n(l,{to:"/modeling/model-components/behaviors.html"},{default:e(()=>[s("Behaviors")]),_:1}),s(" to learn more.")]),A,x,a("p",null,[s("For these types, you must define at least one custom "),n(l,{to:"/modeling/model-components/data-sources.html"},{default:e(()=>[s("Data Source")]),_:1}),s(", and optionally a "),n(l,{to:"/modeling/model-components/behaviors.html"},{default:e(()=>[s("Behaviors")]),_:1}),s(" class as well. If no behaviors are defined, the type is implicitly read-only, equivalent to turning off create/edit/delete via the "),n(l,{to:"/modeling/model-components/attributes/security-attribute.html"},{default:e(()=>[s("Security Attributes")]),_:1}),s(".")]),w])}const P=c(d,[["render",k],["__file","entities.html.vue"]]);export{P as default}; diff --git a/assets/execute.html.29138de0.js b/assets/execute.html.29138de0.js new file mode 100644 index 000000000..d1e724bb8 --- /dev/null +++ b/assets/execute.html.29138de0.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-1e992cc4","path":"/modeling/model-components/attributes/execute.html","title":"[Execute]","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Example Usage","slug":"example-usage","link":"#example-usage","children":[]},{"level":2,"title":"Properties","slug":"properties","link":"#properties","children":[]}],"git":{"updatedTime":1680124405000},"filePathRelative":"modeling/model-components/attributes/execute.md"}');export{e as data}; diff --git a/assets/execute.html.3ada74ae.js b/assets/execute.html.3ada74ae.js new file mode 100644 index 000000000..f923bad3b --- /dev/null +++ b/assets/execute.html.3ada74ae.js @@ -0,0 +1,12 @@ +import{_ as i,y as c,z as p,X as e,B as a,Q as s,$ as l,a5 as r,P as t}from"./framework.fe9a73df.js";const d={},u=e("h1",{id:"execute",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#execute","aria-hidden":"true"},"#"),a(" [Execute]")],-1),D=e("p",null,"Controls permissions for executing of a static or instance method through the API.",-1),h=r(`public class Person
+{
+ public int PersonId { get; set; }
+
+ [Coalesce, Execute(Roles = "Payroll,HR")]
+ public void GiveRaise(int centsPerHour) {
+ ...
+ }
+
+ ...
+}
+
The level of access to allow for the action for the method.
Enum values are:
SecurityPermissionLevels.AllowAll
Allow all users to perform the action for the attribute, including users who are not authenticated at all.SecurityPermissionLevels.AllowAuthorized
Allow only users who are members of the roles specified on the attribute to perform the action. If no roles are specified on the attribute, then all authenticated users are allowed (no anonymous access).SecurityPermissionLevels.DenyAll
Deny the action to all users, regardless of authentication status or authorization level. If DenyAll
is used, no API endpoint for the action will be generated.For example, in the following scenario, the following classes are considered as external types:
PluginMetadata
, exposed through a getter-only property on ApplicationPlugin
.PluginResult
, exposed through a method return on ApplicationPlugin
.PluginHandler
is not because it not exposed by the model, neither directly nor through any of the other external types.
public class AppDbContext : DbContext {
+ public DbSet<Application> Applications { get; set; }
+ public DbSet<ApplicationPlugin> ApplicationPlugins { get; set; }
+}
+
+public class Application {
+ public int ApplicationId { get; set; }
+ public string Name { get; set; }
+ public ICollection<ApplicationPlugin> Plugins { get; set; }
+}
+
+public class ApplicationPlugin {
+ public int ApplicationPluginId { get; set; }
+ public int ApplicationId { get; set; }
+ public Application Application { get; set; }
+
+ public string TypeName { get; set; }
+
+ private PluginHandler GetInstance() =>
+ ((PluginHandler)Activator.CreateInstance(Type.GetType(TypeName)));
+
+ public PluginMetadata Metadata => GetInstance().GetMetadata();
+
+ public PluginResult Invoke(string action, string data) => GetInstance().Invoke(Application, action, data);
+}
+
+public abstract class PluginHandler {
+ public abstract PluginMetadata GetMetadata();
+ public abstract PluginResult Invoke(Application app, string action, string data);
+}
+
+public abstract class PluginMetadata {
+ public bool Name { get; set; }
+ public string Version { get; set; }
+ public ICollection<string> Actions { get; set; }
+}
+
+public abstract class PluginResult {
+ public bool Success { get; set; }
+ public string Message { get; set; }
+}
+
{const{slotScopeIds:z}=y;z&&(U=U?U.concat(z):z);const S=o(g),q=_(i(g),y,S,P,$,U,J);return q&&dn(q)&&q.data==="]"?i(y.anchor=q):(Je=!0,c(y.anchor=a("]"),S,q),q)},A=(g,y,P,$,U,J)=>{if(Je=!0,y.el=null,J){const q=k(g);for(;;){const K=i(g);if(K&&K!==q)l(K);else break}}const z=i(g),S=o(g);return l(g),n(null,y,S,z,P,$,an(S),U),z},k=g=>{let y=0;for(;g;)if(g=i(g),g&&dn(g)&&(g.data==="["&&y++,g.data==="]")){if(y===0)return i(g);y--}return g};return[f,h]}const Ce=ci;function Nl(e){return Fl(e,Il)}function Fl(e,t){const n=lo();n.__VUE__=!0;const{insert:s,remove:r,patchProp:i,createElement:o,createText:l,createComment:c,setText:a,setElementText:f,parentNode:h,nextSibling:p,setScopeId:_=He,insertStaticContent:C}=e,A=(u,d,m,b=null,E=null,R=null,M=!1,x=null,T=!!d.dynamicChildren)=>{if(u===d)return;u&&!dt(u,d)&&(b=O(u),Ee(u,E,R,!0),u=null),d.patchFlag===-2&&(T=!1,d.dynamicChildren=null);const{type:w,ref:j,shapeFlag:F}=d;switch(w){case Ot:k(u,d,m,b);break;case Ae:g(u,d,m,b);break;case Kt:u==null&&y(d,m,b,M);break;case we:N(u,d,m,b,E,R,M,x,T);break;default:F&1?U(u,d,m,b,E,R,M,x,T):F&6?Q(u,d,m,b,E,R,M,x,T):(F&64||F&128)&&w.process(u,d,m,b,E,R,M,x,T,ee)}j!=null&&E&&wn(j,u&&u.ref,R,d||u,!d)},k=(u,d,m,b)=>{if(u==null)s(d.el=l(d.children),m,b);else{const E=d.el=u.el;d.children!==u.children&&a(E,d.children)}},g=(u,d,m,b)=>{u==null?s(d.el=c(d.children||""),m,b):d.el=u.el},y=(u,d,m,b)=>{[u.el,u.anchor]=C(u.children,d,m,b,u.el,u.anchor)},P=({el:u,anchor:d},m,b)=>{let E;for(;u&&u!==d;)E=p(u),s(u,m,b),u=E;s(d,m,b)},$=({el:u,anchor:d})=>{let m;for(;u&&u!==d;)m=p(u),r(u),u=m;r(d)},U=(u,d,m,b,E,R,M,x,T)=>{M=M||d.type==="svg",u==null?J(d,m,b,E,R,M,x,T):q(u,d,E,R,M,x,T)},J=(u,d,m,b,E,R,M,x)=>{let T,w;const{type:j,props:F,shapeFlag:B,transition:W,dirs:X}=u;if(T=u.el=o(u.type,R,F&&F.is,F),B&8?f(T,u.children):B&16&&S(u.children,T,null,b,E,R&&j!=="foreignObject",M,x),X&&Ue(u,null,b,"created"),z(T,u,u.scopeId,M,b),F){for(const ie in F)ie!=="value"&&!Dt(ie)&&i(T,ie,null,F[ie],R,u.children,b,E,I);"value"in F&&i(T,"value",null,F.value),(w=F.onVnodeBeforeMount)&&Oe(w,b,u)}X&&Ue(u,null,b,"beforeMount");const le=(!E||E&&!E.pendingBranch)&&W&&!W.persisted;le&&W.beforeEnter(T),s(T,d,m),((w=F&&F.onVnodeMounted)||le||X)&&Ce(()=>{w&&Oe(w,b,u),le&&W.enter(T),X&&Ue(u,null,b,"mounted")},E)},z=(u,d,m,b,E)=>{if(m&&_(u,m),b)for(let R=0;R{for(let w=T;w {const x=d.el=u.el;let{patchFlag:T,dynamicChildren:w,dirs:j}=d;T|=u.patchFlag&16;const F=u.props||ce,B=d.props||ce;let W;m&<(m,!1),(W=B.onVnodeBeforeUpdate)&&Oe(W,m,d,u),j&&Ue(d,u,m,"beforeUpdate"),m&<(m,!0);const X=E&&d.type!=="foreignObject";if(w?K(u.dynamicChildren,w,x,m,b,X,R):M||re(u,d,x,null,m,b,X,R,!1),T>0){if(T&16)Z(x,d,F,B,m,b,E);else if(T&2&&F.class!==B.class&&i(x,"class",null,B.class,E),T&4&&i(x,"style",F.style,B.style,E),T&8){const le=d.dynamicProps;for(let ie=0;ie {W&&Oe(W,m,d,u),j&&Ue(d,u,m,"updated")},b)},K=(u,d,m,b,E,R,M)=>{for(let x=0;x {if(m!==b){if(m!==ce)for(const x in m)!Dt(x)&&!(x in b)&&i(u,x,m[x],null,M,d.children,E,R,I);for(const x in b){if(Dt(x))continue;const T=b[x],w=m[x];T!==w&&x!=="value"&&i(u,x,w,T,M,d.children,E,R,I)}"value"in b&&i(u,"value",m.value,b.value)}},N=(u,d,m,b,E,R,M,x,T)=>{const w=d.el=u?u.el:l(""),j=d.anchor=u?u.anchor:l("");let{patchFlag:F,dynamicChildren:B,slotScopeIds:W}=d;W&&(x=x?x.concat(W):W),u==null?(s(w,m,b),s(j,m,b),S(d.children,m,j,E,R,M,x,T)):F>0&&F&64&&B&&u.dynamicChildren?(K(u.dynamicChildren,B,m,E,R,M,x),(d.key!=null||E&&d===E.subTree)&&Pi(u,d,!0)):re(u,d,m,j,E,R,M,x,T)},Q=(u,d,m,b,E,R,M,x,T)=>{d.slotScopeIds=x,u==null?d.shapeFlag&512?E.ctx.activate(d,m,b,M,T):L(d,m,b,E,R,M,T):ye(u,d,T)},L=(u,d,m,b,E,R,M)=>{const x=u.component=Kl(u,b,E);if(nn(u)&&(x.ctx.renderer=ee),ql(x),x.asyncDep){if(E&&E.registerDep(x,G),!u.el){const T=x.subTree=de(Ae);g(null,T,d,m)}return}G(x,u,d,m,E,R,M)},ye=(u,d,m)=>{const b=d.component=u.component;if(tl(u,d,m))if(b.asyncDep&&!b.asyncResolved){oe(b,d,m);return}else b.next=d,Qo(b.update),b.update();else d.el=u.el,b.vnode=d},G=(u,d,m,b,E,R,M)=>{const x=()=>{if(u.isMounted){let{next:j,bu:F,u:B,parent:W,vnode:X}=u,le=j,ie;lt(u,!1),j?(j.el=X.el,oe(u,j,M)):j=X,F&&jn(F),(ie=j.props&&j.props.onVnodeBeforeUpdate)&&Oe(ie,W,j,X),lt(u,!0);const he=Bn(u),Fe=u.subTree;u.subTree=he,A(Fe,he,h(Fe.el),O(Fe),u,E,R),j.el=he.el,le===null&&nl(u,he.el),B&&Ce(B,E),(ie=j.props&&j.props.onVnodeUpdated)&&Ce(()=>Oe(ie,W,j,X),E)}else{let j;const{el:F,props:B}=d,{bm:W,m:X,parent:le}=u,ie=At(d);if(lt(u,!1),W&&jn(W),!ie&&(j=B&&B.onVnodeBeforeMount)&&Oe(j,le,d),lt(u,!0),F&&Y){const he=()=>{u.subTree=Bn(u),Y(F,u.subTree,u,E,null)};ie?d.type.__asyncLoader().then(()=>!u.isUnmounted&&he()):he()}else{const he=u.subTree=Bn(u);A(null,he,m,b,u,E,R),d.el=he.el}if(X&&Ce(X,E),!ie&&(j=B&&B.onVnodeMounted)){const he=d;Ce(()=>Oe(j,le,he),E)}(d.shapeFlag&256||le&&At(le.vnode)&&le.vnode.shapeFlag&256)&&u.a&&Ce(u.a,E),u.isMounted=!0,d=m=b=null}},T=u.effect=new On(x,()=>Mn(w),u.scope),w=u.update=()=>T.run();w.id=u.uid,lt(u,!0),w()},oe=(u,d,m)=>{d.component=u;const b=u.vnode.props;u.vnode=d,u.next=null,Pl(u,d.props,b,m),Ol(u,d.children,m),Ft(),Ws(),Lt()},re=(u,d,m,b,E,R,M,x,T=!1)=>{const w=u&&u.children,j=u?u.shapeFlag:0,F=d.children,{patchFlag:B,shapeFlag:W}=d;if(B>0){if(B&128){ot(w,F,m,b,E,R,M,x,T);return}else if(B&256){Ie(w,F,m,b,E,R,M,x,T);return}}W&8?(j&16&&I(w,E,R),F!==w&&f(m,F)):j&16?W&16?ot(w,F,m,b,E,R,M,x,T):I(w,E,R,!0):(j&8&&f(m,""),W&16&&S(F,m,b,E,R,M,x,T))},Ie=(u,d,m,b,E,R,M,x,T)=>{u=u||Ct,d=d||Ct;const w=u.length,j=d.length,F=Math.min(w,j);let B;for(B=0;B j?I(u,E,R,!0,!1,F):S(d,m,b,E,R,M,x,T,F)},ot=(u,d,m,b,E,R,M,x,T)=>{let w=0;const j=d.length;let F=u.length-1,B=j-1;for(;w<=F&&w<=B;){const W=u[w],X=d[w]=T?et(d[w]):Le(d[w]);if(dt(W,X))A(W,X,m,null,E,R,M,x,T);else break;w++}for(;w<=F&&w<=B;){const W=u[F],X=d[B]=T?et(d[B]):Le(d[B]);if(dt(W,X))A(W,X,m,null,E,R,M,x,T);else break;F--,B--}if(w>F){if(w<=B){const W=B+1,X=W B)for(;w<=F;)Ee(u[w],E,R,!0),w++;else{const W=w,X=w,le=new Map;for(w=X;w<=B;w++){const Re=d[w]=T?et(d[w]):Le(d[w]);Re.key!=null&&le.set(Re.key,w)}let ie,he=0;const Fe=B-X+1;let bt=!1,Ns=0;const kt=new Array(Fe);for(w=0;w =Fe){Ee(Re,E,R,!0);continue}let De;if(Re.key!=null)De=le.get(Re.key);else for(ie=X;ie<=B;ie++)if(kt[ie-X]===0&&dt(Re,d[ie])){De=ie;break}De===void 0?Ee(Re,E,R,!0):(kt[De-X]=w+1,De>=Ns?Ns=De:bt=!0,A(Re,d[De],m,null,E,R,M,x,T),he++)}const Fs=bt?Ll(kt):Ct;for(ie=Fs.length-1,w=Fe-1;w>=0;w--){const Re=X+w,De=d[Re],Ls=Re+1 {const{el:R,type:M,transition:x,children:T,shapeFlag:w}=u;if(w&6){Ne(u.component.subTree,d,m,b);return}if(w&128){u.suspense.move(d,m,b);return}if(w&64){M.move(u,d,m,ee);return}if(M===we){s(R,d,m);for(let F=0;F x.enter(R),E);else{const{leave:F,delayLeave:B,afterLeave:W}=x,X=()=>s(R,d,m),le=()=>{F(R,()=>{X(),W&&W()})};B?B(R,X,le):le()}else s(R,d,m)},Ee=(u,d,m,b=!1,E=!1)=>{const{type:R,props:M,ref:x,children:T,dynamicChildren:w,shapeFlag:j,patchFlag:F,dirs:B}=u;if(x!=null&&wn(x,null,m,u,!0),j&256){d.ctx.deactivate(u);return}const W=j&1&&B,X=!At(u);let le;if(X&&(le=M&&M.onVnodeBeforeUnmount)&&Oe(le,d,u),j&6)v(u.component,m,b);else{if(j&128){u.suspense.unmount(m,b);return}W&&Ue(u,null,d,"beforeUnmount"),j&64?u.type.remove(u,d,m,E,ee,b):w&&(R!==we||F>0&&F&64)?I(w,d,m,!1,!0):(R===we&&F&384||!E&&j&16)&&I(T,d,m),b&&yt(u)}(X&&(le=M&&M.onVnodeUnmounted)||W)&&Ce(()=>{le&&Oe(le,d,u),W&&Ue(u,null,d,"unmounted")},m)},yt=u=>{const{type:d,el:m,anchor:b,transition:E}=u;if(d===we){sn(m,b);return}if(d===Kt){$(u);return}const R=()=>{r(m),E&&!E.persisted&&E.afterLeave&&E.afterLeave()};if(u.shapeFlag&1&&E&&!E.persisted){const{leave:M,delayLeave:x}=E,T=()=>M(m,R);x?x(u.el,R,T):T()}else R()},sn=(u,d)=>{let m;for(;u!==d;)m=p(u),r(u),u=m;r(d)},v=(u,d,m)=>{const{bum:b,scope:E,update:R,subTree:M,um:x}=u;b&&jn(b),E.stop(),R&&(R.active=!1,Ee(M,u,d,m)),x&&Ce(x,d),Ce(()=>{u.isUnmounted=!0},d),d&&d.pendingBranch&&!d.isUnmounted&&u.asyncDep&&!u.asyncResolved&&u.suspenseId===d.pendingId&&(d.deps--,d.deps===0&&d.resolve())},I=(u,d,m,b=!1,E=!1,R=0)=>{for(let M=R;M u.shapeFlag&6?O(u.component.subTree):u.shapeFlag&128?u.suspense.next():p(u.anchor||u.el),H=(u,d,m)=>{u==null?d._vnode&&Ee(d._vnode,null,null,!0):A(d._vnode||null,u,d,null,null,null,m),Ws(),vn(),d._vnode=u},ee={p:A,um:Ee,m:Ne,r:yt,mt:L,mc:S,pc:re,pbc:K,n:O,o:e};let fe,Y;return t&&([fe,Y]=t(ee)),{render:H,hydrate:fe,createApp:Ml(H,fe)}}function lt({effect:e,update:t},n){e.allowRecurse=t.allowRecurse=n}function Pi(e,t,n=!1){const s=e.children,r=t.children;if(D(s)&&D(r))for(let i=0;i >1,e[n[l]]0&&(t[s]=n[i-1]),n[i]=s)}}for(i=n.length,o=n[i-1];i-- >0;)n[i]=o,o=t[o];return n}const kl=e=>e.__isTeleport,we=Symbol(void 0),Ot=Symbol(void 0),Ae=Symbol(void 0),Kt=Symbol(void 0),Wt=[];let $e=null;function Ai(e=!1){Wt.push($e=e?null:[])}function $l(){Wt.pop(),$e=Wt[Wt.length-1]||null}let Xt=1;function er(e){Xt+=e}function Ti(e){return e.dynamicChildren=Xt>0?$e||Ct:null,$l(),Xt>0&&$e&&$e.push(e),e}function Hu(e,t,n,s,r,i){return Ti(Mi(e,t,n,s,r,i,!0))}function Oi(e,t,n,s,r){return Ti(de(e,t,n,s,r,!0))}function xn(e){return e?e.__v_isVNode===!0:!1}function dt(e,t){return e.type===t.type&&e.key===t.key}const Fn="__vInternal",Si=({key:e})=>e!=null?e:null,mn=({ref:e,ref_key:t,ref_for:n})=>e!=null?pe(e)||me(e)||V(e)?{i:_e,r:e,k:t,f:!!n}:e:null;function Mi(e,t=null,n=null,s=0,r=null,i=e===we?0:1,o=!1,l=!1){const c={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&Si(t),ref:t&&mn(t),scopeId:li,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetAnchor:null,staticCount:0,shapeFlag:i,patchFlag:s,dynamicProps:r,dynamicChildren:null,appContext:null,ctx:_e};return l?(Os(c,n),i&128&&e.normalize(c)):n&&(c.shapeFlag|=pe(n)?8:16),Xt>0&&!o&&$e&&(c.patchFlag>0||i&6)&&c.patchFlag!==32&&$e.push(c),c}const de=Hl;function Hl(e,t=null,n=null,s=0,r=null,i=!1){if((!e||e===_l)&&(e=Ae),xn(e)){const l=it(e,t,!0);return n&&Os(l,n),Xt>0&&!i&&$e&&(l.shapeFlag&6?$e[$e.indexOf(e)]=l:$e.push(l)),l.patchFlag|=-2,l}if(Jl(e)&&(e=e.__vccOpts),t){t=jl(t);let{class:l,style:c}=t;l&&!pe(l)&&(t.class=hs(l)),ue(c)&&(Yr(c)&&!D(c)&&(c=ge({},c)),t.style=ds(c))}const o=pe(e)?1:sl(e)?128:kl(e)?64:ue(e)?4:V(e)?2:0;return Mi(e,t,n,s,r,o,i,!0)}function jl(e){return e?Yr(e)||Fn in e?ge({},e):e:null}function it(e,t,n=!1){const{props:s,ref:r,patchFlag:i,children:o}=e,l=t?Bl(s||{},t):s;return{__v_isVNode:!0,__v_skip:!0,type:e.type,props:l,key:l&&Si(l),ref:t&&t.ref?n&&r?D(r)?r.concat(mn(t)):[r,mn(t)]:mn(t):r,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:o,target:e.target,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==we?i===-1?16:i|16:i,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:e.transition,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&it(e.ssContent),ssFallback:e.ssFallback&&it(e.ssFallback),el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce}}function Ii(e=" ",t=0){return de(Ot,null,e,t)}function ju(e,t){const n=de(Kt,null,e);return n.staticCount=t,n}function Bu(e="",t=!1){return t?(Ai(),Oi(Ae,null,e)):de(Ae,null,e)}function Le(e){return e==null||typeof e=="boolean"?de(Ae):D(e)?de(we,null,e.slice()):typeof e=="object"?et(e):de(Ot,null,String(e))}function et(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:it(e)}function Os(e,t){let n=0;const{shapeFlag:s}=e;if(t==null)t=null;else if(D(t))n=16;else if(typeof t=="object")if(s&65){const r=t.default;r&&(r._c&&(r._d=!1),Os(e,r()),r._c&&(r._d=!0));return}else{n=32;const r=t._;!r&&!(Fn in t)?t._ctx=_e:r===3&&_e&&(_e.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else V(t)?(t={default:t,_ctx:_e},n=32):(t=String(t),s&64?(n=16,t=[Ii(t)]):n=8);e.children=t,e.shapeFlag|=n}function Bl(...e){const t={};for(let n=0;n ae||_e,St=e=>{ae=e,e.scope.on()},mt=()=>{ae&&ae.scope.off(),ae=null};function Ni(e){return e.vnode.shapeFlag&4}let Mt=!1;function ql(e,t=!1){Mt=t;const{props:n,children:s}=e.vnode,r=Ni(e);Rl(e,n,r,t),Tl(e,s);const i=r?Vl(e,t):void 0;return Mt=!1,i}function Vl(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=Jr(new Proxy(e.ctx,bl));const{setup:s}=n;if(s){const r=e.setupContext=s.length>1?Ql(e):null;St(e),Ft();const i=st(s,e,0,[e.props,r]);if(Lt(),mt(),Fr(i)){if(i.then(mt,mt),t)return i.then(o=>{tr(e,o,t)}).catch(o=>{tn(o,e,0)});e.asyncDep=i}else tr(e,i,t)}else Fi(e,t)}function tr(e,t,n){V(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:ue(t)&&(e.setupState=ei(t)),Fi(e,n)}let nr;function Fi(e,t,n){const s=e.type;if(!e.render){if(!t&&nr&&!s.render){const r=s.template||As(e).template;if(r){const{isCustomElement:i,compilerOptions:o}=e.appContext.config,{delimiters:l,compilerOptions:c}=s,a=ge(ge({isCustomElement:i,delimiters:l},o),c);s.render=nr(r,a)}}e.render=s.render||He}St(e),Ft(),vl(e),Lt(),mt()}function zl(e){return new Proxy(e.attrs,{get(t,n){return xe(e,"get","$attrs"),t[n]}})}function Ql(e){const t=s=>{e.exposed=s||{}};let n;return{get attrs(){return n||(n=zl(e))},slots:e.slots,emit:e.emit,expose:t}}function Ln(e){if(e.exposed)return e.exposeProxy||(e.exposeProxy=new Proxy(ei(Jr(e.exposed)),{get(t,n){if(n in t)return t[n];if(n in Ut)return Ut[n](e)},has(t,n){return n in t||n in Ut}}))}function Yl(e,t=!0){return V(e)?e.displayName||e.name:e.name||t&&e.__name}function Jl(e){return V(e)&&"__vccOpts"in e}const Se=(e,t)=>qo(e,t,Mt);function Ss(e,t,n){const s=arguments.length;return s===2?ue(t)&&!D(t)?xn(t)?de(e,null,[t]):de(e,t):de(e,null,t):(s>3?n=Array.prototype.slice.call(arguments,2):s===3&&xn(n)&&(n=[n]),de(e,t,n))}const Xl=Symbol(""),Zl=()=>je(Xl),Gl="3.2.47",ec="http://www.w3.org/2000/svg",ht=typeof document<"u"?document:null,sr=ht&&ht.createElement("template"),tc={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,s)=>{const r=t?ht.createElementNS(ec,e):ht.createElement(e,n?{is:n}:void 0);return e==="select"&&s&&s.multiple!=null&&r.setAttribute("multiple",s.multiple),r},createText:e=>ht.createTextNode(e),createComment:e=>ht.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>ht.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,s,r,i){const o=n?n.previousSibling:t.lastChild;if(r&&(r===i||r.nextSibling))for(;t.insertBefore(r.cloneNode(!0),n),!(r===i||!(r=r.nextSibling)););else{sr.innerHTML=s?``:e;const l=sr.content;if(s){const c=l.firstChild;for(;c.firstChild;)l.appendChild(c.firstChild);l.removeChild(c)}t.insertBefore(l,n)}return[o?o.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}};function nc(e,t,n){const s=e._vtc;s&&(t=(t?[t,...s]:[...s]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}function sc(e,t,n){const s=e.style,r=pe(n);if(n&&!r){if(t&&!pe(t))for(const i in t)n[i]==null&&ls(s,i,"");for(const i in n)ls(s,i,n[i])}else{const i=s.display;r?t!==n&&(s.cssText=n):t&&e.removeAttribute("style"),"_vod"in e&&(s.display=i)}}const rr=/\s*!important$/;function ls(e,t,n){if(D(n))n.forEach(s=>ls(e,t,s));else if(n==null&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{const s=rc(e,t);rr.test(n)?e.setProperty(_t(s),n.replace(rr,""),"important"):e[s]=n}}const ir=["Webkit","Moz","ms"],Wn={};function rc(e,t){const n=Wn[t];if(n)return n;let s=We(t);if(s!=="filter"&&s in e)return Wn[t]=s;s=Tn(s);for(let r=0;r qn||(ac.then(()=>qn=0),qn=Date.now());function hc(e,t){const n=s=>{if(!s._vts)s._vts=Date.now();else if(s._vts<=n.attached)return;Me(pc(s,n.value),t,5,[s])};return n.value=e,n.attached=dc(),n}function pc(e,t){if(D(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(s=>r=>!r._stopped&&s&&s(r))}else return t}const cr=/^on[a-z]/,gc=(e,t,n,s,r=!1,i,o,l,c)=>{t==="class"?nc(e,s,r):t==="style"?sc(e,n,s):Gt(t)?ps(t)||uc(e,t,n,s,o):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):mc(e,t,s,r))?oc(e,t,s,i,o,l,c):(t==="true-value"?e._trueValue=s:t==="false-value"&&(e._falseValue=s),ic(e,t,s,r))};function mc(e,t,n,s){return s?!!(t==="innerHTML"||t==="textContent"||t in e&&cr.test(t)&&V(n)):t==="spellcheck"||t==="draggable"||t==="translate"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA"||cr.test(t)&&pe(n)?!1:t in e}const Xe="transition",$t="animation",Li=(e,{slots:t})=>Ss(fi,_c(e),t);Li.displayName="Transition";const ki={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String};Li.props=ge({},fi.props,ki);const ct=(e,t=[])=>{D(e)?e.forEach(n=>n(...t)):e&&e(...t)},ur=e=>e?D(e)?e.some(t=>t.length>1):e.length>1:!1;function _c(e){const t={};for(const N in e)N in ki||(t[N]=e[N]);if(e.css===!1)return t;const{name:n="v",type:s,duration:r,enterFromClass:i=`${n}-enter-from`,enterActiveClass:o=`${n}-enter-active`,enterToClass:l=`${n}-enter-to`,appearFromClass:c=i,appearActiveClass:a=o,appearToClass:f=l,leaveFromClass:h=`${n}-leave-from`,leaveActiveClass:p=`${n}-leave-active`,leaveToClass:_=`${n}-leave-to`}=e,C=yc(r),A=C&&C[0],k=C&&C[1],{onBeforeEnter:g,onEnter:y,onEnterCancelled:P,onLeave:$,onLeaveCancelled:U,onBeforeAppear:J=g,onAppear:z=y,onAppearCancelled:S=P}=t,q=(N,Q,L)=>{ut(N,Q?f:l),ut(N,Q?a:o),L&&L()},K=(N,Q)=>{N._isLeaving=!1,ut(N,h),ut(N,_),ut(N,p),Q&&Q()},Z=N=>(Q,L)=>{const ye=N?z:y,G=()=>q(Q,N,L);ct(ye,[Q,G]),fr(()=>{ut(Q,N?c:i),Ze(Q,N?f:l),ur(ye)||ar(Q,s,A,G)})};return ge(t,{onBeforeEnter(N){ct(g,[N]),Ze(N,i),Ze(N,o)},onBeforeAppear(N){ct(J,[N]),Ze(N,c),Ze(N,a)},onEnter:Z(!1),onAppear:Z(!0),onLeave(N,Q){N._isLeaving=!0;const L=()=>K(N,Q);Ze(N,h),Ec(),Ze(N,p),fr(()=>{!N._isLeaving||(ut(N,h),Ze(N,_),ur($)||ar(N,s,k,L))}),ct($,[N,L])},onEnterCancelled(N){q(N,!1),ct(P,[N])},onAppearCancelled(N){q(N,!0),ct(S,[N])},onLeaveCancelled(N){K(N),ct(U,[N])}})}function yc(e){if(e==null)return null;if(ue(e))return[Vn(e.enter),Vn(e.leave)];{const t=Vn(e);return[t,t]}}function Vn(e){return oo(e)}function Ze(e,t){t.split(/\s+/).forEach(n=>n&&e.classList.add(n)),(e._vtc||(e._vtc=new Set)).add(t)}function ut(e,t){t.split(/\s+/).forEach(s=>s&&e.classList.remove(s));const{_vtc:n}=e;n&&(n.delete(t),n.size||(e._vtc=void 0))}function fr(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}let bc=0;function ar(e,t,n,s){const r=e._endId=++bc,i=()=>{r===e._endId&&s()};if(n)return setTimeout(i,n);const{type:o,timeout:l,propCount:c}=vc(e,t);if(!o)return s();const a=o+"end";let f=0;const h=()=>{e.removeEventListener(a,p),i()},p=_=>{_.target===e&&++f>=c&&h()};setTimeout(()=>{f (n[C]||"").split(", "),r=s(`${Xe}Delay`),i=s(`${Xe}Duration`),o=dr(r,i),l=s(`${$t}Delay`),c=s(`${$t}Duration`),a=dr(l,c);let f=null,h=0,p=0;t===Xe?o>0&&(f=Xe,h=o,p=i.length):t===$t?a>0&&(f=$t,h=a,p=c.length):(h=Math.max(o,a),f=h>0?o>a?Xe:$t:null,p=f?f===Xe?i.length:c.length:0);const _=f===Xe&&/\b(transform|all)(,|$)/.test(s(`${Xe}Property`).toString());return{type:f,timeout:h,propCount:p,hasTransform:_}}function dr(e,t){for(;e.length hr(n)+hr(e[s])))}function hr(e){return Number(e.slice(0,-1).replace(",","."))*1e3}function Ec(){return document.body.offsetHeight}const Cc={esc:"escape",space:" ",up:"arrow-up",left:"arrow-left",right:"arrow-right",down:"arrow-down",delete:"backspace"},Du=(e,t)=>n=>{if(!("key"in n))return;const s=_t(n.key);if(t.some(r=>r===s||Cc[r]===s))return e(n)},Uu={beforeMount(e,{value:t},{transition:n}){e._vod=e.style.display==="none"?"":e.style.display,n&&t?n.beforeEnter(e):Ht(e,t)},mounted(e,{value:t},{transition:n}){n&&t&&n.enter(e)},updated(e,{value:t,oldValue:n},{transition:s}){!t!=!n&&(s?t?(s.beforeEnter(e),Ht(e,!0),s.enter(e)):s.leave(e,()=>{Ht(e,!1)}):Ht(e,t))},beforeUnmount(e,{value:t}){Ht(e,t)}};function Ht(e,t){e.style.display=t?e._vod:"none"}const wc=ge({patchProp:gc},tc);let zn,pr=!1;function xc(){return zn=pr?zn:Nl(wc),pr=!0,zn}const Ku=(...e)=>{const t=xc().createApp(...e),{mount:n}=t;return t.mount=s=>{const r=Rc(s);if(r)return n(r,!0,r instanceof SVGElement)},t};function Rc(e){return pe(e)?document.querySelector(e):e}var Pc=([e,t,n])=>e==="meta"&&t.name?`${e}.${t.name}`:["title","base"].includes(e)?e:e==="template"&&t.id?`${e}.${t.id}`:JSON.stringify([e,t,n]),Wu=e=>{const t=new Set,n=[];return e.forEach(s=>{const r=Pc(s);t.has(r)||(t.add(r),n.push(s))}),n},qu=e=>/^(https?:)?\/\//.test(e),Vu=e=>/^mailto:/.test(e),zu=e=>/^tel:/.test(e),Qu=e=>Object.prototype.toString.call(e)==="[object Object]",Yu=e=>e.replace(/\/$/,""),Ju=e=>e.replace(/^\//,""),Xu=(e,t)=>{const n=Object.keys(e).sort((s,r)=>{const i=r.split("/").length-s.split("/").length;return i!==0?i:r.length-s.length});for(const s of n)if(t.startsWith(s))return s;return"/"},Zu=(e,t="/")=>e.replace(/^(https?:)?\/\/[^/]*/,"").replace(new RegExp(`^${t}`),"/");/*! + * vue-router v4.1.6 + * (c) 2022 Eduardo San Martin Morote + * @license MIT + */const Et=typeof window<"u";function Ac(e){return e.__esModule||e[Symbol.toStringTag]==="Module"}const se=Object.assign;function Qn(e,t){const n={};for(const s in t){const r=t[s];n[s]=Be(r)?r.map(e):e(r)}return n}const qt=()=>{},Be=Array.isArray,Tc=/\/$/,Oc=e=>e.replace(Tc,"");function Yn(e,t,n="/"){let s,r={},i="",o="";const l=t.indexOf("#");let c=t.indexOf("?");return l =0&&(c=-1),c>-1&&(s=t.slice(0,c),i=t.slice(c+1,l>-1?l:t.length),r=e(i)),l>-1&&(s=s||t.slice(0,l),o=t.slice(l,t.length)),s=Nc(s!=null?s:t,n),{fullPath:s+(i&&"?")+i+o,path:s,query:r,hash:o}}function Sc(e,t){const n=t.query?e(t.query):"";return t.path+(n&&"?")+n+(t.hash||"")}function gr(e,t){return!t||!e.toLowerCase().startsWith(t.toLowerCase())?e:e.slice(t.length)||"/"}function Mc(e,t,n){const s=t.matched.length-1,r=n.matched.length-1;return s>-1&&s===r&&It(t.matched[s],n.matched[r])&&$i(t.params,n.params)&&e(t.query)===e(n.query)&&t.hash===n.hash}function It(e,t){return(e.aliasOf||e)===(t.aliasOf||t)}function $i(e,t){if(Object.keys(e).length!==Object.keys(t).length)return!1;for(const n in e)if(!Ic(e[n],t[n]))return!1;return!0}function Ic(e,t){return Be(e)?mr(e,t):Be(t)?mr(t,e):e===t}function mr(e,t){return Be(t)?e.length===t.length&&e.every((n,s)=>n===t[s]):e.length===1&&e[0]===t}function Nc(e,t){if(e.startsWith("/"))return e;if(!e)return t;const n=t.split("/"),s=e.split("/");let r=n.length-1,i,o;for(i=0;i 1&&r--;else break;return n.slice(0,r).join("/")+"/"+s.slice(i-(i===s.length?1:0)).join("/")}var Zt;(function(e){e.pop="pop",e.push="push"})(Zt||(Zt={}));var Vt;(function(e){e.back="back",e.forward="forward",e.unknown=""})(Vt||(Vt={}));function Fc(e){if(!e)if(Et){const t=document.querySelector("base");e=t&&t.getAttribute("href")||"/",e=e.replace(/^\w+:\/\/[^\/]+/,"")}else e="/";return e[0]!=="/"&&e[0]!=="#"&&(e="/"+e),Oc(e)}const Lc=/^[^#]+#/;function kc(e,t){return e.replace(Lc,"#")+t}function $c(e,t){const n=document.documentElement.getBoundingClientRect(),s=e.getBoundingClientRect();return{behavior:t.behavior,left:s.left-n.left-(t.left||0),top:s.top-n.top-(t.top||0)}}const kn=()=>({left:window.pageXOffset,top:window.pageYOffset});function Hc(e){let t;if("el"in e){const n=e.el,s=typeof n=="string"&&n.startsWith("#"),r=typeof n=="string"?s?document.getElementById(n.slice(1)):document.querySelector(n):n;if(!r)return;t=$c(r,e)}else t=e;"scrollBehavior"in document.documentElement.style?window.scrollTo(t):window.scrollTo(t.left!=null?t.left:window.pageXOffset,t.top!=null?t.top:window.pageYOffset)}function _r(e,t){return(history.state?history.state.position-t:-1)+e}const cs=new Map;function jc(e,t){cs.set(e,t)}function Bc(e){const t=cs.get(e);return cs.delete(e),t}let Dc=()=>location.protocol+"//"+location.host;function Hi(e,t){const{pathname:n,search:s,hash:r}=t,i=e.indexOf("#");if(i>-1){let l=r.includes(e.slice(i))?e.slice(i).length:1,c=r.slice(l);return c[0]!=="/"&&(c="/"+c),gr(c,"")}return gr(n,e)+s+r}function Uc(e,t,n,s){let r=[],i=[],o=null;const l=({state:p})=>{const _=Hi(e,location),C=n.value,A=t.value;let k=0;if(p){if(n.value=_,t.value=p,o&&o===C){o=null;return}k=A?p.position-A.position:0}else s(_);r.forEach(g=>{g(n.value,C,{delta:k,type:Zt.pop,direction:k?k>0?Vt.forward:Vt.back:Vt.unknown})})};function c(){o=n.value}function a(p){r.push(p);const _=()=>{const C=r.indexOf(p);C>-1&&r.splice(C,1)};return i.push(_),_}function f(){const{history:p}=window;!p.state||p.replaceState(se({},p.state,{scroll:kn()}),"")}function h(){for(const p of i)p();i=[],window.removeEventListener("popstate",l),window.removeEventListener("beforeunload",f)}return window.addEventListener("popstate",l),window.addEventListener("beforeunload",f),{pauseListeners:c,listen:a,destroy:h}}function yr(e,t,n,s=!1,r=!1){return{back:e,current:t,forward:n,replaced:s,position:window.history.length,scroll:r?kn():null}}function Kc(e){const{history:t,location:n}=window,s={value:Hi(e,n)},r={value:t.state};r.value||i(s.value,{back:null,current:s.value,forward:null,position:t.length-1,replaced:!0,scroll:null},!0);function i(c,a,f){const h=e.indexOf("#"),p=h>-1?(n.host&&document.querySelector("base")?e:e.slice(h))+c:Dc()+e+c;try{t[f?"replaceState":"pushState"](a,"",p),r.value=a}catch(_){console.error(_),n[f?"replace":"assign"](p)}}function o(c,a){const f=se({},t.state,yr(r.value.back,c,r.value.forward,!0),a,{position:r.value.position});i(c,f,!0),s.value=c}function l(c,a){const f=se({},r.value,t.state,{forward:c,scroll:kn()});i(f.current,f,!0);const h=se({},yr(s.value,c,null),{position:f.position+1},a);i(c,h,!1),s.value=c}return{location:s,state:r,push:l,replace:o}}function Gu(e){e=Fc(e);const t=Kc(e),n=Uc(e,t.state,t.location,t.replace);function s(i,o=!0){o||n.pauseListeners(),history.go(i)}const r=se({location:"",base:e,go:s,createHref:kc.bind(null,e)},t,n);return Object.defineProperty(r,"location",{enumerable:!0,get:()=>t.location.value}),Object.defineProperty(r,"state",{enumerable:!0,get:()=>t.state.value}),r}function Wc(e){return typeof e=="string"||e&&typeof e=="object"}function ji(e){return typeof e=="string"||typeof e=="symbol"}const Ge={path:"/",name:void 0,params:{},query:{},hash:"",fullPath:"/",matched:[],meta:{},redirectedFrom:void 0},Bi=Symbol("");var br;(function(e){e[e.aborted=4]="aborted",e[e.cancelled=8]="cancelled",e[e.duplicated=16]="duplicated"})(br||(br={}));function Nt(e,t){return se(new Error,{type:e,[Bi]:!0},t)}function qe(e,t){return e instanceof Error&&Bi in e&&(t==null||!!(e.type&t))}const vr="[^/]+?",qc={sensitive:!1,strict:!1,start:!0,end:!0},Vc=/[.+*?^${}()[\]/\\]/g;function zc(e,t){const n=se({},qc,t),s=[];let r=n.start?"^":"";const i=[];for(const a of e){const f=a.length?[]:[90];n.strict&&!a.length&&(r+="/");for(let h=0;h t.length?t.length===1&&t[0]===40+40?1:-1:0}function Yc(e,t){let n=0;const s=e.score,r=t.score;for(;n 0&&t[t.length-1]<0}const Jc={type:0,value:""},Xc=/[a-zA-Z0-9_]/;function Zc(e){if(!e)return[[]];if(e==="/")return[[Jc]];if(!e.startsWith("/"))throw new Error(`Invalid path "${e}"`);function t(_){throw new Error(`ERR (${n})/"${a}": ${_}`)}let n=0,s=n;const r=[];let i;function o(){i&&r.push(i),i=[]}let l=0,c,a="",f="";function h(){!a||(n===0?i.push({type:0,value:a}):n===1||n===2||n===3?(i.length>1&&(c==="*"||c==="+")&&t(`A repeatable param (${a}) must be alone in its segment. eg: '/:ids+.`),i.push({type:1,value:a,regexp:f,repeatable:c==="*"||c==="+",optional:c==="*"||c==="?"})):t("Invalid state to consume buffer"),a="")}function p(){a+=c}for(;l {o(y)}:qt}function o(f){if(ji(f)){const h=s.get(f);h&&(s.delete(f),n.splice(n.indexOf(h),1),h.children.forEach(o),h.alias.forEach(o))}else{const h=n.indexOf(f);h>-1&&(n.splice(h,1),f.record.name&&s.delete(f.record.name),f.children.forEach(o),f.alias.forEach(o))}}function l(){return n}function c(f){let h=0;for(;h =0&&(f.record.path!==n[h].record.path||!Di(f,n[h]));)h++;n.splice(h,0,f),f.record.name&&!wr(f)&&s.set(f.record.name,f)}function a(f,h){let p,_={},C,A;if("name"in f&&f.name){if(p=s.get(f.name),!p)throw Nt(1,{location:f});A=p.record.name,_=se(Cr(h.params,p.keys.filter(y=>!y.optional).map(y=>y.name)),f.params&&Cr(f.params,p.keys.map(y=>y.name))),C=p.stringify(_)}else if("path"in f)C=f.path,p=n.find(y=>y.re.test(C)),p&&(_=p.parse(C),A=p.record.name);else{if(p=h.name?s.get(h.name):n.find(y=>y.re.test(h.path)),!p)throw Nt(1,{location:f,currentLocation:h});A=p.record.name,_=se({},h.params,f.params),C=p.stringify(_)}const k=[];let g=p;for(;g;)k.unshift(g.record),g=g.parent;return{name:A,path:C,params:_,matched:k,meta:su(k)}}return e.forEach(f=>i(f)),{addRoute:i,resolve:a,removeRoute:o,getRoutes:l,getRecordMatcher:r}}function Cr(e,t){const n={};for(const s of t)s in e&&(n[s]=e[s]);return n}function tu(e){return{path:e.path,redirect:e.redirect,name:e.name,meta:e.meta||{},aliasOf:void 0,beforeEnter:e.beforeEnter,props:nu(e),children:e.children||[],instances:{},leaveGuards:new Set,updateGuards:new Set,enterCallbacks:{},components:"components"in e?e.components||null:e.component&&{default:e.component}}}function nu(e){const t={},n=e.props||!1;if("component"in e)t.default=n;else for(const s in e.components)t[s]=typeof n=="boolean"?n:n[s];return t}function wr(e){for(;e;){if(e.record.aliasOf)return!0;e=e.parent}return!1}function su(e){return e.reduce((t,n)=>se(t,n.meta),{})}function xr(e,t){const n={};for(const s in e)n[s]=s in t?t[s]:e[s];return n}function Di(e,t){return t.children.some(n=>n===e||Di(e,n))}const Ui=/#/g,ru=/&/g,iu=/\//g,ou=/=/g,lu=/\?/g,Ki=/\+/g,cu=/%5B/g,uu=/%5D/g,Wi=/%5E/g,fu=/%60/g,qi=/%7B/g,au=/%7C/g,Vi=/%7D/g,du=/%20/g;function Ms(e){return encodeURI(""+e).replace(au,"|").replace(cu,"[").replace(uu,"]")}function hu(e){return Ms(e).replace(qi,"{").replace(Vi,"}").replace(Wi,"^")}function us(e){return Ms(e).replace(Ki,"%2B").replace(du,"+").replace(Ui,"%23").replace(ru,"%26").replace(fu,"`").replace(qi,"{").replace(Vi,"}").replace(Wi,"^")}function pu(e){return us(e).replace(ou,"%3D")}function gu(e){return Ms(e).replace(Ui,"%23").replace(lu,"%3F")}function mu(e){return e==null?"":gu(e).replace(iu,"%2F")}function Rn(e){try{return decodeURIComponent(""+e)}catch{}return""+e}function _u(e){const t={};if(e===""||e==="?")return t;const s=(e[0]==="?"?e.slice(1):e).split("&");for(let r=0;r i&&us(i)):[s&&us(s)]).forEach(i=>{i!==void 0&&(t+=(t.length?"&":"")+n,i!=null&&(t+="="+i))})}return t}function yu(e){const t={};for(const n in e){const s=e[n];s!==void 0&&(t[n]=Be(s)?s.map(r=>r==null?null:""+r):s==null?s:""+s)}return t}const bu=Symbol(""),Pr=Symbol(""),$n=Symbol(""),Is=Symbol(""),fs=Symbol("");function jt(){let e=[];function t(s){return e.push(s),()=>{const r=e.indexOf(s);r>-1&&e.splice(r,1)}}function n(){e=[]}return{add:t,list:()=>e,reset:n}}function tt(e,t,n,s,r){const i=s&&(s.enterCallbacks[r]=s.enterCallbacks[r]||[]);return()=>new Promise((o,l)=>{const c=h=>{h===!1?l(Nt(4,{from:n,to:t})):h instanceof Error?l(h):Wc(h)?l(Nt(2,{from:t,to:h})):(i&&s.enterCallbacks[r]===i&&typeof h=="function"&&i.push(h),o())},a=e.call(s&&s.instances[r],t,n,c);let f=Promise.resolve(a);e.length<3&&(f=f.then(c)),f.catch(h=>l(h))})}function Jn(e,t,n,s){const r=[];for(const i of e)for(const o in i.components){let l=i.components[o];if(!(t!=="beforeRouteEnter"&&!i.instances[o]))if(vu(l)){const a=(l.__vccOpts||l)[t];a&&r.push(tt(a,n,s,i,o))}else{let c=l();r.push(()=>c.then(a=>{if(!a)return Promise.reject(new Error(`Couldn't resolve component "${o}" at "${i.path}"`));const f=Ac(a)?a.default:a;i.components[o]=f;const p=(f.__vccOpts||f)[t];return p&&tt(p,n,s,i,o)()}))}}return r}function vu(e){return typeof e=="object"||"displayName"in e||"props"in e||"__vccOpts"in e}function Ar(e){const t=je($n),n=je(Is),s=Se(()=>t.resolve(Rt(e.to))),r=Se(()=>{const{matched:c}=s.value,{length:a}=c,f=c[a-1],h=n.matched;if(!f||!h.length)return-1;const p=h.findIndex(It.bind(null,f));if(p>-1)return p;const _=Tr(c[a-2]);return a>1&&Tr(f)===_&&h[h.length-1].path!==_?h.findIndex(It.bind(null,c[a-2])):p}),i=Se(()=>r.value>-1&&xu(n.params,s.value.params)),o=Se(()=>r.value>-1&&r.value===n.matched.length-1&&$i(n.params,s.value.params));function l(c={}){return wu(c)?t[Rt(e.replace)?"replace":"push"](Rt(e.to)).catch(qt):Promise.resolve()}return{route:s,href:Se(()=>s.value.href),isActive:i,isExactActive:o,navigate:l}}const Eu=Ps({name:"RouterLink",compatConfig:{MODE:3},props:{to:{type:[String,Object],required:!0},replace:Boolean,activeClass:String,exactActiveClass:String,custom:Boolean,ariaCurrentValue:{type:String,default:"page"}},useLink:Ar,setup(e,{slots:t}){const n=en(Ar(e)),{options:s}=je($n),r=Se(()=>({[Or(e.activeClass,s.linkActiveClass,"router-link-active")]:n.isActive,[Or(e.exactActiveClass,s.linkExactActiveClass,"router-link-exact-active")]:n.isExactActive}));return()=>{const i=t.default&&t.default(n);return e.custom?i:Ss("a",{"aria-current":n.isExactActive?e.ariaCurrentValue:null,href:n.href,onClick:n.navigate,class:r.value},i)}}}),Cu=Eu;function wu(e){if(!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)&&!e.defaultPrevented&&!(e.button!==void 0&&e.button!==0)){if(e.currentTarget&&e.currentTarget.getAttribute){const t=e.currentTarget.getAttribute("target");if(/\b_blank\b/i.test(t))return}return e.preventDefault&&e.preventDefault(),!0}}function xu(e,t){for(const n in t){const s=t[n],r=e[n];if(typeof s=="string"){if(s!==r)return!1}else if(!Be(r)||r.length!==s.length||s.some((i,o)=>i!==r[o]))return!1}return!0}function Tr(e){return e?e.aliasOf?e.aliasOf.path:e.path:""}const Or=(e,t,n)=>e!=null?e:t!=null?t:n,Ru=Ps({name:"RouterView",inheritAttrs:!1,props:{name:{type:String,default:"default"},route:Object},compatConfig:{MODE:3},setup(e,{attrs:t,slots:n}){const s=je(fs),r=Se(()=>e.route||s.value),i=je(Pr,0),o=Se(()=>{let a=Rt(i);const{matched:f}=r.value;let h;for(;(h=f[a])&&!h.components;)a++;return a}),l=Se(()=>r.value.matched[o.value]);pn(Pr,Se(()=>o.value+1)),pn(bu,l),pn(fs,r);const c=hn();return gn(()=>[c.value,l.value,e.name],([a,f,h],[p,_,C])=>{f&&(f.instances[h]=a,_&&_!==f&&a&&a===p&&(f.leaveGuards.size||(f.leaveGuards=_.leaveGuards),f.updateGuards.size||(f.updateGuards=_.updateGuards))),a&&f&&(!_||!It(f,_)||!p)&&(f.enterCallbacks[h]||[]).forEach(A=>A(a))},{flush:"post"}),()=>{const a=r.value,f=e.name,h=l.value,p=h&&h.components[f];if(!p)return Sr(n.default,{Component:p,route:a});const _=h.props[f],C=_?_===!0?a.params:typeof _=="function"?_(a):_:null,k=Ss(p,se({},C,t,{onVnodeUnmounted:g=>{g.component.isUnmounted&&(h.instances[f]=null)},ref:c}));return Sr(n.default,{Component:k,route:a})||k}}});function Sr(e,t){if(!e)return null;const n=e(t);return n.length===1?n[0]:n}const Pu=Ru;function ef(e){const t=eu(e.routes,e),n=e.parseQuery||_u,s=e.stringifyQuery||Rr,r=e.history,i=jt(),o=jt(),l=jt(),c=jo(Ge);let a=Ge;Et&&e.scrollBehavior&&"scrollRestoration"in history&&(history.scrollRestoration="manual");const f=Qn.bind(null,v=>""+v),h=Qn.bind(null,mu),p=Qn.bind(null,Rn);function _(v,I){let O,H;return ji(v)?(O=t.getRecordMatcher(v),H=I):H=v,t.addRoute(H,O)}function C(v){const I=t.getRecordMatcher(v);I&&t.removeRoute(I)}function A(){return t.getRoutes().map(v=>v.record)}function k(v){return!!t.getRecordMatcher(v)}function g(v,I){if(I=se({},I||c.value),typeof v=="string"){const u=Yn(n,v,I.path),d=t.resolve({path:u.path},I),m=r.createHref(u.fullPath);return se(u,d,{params:p(d.params),hash:Rn(u.hash),redirectedFrom:void 0,href:m})}let O;if("path"in v)O=se({},v,{path:Yn(n,v.path,I.path).path});else{const u=se({},v.params);for(const d in u)u[d]==null&&delete u[d];O=se({},v,{params:h(v.params)}),I.params=h(I.params)}const H=t.resolve(O,I),ee=v.hash||"";H.params=f(p(H.params));const fe=Sc(s,se({},v,{hash:hu(ee),path:H.path})),Y=r.createHref(fe);return se({fullPath:fe,hash:ee,query:s===Rr?yu(v.query):v.query||{}},H,{redirectedFrom:void 0,href:Y})}function y(v){return typeof v=="string"?Yn(n,v,c.value.path):se({},v)}function P(v,I){if(a!==v)return Nt(8,{from:I,to:v})}function $(v){return z(v)}function U(v){return $(se(y(v),{replace:!0}))}function J(v){const I=v.matched[v.matched.length-1];if(I&&I.redirect){const{redirect:O}=I;let H=typeof O=="function"?O(v):O;return typeof H=="string"&&(H=H.includes("?")||H.includes("#")?H=y(H):{path:H},H.params={}),se({query:v.query,hash:v.hash,params:"path"in H?{}:v.params},H)}}function z(v,I){const O=a=g(v),H=c.value,ee=v.state,fe=v.force,Y=v.replace===!0,u=J(O);if(u)return z(se(y(u),{state:typeof u=="object"?se({},ee,u.state):ee,force:fe,replace:Y}),I||O);const d=O;d.redirectedFrom=I;let m;return!fe&&Mc(s,H,O)&&(m=Nt(16,{to:d,from:H}),ot(H,H,!0,!1)),(m?Promise.resolve(m):q(d,H)).catch(b=>qe(b)?qe(b,2)?b:Ie(b):oe(b,d,H)).then(b=>{if(b){if(qe(b,2))return z(se({replace:Y},y(b.to),{state:typeof b.to=="object"?se({},ee,b.to.state):ee,force:fe}),I||d)}else b=Z(d,H,!0,Y,ee);return K(d,H,b),b})}function S(v,I){const O=P(v,I);return O?Promise.reject(O):Promise.resolve()}function q(v,I){let O;const[H,ee,fe]=Au(v,I);O=Jn(H.reverse(),"beforeRouteLeave",v,I);for(const u of H)u.leaveGuards.forEach(d=>{O.push(tt(d,v,I))});const Y=S.bind(null,v,I);return O.push(Y),vt(O).then(()=>{O=[];for(const u of i.list())O.push(tt(u,v,I));return O.push(Y),vt(O)}).then(()=>{O=Jn(ee,"beforeRouteUpdate",v,I);for(const u of ee)u.updateGuards.forEach(d=>{O.push(tt(d,v,I))});return O.push(Y),vt(O)}).then(()=>{O=[];for(const u of v.matched)if(u.beforeEnter&&!I.matched.includes(u))if(Be(u.beforeEnter))for(const d of u.beforeEnter)O.push(tt(d,v,I));else O.push(tt(u.beforeEnter,v,I));return O.push(Y),vt(O)}).then(()=>(v.matched.forEach(u=>u.enterCallbacks={}),O=Jn(fe,"beforeRouteEnter",v,I),O.push(Y),vt(O))).then(()=>{O=[];for(const u of o.list())O.push(tt(u,v,I));return O.push(Y),vt(O)}).catch(u=>qe(u,8)?u:Promise.reject(u))}function K(v,I,O){for(const H of l.list())H(v,I,O)}function Z(v,I,O,H,ee){const fe=P(v,I);if(fe)return fe;const Y=I===Ge,u=Et?history.state:{};O&&(H||Y?r.replace(v.fullPath,se({scroll:Y&&u&&u.scroll},ee)):r.push(v.fullPath,ee)),c.value=v,ot(v,I,O,Y),Ie()}let N;function Q(){N||(N=r.listen((v,I,O)=>{if(!sn.listening)return;const H=g(v),ee=J(H);if(ee){z(se(ee,{replace:!0}),H).catch(qt);return}a=H;const fe=c.value;Et&&jc(_r(fe.fullPath,O.delta),kn()),q(H,fe).catch(Y=>qe(Y,12)?Y:qe(Y,2)?(z(Y.to,H).then(u=>{qe(u,20)&&!O.delta&&O.type===Zt.pop&&r.go(-1,!1)}).catch(qt),Promise.reject()):(O.delta&&r.go(-O.delta,!1),oe(Y,H,fe))).then(Y=>{Y=Y||Z(H,fe,!1),Y&&(O.delta&&!qe(Y,8)?r.go(-O.delta,!1):O.type===Zt.pop&&qe(Y,20)&&r.go(-1,!1)),K(H,fe,Y)}).catch(qt)}))}let L=jt(),ye=jt(),G;function oe(v,I,O){Ie(v);const H=ye.list();return H.length?H.forEach(ee=>ee(v,I,O)):console.error(v),Promise.reject(v)}function re(){return G&&c.value!==Ge?Promise.resolve():new Promise((v,I)=>{L.add([v,I])})}function Ie(v){return G||(G=!v,Q(),L.list().forEach(([I,O])=>v?O(v):I()),L.reset()),v}function ot(v,I,O,H){const{scrollBehavior:ee}=e;if(!Et||!ee)return Promise.resolve();const fe=!O&&Bc(_r(v.fullPath,0))||(H||!O)&&history.state&&history.state.scroll||null;return si().then(()=>ee(v,I,fe)).then(Y=>Y&&Hc(Y)).catch(Y=>oe(Y,v,I))}const Ne=v=>r.go(v);let Ee;const yt=new Set,sn={currentRoute:c,listening:!0,addRoute:_,removeRoute:C,hasRoute:k,getRoutes:A,resolve:g,options:e,push:$,replace:U,go:Ne,back:()=>Ne(-1),forward:()=>Ne(1),beforeEach:i.add,beforeResolve:o.add,afterEach:l.add,onError:ye.add,isReady:re,install(v){const I=this;v.component("RouterLink",Cu),v.component("RouterView",Pu),v.config.globalProperties.$router=I,Object.defineProperty(v.config.globalProperties,"$route",{enumerable:!0,get:()=>Rt(c)}),Et&&!Ee&&c.value===Ge&&(Ee=!0,$(r.location).catch(ee=>{}));const O={};for(const ee in Ge)O[ee]=Se(()=>c.value[ee]);v.provide($n,I),v.provide(Is,en(O)),v.provide(fs,c);const H=v.unmount;yt.add(v),v.unmount=function(){yt.delete(v),yt.size<1&&(a=Ge,N&&N(),N=null,c.value=Ge,Ee=!1,G=!1),H()}}};return sn}function vt(e){return e.reduce((t,n)=>t.then(()=>n()),Promise.resolve())}function Au(e,t){const n=[],s=[],r=[],i=Math.max(t.matched.length,e.matched.length);for(let o=0;oIt(a,l))?s.push(l):n.push(l));const c=e.matched[o];c&&(t.matched.find(a=>It(a,c))||r.push(c))}return[n,s,r]}function tf(){return je($n)}function nf(){return je(Is)}const sf=(e,t)=>{const n=e.__vccOpts||e;for(const[s,r]of t)n[s]=r;return n};export{Zo as $,$u as A,Ii as B,Tu as C,hs as D,ds as E,me as F,Rt as G,uo as H,Ou as I,Wl as J,si as K,jo as L,Iu as M,mi as N,V as O,Lu as P,de as Q,Pu as R,Ge as S,Li as T,we as U,ku as V,Bu as W,Mi as X,Mu as Y,Oi as Z,sf as _,Qr as a,Bl as a0,Vu as a1,zu as a2,Fu as a3,Uu as a4,ju as a5,gi as a6,Du as a7,Zu as a8,Su as a9,en as b,Ps as c,Nu as d,D as e,Wu as f,Xu as g,Se as h,pe as i,Ss as j,je as k,qu as l,Ju as m,Ku as n,pi as o,ef as p,Yu as q,hn as r,Gu as s,Qu as t,nf as u,pn as v,gn as w,tf as x,Ai as y,Hu as z}; diff --git a/assets/generation.html.c0787daf.js b/assets/generation.html.c0787daf.js new file mode 100644 index 000000000..118199397 --- /dev/null +++ b/assets/generation.html.c0787daf.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-7231735f","path":"/stacks/agnostic/generation.html","title":"Code Generation Overview","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Running Code Generation","slug":"running-code-generation","link":"#running-code-generation","children":[{"level":3,"title":"CLI Options","slug":"cli-options","link":"#cli-options","children":[]}]},{"level":2,"title":"Generated Code","slug":"generated-code","link":"#generated-code","children":[{"level":3,"title":"Backend C#","slug":"backend-c","link":"#backend-c","children":[]},{"level":3,"title":"Frontend - Vue","slug":"frontend-vue","link":"#frontend-vue","children":[]},{"level":3,"title":"Frontend - Knockout","slug":"frontend-knockout","link":"#frontend-knockout","children":[]}]}],"git":{"updatedTime":1678412142000},"filePathRelative":"stacks/agnostic/generation.md"}');export{e as data}; diff --git a/assets/generation.html.d81ac009.js b/assets/generation.html.d81ac009.js new file mode 100644 index 000000000..535dd2483 --- /dev/null +++ b/assets/generation.html.d81ac009.js @@ -0,0 +1,19 @@ +import{_ as c,y as i,z as p,X as n,Q as o,$ as s,B as e,a5 as r,P as l}from"./framework.fe9a73df.js";const d={},u=n("h1",{id:"code-generation-overview",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#code-generation-overview","aria-hidden":"true"},"#"),e(" Code Generation Overview")],-1),h=n("p",null,"Coalesce's principal purpose is a code generation framework for automating the creation of the boring-but-necessary parts of a web application. Below, you find an overview of the different components of Coalesce's code generation features.",-1),y={class:"table-of-contents"},D=r(` # Running Code Generation
Coalesce's code generation is ran via a dotnet CLI tool,
dotnet coalesce
. In order to invoke this tool, you must have the appropriate references to the package that provides it in your .csproj file:<Project Sdk="Microsoft.NET.Sdk.Web"> + <PropertyGroup> + <TargetFramework>net7.0</TargetFramework> + + <!-- Necessary to use DotNetCliToolReference with modern framework versions --> + <DotnetCliToolTargetFramework>net7.0</DotnetCliToolTargetFramework> + </PropertyGroup> + + ... + + <ItemGroup> + <PackageReference Include="IntelliTect.Coalesce" Version="..." /> + </ItemGroup> + + <ItemGroup> + <DotNetCliToolReference Include="IntelliTect.Coalesce.Tools" Version="..." /> + </ItemGroup> +</Project> +
# CLI Options
`,4),f=n("code",null,"coalesce.json",-1),v=r('There are a couple of extra options which are only available as CLI parameters to
dotnet coalesce
. These options do not affect the behavior of the code generation - only the behavior of the CLI itself.
--debug
- When this flag is specified when runningdotnet coalesce
, Coalesce will wait for a debugger to be attached to its process before starting code generation.
-v|--verbosity <level>
- Set the verbosity of the output. Options aretrace
,debug
,information
,warning
,error
,critical
, andnone
.# Generated Code
Coalesce will generate a full vertical stack of code for you:
# Backend C#
# API Controllers
',7),m=n("code",null,"/Api/Generated",-1),g=n("h4",{id:"c-dtos",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#c-dtos","aria-hidden":"true"},"#"),e(" C# DTOs")],-1),b=n("h3",{id:"frontend-vue",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#frontend-vue","aria-hidden":"true"},"#"),e(" Frontend - Vue")],-1),C=n("h3",{id:"frontend-knockout",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#frontend-knockout","aria-hidden":"true"},"#"),e(" Frontend - Knockout")],-1);function _(k,w){const t=l("router-link"),a=l("RouterLink");return i(),p("div",null,[u,h,n("nav",y,[n("ul",null,[n("li",null,[o(t,{to:"#running-code-generation"},{default:s(()=>[e("Running Code Generation")]),_:1}),n("ul",null,[n("li",null,[o(t,{to:"#cli-options"},{default:s(()=>[e("CLI Options")]),_:1})])])]),n("li",null,[o(t,{to:"#generated-code"},{default:s(()=>[e("Generated Code")]),_:1}),n("ul",null,[n("li",null,[o(t,{to:"#backend-c"},{default:s(()=>[e("Backend C#")]),_:1})]),n("li",null,[o(t,{to:"#frontend-vue"},{default:s(()=>[e("Frontend - Vue")]),_:1})]),n("li",null,[o(t,{to:"#frontend-knockout"},{default:s(()=>[e("Frontend - Knockout")]),_:1})])])])])]),D,n("p",null,[e("All configuration of the way that Coalesce interacts with your projects, including locating, analyzing, and producing generated code, is done in a json configuration file, "),f,e(". Read more about this file at "),o(a,{to:"/topics/coalesce-json.html"},{default:s(()=>[e("Code Generation Configuration")]),_:1}),e(".")]),v,n("p",null,[e("For each of your "),o(a,{to:"/modeling/model-types/entities.html"},{default:s(()=>[e("Entity Models")]),_:1}),e(", "),o(a,{to:"/modeling/model-types/dtos.html"},{default:s(()=>[e("Custom DTOs")]),_:1}),e(", and "),o(a,{to:"/modeling/model-types/services.html"},{default:s(()=>[e("Services")]),_:1}),e(", an API controller is created in the "),m,e(" directory of your web project. These controllers provide a number of endpoints for interacting with your data.")]),n("p",null,[e("These controllers can be secured at a high level using "),o(a,{to:"/modeling/model-components/attributes/security-attribute.html"},{default:s(()=>[e("Security Attributes")]),_:1}),e(", and when applicable to the type, with "),o(a,{to:"/modeling/model-components/data-sources.html"},{default:s(()=>[e("Data Sources")]),_:1}),e(" and "),o(a,{to:"/modeling/model-components/behaviors.html"},{default:s(()=>[e("Behaviors")]),_:1}),e(".")]),g,n("p",null,[e("For each of your "),o(a,{to:"/modeling/model-types/entities.html"},{default:s(()=>[e("Entity Models")]),_:1}),e(", a C# DTO class is created. These classes are used to hold the data that will be serialized and sent to the client, as well as data that has been received from the client before it has been mapped back to your EF POCO class.")]),n("p",null,[e("See "),o(a,{to:"/stacks/agnostic/dtos.html"},{default:s(()=>[e("Generated C# DTOs")]),_:1}),e(" for more information.")]),b,n("p",null,[e("An overview of the Vue generated code can be found at "),o(a,{to:"/stacks/vue/overview.html"},{default:s(()=>[e("Vue Overview")]),_:1}),e(".")]),C,n("p",null,[e("An overview of the legacy Knockout generated code can be found at "),o(a,{to:"/stacks/ko/overview.html"},{default:s(()=>[e("Knockout Overview")]),_:1}),e(".")])])}const x=c(d,[["render",_],["__file","generation.html.vue"]]);export{x as default}; diff --git a/assets/getting-started-modeling.html.bcc466db.js b/assets/getting-started-modeling.html.bcc466db.js new file mode 100644 index 000000000..bb61440ba --- /dev/null +++ b/assets/getting-started-modeling.html.bcc466db.js @@ -0,0 +1 @@ +import{_ as l,y as d,z as r,W as n,X as e,B as t,Q as a,$ as i,a5 as s,P as c}from"./framework.fe9a73df.js";const u={},p=e("h1",{id:"data-modeling",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#data-modeling","aria-hidden":"true"},"#"),t(" Data Modeling")],-1),h=e("p",null,"At this point, you can open up the newly-created solution in Visual Studio and run your application. However, your application won't do much without a data model, so you will probably want to do the following before running:",-1),m=e("code",null,"DbSet<>",-1),_=e("code",null,"AppDbContext",-1),g=e("code",null,"ApplicationUser",-1),y=s("Run
dotnet ef migrations add Init
(Init can be any name) in the data project to create an initial database migration.",2),f=e("p",null,"You're now at a point where you can start creating your own pages!",-1);function w(b,R){const o=c("RouterLink");return d(),r("div",null,[n(" MARKER:data-modeling "),p,h,e("ul",null,[e("li",null,[e("p",null,[t("Create an initial "),a(o,{to:"/modeling/model-types/entities.html"},{default:i(()=>[t("Data Model")]),_:1}),t(" by adding EF entity classes to the data project and the corresponding "),m,t(" properties to "),_,t(". You will notice that the starter project includes a single model, "),g,t(", to start with. Feel free to change this model or remove it entirely. Read "),a(o,{to:"/modeling/model-types/entities.html"},{default:i(()=>[t("Entity Models")]),_:1}),t(" for more information about creating a data model.")])]),y]),f,n(" MARKER:data-modeling-end ")])}const k=l(u,[["render",w],["__file","getting-started-modeling.html.vue"]]);export{k as default}; diff --git a/assets/getting-started-modeling.html.c1e6e2c4.js b/assets/getting-started-modeling.html.c1e6e2c4.js new file mode 100644 index 000000000..056b53bd7 --- /dev/null +++ b/assets/getting-started-modeling.html.c1e6e2c4.js @@ -0,0 +1 @@ +const t=JSON.parse('{"key":"v-ba2d632e","path":"/stacks/agnostic/getting-started-modeling.html","title":"Data Modeling","lang":"en-US","frontmatter":{},"excerpt":"","headers":[],"git":{"updatedTime":1652390840000},"filePathRelative":"stacks/agnostic/getting-started-modeling.md"}');export{t as data}; diff --git a/assets/getting-started.html.2295c26a.js b/assets/getting-started.html.2295c26a.js new file mode 100644 index 000000000..678062cfd --- /dev/null +++ b/assets/getting-started.html.2295c26a.js @@ -0,0 +1,56 @@ +import{_ as c,y as r,z as i,X as s,B as n,Q as a,$ as e,a5 as o,P as t}from"./framework.fe9a73df.js";const D={},d=s("h1",{id:"getting-started-with-knockout",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#getting-started-with-knockout","aria-hidden":"true"},"#"),n(" Getting Started with Knockout")],-1),y=s("h2",{id:"creating-a-project",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#creating-a-project","aria-hidden":"true"},"#"),n(" Creating a Project")],-1),u={class:"custom-container warning"},C=s("p",{class:"custom-container-title"},"WARNING",-1),v=o(` Run Coalesce's code generation by either:
- Running
dotnet coalesce
in the web project's root directory- Running the
coalesce
npm script (Vue) or gulp task (Knockout) in the Task Runner ExplorerThe quickest and easiest way to create a new Coalesce Knockout application is to use the
dotnet new
template. In your favorite shell:`,2),m={href:"https://www.nuget.org/packages/IntelliTect.Coalesce.KnockoutJS.Template/",target:"_blank",rel:"noopener noreferrer"},h=s("img",{src:"https://img.shields.io/nuget/v/IntelliTect.Coalesce.KnockoutJS.Template",alt:""},null,-1),b={href:"https://github.com/IntelliTect/Coalesce.KnockoutJS.Template",target:"_blank",rel:"noopener noreferrer"},g=s("h2",{id:"data-modeling",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#data-modeling","aria-hidden":"true"},"#"),n(" Data Modeling")],-1),E=s("p",null,"At this point, you can open up the newly-created solution in Visual Studio and run your application. However, your application won't do much without a data model, so you will probably want to do the following before running:",-1),_=s("code",null,"DbSet<>",-1),w=s("code",null,"AppDbContext",-1),f=s("code",null,"ApplicationUser",-1),k=o("dotnet new install IntelliTect.Coalesce.KnockoutJS.Template +dotnet new coalesceko +
Run
dotnet ef migrations add Init
(Init can be any name) in the data project to create an initial database migration.",2),A=s("p",null,"You're now at a point where you can start creating your own pages!",-1),F=s("h2",{id:"building-pages-features",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#building-pages-features","aria-hidden":"true"},"#"),n(" Building Pages & Features")],-1),x=s("code",null,"Person",-1),B=s("code",null,"dotnet coalesce",-1),P=o(` Run Coalesce's code generation by either:
- Running
dotnet coalesce
in the web project's root directory- Running the
coalesce
npm script (Vue) or gulp task (Knockout) in the Task Runner Explorernamespace MyApplication.Data.Models +{ + public class Person + { + public int PersonId { get; set; } + public string Name { get; set; } + public DateTimeOffset? BirthDate { get; set; } + } +} +
We can create a details page for a Person by creating:
A controller in
src/MyApplication.Web/Controllers/PersonController.cs
:namespace MyApplication.Web.Controllers +{ + public partial class PersonController + { + public IActionResult Details() => View(); + } +} +
A view in
src/MyApplication.Web/Views/Person/Details.cshtml
:<h1>Person Details</h1> + +<div data-bind="with: person"> + <dl class="dl-horizontal"> + <dt>Name </dt> + <dd data-bind="text: name"></dd> + + <dt>Date of Birth </dt> + <dd data-bind="moment: birthDate, format: 'MM/DD/YYYY hh:mm a'"></dd> + </dl> +</div> + +@section Scripts +{ +<script src="~/js/person.details.js"></script> +<script> + $(function () { + var vm = new MyApplication.PersonDetails(); + ko.applyBindings(vm); + vm.load(); + }); +</script> +} +
And a script in
src/MyApplication.Web/Scripts/person.details.ts
:/// <reference path="viewmodels.generated.d.ts" /> + +module MyApplication { + export class PersonDetails { + public person = new ViewModels.Person(); + + load() { + var id = Coalesce.Utilities.GetUrlParameter("id"); + if (id != null && id != '') { + this.person.load(id); + } + } + } +} +
With these pieces in place, we now have a functioning page that will display details about a person. We can start up the application and navigate to
`,4);function q(M,T){const l=t("RouterLink"),p=t("ExternalLinkIcon");return r(),i("div",null,[d,y,s("div",u,[C,s("p",null,[n("The Coalesce Knockout.js stack is deprecated and will be receiving only critical bug fixes going forward. You are strongly encouraged to start all new Coalesce projects with "),a(l,{to:"/stacks/vue/getting-started.html"},{default:e(()=>[n("Vue")]),_:1}),n(".")])]),v,s("p",null,[s("a",m,[h,a(p)]),n(" • "),s("a",b,[n("View on GitHub"),a(p)])]),g,E,s("ul",null,[s("li",null,[s("p",null,[n("Create an initial "),a(l,{to:"/modeling/model-types/entities.html"},{default:e(()=>[n("Data Model")]),_:1}),n(" by adding EF entity classes to the data project and the corresponding "),_,n(" properties to "),w,n(". You will notice that the starter project includes a single model, "),f,n(", to start with. Feel free to change this model or remove it entirely. Read "),a(l,{to:"/modeling/model-types/entities.html"},{default:e(()=>[n("Entity Models")]),_:1}),n(" for more information about creating a data model.")])]),k]),A,F,s("p",null,[n("Lets say we've created a "),a(l,{to:"/modeling/model-types/entities.html"},{default:e(()=>[n("model")]),_:1}),n(" called "),x,n(" as follows, and we've ran code generation with "),B,n(":")]),P,s("p",null,[n("From this point, one can start adding more fields, more features, and more flair to the page. Check out all the other documentation in the sidebar to see what else Coalesce has to offer, including the "),a(l,{to:"/stacks/ko/overview.html"},{default:e(()=>[n("Knockout Overview")]),_:1}),n(".")])])}const j=c(D,[["render",q],["__file","getting-started.html.vue"]]);export{j as default}; diff --git a/assets/getting-started.html.435781ec.js b/assets/getting-started.html.435781ec.js new file mode 100644 index 000000000..1679197d1 --- /dev/null +++ b/assets/getting-started.html.435781ec.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-91a9c356","path":"/stacks/vue/getting-started.html","title":"Getting Started with Vue","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Creating a Project","slug":"creating-a-project","link":"#creating-a-project","children":[]},{"level":2,"title":"Project Structure","slug":"project-structure","link":"#project-structure","children":[]},{"level":2,"title":"Data Modeling","slug":"data-modeling","link":"#data-modeling","children":[]},{"level":2,"title":"Building Pages & Features","slug":"building-pages-features","link":"#building-pages-features","children":[]}],"git":{"updatedTime":1677795733000},"filePathRelative":"stacks/vue/getting-started.md"}');export{e as data}; diff --git a/assets/getting-started.html.5bf7a7d7.js b/assets/getting-started.html.5bf7a7d7.js new file mode 100644 index 000000000..9c98952f6 --- /dev/null +++ b/assets/getting-started.html.5bf7a7d7.js @@ -0,0 +1,45 @@ +import{_ as c,y as r,z as i,X as n,Q as e,B as s,$ as l,a5 as o,P as p}from"./framework.fe9a73df.js";const D={},d=o(`/Person/Details?id=1
(assuming a person with ID 1 exists - if not, navigate to/Person/Table
and create one).# Getting Started with Vue
# Creating a Project
The quickest and easiest way to create a new Coalesce Vue application is to use the
dotnet new
template. In your favorite shell:`,4),y={href:"https://www.nuget.org/packages/IntelliTect.Coalesce.Vue.Template/",target:"_blank",rel:"noopener noreferrer"},u=n("img",{src:"https://img.shields.io/nuget/v/IntelliTect.Coalesce.Vue.Template",alt:""},null,-1),h={href:"https://github.com/IntelliTect/Coalesce.Vue.Template",target:"_blank",rel:"noopener noreferrer"},m=n("h2",{id:"project-structure",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#project-structure","aria-hidden":"true"},"#"),s(" Project Structure")],-1),v={class:"custom-container tip"},C=n("p",{class:"custom-container-title"},"Important",-1),g={href:"https://vitejs.dev/",target:"_blank",rel:"noopener noreferrer"},b={href:"https://vitejs.dev/guide/",target:"_blank",rel:"noopener noreferrer"},f=o('mkdir MyCompany.MyProject +cd MyCompany.MyProject +dotnet new install IntelliTect.Coalesce.Vue.Template +dotnet new coalescevue +cd *.Web +npm ci +
The structure of the Web project follows the conventions of both ASP.NET Core and Vite. The Vue-specific folders are as follows:
/src
- Files that should be compiled into your application. CSS/SCSS, TypeScript, Vue SFCs, and so on./public
- Static assets that should be served as files. Includes index.html, the root document of the application./wwwroot
- Target for compiled output.During development, no special tooling is required to build your frontend code. Coalesce's
UseViteDevelopmentServer
in ASP.NET Core will take care of that automatically when the application starts. Just make sure NPM packages have been installed (npm ci
).# Data Modeling
At this point, you can open up the newly-created solution in Visual Studio and run your application. However, your application won't do much without a data model, so you will probably want to do the following before running:
',5),_=n("code",null,"DbSet<>",-1),w=n("code",null,"AppDbContext",-1),E=n("code",null,"ApplicationUser",-1),k=o("Run
dotnet ef migrations add Init
(Init can be any name) in the data project to create an initial database migration.",2),F=n("p",null,"You're now at a point where you can start creating your own pages!",-1),V=n("h2",{id:"building-pages-features",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#building-pages-features","aria-hidden":"true"},"#"),s(" Building Pages & Features")],-1),x=n("code",null,"Person",-1),A=n("code",null,"dotnet coalesce",-1),T=o(` Run Coalesce's code generation by either:
- Running
dotnet coalesce
in the web project's root directory- Running the
coalesce
npm script (Vue) or gulp task (Knockout) in the Task Runner Explorer`,1),S={href:"https://vuejs.org/guide/scaling-up/sfc.html",target:"_blank",rel:"noopener noreferrer"},P=n("code",null,"MyApplication.Web/src/views/person-details.vue",-1),j=o(`namespace MyApplication.Data.Models +{ + public class Person + { + public int PersonId { get; set; } + public string Name { get; set; } + public DateTimeOffset? BirthDate { get; set; } + } +} +
`,1),q={class:"custom-container tip"},I=n("p",{class:"custom-container-title"},"Note",-1),M=n("code",null,"string",-1),B=n("code",null,"number",-1),N=o(`<template> + <dl> + <dt>Name</dt> + <dd> + <c-display :model="person" for="name" /> + </dd> + + <dt>Date of Birth</dt> + <dd> + <c-display :model="person" for="birthDate" format="M/d/yyyy" /> + </dd> + </dl> +</template> + +<script setup lang="ts"> +import { PersonViewModel } from "@/viewmodels.g"; + +const props = defineProps<{ id: number }>(); +const person = new PersonViewModel(); + +person.$load(props.id); +</script> +
We then need to add route to this new view. In
MyApplication.Web/src/router.ts
, add a new item to theroutes
array:// In the \`routes\` array, add the following item: +{ + path: '/person/:id', + name: 'person-details', + component: () => import('@/views/person-details.vue'), + props: route => ({ id: +route.params.id }), +}, +
With these pieces in place, we now have a functioning page that will display details about a person. We can start up the application (or, if it was already running, refresh the page) and navigate to
`,3);function R(W,L){const t=p("ExternalLinkIcon"),a=p("RouterLink");return r(),i("div",null,[d,n("p",null,[n("a",y,[u,e(t)]),s(" • "),n("a",h,[s("View on GitHub"),e(t)])]),m,n("div",v,[C,n("p",null,[s("The Vue template is based on "),n("a",g,[s("Vite"),e(t)]),s(". You are strongly encouraged to read through at least the first few pages of the "),n("a",b,[s("Vite Documentation"),e(t)]),s(" before getting started on any development.")])]),f,n("ul",null,[n("li",null,[n("p",null,[s("Create an initial "),e(a,{to:"/modeling/model-types/entities.html"},{default:l(()=>[s("Data Model")]),_:1}),s(" by adding EF entity classes to the data project and the corresponding "),_,s(" properties to "),w,s(". You will notice that the starter project includes a single model, "),E,s(", to start with. Feel free to change this model or remove it entirely. Read "),e(a,{to:"/modeling/model-types/entities.html"},{default:l(()=>[s("Entity Models")]),_:1}),s(" for more information about creating a data model.")])]),k]),F,V,n("p",null,[s("Lets say we've created a "),e(a,{to:"/modeling/model-types/entities.html"},{default:l(()=>[s("model")]),_:1}),s(" called "),x,s(" as follows, and we've ran code generation with "),A,s(":")]),T,n("p",null,[s("We can create a details page for a Person by creating a "),n("a",S,[s("Single File Component"),e(t)]),s(" in "),P,s(":")]),j,n("div",q,[I,n("p",null,[s("In the code above, "),e(a,{to:"/stacks/vue/coalesce-vue-vuetify/components/c-display.html"},{default:l(()=>[s("c-display")]),_:1}),s(" is a component that comes from the "),e(a,{to:"/stacks/vue/coalesce-vue-vuetify/overview.html"},{default:l(()=>[s("Vuetify Components")]),_:1}),s(" for Coalesce.")]),n("p",null,[s("For simple property types like "),M,s(" and "),B,s(" you can always use simple template interpolation syntax, but for more complex properties like dates, "),e(a,{to:"/stacks/vue/coalesce-vue-vuetify/components/c-display.html"},{default:l(()=>[s("c-display")]),_:1}),s(" is handy to use because it includes features like built-in date formatting.")])]),N,n("p",null,[s("From this point, you can start adding more fields, more features, and more flair to the page. Check out all the other documentation in the sidebar to see what else Coalesce has to offer, including the "),e(a,{to:"/stacks/vue/overview.html"},{default:l(()=>[s("Vue Overview")]),_:1}),s(".")])])}const G=c(D,[["render",R],["__file","getting-started.html.vue"]]);export{G as default}; diff --git a/assets/getting-started.html.72eb3d61.js b/assets/getting-started.html.72eb3d61.js new file mode 100644 index 000000000..334678cdf --- /dev/null +++ b/assets/getting-started.html.72eb3d61.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-83dc9a7e","path":"/stacks/ko/getting-started.html","title":"Getting Started with Knockout","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Creating a Project","slug":"creating-a-project","link":"#creating-a-project","children":[]},{"level":2,"title":"Data Modeling","slug":"data-modeling","link":"#data-modeling","children":[]},{"level":2,"title":"Building Pages & Features","slug":"building-pages-features","link":"#building-pages-features","children":[]}],"git":{"updatedTime":1672451159000},"filePathRelative":"stacks/ko/getting-started.md"}');export{e as data}; diff --git a/assets/hidden.html.383280bc.js b/assets/hidden.html.383280bc.js new file mode 100644 index 000000000..abc8a9a19 --- /dev/null +++ b/assets/hidden.html.383280bc.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-f75859a2","path":"/modeling/model-components/attributes/hidden.html","title":"[Hidden]","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Example Usage","slug":"example-usage","link":"#example-usage","children":[]},{"level":2,"title":"Properties","slug":"properties","link":"#properties","children":[]}],"git":{"updatedTime":1677792094000},"filePathRelative":"modeling/model-components/attributes/hidden.md"}');export{e as data}; diff --git a/assets/hidden.html.bc0163d7.js b/assets/hidden.html.bc0163d7.js new file mode 100644 index 000000000..c3a228b1e --- /dev/null +++ b/assets/hidden.html.bc0163d7.js @@ -0,0 +1,8 @@ +import{_ as i,y as r,z as c,X as e,B as s,Q as a,$ as p,a5 as t,P as n}from"./framework.fe9a73df.js";const d={},D=e("h1",{id:"hidden",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#hidden","aria-hidden":"true"},"#"),s(" [Hidden]")],-1),u=e("p",null,"Mark an property as hidden from the edit, List or All areas.",-1),h={class:"custom-container danger"},y=e("p",{class:"custom-container-title"},"DANGER",-1),m=e("p",null,[s("This attribute is "),e("strong",null,"not a security attribute"),s(" - it only affects the rendering of the admin pages. It has no impact on data visibility in the API.")],-1),b=t(`/person/1
(assuming a person with ID 1 exists - if not, navigate to/admin/Person
and create one).# Example Usage
public class Person +{ + public int PersonId { get; set; } + + [Hidden(HiddenAttribute.Areas.All)] + public int? IncomeLevelId { get; set; } +} +
# Properties
`,3),v=t("The areas in which the property should be hidden.
Enum values are:
",3);function _(C,f){const o=n("RouterLink"),l=n("Prop");return r(),c("div",null,[D,u,e("div",h,[y,m,e("p",null,[s("Do not use it to keep certain data private - use the "),a(o,{to:"/modeling/model-components/attributes/security-attribute.html"},{default:p(()=>[s("Security Attributes")]),_:1}),s(" family of attributes for that.")])]),b,a(l,{def:"public Areas Area { get; set; } = Areas.All;",ctor:"1"}),v])}const A=i(d,[["render",_],["__file","hidden.html.vue"]]);export{A as default}; diff --git a/assets/history.html.1b31599a.js b/assets/history.html.1b31599a.js new file mode 100644 index 000000000..2e4a56ef1 --- /dev/null +++ b/assets/history.html.1b31599a.js @@ -0,0 +1 @@ +import{_ as e,y as t,z as i,a5 as a}from"./framework.fe9a73df.js";const o={},s=a('
HiddenAttribute.Areas.None
Hide from no generated views. Primary and Foreign keys are hidden by default - setting this value explicitly can override this default behavior.HiddenAttribute.Areas.All
Hide from all generated viewsHiddenAttribute.Areas.List
Hide from generated list views only (Knockout Table/Cards, Vuec-admin-table
)HiddenAttribute.Areas.Edit
Hide from generated editor only (Knockout CreateEdit, Vuec-admin-editor
)# Welcome to Coalesce's documentation!
Coalesce is a framework based on ASP.NET Core that makes rapidly building awesome websites much easier. A project that would take a 3 months to complete now takes 1 month. We built this because we got tired of writing all the boiler plate code that is necessary to make amazing sites.
It does this by allowing developers to focus on the creative aspects of the solution. The more mundane parts are generated automatically. This means that you get to focus on data modeling, business logic and front-end development. Coalesce does the plumbing.
Here is a typical workflow
- Build an EF Core data model with business logic
- Coalesce generates controllers, TypeScript view models, API and view model documentation, and admin pages/examples
- Build an interactive and intuitive user experience
- Rinse and repeat
# Core Features
- Built on the latest Microsoft ASP.NET Core
- Easy to learn
- TypeScript from the ground up
- Flexibility to use MVC patterns as required
- Admin pages for all your models are build automatically and include features like searching, sorting, and paging
- Robust documentation for the framework
- Automatically generated documentation for the API layer and TypeScript view models
- Feature rich TypeScript view models that can be easily extended
- Many extension points for customizations
- Abstraction that doesn't require you know how everything works
- Security and data trimming by role is built in
- Flexibility about which data to return to the client
- Open source
# Is Coalesce for Everyone and Every Project?
Coalesce was designed to create line-of-business applications. It provides a more customizable and maintainable alternative to off the shelf customizable products like SharePoint and Sales Force.
You should consider using Coalesce if your project:
- Is small to medium size (1-200 classes)
- Requires an interactive user experience
- Has data entry requirements, especially forms, tables, etc.
- Needs to get started quickly with functional prototypes that can become production software
# Design Decisions and Limitations
Coalesce is specifically designed to meet the needs of web developers. However, there are lots of ways to do this. We have made a set of decisions which we believe makes for a great development experience
- ASP.NET Core: there is no intent to back port this to an earlier version
- EF Core for the object relational mapper
- Currently uses the full framework because .NET Core doesn't supported the required functionality, yet
- Knockout for client-side data binding
- Business logic most easily lives in the model classes
- Coalesce is designed for relational databases. This might change in the future, but not until we have a compelling use case.
# How Does it Work?
After you create your classes and the EF data context, Coalesce uses this information to generate code. When the Coalesce CLI (command line interface) is run, the following things happen:
- The model is validated to ensure that all the Coalesce specific requirements are met. This includes things like ensuring that all classes have a primary key assigned, validating that linked child objects have a key to their parent, etc. If issues are found, generation stops and the errors are displayed with advise to fix the issues.
- The core files needed for Coalesce are copied to the target project. This includes TypeScript base classes, customizable templates, and other files for extension points. Each file is copied twice, once as a file that can be modified in the project and once as an original file. This ensures that if any changes are made by the user these files Coalesce will not overwrite the your changes.
- The API controllers are generated. One is generated for each object. This includes methods that get a list of items, get a specific item, save an item, etc.
- The TypeScript view models are created. There are two view models for each object. One is a list view model which allows for getting an displaying lists of a type of object. This includes full functionality to sort, filter, search, page, etc. Additionally, a view model that represents the individual object is also created. This has all the properties and methods of the server side object. This is basically a client-side proxy object for representing and manipulating the object on the server side. These objects seamlessly use the API controllers to interact with the server.
- Next, the View controller are created. One is create for each model class and provide a tabular view, a card view (for mobile), an editor, and documentation.
- Finally, the CSHTML views for the controller are created. These are the actual CSHTML for the above controllers. These not only provide administrative view and editing features, but also serve as an example of how to use the framework
# General Guidance
Here are a few things we have found helpful when using Coalesce
- Learn and embrace the Coalesce paradigm and work with it rather than trying to do things another way.
- Following what we refer to as the 'well worn path' is very helpful. Try to stick to standard ways to do things rather than trying to use esoteric features.
- Keep your models as consistent and straightforward as possible. Use relational modeling best practices.
- Remember that public methods on your class models are added to the client side view models and this makes calling business logic from the client really easy.
- Don't be afraid to fall back to building parts of your site using traditional methods. Coalesce isn't right for everything. But, honestly, we have only done this a few times, like 3.
# The Story
# Why Coalesce
In 2014 several developers from IntelliTect got together to talk about our craft. There were lots of different backgrounds, but recently we had all been writing web code in C#. We discussed things we enjoyed and things we dreaded. There was an underlying commitment to providing customers with great sites at a reasonable cost. However, those things often seemed at odds because of the complexity of web development.
# The Problem
For example, writing AJAX drop down lists with type ahead takes quite a bit of plumbing. Layer onto this the need for view models that allow for validation and saving as the user moves from field to field. We absolutely want want to deliver visually pleasing sites with complex UI paradigms. However, all this excellence adds up: complex view models, complex APIs, data binding, ugh.
Then there is that sinking feeling when you have to add another class to the project knowing that you are going to need to create all this yet again and you consider taking short cuts. Will there really be more than about 20 items in this table, maybe we don't need paging. Inevitably, the customer asks for admin screens. We consider giving them SQL Server Management Console and then consider using the built in ASP.NET list and editor pages. Better sense wins out and we end up spending two weeks building slick admin pages with paging, searching, sorting, etc.
# The Path to the Solution
That evening we starting talking about the things we loved to do:
- Data modeling
- Figuring out and writing business logic
- Working with customers
- Making cool user interfaces
- Creating something new and awesome
We also lists things that we didn't enjoy
- Writing the same controller again
- Creating a view model for a class that is similar but different from another one in the project
- Putting sorting and paging on every admin page
- Basically doing anything that feels repetitive or boilerplate
Over the next few months we talked about this issue, but couldn't find the right abstraction. We talked about other solutions that solve parts of the problem and considered putting together something from several pieces. Nothing felt unified and we ended up with leaky abstractions. We needed some way to divide the problem so that we could build the fun stuff and have something generate the boring stuff. This solution needed to be robust enough to satisfy our customer's needs and also be of use to developers without their needing to know the inner workings of the system.
# Our Solution
What if we could build the models and business logic and have a tool build everything except the UI? There are great tools like Entity Framework for modeling and good tooling for minimizing duplicate code in user interfaces. And so Coalesce was born, a tool that would bring together the backend and front end development. '
Coalesce takes Entity Framework Core models and builds controllers, TypeScript view models, and admin pages automatically. These are built in a general way so that they can be applied to many different scenarios. There will always be pages that need to be written by hand and we intentionally don't support many edge cases in order to keep things simple. There is nothing wrong with building something by hand.
# How has it Worked?
We have been using Coalesce for many of our web projects with great success. Typically, a project is taking about 1/3 the time it was taking before once developers ramp up. The ramp up on Coalesce has typically been a couple of days. We realized that in order for Coalesce to be useful it need to be intuitive to use and easy to understand. We have intentionally used simple paradigms to minimize the learning curve. There are complex bits, but hopefully, those are well hidden and documented as needed.
',37),n=[s];function l(r,d){return t(),i("div",null,n)}const c=e(o,[["render",l],["__file","history.html.vue"]]);export{c as default}; diff --git a/assets/history.html.f58a3d69.js b/assets/history.html.f58a3d69.js new file mode 100644 index 000000000..700695fa2 --- /dev/null +++ b/assets/history.html.f58a3d69.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-a7d34384","path":"/history.html","title":"Welcome to Coalesce's documentation!","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Core Features","slug":"core-features","link":"#core-features","children":[]},{"level":2,"title":"Is Coalesce for Everyone and Every Project?","slug":"is-coalesce-for-everyone-and-every-project","link":"#is-coalesce-for-everyone-and-every-project","children":[]},{"level":2,"title":"Design Decisions and Limitations","slug":"design-decisions-and-limitations","link":"#design-decisions-and-limitations","children":[]},{"level":2,"title":"How Does it Work?","slug":"how-does-it-work","link":"#how-does-it-work","children":[]},{"level":2,"title":"General Guidance","slug":"general-guidance","link":"#general-guidance","children":[]},{"level":2,"title":"Why Coalesce","slug":"why-coalesce","link":"#why-coalesce","children":[]},{"level":2,"title":"The Problem","slug":"the-problem","link":"#the-problem","children":[]},{"level":2,"title":"The Path to the Solution","slug":"the-path-to-the-solution","link":"#the-path-to-the-solution","children":[]},{"level":2,"title":"Our Solution","slug":"our-solution","link":"#our-solution","children":[]},{"level":2,"title":"How has it Worked?","slug":"how-has-it-worked","link":"#how-has-it-worked","children":[]}],"git":{"updatedTime":1652390840000},"filePathRelative":"history.md"}`);export{e as data}; diff --git a/assets/include-tree.html.5aed11ea.js b/assets/include-tree.html.5aed11ea.js new file mode 100644 index 000000000..fc2d7c4de --- /dev/null +++ b/assets/include-tree.html.5aed11ea.js @@ -0,0 +1,120 @@ +import{_ as r,y as D,z as i,X as s,Q as a,$ as n,B as e,a5 as p,P as t}from"./framework.fe9a73df.js";const y={},d=s("h1",{id:"include-tree",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#include-tree","aria-hidden":"true"},"#"),e(" Include Tree")],-1),u=s("p",null,[e("When Coalesce maps from the your POCO objects that are returned from EF Core queries, it will follow a structure called an "),s("code",null,"IncludeTree"),e(" to determine what relationships to follow and how deep to go in re-creating that structure in the mapped DTOs.")],-1),C={class:"table-of-contents"},m=p(`# Purpose
Without an
IncludeTree
present, Coalesce will map the entire object graph that is reachable from the root object. This can often spiral out of control if there aren't any rules defining how far to go while turning this graph into a tree.For example, suppose you had the following model with a many-to-many relationship (key properties omitted for brevity):
public class Employee +{ + [ManyToMany("Projects")] + public ICollection<EmployeeProject> EmployeeProjects { get; set; } + + public static IQueryable<Employee> WithProjectsAndMembers(AppDbContext db, ClaimsPrincipal user) + { + // Load all projects of an employee, as well as all members of those projects. + return db.Employees + .Include(e => e.EmployeeProjects) + .ThenInclude(ep => ep.Project.EmployeeProjects) + .ThenInclude(ep => ep.Employee); + } +} + +public class Project +{ + [ManyToMany("Employees")] + public ICollection<EmployeeProject> EmployeeProjects { get; set; } +} + +public class EmployeeProject +{ + public Employee Employee { get; set; } + public Project Project { get; set; } +} +
Now, imagine that you have five employees and five projects, with every employee being a member of every project (i.e. there are 25 EmployeeProject rows).
Your client code makes a call to the Coalesce-generated API to load Employee #1 using the custom data source:
`,6),h=s("div",{class:"language-typescript line-numbers-mode","data-ext":"ts"},[s("pre",{class:"shiki",style:{"background-color":"#1E1E1E"}},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"import"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"Employee"),s("span",{style:{color:"#D4D4D4"}}," } "),s("span",{style:{color:"#C586C0"}},"from"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#CE9178"}},"'@/viewmodels.g'")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"import"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"EmployeeViewModel"),s("span",{style:{color:"#D4D4D4"}}," } "),s("span",{style:{color:"#C586C0"}},"from"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#CE9178"}},"'@/viewmodels.g'")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#9CDCFE"}},"employee"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#DCDCAA"}},"EmployeeViewModel"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"employee"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"$dataSource"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#9CDCFE"}},"Employee"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"DataSources"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"WithProjectsAndMembers"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"employee"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"$load"),s("span",{style:{color:"#D4D4D4"}},"("),s("span",{style:{color:"#B5CEA8"}},"1"),s("span",{style:{color:"#D4D4D4"}},");")]),e(` +`),s("span",{class:"line"})])]),s("div",{class:"line-numbers","aria-hidden":"true"},[s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"})])],-1),E=s("div",{class:"language-typescript line-numbers-mode","data-ext":"ts"},[s("pre",{class:"shiki",style:{"background-color":"#1E1E1E"}},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#9CDCFE"}},"employee"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#9CDCFE"}},"ViewModels"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"Employee"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"employee"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"dataSource"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#9CDCFE"}},"employee"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"dataSources"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"WithProjectsAndMembers"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"employee"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"load"),s("span",{style:{color:"#D4D4D4"}},"("),s("span",{style:{color:"#B5CEA8"}},"1"),s("span",{style:{color:"#D4D4D4"}},");")]),e(` +`),s("span",{class:"line"})])]),s("div",{class:"line-numbers","aria-hidden":"true"},[s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"})])],-1),v=s("p",null,[e("If you're already familiar with the fact that an "),s("code",null,"IncludeTree"),e(" is implicitly created in this scenario, then imagine for a moment that this is not the case (if you're not familiar with this fact, then keep reading!).")],-1),b=s("code",null,"DbContext",-1),F=p(`To map these objects to DTOs, we start with the root (employee #1) and expand outward from there until the entire object graph has been faithfully re-created with DTO objects, including all navigation properties.
The root DTO object (employee #1) then eventually is passed to the JSON serializer by ASP.NET Core to formulate the response to the request. As the object is serialized to JSON, the only objects that are not serialized are those that were already serialized as an ancestor of itself. What this ultimately means is that the structure of the serialized JSON with our example scenario ends up following a pattern like this (the vast majority of items have been omitted):
Employee#1 + EmployeeProject#1 + Project#1 + EmployeeProject#6 + Employee#2 + EmployeeProject#7 + Project#2 + ... continues down through all remaining employees and projects. + ... + EmployeeProject#11 + Employee#3 + ... + EmployeeProject#2 + Project#2 + ... +
See how the structure includes the EmployeeProjects of Employee#2? We didn't write our custom data source calls to
.Include
in such a way that indicated that we wanted the root employee, their projects, the employees of those projects, and then the projects of those employees. But, because the JSON serializer blindly follows the object graph, that's what gets serialized. It turns out that the depth of the tree increases on the order ofO(n^2)
, and the total size increases on the order ofΩ(n!)
.This is where
IncludeTree
comes in. When you use a custom data source like we did above, Coalesce automatically captures the structure of the calls to.Include
and.ThenInclude
, and uses this to perform trimming during creation of the DTO objects.With an
IncludeTree
in place, our new serialized structure looks like this:Employee#1 + EmployeeProject#1 + Project#1 + EmployeeProject#6 + Employee#2 + EmployeeProject#11 + Employee#3 + ... + EmployeeProject#2 + Project#2 + ... +
No more extra data trailing off the end of the projects' employees!
# Usage
# Custom Data Sources
`,10),g=s("code",null,"IncludeTree",-1),f=s("code",null,".Include",-1),_=s("code",null,".ThenInclude",-1),A=s("code",null,"IncludeTree",-1),w=p(`However, there are sometimes cases where you perform complex loading in these methods that involves loading data into the current
DbContext
outside of theIQueryable
that is returned from the method. The most common situation for this is needing to conditionally load related data - for example, load all children of an object where the child has a certain value of a Status property.In these cases, Coalesce provides a pair of extension methods,
.IncludedSeparately
and.ThenIncluded
, that can be used to merge in the structure of the data that was loaded separately from the mainIQueryable
.For example:
`,4),I=s("code",null,"GetIncludeTree",-1),j=p(`public override IQueryable<Employee> GetQuery() +{ + // Load all projects that are complete, and their members, into the db context. + Db.Projects + .Include(p => p.EmployeeProjects).ThenInclude(ep => ep.Employee) + .Where(p => p.Status == ProjectStatus.Complete) + .Load(); + + // Return an employee query, and notify Coalesce that we loaded the projects in a different query. + return Db.Employees + .IncludedSeparately(e => e.EmployeeProjects) + .ThenIncluded(ep => ep.Project.EmployeeProjects) + .ThenIncluded(ep => ep.Employee); +} +
public override IncludeTree GetIncludeTree(IQueryable<T> query, IDataSourceParameters parameters) => Db + .Employees + .IncludedSeparately(e => e.EmployeeProjects) + .ThenIncluded(ep => ep.Project.EmployeeProjects) + .ThenIncluded(ep => ep.Employee) + .GetIncludeTree(); +
# Model Methods
`,2),T=s("code",null,"IncludeTree",-1),x=s("code",null,"IncludeTree",-1),P={class:"custom-container tip"},B=s("p",{class:"custom-container-title"},"TIP",-1),k=s("p",null,[e("An "),s("code",null,"IncludeTree"),e(" can be obtained from any "),s("code",null,"IQueryable"),e(" by calling the "),s("code",null,"GetIncludeTree"),e(" extension method ("),s("code",null,"using IntelliTect.Coalesce.Helpers.IncludeTree"),e(").")],-1),S=s("code",null,"DbContext",-1),O=s("code",null,"Enumerable.Empty().AsQueryable()",-1),M=s("code",null,"IQueryable",-1),q=s("strong",null,"must",-1),z=s("code",null,"IncludedSeparately",-1),N=s("code",null,"Include",-1),Q=s("code",null,"DbSet",-1),W=p(` See the following two techniques for returning an
IncludeTree
from a custom method:# ItemResult.IncludeTree
The easiest and most versatile way to return an
IncludeTree
from a custom method is to make that method return anItemResult<T>
, and then set theIncludeTree
property of theItemResult
object. For example:public class Employee +{ + public async Task<ItemResult<ICollection<Employee>>> GetChainOfCommand(AppDbContext db) + { + IQueryable<Employee> query = db.Employees + .Include(e => e.Supervisor); + + var ret = new List<Employee>(); + var current = this; + while (current.Supervisor != null) + { + ret.Push(current); + current = await query.FirstOrDefaultAsync(e => e.EmployeeId == current.SupervisorId); + } + + return new(ret, includeTree: query.GetIncludeTree()); + } +} +
# Out Parameter
To tell Coalesce about the structure of the data returned from a model method, you can also add
out IncludeTree includeTree
to the signature of the method. Inside your method, setincludeTree
to an instance of anIncludeTree
. However, this approach cannot be used onasync
methods, sinceout
parameters are not allowed on async methods in C#. For example:public class Employee +{ + public ICollection<Employee> GetChainOfCommand(AppDbContext db, out IncludeTree includeTree) + { + IQueryable<Employee> query = db.Employees + .Include(e => e.Supervisor); + + var ret = new List<Employee>(); + var current = this; + while (current.Supervisor != null) + { + ret.Push(current); + current = query.FirstOrDefault(e => e.EmployeeId == current.SupervisorId); + } + + includeTree = query.GetIncludeTree(); + + return ret; + } +} +
# External Type Caveats
`,8),G=s("code",null,"IncludeTree",-1),L=s("p",null,"By not filtering unmapped properties, you as the developer don't need to account for them in every place throughout your application where they appear - instead, they 'just work' and show up on the client as expected.",-1),R=s("p",null,[e("Note also that this statement does not apply to database-mapped objects that hang off of unmapped objects - any time a database-mapped object appears, it will be controlled by your include tree. If no include tree is present (because nothing was specified for the unmapped property), these mapped objects hanging off of unmapped objects will be serialized freely and with all circular references, unless you include some calls to "),s("code",null,".IncludedSeparately(m => m.MyUnmappedProperty.MyMappedProperty)"),e(" to limit those objects down.")],-1);function V(J,H){const o=t("router-link"),c=t("CodeTabs"),l=t("RouterLink");return D(),i("div",null,[d,u,s("nav",C,[s("ul",null,[s("li",null,[a(o,{to:"#purpose"},{default:n(()=>[e("Purpose")]),_:1})]),s("li",null,[a(o,{to:"#usage"},{default:n(()=>[e("Usage")]),_:1}),s("ul",null,[s("li",null,[a(o,{to:"#custom-data-sources"},{default:n(()=>[e("Custom Data Sources")]),_:1})]),s("li",null,[a(o,{to:"#model-methods"},{default:n(()=>[e("Model Methods")]),_:1})]),s("li",null,[a(o,{to:"#external-type-caveats"},{default:n(()=>[e("External Type Caveats")]),_:1})])])])])]),m,a(c,null,{vue:n(()=>[h]),knockout:n(()=>[E]),_:1}),v,s("p",null,[e("After Coalesce has called your "),a(l,{to:"/modeling/model-components/data-sources.html"},{default:n(()=>[e("Data Sources")]),_:1}),e(" and evaluated the EF IQueryable returned, there are now 35 objects loaded into the current "),b,e(" being used to handle this request - the 5 employees, 5 projects, and 25 relationships.")]),F,s("p",null,[e("In most cases, you don't have to worry about creating an "),g,e(". When using the "),a(l,{to:"/modeling/model-components/data-sources.html#standard-data-source"},{default:n(()=>[e("Standard Data Source")]),_:1}),e(" (or a derivative), the structure of the "),f,e(" and "),_,e(" calls will be captured automatically and be turned into an "),A,e(".")]),w,s("p",null,[e("You can also override the "),I,e(" method of the "),a(l,{to:"/modeling/model-components/data-sources.html#standard-data-source"},{default:n(()=>[e("Standard Data Source")]),_:1}),e(" to achieve the same result:")]),j,s("p",null,[e("If you have "),a(l,{to:"/modeling/model-components/methods.html"},{default:n(()=>[e("custom methods")]),_:1}),e(" that return object data, you may also want to control the structure of the returned data when it is serialized. Fortunately, you can also use "),T,e(" in these situations. Without an "),x,e(", the entire object graph is traversed and serialized without limit.")]),s("div",P,[B,k,s("p",null,[e("In situations where your root object isn't on your "),S,e(" (see "),a(l,{to:"/modeling/model-types/external-types.html"},{default:n(()=>[e("External Types")]),_:1}),e("), you can use "),O,e(" to get an "),M,e(" to start from. When you do this, you "),q,e(" use "),z,e(" - the regular EF "),N,e(" method won't work without a "),Q,e(".")])]),W,s("p",null,[e("One important point remains regarding "),G,e(" - it is not used to control the serialization of objects which are not mapped to the database, known as "),a(l,{to:"/modeling/model-types/external-types.html"},{default:n(()=>[e("External Types")]),_:1}),e(". External Types are always put into the DTOs when encountered (unless otherwise prevented by "),a(l,{to:"/modeling/model-components/attributes/dto-includes-excludes.html"},{default:n(()=>[e("[DtoIncludes] & [DtoExcludes]")]),_:1}),e(" or "),a(l,{to:"/modeling/model-components/attributes/security-attribute.html"},{default:n(()=>[e("Security Attributes")]),_:1}),e("), with the assumption that because these objects are created by you (as opposed to Entity Framework), you are responsible for preventing any undesired circular references.")]),L,R])}const $=r(y,[["render",V],["__file","include-tree.html.vue"]]);export{$ as default}; diff --git a/assets/include-tree.html.78a40435.js b/assets/include-tree.html.78a40435.js new file mode 100644 index 000000000..ae3f2ec78 --- /dev/null +++ b/assets/include-tree.html.78a40435.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-5ee46ccb","path":"/concepts/include-tree.html","title":"Include Tree","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Purpose","slug":"purpose","link":"#purpose","children":[]},{"level":2,"title":"Usage","slug":"usage","link":"#usage","children":[{"level":3,"title":"Custom Data Sources","slug":"custom-data-sources","link":"#custom-data-sources","children":[]},{"level":3,"title":"Model Methods","slug":"model-methods","link":"#model-methods","children":[]},{"level":3,"title":"External Type Caveats","slug":"external-type-caveats","link":"#external-type-caveats","children":[]}]}],"git":{"updatedTime":1664236724000},"filePathRelative":"concepts/include-tree.md"}');export{e as data}; diff --git a/assets/includes.html.08fa3b0f.js b/assets/includes.html.08fa3b0f.js new file mode 100644 index 000000000..2626761c7 --- /dev/null +++ b/assets/includes.html.08fa3b0f.js @@ -0,0 +1,55 @@ +import{_ as p,y as D,z as d,X as s,Q as n,$ as l,B as e,a5 as r,P as t}from"./framework.fe9a73df.js";const u={},y=s("h1",{id:"includes-string",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#includes-string","aria-hidden":"true"},"#"),e(" Includes String")],-1),C=s("p",null,'Coalesce provides a number of extension points for loading & serialization which make use of a concept called an "includes string" (also referred to as "include string" or just "includes").',-1),h={class:"table-of-contents"},m=s("h2",{id:"includes-string-1",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#includes-string-1","aria-hidden":"true"},"#"),e(" Includes String")],-1),b=s("p",null,"The includes string is simply a string which can be set to any arbitrary value. It is passed from the client to the server in order to control data loading and serialization. It can be set on both the TypeScript ViewModels and the ListViewModels.",-1),v=s("div",{class:"language-typescript line-numbers-mode","data-ext":"ts"},[s("pre",{class:"shiki",style:{"background-color":"#1E1E1E"}},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"import"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"PersonViewModel"),s("span",{style:{color:"#D4D4D4"}},", "),s("span",{style:{color:"#9CDCFE"}},"PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}}," } "),s("span",{style:{color:"#C586C0"}},"from"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#CE9178"}},"'@/viewmodels.g'")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#9CDCFE"}},"person"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#DCDCAA"}},"PersonViewModel"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"person"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"$includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"details"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#DCDCAA"}},"PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"$includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"details"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"})])]),s("div",{class:"line-numbers","aria-hidden":"true"},[s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"})])],-1),_=s("div",{class:"language-typescript line-numbers-mode","data-ext":"ts"},[s("pre",{class:"shiki",style:{"background-color":"#1E1E1E"}},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#9CDCFE"}},"person"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#9CDCFE"}},"ViewModels"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"Person"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"person"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"details"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#9CDCFE"}},"ListViewModels"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"PersonList"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"details"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"})])]),s("div",{class:"line-numbers","aria-hidden":"true"},[s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"})])],-1),E=s("p",null,"The default value (i.e. no action) is the empty string.",-1),g=s("h3",{id:"special-values",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#special-values","aria-hidden":"true"},"#"),e(" Special Values")],-1),w=s("p",null,[e("There are a few values of "),s("code",null,"includes"),e(" that are either set by default in the auto-generated views, or otherwise have special meaning:")],-1),f=s("thead",null,[s("tr",null,[s("th",null,"Value"),s("th",null,"Description")])],-1),F=s("td",null,[s("code",null,"'none'")],-1),L=s("code",null,"includes",-1),A=s("code",null,"none",-1),x=s("td",null,[s("code",null,"'admin-list'")],-1),k=s("td",null,[s("code",null,"'admin-editor'")],-1),P=s("tr",null,[s("td",null,[s("code",null,"'Editor'")]),s("td",null,"Used when loading an object in the generated Knockout CreateEdit views.")],-1),V=s("tr",null,[s("td",null,[s("code",null,"'ListGen'")]),s("td",null,[e("Used when loading a list of objects in the generated Knockout Table and Cards views. For example, "),s("code",null,"PersonListGen")])],-1),B=s("h2",{id:"dtoincludes-dtoexcludes",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#dtoincludes-dtoexcludes","aria-hidden":"true"},"#"),e(" DtoIncludes & DtoExcludes")],-1),I=s("p",null,[e("There are two C# attributes, "),s("code",null,"DtoIncludes"),e(" and "),s("code",null,"DtoExcludes"),e(", that can be used to annotate your data model in order to control what data gets put into the DTOs and ultimately serialized to JSON and sent out to the client.")],-1),j=s("p",null,[e("When the database entries are returned to the client they will be trimmed based on the requested includes string and the values in "),s("code",null,"DtoExcludes"),e(" and "),s("code",null,"DtoIncludes"),e(".")],-1),M={class:"custom-container danger"},S=s("p",{class:"custom-container-title"},"DANGER",-1),T=s("p",null,[e("These attributes are "),s("strong",null,"not security attributes"),e(" - consumers of your application's API can set the includes string to any value when making a request.")],-1),q=s("p",null,[e("It is important to note that the value of the includes string will match against these attributes on "),s("em",null,"any"),e(" of your models that appears in the object graph being mapped to DTOs - it is not limited only to the model type of the root object.")],-1),N={class:"custom-container tip"},$=s("p",{class:"custom-container-title"},"Important",-1),U=s("code",null,"DtoIncludes",-1),z=s("em",null,"permits",-1),O=s("em",null,"already",-1),G=r(` # Example Usage
Server code:
public class Person +{ + // Don't include CreatedBy when editing - will be included for all other views + [DtoExcludes("Editor")] + public AppUser CreatedBy { get; set; } + + // Only include the Person's Department when \`includes == "details"\` on the TypeScript ViewModel. + [DtoIncludes("details")] + public Department Department { get; set; } + + // LastName will be included in all views + public string LastName { get; set; } +} + +public class Department +{ + [DtoIncludes("details")] + public ICollection<Person> People { get; set; } +} +
Client code:
`,4),R=s("div",{class:"language-typescript line-numbers-mode","data-ext":"ts"},[s("pre",{class:"shiki",style:{"background-color":"#1E1E1E"}},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"import"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}}," } "),s("span",{style:{color:"#C586C0"}},"from"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#CE9178"}},"'@/viewmodels.g'")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"const"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#4FC1FF"}},"personList"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#DCDCAA"}},"PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"$includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"Editor"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"await"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"$load"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#6A9955"}},"// Objects in personList.$items will not contain CreatedBy nor Department objects.")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"const"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#4FC1FF"}},"personList2"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#DCDCAA"}},"PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList2"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"$includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"details"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"await"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"$load"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#6A9955"}},"// Objects in personList2.items will be allowed to contain both CreatedBy and Department objects. ")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#6A9955"}},"// Department will be allowed to include its other Person objects.")]),e(` +`),s("span",{class:"line"})])]),s("div",{class:"line-numbers","aria-hidden":"true"},[s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"})])],-1),K=s("div",{class:"language-typescript line-numbers-mode","data-ext":"ts"},[s("pre",{class:"shiki",style:{"background-color":"#1E1E1E"}},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#9CDCFE"}},"ListViewModels"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"PersonList"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"Editor"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"load"),s("span",{style:{color:"#D4D4D4"}},"(() "),s("span",{style:{color:"#569CD6"}},"=>"),s("span",{style:{color:"#D4D4D4"}}," {")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#6A9955"}},"// objects in personList.items will not contain CreatedBy nor Department objects.")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#D4D4D4"}},"});")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#9CDCFE"}},"personList2"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#9CDCFE"}},"ListViewModels"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"PersonList"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList2"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"details"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList2"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"load"),s("span",{style:{color:"#D4D4D4"}},"(() "),s("span",{style:{color:"#569CD6"}},"=>"),s("span",{style:{color:"#D4D4D4"}}," {")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#D4D4D4"}}," "),s("span",{style:{color:"#6A9955"}},"// objects in personList2.items will be allowed to contain both CreatedBy and Department objects. Department will be allowed to include its other Person objects.")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#D4D4D4"}},"});")]),e(` +`),s("span",{class:"line"})])]),s("div",{class:"line-numbers","aria-hidden":"true"},[s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"}),s("div",{class:"line-number"})])],-1),J=s("h3",{id:"properties",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#properties","aria-hidden":"true"},"#"),e(" Properties")],-1),Q=s("code",null,"includes",-1),W=r("For
DtoIncludes
, this will be the values ofincludes
for which this property will be allowed to be serialized and sent to the client.For
",2);function X(H,Y){const a=t("router-link"),c=t("CodeTabs"),o=t("RouterLink"),i=t("Prop");return D(),d("div",null,[y,C,s("nav",h,[s("ul",null,[s("li",null,[n(a,{to:"#includes-string-1"},{default:l(()=>[e("Includes String")]),_:1}),s("ul",null,[s("li",null,[n(a,{to:"#special-values"},{default:l(()=>[e("Special Values")]),_:1})])])]),s("li",null,[n(a,{to:"#dtoincludes-dtoexcludes"},{default:l(()=>[e("DtoIncludes & DtoExcludes")]),_:1}),s("ul",null,[s("li",null,[n(a,{to:"#example-usage"},{default:l(()=>[e("Example Usage")]),_:1})]),s("li",null,[n(a,{to:"#properties"},{default:l(()=>[e("Properties")]),_:1})])])])])]),m,b,n(c,null,{vue:l(()=>[v]),knockout:l(()=>[_]),_:1}),E,g,w,s("table",null,[f,s("tbody",null,[s("tr",null,[F,s("td",null,[e("Setting "),L,e(" to "),A,e(" suppresses the "),n(o,{to:"/modeling/model-components/data-sources.html#default-loading-behavior"},{default:l(()=>[e("Default Loading Behavior")]),_:1}),e(" provided by the "),n(o,{to:"/modeling/model-components/data-sources.html#standard-data-source"},{default:l(()=>[e("Standard Data Source")]),_:1}),e(" - The resulting data will be the requested object (or list of objects) and nothing more.")])]),s("tr",null,[x,s("td",null,[e("Used when loading a list of objects in the "),n(o,{to:"/stacks/vue/coalesce-vue-vuetify/components/c-admin-table-page.html"},{default:l(()=>[e("Vue admin list page")]),_:1}),e(".")])]),s("tr",null,[k,s("td",null,[e("Used when loading an object in the "),n(o,{to:"/stacks/vue/coalesce-vue-vuetify/components/c-admin-editor.html"},{default:l(()=>[e("Vue admin editor component")]),_:1}),e(".")])]),P,V])]),B,s("p",null,[e("Main document: "),n(o,{to:"/modeling/model-components/attributes/dto-includes-excludes.html"},{default:l(()=>[e("[DtoIncludes] & [DtoExcludes]")]),_:1}),e(".")]),I,j,s("div",M,[S,T,s("p",null,[e("Do not use them to keep certain data private - use the "),n(o,{to:"/modeling/model-components/attributes/security-attribute.html"},{default:l(()=>[e("Security Attributes")]),_:1}),e(" family of attributes for that.")])]),q,s("div",N,[$,s("p",null,[U,e(" does not ensure that specific data will be loaded from the database. It only "),z,e(" what is "),O,e(" loaded into the current EF DbContext to be returned from the API. See "),n(o,{to:"/modeling/model-components/data-sources.html"},{default:l(()=>[e("Data Sources")]),_:1}),e(" to learn how to control what data gets loaded from the database.")])]),G,n(c,null,{vue:l(()=>[R]),knockout:l(()=>[K]),_:1}),J,n(i,{def:"public string ContentViews { get; set; }",ctor:"1"}),e(),s("p",null,[e("A comma-delimited list of values of "),n(o,{to:"/concepts/includes.html"},{default:l(()=>[Q]),_:1}),e(" on which to operate.")]),W])}const ss=p(u,[["render",X],["__file","includes.html.vue"]]);export{ss as default}; diff --git a/assets/includes.html.5136332d.js b/assets/includes.html.5136332d.js new file mode 100644 index 000000000..52e3642f5 --- /dev/null +++ b/assets/includes.html.5136332d.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-5d077123","path":"/concepts/includes.html","title":"Includes String","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Includes String","slug":"includes-string-1","link":"#includes-string-1","children":[{"level":3,"title":"Special Values","slug":"special-values","link":"#special-values","children":[]}]},{"level":2,"title":"DtoIncludes & DtoExcludes","slug":"dtoincludes-dtoexcludes","link":"#dtoincludes-dtoexcludes","children":[{"level":3,"title":"Example Usage","slug":"example-usage","link":"#example-usage","children":[]},{"level":3,"title":"Properties","slug":"properties","link":"#properties","children":[]}]}],"git":{"updatedTime":1687826807000},"filePathRelative":"concepts/includes.md"}');export{e as data}; diff --git a/assets/index.esm.dea45d94.js b/assets/index.esm.dea45d94.js new file mode 100644 index 000000000..c7ce1c8e9 --- /dev/null +++ b/assets/index.esm.dea45d94.js @@ -0,0 +1,7 @@ +var Ge=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{},ve={exports:{}};(function(h,b){(function(j,n){h.exports=n()})(Ge,function(){return j={770:function(S,L,y){var E=this&&this.__importDefault||function(l){return l&&l.__esModule?l:{default:l}};Object.defineProperty(L,"__esModule",{value:!0}),L.setDefaultDebugCall=L.createOnigScanner=L.createOnigString=L.loadWASM=L.OnigScanner=L.OnigString=void 0;const N=E(y(418));let r=null,d=!1;class k{constructor(u){const p=u.length,o=k._utf8ByteLength(u),c=o!==p,e=c?new Uint32Array(p+1):null;c&&(e[p]=o);const t=c?new Uint32Array(o+1):null;c&&(t[o]=p);const s=new Uint8Array(o);let _=0;for(let v=0;vDtoExcludes
, this will be the values ofincludes
for which this property will not be serialized and sent to the client.=55296&&P<=56319&&v+1
=56320&&q<=57343&&(A=65536+(P-55296<<10)|q-56320,I=!0)}c&&(e[v]=_,I&&(e[v+1]=_),A<=127?t[_+0]=v:A<=2047?(t[_+0]=v,t[_+1]=v):A<=65535?(t[_+0]=v,t[_+1]=v,t[_+2]=v):(t[_+0]=v,t[_+1]=v,t[_+2]=v,t[_+3]=v)),A<=127?s[_++]=A:A<=2047?(s[_++]=192|(1984&A)>>>6,s[_++]=128|(63&A)>>>0):A<=65535?(s[_++]=224|(61440&A)>>>12,s[_++]=128|(4032&A)>>>6,s[_++]=128|(63&A)>>>0):(s[_++]=240|(1835008&A)>>>18,s[_++]=128|(258048&A)>>>12,s[_++]=128|(4032&A)>>>6,s[_++]=128|(63&A)>>>0),I&&v++}this.utf16Length=p,this.utf8Length=o,this.utf16Value=u,this.utf8Value=s,this.utf16OffsetToUtf8=e,this.utf8OffsetToUtf16=t}static _utf8ByteLength(u){let p=0;for(let o=0,c=u.length;o
=55296&&e<=56319&&o+1 =56320&&_<=57343&&(t=65536+(e-55296<<10)|_-56320,s=!0)}p+=t<=127?1:t<=2047?2:t<=65535?3:4,s&&o++}return p}createString(u){const p=u._omalloc(this.utf8Length);return u.HEAPU8.set(this.utf8Value,p),p}}class w{constructor(u){if(this.id=++w.LAST_ID,!r)throw new Error("Must invoke loadWASM first.");this._onigBinding=r,this.content=u;const p=new k(u);this.utf16Length=p.utf16Length,this.utf8Length=p.utf8Length,this.utf16OffsetToUtf8=p.utf16OffsetToUtf8,this.utf8OffsetToUtf16=p.utf8OffsetToUtf16,this.utf8Length<1e4&&!w._sharedPtrInUse?(w._sharedPtr||(w._sharedPtr=r._omalloc(1e4)),w._sharedPtrInUse=!0,r.HEAPU8.set(p.utf8Value,w._sharedPtr),this.ptr=w._sharedPtr):this.ptr=p.createString(r)}convertUtf8OffsetToUtf16(u){return this.utf8OffsetToUtf16?u<0?0:u>this.utf8Length?this.utf16Length:this.utf8OffsetToUtf16[u]:u}convertUtf16OffsetToUtf8(u){return this.utf16OffsetToUtf8?u<0?0:u>this.utf16Length?this.utf8Length:this.utf16OffsetToUtf8[u]:u}dispose(){this.ptr===w._sharedPtr?w._sharedPtrInUse=!1:this._onigBinding._ofree(this.ptr)}}L.OnigString=w,w.LAST_ID=0,w._sharedPtr=0,w._sharedPtrInUse=!1;class i{constructor(u){if(!r)throw new Error("Must invoke loadWASM first.");const p=[],o=[];for(let s=0,_=u.length;s<_;s++){const v=new k(u[s]);p[s]=v.createString(r),o[s]=v.utf8Length}const c=r._omalloc(4*u.length);r.HEAPU32.set(p,c/4);const e=r._omalloc(4*u.length);r.HEAPU32.set(o,e/4);const t=r._createOnigScanner(c,e,u.length);for(let s=0,_=u.length;s<_;s++)r._ofree(p[s]);r._ofree(e),r._ofree(c),t===0&&function(s){throw new Error(s.UTF8ToString(s._getLastOnigError()))}(r),this._onigBinding=r,this._ptr=t}dispose(){this._onigBinding._freeOnigScanner(this._ptr)}findNextMatchSync(u,p,o){let c=d,e=0;if(typeof o=="number"?(8&o&&(c=!0),e=o):typeof o=="boolean"&&(c=o),typeof u=="string"){u=new w(u);const t=this._findNextMatchSync(u,p,c,e);return u.dispose(),t}return this._findNextMatchSync(u,p,c,e)}_findNextMatchSync(u,p,o,c){const e=this._onigBinding;let t;if(t=o?e._findNextOnigScannerMatchDbg(this._ptr,u.id,u.ptr,u.utf8Length,u.convertUtf16OffsetToUtf8(p),c):e._findNextOnigScannerMatch(this._ptr,u.id,u.ptr,u.utf8Length,u.convertUtf16OffsetToUtf8(p),c),t===0)return null;const s=e.HEAPU32;let _=t/4;const v=s[_++],P=s[_++];let A=[];for(let I=0;I WebAssembly.instantiateStreaming(t,s)}(e):function(t){return async s=>{const _=await t.arrayBuffer();return WebAssembly.instantiate(_,s)}}(e):function(t){return s=>WebAssembly.instantiate(t,s)}(e)}return g=new Promise((e,t)=>{o=e,c=t}),function(e,t,s,_){N.default({print:t,instantiateWasm:(v,P)=>{if(typeof performance>"u"){const A=()=>Date.now();v.env.emscripten_get_now=A,v.wasi_snapshot_preview1.emscripten_get_now=A}return e(v).then(A=>P(A.instance),_),{}}}).then(v=>{r=v,s()})}(u,p,o,c),g},L.createOnigString=function(l){return new w(l)},L.createOnigScanner=function(l){return new i(l)},L.setDefaultDebugCall=function(l){d=l}},418:S=>{var L=(typeof document<"u"&&document.currentScript&&document.currentScript.src,function(y){var E,N,r=(y=y||{})!==void 0?y:{};r.ready=new Promise(function(O,W){E=O,N=W});var d,k={};for(d in r)r.hasOwnProperty(d)&&(k[d]=r[d]);var w,i=!1,T="";function g(O){return r.locateFile?r.locateFile(O,T):T+O}w=function(O){var W;return typeof readbuffer=="function"?new Uint8Array(readbuffer(O)):(e(typeof(W=read(O,"binary"))=="object"),W)},typeof scriptArgs<"u"&&scriptArgs,typeof onig_print<"u"&&(typeof console>"u"&&(console={}),console.log=onig_print,console.warn=console.error=typeof printErr<"u"?printErr:onig_print);var l=r.print||console.log.bind(console),u=r.printErr||console.warn.bind(console);for(d in k)k.hasOwnProperty(d)&&(r[d]=k[d]);k=null,r.arguments&&r.arguments,r.thisProgram&&r.thisProgram,r.quit&&r.quit;var p,o;r.wasmBinary&&(p=r.wasmBinary),r.noExitRuntime,typeof WebAssembly!="object"&&$("no native wasm support detected");var c=!1;function e(O,W){O||$("Assertion failed: "+W)}var t,s,_,v=typeof TextDecoder<"u"?new TextDecoder("utf8"):void 0;function P(O,W,ne){for(var fe=W+ne,ee=W;O[ee]&&!(ee>=fe);)++ee;if(ee-W>16&&O.subarray&&v)return v.decode(O.subarray(W,ee));for(var oe="";W
>10,56320|1023&Oe)}}else oe+=String.fromCharCode((31&ce)<<6|me)}else oe+=String.fromCharCode(ce)}return oe}function A(O,W){return O?P(s,O,W):""}function I(O,W){return O%W>0&&(O+=W-O%W),O}function q(O){t=O,r.HEAP8=new Int8Array(O),r.HEAP16=new Int16Array(O),r.HEAP32=_=new Int32Array(O),r.HEAPU8=s=new Uint8Array(O),r.HEAPU16=new Uint16Array(O),r.HEAPU32=new Uint32Array(O),r.HEAPF32=new Float32Array(O),r.HEAPF64=new Float64Array(O)}typeof TextDecoder<"u"&&new TextDecoder("utf-16le"),r.INITIAL_MEMORY;var H,K=[],Z=[],he=[],f=[];function a(){if(r.preRun)for(typeof r.preRun=="function"&&(r.preRun=[r.preRun]);r.preRun.length;)M(r.preRun.shift());Y(K)}function m(){Y(Z)}function x(){Y(he)}function C(){if(r.postRun)for(typeof r.postRun=="function"&&(r.postRun=[r.postRun]);r.postRun.length;)F(r.postRun.shift());Y(f)}function M(O){K.unshift(O)}function F(O){f.unshift(O)}Z.push({func:function(){Ie()}});var G=0,B=null;function D(O){G++,r.monitorRunDependencies&&r.monitorRunDependencies(G)}function z(O){if(G--,r.monitorRunDependencies&&r.monitorRunDependencies(G),G==0&&B){var W=B;B=null,W()}}function $(O){r.onAbort&&r.onAbort(O),u(O+=""),c=!0,O="abort("+O+"). Build with -s ASSERTIONS=1 for more info.";var W=new WebAssembly.RuntimeError(O);throw N(W),W}function R(O,W){return String.prototype.startsWith?O.startsWith(W):O.indexOf(W)===0}r.preloadedImages={},r.preloadedAudios={};var U="data:application/octet-stream;base64,";function V(O){return R(O,U)}var X,Q="onig.wasm";function re(O){try{if(O==Q&&p)return new Uint8Array(p);if(w)return w(O);throw"both async and sync fetching of the wasm failed"}catch(W){$(W)}}function te(){return p||!i||typeof fetch!="function"?Promise.resolve().then(function(){return re(Q)}):fetch(Q,{credentials:"same-origin"}).then(function(O){if(!O.ok)throw"failed to load wasm binary file at '"+Q+"'";return O.arrayBuffer()}).catch(function(){return re(Q)})}function se(){var O={env:Re,wasi_snapshot_preview1:Re};function W(ee,oe){var ce=ee.exports;r.asm=ce,q((o=r.asm.memory).buffer),H=r.asm.__indirect_function_table,z()}function ne(ee){W(ee.instance)}function fe(ee){return te().then(function(oe){return WebAssembly.instantiate(oe,O)}).then(ee,function(oe){u("failed to asynchronously prepare wasm: "+oe),$(oe)})}if(D(),r.instantiateWasm)try{return r.instantiateWasm(O,W)}catch(ee){return u("Module.instantiateWasm callback failed with error: "+ee),!1}return(p||typeof WebAssembly.instantiateStreaming!="function"||V(Q)||typeof fetch!="function"?fe(ne):fetch(Q,{credentials:"same-origin"}).then(function(ee){return WebAssembly.instantiateStreaming(ee,O).then(ne,function(oe){return u("wasm streaming compile failed: "+oe),u("falling back to ArrayBuffer instantiation"),fe(ne)})})).catch(N),{}}function Y(O){for(;O.length>0;){var W=O.shift();if(typeof W!="function"){var ne=W.func;typeof ne=="number"?W.arg===void 0?H.get(ne)():H.get(ne)(W.arg):ne(W.arg===void 0?null:W.arg)}else W(r)}}function ie(O,W,ne){s.copyWithin(O,W,W+ne)}function J(){return s.length}function ue(O){try{return o.grow(O-t.byteLength+65535>>>16),q(o.buffer),1}catch{}}function ae(O){var W=J(),ne=2147483648;if(O>ne)return!1;for(var fe=1;fe<=4;fe*=2){var ee=W*(1+.2/fe);if(ee=Math.min(ee,O+100663296),ue(Math.min(ne,I(Math.max(O,ee),65536))))return!0}return!1}V(Q)||(Q=g(Q)),X=typeof dateNow<"u"?dateNow:function(){return performance.now()};var pe={mappings:{},buffers:[null,[],[]],printChar:function(O,W){var ne=pe.buffers[O];W===0||W===10?((O===1?l:u)(P(ne,0)),ne.length=0):ne.push(W)},varargs:void 0,get:function(){return pe.varargs+=4,_[pe.varargs-4>>2]},getStr:function(O){return A(O)},get64:function(O,W){return O}};function ke(O,W,ne,fe){for(var ee=0,oe=0;oe >2],me=_[W+(8*oe+4)>>2],_e=0;_e >2]=ee,0}function ge(O){}var de,Re={emscripten_get_now:X,emscripten_memcpy_big:ie,emscripten_resize_heap:ae,fd_write:ke,setTempRet0:ge},Ie=(se(),r.___wasm_call_ctors=function(){return(Ie=r.___wasm_call_ctors=r.asm.__wasm_call_ctors).apply(null,arguments)});function je(O){function W(){de||(de=!0,r.calledRun=!0,c||(m(),x(),E(r),r.onRuntimeInitialized&&r.onRuntimeInitialized(),C()))}G>0||(a(),G>0||(r.setStatus?(r.setStatus("Running..."),setTimeout(function(){setTimeout(function(){r.setStatus("")},1),W()},1)):W()))}if(r.___errno_location=function(){return(r.___errno_location=r.asm.__errno_location).apply(null,arguments)},r._omalloc=function(){return(r._omalloc=r.asm.omalloc).apply(null,arguments)},r._ofree=function(){return(r._ofree=r.asm.ofree).apply(null,arguments)},r._getLastOnigError=function(){return(r._getLastOnigError=r.asm.getLastOnigError).apply(null,arguments)},r._createOnigScanner=function(){return(r._createOnigScanner=r.asm.createOnigScanner).apply(null,arguments)},r._freeOnigScanner=function(){return(r._freeOnigScanner=r.asm.freeOnigScanner).apply(null,arguments)},r._findNextOnigScannerMatch=function(){return(r._findNextOnigScannerMatch=r.asm.findNextOnigScannerMatch).apply(null,arguments)},r._findNextOnigScannerMatchDbg=function(){return(r._findNextOnigScannerMatchDbg=r.asm.findNextOnigScannerMatchDbg).apply(null,arguments)},r.stackSave=function(){return(r.stackSave=r.asm.stackSave).apply(null,arguments)},r.stackRestore=function(){return(r.stackRestore=r.asm.stackRestore).apply(null,arguments)},r.stackAlloc=function(){return(r.stackAlloc=r.asm.stackAlloc).apply(null,arguments)},r.dynCall_jiji=function(){return(r.dynCall_jiji=r.asm.dynCall_jiji).apply(null,arguments)},r.UTF8ToString=A,B=function O(){de||je(),de||(B=O)},r.run=je,r.preInit)for(typeof r.preInit=="function"&&(r.preInit=[r.preInit]);r.preInit.length>0;)r.preInit.pop()();return je(),y.ready});S.exports=L}},n={},function S(L){var y=n[L];if(y!==void 0)return y.exports;var E=n[L]={exports:{}};return j[L].call(E.exports,E,E.exports,S),E.exports}(770);var j,n})})(ve);var Te={exports:{}};(function(h,b){(function(j,n){h.exports=n()})(Ge,function(){return function(j){var n={};function S(L){if(n[L])return n[L].exports;var y=n[L]={i:L,l:!1,exports:{}};return j[L].call(y.exports,y,y.exports,S),y.l=!0,y.exports}return S.m=j,S.c=n,S.d=function(L,y,E){S.o(L,y)||Object.defineProperty(L,y,{enumerable:!0,get:E})},S.r=function(L){typeof Symbol<"u"&&Symbol.toStringTag&&Object.defineProperty(L,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(L,"__esModule",{value:!0})},S.t=function(L,y){if(1&y&&(L=S(L)),8&y||4&y&&typeof L=="object"&&L&&L.__esModule)return L;var E=Object.create(null);if(S.r(E),Object.defineProperty(E,"default",{enumerable:!0,value:L}),2&y&&typeof L!="string")for(var N in L)S.d(E,N,function(r){return L[r]}.bind(null,N));return E},S.n=function(L){var y=L&&L.__esModule?function(){return L.default}:function(){return L};return S.d(y,"a",y),y},S.o=function(L,y){return Object.prototype.hasOwnProperty.call(L,y)},S.p="",S(S.s=3)}([function(j,n,S){Object.defineProperty(n,"__esModule",{value:!0});var L=S(1),y=S(5),E=S(6),N=S(2),r=typeof performance>"u"?function(){return Date.now()}:function(){return performance.now()};n.createGrammar=function(f,a,m,x,C,M){return new e(f,a,m,x,C,M)};var d=function(f){this.scopeName=f};n.FullScopeDependency=d;var k=function(){function f(a,m){this.scopeName=a,this.include=m}return f.prototype.toKey=function(){return this.scopeName+"#"+this.include},f}();n.PartialScopeDependency=k;var w=function(){function f(){this.full=[],this.partial=[],this.visitedRule=new Set,this._seenFull=new Set,this._seenPartial=new Set}return f.prototype.add=function(a){a instanceof d?this._seenFull.has(a.scopeName)||(this._seenFull.add(a.scopeName),this.full.push(a)):this._seenPartial.has(a.toKey())||(this._seenPartial.add(a.toKey()),this.partial.push(a))},f}();function i(f,a,m,x,C){for(var M=0,F=x;M =0){var $=D.substring(0,z),R=D.substring(z+1);$===a.scopeName?T(f,a,a,R,B):$===m.scopeName?T(f,a,m,R,B):f.add(new k($,D.substring(z+1)))}else f.add(new d(D))}}}}function T(f,a,m,x,C){C===void 0&&(C=m.repository),C&&C[x]&&i(f,a,m,[C[x]],C)}function g(f,a,m){if(m.patterns&&Array.isArray(m.patterns)&&i(f,a,m,m.patterns,m.repository),m.injections){var x=[];for(var C in m.injections)x.push(m.injections[C]);i(f,a,m,x,m.repository)}}function l(f,a){if(!f)return!1;if(f===a)return!0;var m=a.length;return f.length>m&&f.substr(0,m)===a&&f[m]==="."}function u(f,a){if(a.length >")}var D=Object.keys(this._embeddedLanguages).map(function(z){return f._escapeRegExpCharacters(z)});D.length===0?this._embeddedLanguagesRegex=null:(D.sort(),D.reverse(),this._embeddedLanguagesRegex=new RegExp("^(("+D.join(")|(")+"))($|\\.)",""))}return f.prototype.onDidChangeTheme=function(){this._cache=new Map,this._defaultMetaData=new o("",this._initialLanguage,0,[this._themeProvider.getDefaults()])},f.prototype.getDefaultMetadata=function(){return this._defaultMetaData},f._escapeRegExpCharacters=function(a){return a.replace(/[\-\\\{\}\*\+\?\|\^\$\.\,\[\]\(\)\#\s]/g,"\\$&")},f.prototype.getMetadataForScope=function(a){if(a===null)return f._NULL_SCOPE_METADATA;var m=this._cache.get(a);return m||(m=this._doGetMetadataForScope(a),this._cache.set(a,m),m)},f.prototype._doGetMetadataForScope=function(a){var m=this._scopeToLanguage(a),x=this._toStandardTokenType(a),C=this._themeProvider.themeMatch(a);return new o(a,m,x,C)},f.prototype._scopeToLanguage=function(a){if(!a||!this._embeddedLanguagesRegex)return 0;var m=a.match(this._embeddedLanguagesRegex);if(!m)return 0;var x=this._embeddedLanguages[m[1]]||0;return x||0},f.prototype._toStandardTokenType=function(a){var m=a.match(f.STANDARD_TOKEN_TYPE_REGEXP);if(!m)return 0;switch(m[1]){case"comment":return 1;case"string":return 2;case"regex":return 4;case"meta.embedded":return 8}throw new Error("Unexpected match for standard token type!")},f._NULL_SCOPE_METADATA=new o("",0,0,null),f.STANDARD_TOKEN_TYPE_REGEXP=/\b(comment|string|regex|meta\.embedded)\b/,f}(),e=function(){function f(a,m,x,C,M,F){if(this._scopeMetadataProvider=new c(m,M,x),this._onigLib=F,this._rootId=-1,this._lastRuleId=0,this._ruleId2desc=[null],this._includedGrammars={},this._grammarRepository=M,this._grammar=s(a,null),this._injections=null,this._tokenTypeMatchers=[],C)for(var G=0,B=Object.keys(C);G z)break;for(;D.length>0&&D[D.length-1].endPos<=U.start;)C.produceFromScopes(D[D.length-1].scopes,D[D.length-1].endPos),D.pop();if(D.length>0?C.produceFromScopes(D[D.length-1].scopes,U.start):C.produce(x,U.start),R.retokenizeCapturedWithRuleId){var V=R.getName(G,F),X=x.contentNameScopesList.push(f,V),Q=R.getContentName(G,F),re=X.push(f,Q),te=x.push(R.retokenizeCapturedWithRuleId,U.start,-1,!1,null,X,re),se=f.createOnigString(G.substring(0,U.end));A(f,se,m&&U.start===0,U.start,te,C,!1),t(se)}else{var Y=R.getName(G,F);if(Y!==null){var ie=(D.length>0?D[D.length-1].scopes:x.contentNameScopesList).push(f,Y);D.push(new K(ie,U.end))}}}}}for(;D.length>0;)C.produceFromScopes(D[D.length-1].scopes,D[D.length-1].endPos),D.pop()}}function v(f){for(var a=[],m=0,x=f.rules.length;m 5&&console.warn("Rule "+re.debugName+" ("+re.id+") matching took "+ie+" against '"+R+"'"),Y&&console.log("matched rule id: "+te.rules[Y.index]+" from "+Y.captureIndices[0].start+" to "+Y.captureIndices[0].end)}return Y?{captureIndices:Y.captureIndices,matchedRuleId:te.rules[Y.index]}:null}(f,a,m,x,C,M),G=f.getInjections();if(G.length===0)return F;var B=function($,R,U,V,X,Q,re){for(var te,se=Number.MAX_VALUE,Y=null,ie=0,J=Q.contentNameScopesList.generateScopes(),ue=0,ae=$.length;ue =se)&&(se=de,Y=ge.captureIndices,te=ke.rules[ge.index],ie=pe.priority,se===X))break}}}return Y?{priorityMatch:ie===-1,captureIndices:Y,matchedRuleId:te}:null}(G,f,a,m,x,C,M);if(!B)return F;if(!F)return B;var D=F.captureIndices[0].start,z=B.captureIndices[0].start;return z X&&(X=ae.captureIndices[0].end,V=!1))}return{stack:Q,linePos:X,anchorPosition:te,isFirstLine:V}}(f,a,m,x,C,M);C=z.stack,x=z.linePos,m=z.isFirstLine,D=z.anchorPosition}for(;!B;)$();function $(){N.DebugFlags.InDebugMode&&(console.log(""),console.log("@@scanNext "+x+": |"+a.content.substr(x).replace(/\n$/,"\\n")+"|"));var R=P(f,a,m,x,C,D);if(!R)return N.DebugFlags.InDebugMode&&console.log(" no more matches."),M.produce(C,G),void(B=!0);var U=R.captureIndices,V=R.matchedRuleId,X=!!(U&&U.length>0)&&U[0].end>x;if(V===-1){var Q=C.getRule(f);N.DebugFlags.InDebugMode&&console.log(" popping "+Q.debugName+" - "+Q.debugEndRegExp),M.produce(C,U[0].start),C=C.setContentNameScopesList(C.nameScopesList),_(f,a,m,C,M,Q.endCaptures,U),M.produce(C,U[0].end);var re=C;if(C=C.pop(),D=re.getAnchorPos(),!X&&re.getEnterPos()===x)return N.DebugFlags.InDebugMode&&console.error("[1] - Grammar is in an endless loop - Grammar pushed & popped a rule without advancing"),C=re,M.produce(C,G),void(B=!0)}else{var te=f.getRule(V);M.produce(C,U[0].start);var se=C,Y=te.getName(a.content,U),ie=C.contentNameScopesList.push(f,Y);if(C=C.push(V,x,D,U[0].end===G,null,ie,ie),te instanceof y.BeginEndRule){var J=te;N.DebugFlags.InDebugMode&&console.log(" pushing "+J.debugName+" - "+J.debugBeginRegExp),_(f,a,m,C,M,J.beginCaptures,U),M.produce(C,U[0].end),D=U[0].end;var ue=J.getContentName(a.content,U),ae=ie.push(f,ue);if(C=C.setContentNameScopesList(ae),J.endHasBackReferences&&(C=C.setEndRule(J.getEndWithResolvedBackReferences(a.content,U))),!X&&se.hasSameRuleAs(C))return N.DebugFlags.InDebugMode&&console.error("[2] - Grammar is in an endless loop - Grammar pushed the same rule without advancing"),C=C.pop(),M.produce(C,G),void(B=!0)}else if(te instanceof y.BeginWhileRule){if(J=te,N.DebugFlags.InDebugMode&&console.log(" pushing "+J.debugName),_(f,a,m,C,M,J.beginCaptures,U),M.produce(C,U[0].end),D=U[0].end,ue=J.getContentName(a.content,U),ae=ie.push(f,ue),C=C.setContentNameScopesList(ae),J.whileHasBackReferences&&(C=C.setEndRule(J.getWhileWithResolvedBackReferences(a.content,U))),!X&&se.hasSameRuleAs(C))return N.DebugFlags.InDebugMode&&console.error("[3] - Grammar is in an endless loop - Grammar pushed the same rule without advancing"),C=C.pop(),M.produce(C,G),void(B=!0)}else{var pe=te;if(N.DebugFlags.InDebugMode&&console.log(" matched "+pe.debugName+" - "+pe.debugMatchRegExp),_(f,a,m,C,M,pe.captures,U),M.produce(C,U[0].end),C=C.pop(),!X)return N.DebugFlags.InDebugMode&&console.error("[4] - Grammar is in an endless loop - Grammar is not advancing, nor is it pushing/popping"),C=C.safePop(),M.produce(C,G),void(B=!0)}}U[0].end>x&&(x=U[0].end,m=!1)}return C}n.Grammar=e;var I=function(){function f(){}return f.toBinaryStr=function(a){for(var m=a.toString(2);m.length<32;)m="0"+m;return m},f.printMetadata=function(a){var m=f.getLanguageId(a),x=f.getTokenType(a),C=f.getFontStyle(a),M=f.getForeground(a),F=f.getBackground(a);console.log({languageId:m,tokenType:x,fontStyle:C,foreground:M,background:F})},f.getLanguageId=function(a){return(255&a)>>>0},f.getTokenType=function(a){return(1792&a)>>>8},f.getFontStyle=function(a){return(14336&a)>>>11},f.getForeground=function(a){return(8372224&a)>>>14},f.getBackground=function(a){return(4286578688&a)>>>23},f.set=function(a,m,x,C,M,F){var G=f.getLanguageId(a),B=f.getTokenType(a),D=f.getFontStyle(a),z=f.getForeground(a),$=f.getBackground(a);return m!==0&&(G=m),x!==0&&(B=x===8?0:x),C!==-1&&(D=C),M!==0&&(z=M),F!==0&&($=F),(G<<0|B<<8|D<<11|z<<14|$<<23)>>>0},f}();n.StackElementMetadata=I;var q=function(){function f(a,m,x){this.parent=a,this.scope=m,this.metadata=x}return f._equals=function(a,m){for(;;){if(a===m||!a&&!m)return!0;if(!a||!m||a.scope!==m.scope||a.metadata!==m.metadata)return!1;a=a.parent,m=m.parent}},f.prototype.equals=function(a){return f._equals(this,a)},f._matchesScope=function(a,m,x){return m===a||a.substring(0,x.length)===x},f._matches=function(a,m){if(m===null)return!0;for(var x=m.length,C=0,M=m[C],F=M+".";a;){if(this._matchesScope(a.scope,M,F)){if(++C===x)return!0;F=(M=m[C])+"."}a=a.parent}return!1},f.mergeMetadata=function(a,m,x){if(x===null)return a;var C=-1,M=0,F=0;if(x.themeData!==null)for(var G=0,B=x.themeData.length;G=0?f._push(this,a,m.split(/ /g)):f._push(this,a,[m])},f._generateScopes=function(a){for(var m=[],x=0;a;)m[x++]=a.scope,a=a.parent;return m.reverse(),m},f.prototype.generateScopes=function(){return f._generateScopes(this)},f}();n.ScopeListElement=q;var H=function(){function f(a,m,x,C,M,F,G,B){this.parent=a,this.depth=this.parent?this.parent.depth+1:1,this.ruleId=m,this._enterPos=x,this._anchorPos=C,this.beginRuleCapturedEOL=M,this.endRule=F,this.nameScopesList=G,this.contentNameScopesList=B}return f._structuralEquals=function(a,m){for(;;){if(a===m||!a&&!m)return!0;if(!a||!m||a.depth!==m.depth||a.ruleId!==m.ruleId||a.endRule!==m.endRule)return!1;a=a.parent,m=m.parent}},f._equals=function(a,m){return a===m||!!this._structuralEquals(a,m)&&a.contentNameScopesList.equals(m.contentNameScopesList)},f.prototype.clone=function(){return this},f.prototype.equals=function(a){return a!==null&&f._equals(this,a)},f._reset=function(a){for(;a;)a._enterPos=-1,a._anchorPos=-1,a=a.parent},f.prototype.reset=function(){f._reset(this)},f.prototype.pop=function(){return this.parent},f.prototype.safePop=function(){return this.parent?this.parent:this},f.prototype.push=function(a,m,x,C,M,F,G){return new f(this,a,m,x,C,M,F,G)},f.prototype.getEnterPos=function(){return this._enterPos},f.prototype.getAnchorPos=function(){return this._anchorPos},f.prototype.getRule=function(a){return a.getRule(this.ruleId)},f.prototype._writeString=function(a,m){return this.parent&&(m=this.parent._writeString(a,m)),a[m++]="("+this.ruleId+", TODO-"+this.nameScopesList+", TODO-"+this.contentNameScopesList+")",m},f.prototype.toString=function(){var a=[];return this._writeString(a,0),"["+a.join(",")+"]"},f.prototype.setContentNameScopesList=function(a){return this.contentNameScopesList===a?this:this.parent.push(this.ruleId,this._enterPos,this._anchorPos,this.beginRuleCapturedEOL,this.endRule,this.nameScopesList,a)},f.prototype.setEndRule=function(a){return this.endRule===a?this:new f(this.parent,this.ruleId,this._enterPos,this._anchorPos,this.beginRuleCapturedEOL,a,this.nameScopesList,this.contentNameScopesList)},f.prototype.hasSameRuleAs=function(a){return this.ruleId===a.ruleId},f.NULL=new f(null,0,0,0,!1,null,null,null),f}();n.StackElement=H;var K=function(f,a){this.scopes=f,this.endPos=a};n.LocalStackElement=K;var Z=function(){function f(a,m,x){this._emitBinaryTokens=a,this._tokenTypeOverrides=x,N.DebugFlags.InDebugMode?this._lineText=m:this._lineText=null,this._tokens=[],this._binaryTokens=[],this._lastTokenEndIndex=0}return f.prototype.produce=function(a,m){this.produceFromScopes(a.contentNameScopesList,m)},f.prototype.produceFromScopes=function(a,m){if(!(this._lastTokenEndIndex>=m)){if(this._emitBinaryTokens){for(var x=a.metadata,C=0,M=this._tokenTypeOverrides;C 0&&this._binaryTokens[this._binaryTokens.length-1]===x||(this._binaryTokens.push(this._lastTokenEndIndex),this._binaryTokens.push(x)),void(this._lastTokenEndIndex=m)}var G=a.generateScopes();if(N.DebugFlags.InDebugMode){console.log(" token: |"+this._lineText.substring(this._lastTokenEndIndex,m).replace(/\n$/,"\\n")+"|");for(var B=0;B 0&&this._tokens[this._tokens.length-1].startIndex===m-1&&this._tokens.pop(),this._tokens.length===0&&(this._lastTokenEndIndex=-1,this.produce(a,m),this._tokens[this._tokens.length-1].startIndex=0),this._tokens},f.prototype.getBinaryResult=function(a,m){this._binaryTokens.length>0&&this._binaryTokens[this._binaryTokens.length-2]===m-1&&(this._binaryTokens.pop(),this._binaryTokens.pop()),this._binaryTokens.length===0&&(this._lastTokenEndIndex=-1,this.produce(a,m),this._binaryTokens[this._binaryTokens.length-2]=0);for(var x=new Uint32Array(this._binaryTokens.length),C=0,M=this._binaryTokens.length;C 0&&l[l.length-1])||t[0]!==6&&t[0]!==2)){p=0;continue}if(t[0]===3&&(!l||t[1]>l[0]&&t[1] ")},w.prototype._loadGrammar=function(i,T,g,l){return L(this,void 0,void 0,function(){var u,p,o,c,e,t,s,_,v,P,A,I,q=this;return y(this,function(H){switch(H.label){case 0:u=new Set,p=new Set,u.add(i),o=[new d.FullScopeDependency(i)],H.label=1;case 1:return o.length>0?(c=o,o=[],[4,Promise.all(c.map(function(K){return q._loadSingleGrammar(K.scopeName)}))]):[3,3];case 2:for(H.sent(),e=new d.ScopeDependencyCollector,t=0,s=c;t 0&&i[i.length-1])||o[0]!==6&&o[0]!==2)){g=0;continue}if(o[0]===3&&(!i||o[1]>i[0]&&o[1]v&&(v=I);for(var A=0;A<=v;A++)_[A]=null;for(var P in e)if(P!=="$vscodeTextmateLocation"){var I=parseInt(P,10),q=0;e[P].patterns&&(q=c.getCompiledRuleId(e[P],t,s)),_[I]=c.createCaptureRule(t,e[P].$vscodeTextmateLocation,e[P].name,e[P].contentName,q)}}return _},c._compilePatterns=function(e,t,s){var _=[];if(e)for(var v=0,P=e.length;v =0?(H=A.include.substring(0,Z),K=A.include.substring(Z+1)):H=A.include;var he=t.getExternalGrammar(H,s);if(he)if(K){var f=he.repository[K];f&&(I=c.getCompiledRuleId(f,t,he.repository))}else I=c.getCompiledRuleId(he.repository.$self,t,he.repository)}else I=c.getCompiledRuleId(A,t,s);if(I!==-1){var a=t.getRule(I),m=!1;if((a instanceof l||a instanceof u||a instanceof p)&&a.hasMissingPatterns&&a.patterns.length===0&&(m=!0),m)continue;_.push(I)}}return{patterns:_,hasMissingPatterns:(e?e.length:0)!==_.length}},c}();n.RuleFactory=o},function(j,n,S){function L(y){return!!y&&!!y.match(/[\w\.:]+/)}Object.defineProperty(n,"__esModule",{value:!0}),n.createMatchers=function(y,E){for(var N,r,d,k=[],w=(d=(r=/([LR]:|[\w\.:][\w\.:\-]*|[\,\|\-\(\)])/g).exec(N=y),{next:function(){if(!d)return null;var p=d[0];return d=r.exec(N),p}}),i=w.next();i!==null;){var T=0;if(i.length===2&&i.charAt(1)===":"){switch(i.charAt(0)){case"R":T=1;break;case"L":T=-1;break;default:console.log("Unknown priority "+i+" in scope selector")}i=w.next()}var g=u();if(k.push({matcher:g,priority:T}),i!==",")break;i=w.next()}return k;function l(){if(i==="-"){i=w.next();var p=l();return function(e){return!!p&&!p(e)}}if(i==="("){i=w.next();var o=function(){for(var e=[],t=u();t&&(e.push(t),i==="|"||i===",");){do i=w.next();while(i==="|"||i===",");t=u()}return function(s){return e.some(function(_){return _(s)})}}();return i===")"&&(i=w.next()),o}if(L(i)){var c=[];do c.push(i),i=w.next();while(L(i));return function(e){return E(c,e)}}return null}function u(){for(var p=[],o=l();o;)p.push(o),o=l();return function(c){return p.every(function(e){return e(c)})}}}},function(j,n){var S,L,y=j.exports={};function E(){throw new Error("setTimeout has not been defined")}function N(){throw new Error("clearTimeout has not been defined")}function r(p){if(S===setTimeout)return setTimeout(p,0);if((S===E||!S)&&setTimeout)return S=setTimeout,setTimeout(p,0);try{return S(p,0)}catch{try{return S.call(null,p,0)}catch{return S.call(this,p,0)}}}(function(){try{S=typeof setTimeout=="function"?setTimeout:E}catch{S=E}try{L=typeof clearTimeout=="function"?clearTimeout:N}catch{L=N}})();var d,k=[],w=!1,i=-1;function T(){w&&d&&(w=!1,d.length?k=d.concat(k):i=-1,k.length&&g())}function g(){if(!w){var p=r(T);w=!0;for(var o=k.length;o;){for(d=k,k=[];++i
1)for(var c=1;c 0;)y.charCodeAt(d)===10?(d++,k++,w=0):(d++,w++),R--}function T(R){N===null?d=R:i(R-d)}function g(){for(;d 0&&y.charCodeAt(0)===65279&&(d=1);var o=0,c=null,e=[],t=[],s=null;function _(R,U){e.push(o),t.push(c),o=R,c=U}function v(){if(e.length===0)return P("illegal state stack");o=e.pop(),c=t.pop()}function P(R){throw new Error("Near offset "+d+": "+R+" ~~~"+y.substr(d,50)+"~~~")}var A,I,q,H=function(){if(s===null)return P("missing ");var R={};N!==null&&(R[N]={filename:E,line:k,char:w}),c[s]=R,s=null,_(1,R)},K=function(){if(s===null)return P("missing ");var R=[];c[s]=R,s=null,_(2,R)},Z=function(){var R={};N!==null&&(R[N]={filename:E,line:k,char:w}),c.push(R),_(1,R)},he=function(){var R=[];c.push(R),_(2,R)};function f(){if(o!==1)return P("unexpected ");v()}function a(){return o===1||o!==2?P("unexpected "):void v()}function m(R){if(o===1){if(s===null)return P("missing ");c[s]=R,s=null}else o===2?c.push(R):c=R}function x(R){if(isNaN(R))return P("cannot parse float");if(o===1){if(s===null)return P("missing ");c[s]=R,s=null}else o===2?c.push(R):c=R}function C(R){if(isNaN(R))return P("cannot parse integer");if(o===1){if(s===null)return P("missing ");c[s]=R,s=null}else o===2?c.push(R):c=R}function M(R){if(o===1){if(s===null)return P("missing ");c[s]=R,s=null}else o===2?c.push(R):c=R}function F(R){if(o===1){if(s===null)return P("missing ");c[s]=R,s=null}else o===2?c.push(R):c=R}function G(R){if(o===1){if(s===null)return P("missing ");c[s]=R,s=null}else o===2?c.push(R):c=R}function B(R){if(R.isClosed)return"";var U=p("");return u(">"),U.replace(/([0-9]+);/g,function(V,X){return String.fromCodePoint(parseInt(X,10))}).replace(/([0-9a-f]+);/g,function(V,X){return String.fromCodePoint(parseInt(X,16))}).replace(/&|<|>|"|'/g,function(V){switch(V){case"&":return"&";case"<":return"<";case">":return">";case""":return'"';case"'":return"'"}return V})}for(;d =r));){var D=y.charCodeAt(d);if(i(1),D!==60)return P("expected <");if(d>=r)return P("unexpected end of input");var z=y.charCodeAt(d);if(z!==63)if(z!==33){if(z===47){if(i(1),g(),l("plist")){u(">");continue}if(l("dict")){u(">"),f();continue}if(l("array")){u(">"),a();continue}return P("unexpected closed tag")}var $=(I=void 0,q=void 0,I=p(">"),q=!1,I.charCodeAt(I.length-1)===47&&(q=!0,I=I.substring(0,I.length-1)),{name:I.trim(),isClosed:q});switch($.name){case"dict":o===1?H():o===2?Z():(c={},N!==null&&(c[N]={filename:E,line:k,char:w}),_(1,c)),$.isClosed&&f();continue;case"array":o===1?K():o===2?he():_(2,c=[]),$.isClosed&&a();continue;case"key":A=B($),o!==1?P("unexpected "):s!==null?P("too many "):s=A;continue;case"string":m(B($));continue;case"real":x(parseFloat(B($)));continue;case"integer":C(parseInt(B($),10));continue;case"date":M(new Date(B($)));continue;case"data":F(B($));continue;case"true":B($),G(!0);continue;case"false":B($),G(!1);continue}if(!/^plist/.test($.name))return P("unexpected opened tag "+$.name)}else{if(i(1),l("--")){u("-->");continue}u(">")}else i(1),u("?>")}return c}Object.defineProperty(n,"__esModule",{value:!0}),n.parseWithLocation=function(y,E,N){return L(y,E,N)},n.parse=function(y){return L(y,null,null)}},function(j,n,S){function L(r,d){throw new Error("Near offset "+r.pos+": "+d+" ~~~"+r.source.substr(r.pos,50)+"~~~")}Object.defineProperty(n,"__esModule",{value:!0}),n.parse=function(r,d,k){var w=new y(r),i=new E,T=0,g=null,l=[],u=[];function p(){l.push(T),u.push(g)}function o(){T=l.pop(),g=u.pop()}function c(_){L(w,_)}for(;N(w,i);){if(T===0){if(g!==null&&c("too many constructs in root"),i.type===3){g={},k&&(g.$vscodeTextmateLocation=i.toLocation(d)),p(),T=1;continue}if(i.type===2){g=[],p(),T=4;continue}c("unexpected token in root")}if(T===2){if(i.type===5){o();continue}if(i.type===7){T=3;continue}c("expected , or }")}if(T===1||T===3){if(T===1&&i.type===5){o();continue}if(i.type===1){var e=i.value;if(N(w,i)&&i.type===6||c("expected colon"),N(w,i)||c("expected value"),T=2,i.type===1){g[e]=i.value;continue}if(i.type===8){g[e]=null;continue}if(i.type===9){g[e]=!0;continue}if(i.type===10){g[e]=!1;continue}if(i.type===11){g[e]=parseFloat(i.value);continue}if(i.type===2){var t=[];g[e]=t,p(),T=4,g=t;continue}if(i.type===3){var s={};k&&(s.$vscodeTextmateLocation=i.toLocation(d)),g[e]=s,p(),T=1,g=s;continue}}c("unexpected token in dict")}if(T===5){if(i.type===4){o();continue}if(i.type===7){T=6;continue}c("expected , or ]")}if(T===4||T===6){if(T===4&&i.type===4){o();continue}if(T=5,i.type===1){g.push(i.value);continue}if(i.type===8){g.push(null);continue}if(i.type===9){g.push(!0);continue}if(i.type===10){g.push(!1);continue}if(i.type===11){g.push(parseFloat(i.value));continue}if(i.type===2){t=[],g.push(t),p(),T=4,g=t;continue}if(i.type===3){s={},k&&(s.$vscodeTextmateLocation=i.toLocation(d)),g.push(s),p(),T=1,g=s;continue}c("unexpected token in array")}c("unknown state")}return u.length!==0&&c("unclosed constructs"),g};var y=function(r){this.source=r,this.pos=0,this.len=r.length,this.line=1,this.char=0},E=function(){function r(){this.value=null,this.type=0,this.offset=-1,this.len=-1,this.line=-1,this.char=-1}return r.prototype.toLocation=function(d){return{filename:d,line:this.line,char:this.char}},r}();function N(r,d){d.value=null,d.type=0,d.offset=-1,d.len=-1,d.line=-1,d.char=-1;for(var k,w=r.source,i=r.pos,T=r.len,g=r.line,l=r.char;;){if(i>=T)return!1;if((k=w.charCodeAt(i))!==32&&k!==9&&k!==13){if(k!==10)break;i++,g++,l=0}else i++,l++}if(d.offset=i,d.line=g,d.char=l,k===34){for(d.type=1,i++,l++;;){if(i>=T)return!1;if(k=w.charCodeAt(i),i++,l++,k!==92){if(k===34)break}else i++,l++}d.value=w.substring(d.offset+1,i-1).replace(/\\u([0-9A-Fa-f]{4})/g,function(u,p){return String.fromCodePoint(parseInt(p,16))}).replace(/\\(.)/g,function(u,p){switch(p){case'"':return'"';case"\\":return"\\";case"/":return"/";case"b":return"\b";case"f":return"\f";case"n":return` +`;case"r":return"\r";case"t":return" ";default:L(r,"invalid escape sequence")}throw new Error("unreachable")})}else if(k===91)d.type=2,i++,l++;else if(k===123)d.type=3,i++,l++;else if(k===93)d.type=4,i++,l++;else if(k===125)d.type=5,i++,l++;else if(k===58)d.type=6,i++,l++;else if(k===44)d.type=7,i++,l++;else if(k===110){if(d.type=8,i++,l++,(k=w.charCodeAt(i))!==117||(i++,l++,(k=w.charCodeAt(i))!==108)||(i++,l++,(k=w.charCodeAt(i))!==108))return!1;i++,l++}else if(k===116){if(d.type=9,i++,l++,(k=w.charCodeAt(i))!==114||(i++,l++,(k=w.charCodeAt(i))!==117)||(i++,l++,(k=w.charCodeAt(i))!==101))return!1;i++,l++}else if(k===102){if(d.type=10,i++,l++,(k=w.charCodeAt(i))!==97||(i++,l++,(k=w.charCodeAt(i))!==108)||(i++,l++,(k=w.charCodeAt(i))!==115)||(i++,l++,(k=w.charCodeAt(i))!==101))return!1;i++,l++}else for(d.type=11;;){if(i>=T)return!1;if(!((k=w.charCodeAt(i))===46||k>=48&&k<=57||k===101||k===69||k===45||k===43))break;i++,l++}return d.len=i-d.offset,d.value===null&&(d.value=w.substr(d.offset,d.len)),r.pos=i,r.line=g,r.char=l,!0}},function(j,n,S){Object.defineProperty(n,"__esModule",{value:!0});var L=function(g,l,u,p,o,c){this.scope=g,this.parentScopes=l,this.index=u,this.fontStyle=p,this.foreground=o,this.background=c};function y(g){return!!/^#[0-9a-f]{6}$/i.test(g)||!!/^#[0-9a-f]{8}$/i.test(g)||!!/^#[0-9a-f]{3}$/i.test(g)||!!/^#[0-9a-f]{4}$/i.test(g)}function E(g){if(!g)return[];if(!g.settings||!Array.isArray(g.settings))return[];for(var l=g.settings,u=[],p=0,o=0,c=l.length;o 1&&(H=I.slice(0,I.length-1)).reverse(),u[p++]=new L(q,H,o,s,P,A)}}}return u}function N(g,l){g.sort(function(A,I){var q=k(A.scope,I.scope);return q!==0||(q=w(A.parentScopes,I.parentScopes))!==0?q:A.index-I.index});for(var u=0,p="#000000",o="#ffffff";g.length>=1&&g[0].scope==="";){var c=g.shift();c.fontStyle!==-1&&(u=c.fontStyle),c.foreground!==null&&(p=c.foreground),c.background!==null&&(o=c.background)}for(var e=new r(l),t=new i(0,null,u,e.getId(p),e.getId(o)),s=new T(new i(0,null,-1,0,0),[]),_=0,v=g.length;_ l?1:0}function w(g,l){if(g===null&&l===null)return 0;if(!g)return-1;if(!l)return 1;var u=g.length,p=l.length;if(u===p){for(var o=0;ol?console.log("how did this happen?"):this.scopeDepth=l,u!==-1&&(this.fontStyle=u),p!==0&&(this.foreground=p),o!==0&&(this.background=o)},g}();n.ThemeTrieElementRule=i;var T=function(){function g(l,u,p){u===void 0&&(u=[]),p===void 0&&(p={}),this._mainRule=l,this._rulesWithParentScopes=u,this._children=p}return g._sortBySpecificity=function(l){return l.length===1||l.sort(this._cmpBySpecificity),l},g._cmpBySpecificity=function(l,u){if(l.scopeDepth===u.scopeDepth){var p=l.parentScopes,o=u.parentScopes,c=p===null?0:p.length,e=o===null?0:o.length;if(c===e)for(var t=0;t >>0}static getTokenType(b){return(b&1792)>>>8}static getFontStyle(b){return(b&14336)>>>11}static getForeground(b){return(b&8372224)>>>14}static getBackground(b){return(b&4286578688)>>>23}static set(b,j,n,S,L,y){let E=le.getLanguageId(b),N=le.getTokenType(b),r=le.getFontStyle(b),d=le.getForeground(b),k=le.getBackground(b);return j!==0&&(E=j),n!==0&&(N=n===8?0:n),S!==be.NotSet&&(r=S),L!==0&&(d=L),y!==0&&(k=y),(E<<0|N<<8|r<<11|d<<14|k<<23)>>>0}}function qe(h){return h.endsWith("/")||h.endsWith("\\")?h.slice(0,-1):h}function ze(h){return h.startsWith("./")?h.slice(2):h}function He(h){const b=h.split(/[\/\\]/g);return b[b.length-2]}function Ve(...h){return h.map(qe).map(ze).join("/")}function Ke(h,b){const j=new Map;for(const n of h){const S=b(n);j.has(S)?j.get(S).push(n):j.set(S,[n])}return j}function Xe(h,b){b===void 0&&(b=!1);var j=h.length,n=0,S="",L=0,y=16,E=0,N=0,r=0,d=0,k=0;function w(o,c){for(var e=0,t=0;e =48&&s<=57)t=t*16+s-48;else if(s>=65&&s<=70)t=t*16+s-65+10;else if(s>=97&&s<=102)t=t*16+s-97+10;else break;n++,e++}return e =j){o+=h.substring(c,n),k=2;break}var e=h.charCodeAt(n);if(e===34){o+=h.substring(c,n),n++;break}if(e===92){if(o+=h.substring(c,n),n++,n>=j){k=2;break}var t=h.charCodeAt(n++);switch(t){case 34:o+='"';break;case 92:o+="\\";break;case 47:o+="/";break;case 98:o+="\b";break;case 102:o+="\f";break;case 110:o+=` +`;break;case 114:o+="\r";break;case 116:o+=" ";break;case 117:var s=w(4,!0);s>=0?o+=String.fromCharCode(s):k=4;break;default:k=5}c=n;continue}if(e>=0&&e<=31)if(Le(e)){o+=h.substring(c,n),k=2;break}else k=6;n++}return o}function l(){if(S="",k=0,L=n,N=E,d=r,n>=j)return L=j,y=17;var o=h.charCodeAt(n);if(Ce(o)){do n++,S+=String.fromCharCode(o),o=h.charCodeAt(n);while(Ce(o));return y=15}if(Le(o))return n++,S+=String.fromCharCode(o),o===13&&h.charCodeAt(n)===10&&(n++,S+=` +`),E++,r=n,y=14;switch(o){case 123:return n++,y=1;case 125:return n++,y=2;case 91:return n++,y=3;case 93:return n++,y=4;case 58:return n++,y=6;case 44:return n++,y=5;case 34:return n++,S=g(),y=10;case 47:var c=n-1;if(h.charCodeAt(n+1)===47){for(n+=2;n =12&&o<=15);return o}return{setPosition:i,getPosition:function(){return n},scan:b?p:l,getToken:function(){return y},getTokenValue:function(){return S},getTokenOffset:function(){return L},getTokenLength:function(){return n-L},getTokenStartLine:function(){return N},getTokenStartCharacter:function(){return L-d},getTokenError:function(){return k}}}function Ce(h){return h===32||h===9||h===11||h===12||h===160||h===5760||h>=8192&&h<=8203||h===8239||h===8287||h===12288||h===65279}function Le(h){return h===10||h===13||h===8232||h===8233}function ye(h){return h>=48&&h<=57}var we;(function(h){h.DEFAULT={allowTrailingComma:!1}})(we||(we={}));function Je(h,b,j){b===void 0&&(b=[]),j===void 0&&(j=we.DEFAULT);var n=null,S=[],L=[];function y(N){Array.isArray(S)?S.push(N):n!==null&&(S[n]=N)}var E={onObjectBegin:function(){var N={};y(N),L.push(S),S=N,n=null},onObjectProperty:function(N){n=N},onObjectEnd:function(){S=L.pop()},onArrayBegin:function(){var N=[];y(N),L.push(S),S=N,n=null},onArrayEnd:function(){S=L.pop()},onLiteralValue:y,onError:function(N,r,d){b.push({error:N,offset:r,length:d})}};return Ye(h,E,j),S[0]}function Ye(h,b,j){j===void 0&&(j=we.DEFAULT);var n=Xe(h,!1);function S(v){return v?function(){return v(n.getTokenOffset(),n.getTokenLength(),n.getTokenStartLine(),n.getTokenStartCharacter())}:function(){return!0}}function L(v){return v?function(P){return v(P,n.getTokenOffset(),n.getTokenLength(),n.getTokenStartLine(),n.getTokenStartCharacter())}:function(){return!0}}var y=S(b.onObjectBegin),E=L(b.onObjectProperty),N=S(b.onObjectEnd),r=S(b.onArrayBegin),d=S(b.onArrayEnd),k=L(b.onLiteralValue),w=L(b.onSeparator),i=S(b.onComment),T=L(b.onError),g=j&&j.disallowComments,l=j&&j.allowTrailingComma;function u(){for(;;){var v=n.scan();switch(n.getTokenError()){case 4:p(14);break;case 5:p(15);break;case 3:p(13);break;case 1:g||p(11);break;case 2:p(12);break;case 6:p(16);break}switch(v){case 12:case 13:g?p(10):i();break;case 16:p(1);break;case 15:case 14:break;default:return v}}}function p(v,P,A){if(P===void 0&&(P=[]),A===void 0&&(A=[]),T(v),P.length+A.length>0)for(var I=n.getToken();I!==17;){if(P.indexOf(I)!==-1){u();break}else if(A.indexOf(I)!==-1)break;I=u()}}function o(v){var P=n.getTokenValue();return v?k(P):E(P),u(),!0}function c(){switch(n.getToken()){case 11:var v=n.getTokenValue(),P=Number(v);isNaN(P)&&(p(2),P=0),k(P);break;case 7:k(null);break;case 8:k(!0);break;case 9:k(!1);break;default:return!1}return u(),!0}function e(){return n.getToken()!==10?(p(3,[],[2,5]),!1):(o(!1),n.getToken()===6?(w(":"),u(),_()||p(4,[],[2,5])):p(5,[],[2,5]),!0)}function t(){y(),u();for(var v=!1;n.getToken()!==2&&n.getToken()!==17;){if(n.getToken()===5){if(v||p(4,[],[]),w(","),u(),n.getToken()===2&&l)break}else v&&p(6,[],[]);e()||p(4,[],[2,5]),v=!0}return N(),n.getToken()!==2?p(7,[2],[]):u(),!0}function s(){r(),u();for(var v=!1;n.getToken()!==4&&n.getToken()!==17;){if(n.getToken()===5){if(v||p(4,[],[]),w(","),u(),n.getToken()===4&&l)break}else v&&p(6,[],[]);_()||p(4,[],[4,5]),v=!0}return d(),n.getToken()!==4?p(8,[4],[]):u(),!0}function _(){switch(n.getToken()){case 3:return s();case 1:return t();case 10:return o(!0);default:return c()}}return u(),n.getToken()===17?j.allowEmptyContent?!0:(p(4,[],[]),!1):_()?(n.getToken()!==17&&p(9,[],[]),!0):(p(4,[],[]),!1)}var Qe=Je;const Ze=typeof self<"u"&&typeof self.WorkerGlobalScope<"u",Ae=Ze||typeof window<"u"&&typeof window.document<"u"&&typeof fetch<"u";let Ne="",Pe="";function vt(h){Ne=h}function et(h){Pe=h}let Se=null;async function tt(){if(!Se){let h;if(Ae)typeof Pe=="string"?h=ve.exports.loadWASM({data:await fetch(Be("dist/onig.wasm")).then(b=>b.arrayBuffer())}):h=ve.exports.loadWASM(Pe);else{const j=require("path").join(require.resolve("vscode-oniguruma"),"../onig.wasm"),S=require("fs").readFileSync(j).buffer;h=ve.exports.loadWASM(S)}Se=h.then(()=>({createOnigScanner(b){return ve.exports.createOnigScanner(b)},createOnigString(b){return ve.exports.createOnigString(b)}}))}return Se}function Be(h){if(Ae)return Ne||console.warn("[Shiki] no CDN provider found, use `setCDN()` to specify the CDN for loading the resources before calling `getHighlighter()`"),`${Ne}${h}`;{const b=require("path");return b.isAbsolute(h)?h:b.resolve(__dirname,"..",h)}}async function nt(h){const b=Be(h);return Ae?await fetch(b).then(j=>j.text()):await require("fs").promises.readFile(b,"utf-8")}async function We(h){const b=[],j=Qe(await nt(h),b,{allowTrailingComma:!0});if(b.length)throw b[0];return j}async function Fe(h){let b=await We(h);const j=Ue(b);if(j.include){const n=await Fe(Ve(He(h),j.include));n.settings&&(j.settings=n.settings.concat(j.settings)),n.bg&&!j.bg&&(j.bg=n.bg),n.colors&&(j.colors=Object.assign(Object.assign({},n.colors),j.colors)),delete j.include}return j}async function rt(h){return await We(h)}function st(h){h.settings||(h.settings=[]),!(h.settings[0]&&h.settings[0].settings&&!h.settings[0].scope)&&h.settings.unshift({settings:{foreground:h.fg,background:h.bg}})}function Ue(h){const b=h.type||"dark",j=Object.assign(Object.assign({name:h.name,type:b},h),at(h));return h.include&&(j.include=h.include),h.tokenColors&&(j.settings=h.tokenColors,delete j.tokenColors),st(j),j}const Ee={light:"#333333",dark:"#bbbbbb"},Me={light:"#fffffe",dark:"#1e1e1e"};function at(h){var b,j,n,S,L,y;let E,N,r=h.settings?h.settings:h.tokenColors;const d=r?r.find(k=>!k.name&&!k.scope):void 0;return!((b=d==null?void 0:d.settings)===null||b===void 0)&&b.foreground&&(E=d.settings.foreground),!((j=d==null?void 0:d.settings)===null||j===void 0)&&j.background&&(N=d.settings.background),!E&&((S=(n=h)===null||n===void 0?void 0:n.colors)===null||S===void 0?void 0:S["editor.foreground"])&&(E=h.colors["editor.foreground"]),!N&&((y=(L=h)===null||L===void 0?void 0:L.colors)===null||y===void 0?void 0:y["editor.background"])&&(N=h.colors["editor.background"]),E||(E=h.type==="light"?Ee.light:Ee.dark),N||(N=h.type==="light"?Me.light:Me.dark),{fg:E,bg:N}}class ot{constructor(b,j){this.languagesPath="languages/",this.languageMap={},this.scopeToLangMap={},this._onigLibPromise=b,this._onigLibName=j}get onigLib(){return this._onigLibPromise}getOnigLibName(){return this._onigLibName}getLangRegistration(b){return this.languageMap[b]}async loadGrammar(b){const j=this.scopeToLangMap[b];if(!j)return null;if(j.grammar)return j.grammar;const n=await rt(xe.includes(j)?`${this.languagesPath}${j.path}`:j.path);return j.grammar=n,n}addLanguage(b){this.languageMap[b.id]=b,b.aliases&&b.aliases.forEach(j=>{this.languageMap[j]=b}),this.scopeToLangMap[b.scopeName]=b}}function it(h,b,j,n,S){let L=j.split(/\r\n|\r|\n/),y=Te.exports.INITIAL,E=[],N=[];for(let r=0,d=L.length;r =0&&L>=0;)De(b[S],n[L])&&S--,L--;return S===-1}function lt(h,b,j){let n=[],S=0;for(let L=0,y=h.settings.length;L r.trim());else if(Array.isArray(E.scope))N=E.scope;else continue;for(let r=0,d=N.length;r y.line);let L="";return L+=` `,b.langId&&(L+=`",L}const ht={"&":"&","<":"<",">":">",'"':""","'":"'"};function ft(h){return h.replace(/[&<>"']/g,b=>ht[b])}function dt(h){var b;const j=new Set(["line"]);for(const n of h)for(const S of(b=n.classes)!==null&&b!==void 0?b:[])j.add(S);return Array.from(j)}class gt extends Te.exports.Registry{constructor(b){super(b),this._resolver=b,this.themesPath="themes/",this._resolvedThemes={},this._resolvedGrammars={}}getTheme(b){return typeof b=="string"?this._resolvedThemes[b]:b}async loadTheme(b){return typeof b=="string"?(this._resolvedThemes[b]||(this._resolvedThemes[b]=await Fe(`${this.themesPath}${b}.json`)),this._resolvedThemes[b]):(b=Ue(b),b.name&&(this._resolvedThemes[b.name]=b),b)}async loadThemes(b){return await Promise.all(b.map(j=>this.loadTheme(j)))}getLoadedThemes(){return Object.keys(this._resolvedThemes)}getGrammar(b){return this._resolvedGrammars[b]}async loadLanguage(b){const j=await this.loadGrammar(b.scopeName);this._resolvedGrammars[b.id]=j,b.aliases&&b.aliases.forEach(n=>{this._resolvedGrammars[n]=j})}async loadLanguages(b){for(const j of b)this._resolver.addLanguage(j);for(const j of b)await this.loadLanguage(j)}getLoadedLanguages(){return Object.keys(this._resolvedGrammars)}}function $e(h){return typeof h=="string"?xe.find(b=>{var j;return b.id===h||((j=b.aliases)===null||j===void 0?void 0:j.includes(h))}):h}function mt(h){var b;let j=xe,n=h.themes||[];return!((b=h.langs)===null||b===void 0)&&b.length&&(j=h.langs.map($e)),h.theme&&n.unshift(h.theme),n.length||(n=["nord"]),{_languages:j,_themes:n}}async function bt(h){var b,j;const{_languages:n,_themes:S}=mt(h),L=new ot(tt(),"vscode-oniguruma"),y=new gt(L);!((b=h.paths)===null||b===void 0)&&b.themes&&(y.themesPath=h.paths.themes),!((j=h.paths)===null||j===void 0)&&j.languages&&(L.languagesPath=h.paths.languages);const N=(await y.loadThemes(S))[0];let r;await y.loadLanguages(n);const d={"#000001":"var(--shiki-color-text)","#000002":"var(--shiki-color-background)","#000004":"var(--shiki-token-constant)","#000005":"var(--shiki-token-string)","#000006":"var(--shiki-token-comment)","#000007":"var(--shiki-token-keyword)","#000008":"var(--shiki-token-parameter)","#000009":"var(--shiki-token-function)","#000010":"var(--shiki-token-string-expression)","#000011":"var(--shiki-token-punctuation)","#000012":"var(--shiki-token-link)"};function k(t,s){t.bg=d[t.bg]||t.bg,t.fg=d[t.fg]||t.fg,s.forEach((_,v)=>{s[v]=d[_]||_})}function w(t){const s=t?y.getTheme(t):N;if(!s)throw Error(`No theme registration for ${t}`);(!r||r.name!==s.name)&&(y.setTheme(s),r=s);const _=y.getColorMap();return s.name==="css-variables"&&k(s,_),{_theme:s,_colorMap:_}}function i(t){const s=y.getGrammar(t);if(!s)throw Error(`No language registration for ${t}`);return{_grammar:s}}function T(t,s="text",_,v={includeExplanation:!0}){if(_t(s))return[...t.split(/\r\n|\r|\n/).map(H=>[{content:H}])];const{_grammar:P}=i(s),{_theme:A,_colorMap:I}=w(_);return it(A,I,t,P,v)}function g(t,s="text",_){let v;typeof s=="object"?v=s:v={lang:s,theme:_};const P=T(t,v.lang,v.theme,{includeExplanation:!1}),{_theme:A}=w(v.theme);return pt(P,{fg:A.fg,bg:A.bg,lineOptions:v==null?void 0:v.lineOptions})}async function l(t){await y.loadTheme(t)}async function u(t){const s=$e(t);L.addLanguage(s),await y.loadLanguage(s)}function p(){return y.getLoadedThemes()}function o(){return y.getLoadedLanguages()}function c(t){const{_theme:s}=w(t);return s.bg}function e(t){const{_theme:s}=w(t);return s.fg}return{codeToThemedTokens:T,codeToHtml:g,getTheme:t=>w(t)._theme,loadTheme:l,loadLanguage:u,getBackgroundColor:c,getForegroundColor:e,getLoadedThemes:p,getLoadedLanguages:o}}function _t(h){return!h||["plaintext","txt","text"].includes(h)}function kt(h){et(h)}export{xe as BUNDLED_LANGUAGES,yt as BUNDLED_THEMES,be as FontStyle,bt as getHighlighter,Fe as loadTheme,pt as renderToHtml,vt as setCDN,kt as setOnigasmWASM,et as setWasm,Ue as toShikiTheme}; diff --git a/assets/index.html.5d377a5b.js b/assets/index.html.5d377a5b.js new file mode 100644 index 000000000..5339e836a --- /dev/null +++ b/assets/index.html.5d377a5b.js @@ -0,0 +1 @@ +import{_ as s,y as r,z as d,X as e,B as t,Q as o,$ as i,a5 as c,P as l}from"./framework.fe9a73df.js";const u={},h=e("h1",{id:"coalesce",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#coalesce","aria-hidden":"true"},"#"),t(" Coalesce")],-1),p={href:"https://intellitect.com",target:"_blank",rel:"noopener noreferrer"},_=e("li",null,"ASP.NET Core",-1),m=e("li",null,"EF Core",-1),f=e("li",null,"TypeScript",-1),g={href:"https://vuejs.org/",target:"_blank",rel:"noopener noreferrer"},k=c('${b.langId}`),L+="",h.forEach((y,E)=>{var N;const r=E+1,d=(N=S.get(r))!==null&&N!==void 0?N:[],k=dt(d).join(" ");L+=``,y.forEach(w=>{const i=[`color: ${w.color||b.fg}`];w.fontStyle&be.Italic&&i.push("font-style: italic"),w.fontStyle&be.Bold&&i.push("font-weight: bold"),w.fontStyle&be.Underline&&i.push("text-decoration: underline"),L+=`${ft(w.content)}`}),L+=` +`}),L=L.replace(/\n*$/,""),L+="
What do I do?
You are responsible for the interesting parts of your application:
- Data Model
- Business Logic
- External Integrations
- Page Content
- Site Design
- Custom Scripting
What is done for me?
Coalesce builds the part of your application that are mundane and monotonous to build:
',5),w=e("li",null,"APIs to interact with your models via endpoints like List, Get, Save, and more.",-1),y=e("li",null,"A complete set of admin pages are provided, allowing you to read, create, edit, and delete data straight away without writing any additional code.",-1),b=e("h1",{id:"getting-started",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#getting-started","aria-hidden":"true"},"#"),t(" Getting Started")],-1);function v(x,C){const n=l("ExternalLinkIcon"),a=l("RouterLink");return r(),d("div",null,[h,e("p",null,[t("Designed to help you quickly build amazing web applications, Coalesce is a rapid-development code generation framework, created by "),e("a",p,[t("IntelliTect"),o(n)]),t(" and built on top of:")]),e("ul",null,[_,m,f,e("li",null,[e("a",g,[t("Vue"),o(n)])])]),k,e("ul",null,[e("li",null,[t("Client side "),o(a,{to:"/stacks/vue/layers/viewmodels.html"},{default:i(()=>[t("TypeScript ViewModels")]),_:1}),t(" that mirror your data model for both lists and individual objects. Utilize these to rapidly build out your application's various pages.")]),w,e("li",null,[t("Out-of-the-box "),o(a,{to:"/stacks/vue/coalesce-vue-vuetify/overview.html"},{default:i(()=>[t("Vue Components")]),_:1}),t(" for common controls like dates, selecting objects via drop downs, enums, etc. Dropdowns support searching and paging automatically.")]),y]),b,e("p",null,[t("To get started with Coalesce, check out "),o(a,{to:"/stacks/vue/getting-started.html"},{default:i(()=>[t("Getting Started with Vue")]),_:1}),t(".")]),e("p",null,[t("While Knockout.js is still supported by Coalesce, it is a deprecated option and not recommended for new projects. If you do still want to choose Knockout, click "),o(a,{to:"/stacks/ko/getting-started.html"},{default:i(()=>[t("here")]),_:1}),t(".")])])}const V=s(u,[["render",v],["__file","index.html.vue"]]);export{V as default}; diff --git a/assets/index.html.6f4afde9.js b/assets/index.html.6f4afde9.js new file mode 100644 index 000000000..a12d31cf8 --- /dev/null +++ b/assets/index.html.6f4afde9.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-8daa1a0e","path":"/","title":"Coalesce Documentation","lang":"en-US","frontmatter":{"lang":"en-US","title":"Coalesce Documentation","description":"Documentation home page for IntelliTect.Coalesce"},"excerpt":"","headers":[{"level":2,"title":"What do I do?","slug":"what-do-i-do","link":"#what-do-i-do","children":[]},{"level":2,"title":"What is done for me?","slug":"what-is-done-for-me","link":"#what-is-done-for-me","children":[]}],"git":{"updatedTime":1677792094000},"filePathRelative":"index.md"}');export{e as data}; diff --git a/assets/inject.html.6b988db4.js b/assets/inject.html.6b988db4.js new file mode 100644 index 000000000..311e9e92c --- /dev/null +++ b/assets/inject.html.6b988db4.js @@ -0,0 +1,13 @@ +import{_ as o,y as p,z as t,X as n,B as s,Q as l,$ as e,a5 as c,P as r}from"./framework.fe9a73df.js";const D={},i=n("h1",{id:"inject",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#inject","aria-hidden":"true"},"#"),s(" [Inject]")],-1),y=n("p",null,"========",-1),d=n("code",null,"IServiceProvider",-1),u=c(`This gets translated to a
Microsoft.AspNetCore.Mvc.FromServicesAttribute
in the generated API controller's action.Example Usage
`,3);function C(m,v){const a=r("RouterLink");return p(),t("div",null,[i,y,n("p",null,[s("Used to mark a "),l(a,{to:"/modeling/model-components/methods.html"},{default:e(()=>[s("Method")]),_:1}),s(" parameter for dependency injection from the application's "),d,s(".")]),n("p",null,[s("See "),l(a,{to:"/modeling/model-components/methods.html"},{default:e(()=>[s("Methods")]),_:1}),s(" for more.")]),u])}const g=o(D,[["render",C],["__file","inject.html.vue"]]);export{g as default}; diff --git a/assets/inject.html.859d9f07.js b/assets/inject.html.859d9f07.js new file mode 100644 index 000000000..e115049bf --- /dev/null +++ b/assets/inject.html.859d9f07.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-60941fa8","path":"/modeling/model-components/attributes/inject.html","title":"[Inject]","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Example Usage","slug":"example-usage","link":"#example-usage","children":[]}],"git":{"updatedTime":1652390840000},"filePathRelative":"modeling/model-components/attributes/inject.md"}');export{e as data}; diff --git a/assets/internal-use.html.bddc07d7.js b/assets/internal-use.html.bddc07d7.js new file mode 100644 index 000000000..a8882d1bc --- /dev/null +++ b/assets/internal-use.html.bddc07d7.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-440fbe82","path":"/modeling/model-components/attributes/internal-use.html","title":"[InternalUse]","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Example Usage","slug":"example-usage","link":"#example-usage","children":[]}],"git":{"updatedTime":1652390840000},"filePathRelative":"modeling/model-components/attributes/internal-use.md"}');export{e as data}; diff --git a/assets/internal-use.html.d0d63e3d.js b/assets/internal-use.html.d0d63e3d.js new file mode 100644 index 000000000..6639c3350 --- /dev/null +++ b/assets/internal-use.html.d0d63e3d.js @@ -0,0 +1,21 @@ +import{_ as o,y as p,z as t,X as s,B as n,Q as l,$ as e,a5 as D,P as r}from"./framework.fe9a73df.js";const c={},i=s("h1",{id:"internaluse",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#internaluse","aria-hidden":"true"},"#"),n(" [InternalUse]")],-1),y=s("p",null,"Used to mark a type, property or method for internal use. Internal Use members are:",-1),d=s("ul",null,[s("li",null,"Not exposed via the API."),s("li",null,"Not present in the generated TypeScript view models."),s("li",null,"Not present nor accounted for in the generated C# DTOs."),s("li",null,"Not present in the generated editor or list views.")],-1),C=s("p",null,[n("Note that this only needs to be used on members that are public. Non-public members (including "),s("code",null,"internal"),n(") are always invisible to Coalesce.")],-1),u=s("h2",{id:"example-usage",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#example-usage","aria-hidden":"true"},"#"),n(" Example Usage")],-1),m=s("code",null,"Color",-1),v=s("code",null,"ColorHex",-1),b=D(`public class Person +{ + public int PersonId { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + + public string GetFullName([Inject] ILogger<Person> logger) + { + logger.LogInformation("Person " + PersonId + "'s full name was requested"); + return FirstName + " " + LastName"; + } +} +
If no color is saved in the database (the user hasn't picked a color), one is deterministically created.
`,2);function h(E,_){const a=r("RouterLink");return p(),t("div",null,[i,y,d,s("p",null,[n("Effectively, an Internal Use member is invisible to Coalesce. This attribute can be considered a "),l(a,{to:"/modeling/model-components/attributes/security-attribute.html"},{default:e(()=>[n("Security Attribute")]),_:1}),n(".")]),C,u,s("p",null,[n("In this example, "),m,n(" is the property exposed to the API, but "),v,n(" is the property that maps to the database that stores the value. A helper method also exists for the color generation, but needs no attribute to be hidden since methods must be explicitly exposed with "),l(a,{to:"/modeling/model-components/attributes/coalesce.html"},{default:e(()=>[n("[Coalesce]")]),_:1}),n(".")]),b])}const A=o(c,[["render",h],["__file","internal-use.html.vue"]]);export{A as default}; diff --git a/assets/list-text.html.29c3267b.js b/assets/list-text.html.29c3267b.js new file mode 100644 index 000000000..990cbf6da --- /dev/null +++ b/assets/list-text.html.29c3267b.js @@ -0,0 +1,13 @@ +import{_ as s,y as n,z as a,a5 as l}from"./framework.fe9a73df.js";const e={},p=l(`public class ApplicationUser +{ + public int ApplicationUserId { get; set; } + + [InternalUse] + public string ColorHex { get; set; } + + [NotMapped] + public string Color + { + get => ColorHex ?? GenerateColor(ApplicationUserId).ToRGBHexString(); + set => ColorHex = value; + } + + public static HSLColor GenerateColor(int? seed = null) + { + var random = seed.HasValue ? new Random(seed.Value) : new Random(); + return new HSLColor(random.NextDouble(), random.Next(40, 100) / 100d, random.Next(25, 65) / 100d); + } +} +
[ListText]
When a textual representation of an object needs to be displayed in the UI, this attribute controls which property will be used. Examples include dropdowns and cells in admin UI tables.
If this attribute is not used, and a property named
Name
exists on the model, that property will be used. Otherwise, the primary key will be used.Example Usage
`,5),o=[p];function t(c,r){return n(),a("div",null,o)}const i=s(e,[["render",t],["__file","list-text.html.vue"]]);export{i as default}; diff --git a/assets/list-text.html.7340eedf.js b/assets/list-text.html.7340eedf.js new file mode 100644 index 000000000..12a3a7e08 --- /dev/null +++ b/assets/list-text.html.7340eedf.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-05ce079d","path":"/modeling/model-components/attributes/list-text.html","title":"[ListText]","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Example Usage","slug":"example-usage","link":"#example-usage","children":[]}],"git":{"updatedTime":1652390840000},"filePathRelative":"modeling/model-components/attributes/list-text.md"}');export{e as data}; diff --git a/assets/list-view-model.html.29d6ec3a.js b/assets/list-view-model.html.29d6ec3a.js new file mode 100644 index 000000000..d2b1a9963 --- /dev/null +++ b/assets/list-view-model.html.29d6ec3a.js @@ -0,0 +1 @@ +import{_ as i,y as n,z as l,X as e,B as t,Q as o,$ as a,P as c}from"./framework.fe9a73df.js";const r={},d=e("h1",{id:"typescript-list-viewmodels",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#typescript-list-viewmodels","aria-hidden":"true"},"#"),t(" TypeScript List ViewModels")],-1),h=e("p",null,"This is a disambiguation page for a concept in Coalesce that is implemented differently between the available front-end stack choices. Please select your preferred stack.",-1),u=e("h2",{id:"vue",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#vue","aria-hidden":"true"},"#"),t(" Vue")],-1),_=e("h2",{id:"knockout",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#knockout","aria-hidden":"true"},"#"),t(" Knockout")],-1);function p(m,f){const s=c("RouterLink");return n(),l("div",null,[d,h,u,e("p",null,[t("See: "),o(s,{to:"/stacks/vue/layers/viewmodels.html"},{default:a(()=>[t("Vue ListViewModels")]),_:1})]),_,e("p",null,[t("See: "),o(s,{to:"/stacks/ko/client/list-view-model.html"},{default:a(()=>[t("Knockout ListViewModels")]),_:1})])])}const v=i(r,[["render",p],["__file","list-view-model.html.vue"]]);export{v as default}; diff --git a/assets/list-view-model.html.80510cd0.js b/assets/list-view-model.html.80510cd0.js new file mode 100644 index 000000000..2a7597095 --- /dev/null +++ b/assets/list-view-model.html.80510cd0.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-32435eb2","path":"/stacks/disambiguation/list-view-model.html","title":"TypeScript List ViewModels","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Vue","slug":"vue","link":"#vue","children":[]},{"level":2,"title":"Knockout","slug":"knockout","link":"#knockout","children":[]}],"git":{"updatedTime":1652390840000},"filePathRelative":"stacks/disambiguation/list-view-model.md"}');export{e as data}; diff --git a/assets/list-view-model.html.c0ac40ce.js b/assets/list-view-model.html.c0ac40ce.js new file mode 100644 index 000000000..8551b647b --- /dev/null +++ b/assets/list-view-model.html.c0ac40ce.js @@ -0,0 +1,13 @@ +import{_ as r,y as d,z as c,X as e,B as t,Q as s,$ as n,a5 as u,P as i}from"./framework.fe9a73df.js";const h={},p=e("h1",{id:"typescript-listviewmodels",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#typescript-listviewmodels","aria-hidden":"true"},"#"),t(" TypeScript ListViewModels")],-1),m=e("p",null,"These ListViewModels, like the ViewModels, are dependent on Knockout and are designed to be used directly from Knockout bindings in your HTML.",-1),f={class:"table-of-contents"},g=e("h2",{id:"base-members",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#base-members","aria-hidden":"true"},"#"),t(" Base Members")],-1),b=e("p",null,[t("The following members are defined on "),e("code",null,"BaseListViewModel<>"),t(" and are available to the ListViewModels for all of your model types:")],-1),_=e("p",null,"Name of the primary key of the model that this list represents.",-1),y=e("p",null,"The collection of items that have been loaded from the server.",-1),w=e("p",null,"Adds a new item to the items collection.",-1),v=e("p",null,"Deletes an item and removes it from the items collection.",-1),D=e("p",null,"An arbitrary URL query string to append to the API call when loading the list of items.",-1),C=e("p",null,"True if the list is loading.",-1),k=e("p",null,"True once the list has been loaded.",-1),S=e("p",null,"Load the list using current parameters for paging, searching, etc Result is placed into the items property.",-1),M=e("p",null,"If a load failed, this is a message about why it failed.",-1),T=e("p",null,"Gets the count of items without getting all the items. Result is placed into the count property.",-1),x=e("p",null,"The result of getCount(), or the total on this page.",-1),L=e("p",null,"Total count of items, even ones that are not on the page.",-1),V=e("p",null,"Change to the next page.",-1),P=e("p",null,"True if there is another page after the current page.",-1),O=e("p",null,"Change to the previous page.",-1),N=e("p",null,"True if there is another page before the current page.",-1),E=e("p",null,"Page number. This can be set to get a new page.",-1),K=e("p",null,"Total page count",-1),I=e("p",null,"Number of items on a page.",-1),F=e("p",null,"Name of a field by which this list will be loaded in ascending order.",-1),B=e("code",null,'"none"',-1),j=e("p",null,"Name of a field by which this list will be loaded in descending order.",-1),A=e("p",null,"Toggles sorting between ascending, descending, and no order on the specified field.",-1),q=e("h2",{id:"model-specific-members",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#model-specific-members","aria-hidden":"true"},"#"),t(" Model-Specific Members")],-1),R=e("h3",{id:"configuration",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#configuration","aria-hidden":"true"},"#"),t(" Configuration")],-1),W=e("h3",{id:"filter-object",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#filter-object","aria-hidden":"true"},"#"),t(" Filter Object")],-1),Q=e("p",null,[t("For each exposed scalar property on the underlying EF POCO, "),e("code",null,"filter"),t(" will have a corresponding property. If the "),e("code",null,"filter"),t(" object is set, requests made to the server to retrieve data will be passed all the values in this object via the URL's query string. These parameters will filter the resulting data to only rows where the parameter values match the row's values. For example, if "),e("code",null,"filter.companyId"),t(" is set to a value, only people from that company will be returned.")],-1),z=u(`public class Person +{ + public int PersonId { get; set; } + + public string FirstName { get; set; } + + public string LastName { get; set; } + + [ListText] + [NotMapped] + public string Name => FirstName + " " + LastName +} +
- Dates with a time component will be matched exactly.
- Dates with no time component will match any dates that fell on that day.
- Strings will match exactly unless an asterisk is found, in which case they will be matched with
string.StartsWith
.- Enums will match by string or numeric value. Multiple comma-delimited values will create a filter that will match on any of the provided values.
- Numeric values will match exactly. Multiple comma-delimited values will create a filter that will match on any of the provided values.
Example usage:
var list = new ListViewModels.PersonList(); +list.filter = { lastName: "Erickson" }; +list.load(); +
`,4),J=e("h3",{id:"datasources",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#datasources","aria-hidden":"true"},"#"),t(" DataSources")],-1),G=e("code",null,"ListViewModels. Static Method Members
DataSources",-1),U=e("code",null,"ViewModel",-1),H=e("code",null,"ListViewModel",-1),X=e("code",null,"dataSources",-1),$=e("code",null,"dataSource",-1);function Y(Z,ee){const a=i("RouterLink"),l=i("router-link"),o=i("Prop");return d(),c("div",null,[p,e("p",null,[t("In addition to "),s(a,{to:"/stacks/ko/client/view-model.html"},{default:n(()=>[t("TypeScript ViewModels")]),_:1}),t(" for interacting with instances of your data classes in TypeScript, Coalesce will also generated a List ViewModel for loading searched, sorted, paginated data from the server.")]),m,e("nav",f,[e("ul",null,[e("li",null,[s(l,{to:"#base-members"},{default:n(()=>[t("Base Members")]),_:1})]),e("li",null,[s(l,{to:"#model-specific-members"},{default:n(()=>[t("Model-Specific Members")]),_:1}),e("ul",null,[e("li",null,[s(l,{to:"#configuration"},{default:n(()=>[t("Configuration")]),_:1})]),e("li",null,[s(l,{to:"#filter-object"},{default:n(()=>[t("Filter Object")]),_:1})]),e("li",null,[s(l,{to:"#static-method-members"},{default:n(()=>[t("Static Method Members")]),_:1})]),e("li",null,[s(l,{to:"#datasources"},{default:n(()=>[t("DataSources")]),_:1})])])])])]),g,b,s(o,{def:"modelKeyName: string",lang:"ts"}),_,s(o,{def:"includes: string",lang:"ts"}),e("p",null,[t("String that is used to control loading and serialization on the server. See "),s(a,{to:"/concepts/includes.html"},{default:n(()=>[t("Includes String")]),_:1}),t(" for more information.")]),s(o,{def:"items: KnockoutObservableArray ",lang:"ts"}),y,s(o,{def:"addNewItem: (): TItem",lang:"ts"}),w,s(o,{def:"deleteItem: (item: TItem): JQueryPromise ",lang:"ts"}),v,s(o,{def:"queryString: string",lang:"ts"}),D,s(o,{def:"search: KnockoutObservable ",lang:"ts"}),e("p",null,[t("Search criteria for the list. This can be easily bound to with a text box for easy search behavior. See "),s(a,{to:"/modeling/model-components/attributes/search.html"},{default:n(()=>[t("[Search]")]),_:1}),t(" for a detailed look at how searching works in Coalesce.")]),s(o,{def:"isLoading: KnockoutObservable ",lang:"ts"}),C,s(o,{def:"isLoaded: KnockoutObservable ",lang:"ts"}),k,s(o,{def:"load: (callback?: any): JQueryPromise ",lang:"ts"}),S,s(o,{def:"message: KnockoutObservable ",lang:"ts"}),M,s(o,{def:"getCount: (callback?: any): JQueryPromise ",lang:"ts"}),T,s(o,{def:"count: KnockoutObservable ",lang:"ts"}),x,s(o,{def:"totalCount: KnockoutObservable ",lang:"ts"}),L,s(o,{def:"nextPage: (): void",lang:"ts"}),V,s(o,{def:"nextPageEnabled: KnockoutComputed ",lang:"ts"}),P,s(o,{def:"previousPage: (): void",lang:"ts"}),O,s(o,{def:"previousPageEnabled: KnockoutComputed ",lang:"ts"}),N,s(o,{def:"page: KnockoutObservable ",lang:"ts"}),E,s(o,{def:"pageCount: KnockoutObservable ",lang:"ts"}),K,s(o,{def:"pageSize: KnockoutObservable ",lang:"ts"}),I,s(o,{def:"orderBy: KnockoutObservable ",lang:"ts"}),F,e("p",null,[t("If set to "),B,t(", default sorting behavior, including behavior defined with use of "),s(a,{to:"/modeling/model-components/attributes/default-order-by.html"},{default:n(()=>[t("[DefaultOrderBy]")]),_:1}),t(" in C# POCOs, is suppressed.")]),s(o,{def:"orderByDescending: KnockoutObservable ",lang:"ts"}),j,s(o,{def:"orderByToggle: (field: string): void",lang:"ts"}),A,q,R,s(o,{def:"static coalesceConfig: Coalesce.ListViewModelConfiguration ",lang:"ts",id:"member-class-config"}),e("p",null,[t("A static configuration object for configuring all instances of the ListViewModel's type is created. See "),s(a,{to:"/stacks/ko/client/model-config.html"},{default:n(()=>[t("ViewModel Configuration")]),_:1}),t(".")]),s(o,{def:"coalesceConfig: Coalesce.ListViewModelConfiguration ",lang:"ts",id:"member-instance-config"}),e("p",null,[t("An per-instance configuration object for configuring each specific ListViewModel instance is created. See "),s(a,{to:"/stacks/ko/client/model-config.html"},{default:n(()=>[t("ViewModel Configuration")]),_:1}),t(".")]),W,s(o,{def:`public filter: { + personId?: string + firstName?: string + lastName?: string + gender?: string + companyId?: string +} = null;`,lang:"ts",id:"code-filter-object"}),Q,e("p",null,[t("These parameters all allow for freeform string values, allowing the server to implement any kind of filtering logic desired. The "),s(a,{to:"/modeling/model-components/data-sources.html#standard-data-source"},{default:n(()=>[t("Standard Data Source")]),_:1}),t(" will perform the following depending on the property type:")]),z,s(o,{def:`public readonly namesStartingWith = new Person.NamesStartingWith(this); +public static NamesStartingWith = class NamesStartingWith extends Coalesce.ClientMethod { ... };`,lang:"ts",id:"code-static-method-members"}),e("p",null,[t("For each exposed "),s(a,{to:"/modeling/model-components/methods.html#static-methods"},{default:n(()=>[t("Static Method")]),_:1}),t(" on your POCO, the members outlined in "),s(a,{to:"/stacks/ko/client/methods.html"},{default:n(()=>[t("Methods - Generated TypeScript")]),_:1}),t(" will be created.")]),J,s(o,{def:` +public dataSources = ListViewModels.PersonDataSources; +public dataSource: DataSource = new this.dataSources.Default();`,lang:"ts",id:"code-data-source-members"}),e("p",null,[t("For each of the "),s(a,{to:"/modeling/model-components/data-sources.html"},{default:n(()=>[t("Data Sources")]),_:1}),t(" on the class, a corresponding class will be added to a namespace named "),G,t(". This namespace can always be accessed on both "),U,t(" and "),H,t(" instances via the "),X,t(" property, and class instances can be assigned to the "),$,t(" property.")])])}const se=r(h,[["render",Y],["__file","list-view-model.html.vue"]]);export{se as default}; diff --git a/assets/list-view-model.html.c0e8f6d8.js b/assets/list-view-model.html.c0e8f6d8.js new file mode 100644 index 000000000..2fc238346 --- /dev/null +++ b/assets/list-view-model.html.c0e8f6d8.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-30cbf62d","path":"/stacks/ko/client/list-view-model.html","title":"TypeScript ListViewModels","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Base Members","slug":"base-members","link":"#base-members","children":[]},{"level":2,"title":"Model-Specific Members","slug":"model-specific-members","link":"#model-specific-members","children":[{"level":3,"title":"Configuration","slug":"configuration","link":"#configuration","children":[]},{"level":3,"title":"Filter Object","slug":"filter-object","link":"#filter-object","children":[]},{"level":3,"title":"Static Method Members","slug":"static-method-members","link":"#static-method-members","children":[]},{"level":3,"title":"DataSources","slug":"datasources","link":"#datasources","children":[]}]}],"git":{"updatedTime":1654889604000},"filePathRelative":"stacks/ko/client/list-view-model.md"}');export{e as data}; diff --git a/assets/load-from-data-source.html.37349d94.js b/assets/load-from-data-source.html.37349d94.js new file mode 100644 index 000000000..e333257e9 --- /dev/null +++ b/assets/load-from-data-source.html.37349d94.js @@ -0,0 +1,12 @@ +import{_ as o,y as p,z as t,Q as a,X as c,B as s,$ as r,a5 as D,P as n}from"./framework.fe9a73df.js";const i={},d=D(` [LoadFromDataSource]
Specifies that the targeted model instance method should load the instance it is called on from the specified data source when invoked from an API endpoint. By default, the default data source for the model's type will be used.
Example Usage
public class Person +{ + public int PersonId { get; set; } + public string FirstName { get; set; } + + [Coalesce, LoadFromDataSource(typeof(WithoutCases))] + public void ChangeSpacesToDashesInName() + { + FirstName = FirstName.Replace(" ", "-"); + } +} +
`,5);function y(u,m){const e=n("Prop"),l=n("RouterLink");return p(),t("div",null,[d,a(e,{def:"public Type DataSourceType { get; }",ctor:"1"}),c("p",null,[s("The name of the "),a(l,{to:"/modeling/model-components/data-sources.html"},{default:r(()=>[s("Data Source")]),_:1}),s(" to load the instance object from.")])])}const C=o(i,[["render",y],["__file","load-from-data-source.html.vue"]]);export{C as default}; diff --git a/assets/load-from-data-source.html.ec39c8f4.js b/assets/load-from-data-source.html.ec39c8f4.js new file mode 100644 index 000000000..a892be8cd --- /dev/null +++ b/assets/load-from-data-source.html.ec39c8f4.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-e0635f52","path":"/modeling/model-components/attributes/load-from-data-source.html","title":"[LoadFromDataSource]","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Example Usage","slug":"example-usage","link":"#example-usage","children":[]},{"level":2,"title":"Properties","slug":"properties","link":"#properties","children":[]}],"git":{"updatedTime":1652390840000},"filePathRelative":"modeling/model-components/attributes/load-from-data-source.md"}');export{e as data}; diff --git a/assets/many-to-many.html.41f6bed5.js b/assets/many-to-many.html.41f6bed5.js new file mode 100644 index 000000000..85755102d --- /dev/null +++ b/assets/many-to-many.html.41f6bed5.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-d6277114","path":"/modeling/model-components/attributes/many-to-many.html","title":"[ManyToMany]","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Example Usage","slug":"example-usage","link":"#example-usage","children":[]},{"level":2,"title":"Properties","slug":"properties","link":"#properties","children":[]}],"git":{"updatedTime":1680212877000},"filePathRelative":"modeling/model-components/attributes/many-to-many.md"}');export{e as data}; diff --git a/assets/many-to-many.html.806b3ca1.js b/assets/many-to-many.html.806b3ca1.js new file mode 100644 index 000000000..cd724a568 --- /dev/null +++ b/assets/many-to-many.html.806b3ca1.js @@ -0,0 +1,10 @@ +import{_ as t,y as p,z as r,X as s,B as n,Q as e,$ as i,a5 as c,P as o}from"./framework.fe9a73df.js";const D={},y=s("h1",{id:"manytomany",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#manytomany","aria-hidden":"true"},"#"),n(" [ManyToMany]")],-1),d=s("p",null,"Used to specify a Many to Many relationship. Because EF core does not support automatic intermediate mapping tables, this field is used to allow for direct reference of the many-to-many collections from the ViewModel.",-1),h=c(` Properties
Example Usage
public class Person +{ + public int PersonId { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + + [ManyToMany("Appointments")] + public ICollection<PersonAppointment> PersonAppointments { get; set; } +} +
`,3),m=s("p",null,"The name of the collection that will contain the set of objects on the other side of the many-to-many relationship.",-1),u=s("p",null,"The name of the navigation property on the middle entity that points at the far side of the many-to-many relationship. Use this to resolve ambiguities when the middle table of the many-to-many relationship has more than two reference navigation properties on it.",-1);function C(f,b){const l=o("RouterLink"),a=o("Prop");return p(),r("div",null,[y,d,s("p",null,[n("The named specified in the attribute will be used as the name of a collection of the objects on the other side of the relationship in the generated "),e(l,{to:"/stacks/disambiguation/view-model.html"},{default:i(()=>[n("TypeScript ViewModels")]),_:1}),n(".")]),h,e(a,{def:"public string CollectionName { get; }",ctor:"1"}),m,e(a,{def:"public string FarNavigationProperty { get; set; }"}),u])}const _=t(D,[["render",C],["__file","many-to-many.html.vue"]]);export{_ as default}; diff --git a/assets/metadata.html.23f4eb70.js b/assets/metadata.html.23f4eb70.js new file mode 100644 index 000000000..1717cb863 --- /dev/null +++ b/assets/metadata.html.23f4eb70.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-3b29db1e","path":"/stacks/vue/layers/metadata.html","title":"Vue Metadata Layer","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Concepts","slug":"concepts","link":"#concepts","children":[{"level":3,"title":"Metadata","slug":"metadata","link":"#metadata","children":[]},{"level":3,"title":"Type","slug":"type","link":"#type","children":[]},{"level":3,"title":"Value","slug":"value","link":"#value","children":[]},{"level":3,"title":"Property","slug":"property","link":"#property","children":[]},{"level":3,"title":"Domain","slug":"domain","link":"#domain","children":[]}]}],"git":{"updatedTime":1677792094000},"filePathRelative":"stacks/vue/layers/metadata.md"}');export{e as data}; diff --git a/assets/metadata.html.cbe35d07.js b/assets/metadata.html.cbe35d07.js new file mode 100644 index 000000000..3a22517cf --- /dev/null +++ b/assets/metadata.html.cbe35d07.js @@ -0,0 +1 @@ +import{_ as h,y as p,z as u,W as l,X as e,Q as a,$ as o,B as t,a5 as i,P as s}from"./framework.fe9a73df.js";const m={},f=e("h1",{id:"vue-metadata-layer",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#vue-metadata-layer","aria-hidden":"true"},"#"),t(" Vue Metadata Layer")],-1),y=e("p",null,[t("The metadata layer, generated as "),e("code",null,"metadata.g.ts"),t(", contains information about the types, properties, methods, and other components of your data model. Because Vue applications are typically compiled into a set of static assets, it is necessary for the frontend code to have a representation of your data model as an analog to the "),e("code",null,"ReflectionRepository"),t(" that is available at runtime in your .NET app.")],-1),_={class:"table-of-contents"},v=e("h2",{id:"concepts",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#concepts","aria-hidden":"true"},"#"),t(" Concepts")],-1),b={href:"https://github.com/IntelliTect/Coalesce/blob/dev/src/coalesce-vue/src/metadata.ts",target:"_blank",rel:"noopener noreferrer"},g=e("h3",{id:"metadata",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#metadata","aria-hidden":"true"},"#"),t(" Metadata")],-1),x=e("p",null,[t("All objects in the metadata layer that represent any kind of metadata have, at the very least, a "),e("code",null,"name"),t(", the name of the metadata element in code (type names, property names, parameter names, etc). and a "),e("code",null,"displayName"),t(", the human-readable form of the name that is suitable for presentation when needed. Names follow the casing convention of their corresponding language elements - types are PascalCased, while other things like properties, methods, and parameters are camelCased.")],-1),T=e("h3",{id:"type",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#type","aria-hidden":"true"},"#"),t(" Type")],-1),k=e("code",null,"model",-1),C=e("code",null,"object",-1),V=e("h3",{id:"value",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#value","aria-hidden":"true"},"#"),t(" Value")],-1),w=e("p",null,"In the metadata layer, a Value is the usage of a type. This could be any type - strings, numbers, enums, classes, or even void. Values can be found in the collection of an object's properties, a method's parameters or return value, or as a data source's parameters.",-1),N=e("p",null,"All values have the following properties:",-1),R=i(" Properties
Type could be a language primitive like
",1),E=i('string
ornumber
, a non-primitive JavaScript type (date
,file
), or in the case of a custom Type, the type kind of that type (model
,enum
,object
). For custom types, an additional propertytypeDef
will refer to the Type metadata for that type.Role represents what purpose the value serves in a relational model. Either
value
(the default - no relational role),primaryKey
,foreignKey
,referenceNavigation
, orcollectionNavigation
.Property
A Property is a more refined Value that contains a number of additional fields based on the
role
of the property.Domain
The type of the default export of the generated metadata. Serves as a single root from which all other metadata can be accessed. Contains fields
',5);function P(A,M){const n=s("router-link"),c=s("ExternalLinkIcon"),d=s("RouterLink"),r=s("Prop");return p(),u("div",null,[f,l(" MARKER:summary "),y,l(" MARKER:summary-end "),e("nav",_,[e("ul",null,[e("li",null,[a(n,{to:"#concepts"},{default:o(()=>[t("Concepts")]),_:1}),e("ul",null,[e("li",null,[a(n,{to:"#metadata"},{default:o(()=>[t("Metadata")]),_:1})]),e("li",null,[a(n,{to:"#type"},{default:o(()=>[t("Type")]),_:1})]),e("li",null,[a(n,{to:"#value"},{default:o(()=>[t("Value")]),_:1})]),e("li",null,[a(n,{to:"#property"},{default:o(()=>[t("Property")]),_:1})]),e("li",null,[a(n,{to:"#domain"},{default:o(()=>[t("Domain")]),_:1})])])])])]),v,e("p",null,[t("The following is a non-exhaustive list of the general concepts used by the metadata layer. The "),e("a",b,[t("source code of coalesce-vue"),a(c)]),t(" provides the most exhaustive set of documentation about the metadata layer:")]),g,x,T,e("p",null,[t("All custom types exposed by your application's data model will have a Type metadata object generated. This includes both C# classes, and C# enums. Class types include "),k,t(" (for "),a(d,{to:"/modeling/model-types/entities.html"},{default:o(()=>[t("Entity Models")]),_:1}),t(" and "),a(d,{to:"/modeling/model-types/dtos.html"},{default:o(()=>[t("Custom DTOs")]),_:1}),t(") and "),C,t(" (for "),a(d,{to:"/modeling/model-types/external-types.html"},{default:o(()=>[t("External Types")]),_:1}),t(").")]),V,w,N,a(r,{def:"type: TypeDiscriminator",lang:"ts"}),R,a(r,{def:"role: ValueRole",lang:"ts"}),E])}const B=h(m,[["render",P],["__file","metadata.html.vue"]]);export{B as default}; diff --git a/assets/methods.html.2354376c.js b/assets/methods.html.2354376c.js new file mode 100644 index 000000000..ad8ad17c9 --- /dev/null +++ b/assets/methods.html.2354376c.js @@ -0,0 +1,6 @@ +import{_ as c,y as d,z as h,X as e,B as t,Q as s,$ as n,a5 as u,P as a}from"./framework.fe9a73df.js";const p={},m=e("h1",{id:"typescript-method-objects",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#typescript-method-objects","aria-hidden":"true"},"#"),t(" TypeScript Method Objects")],-1),b=u(`types
,enums
, andservices
as organizing structures for the different kinds of custom types.Here's an example for a method called Rename that takes a single parameter 'string name' and returns a string.
`,2),f={class:"table-of-contents"},g=e("h2",{id:"base-members",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#base-members","aria-hidden":"true"},"#"),t(" Base Members")],-1),_=e("p",null,"The following members are available on the method object for all client methods:",-1),v=e("p",null,"Observable that will contain the results of the method call after it is complete.",-1),w=e("p",null,"Observable with the raw, deserialized JSON result of the method call. If the method call returns an object, this will contain the deserialized JSON object from the server before it has been loaded into ViewModels and its properties loaded into observables.",-1),D=e("p",null,"Observable boolean which is true while the call to the server is pending.",-1),y=e("p",null,"If the method was not successful, this contains exception information.",-1),k=e("p",null,"Observable boolean that indicates whether the method call was successful or not.",-1),O=e("h2",{id:"listresult-t-base-members",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#listresult-t-base-members","aria-hidden":"true"},"#"),t(),e("code",null,"ListResultpublic string Rename(string name) +{ + FirstName = name; + return FullName; // Return the new full name of the person. +} +
"),t(" Base Members")],-1),C=e("p",null,[t("For methods that return a "),e("code",null,"ListResult "),t(", the following additional members on the method object will be available:")],-1),M=e("p",null,"Page number of the results.",-1),T=e("p",null,"Page size of the results.",-1),R=e("p",null,"Total number of possible result pages.",-1),L=e("p",null,"Total number of results.",-1),x=e("h2",{id:"method-specific-members",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#method-specific-members","aria-hidden":"true"},"#"),t(" Method-specific Members")],-1),j=e("p",null,[t("Function that takes all the method parameters and a callback. If "),e("code",null,"reload"),t(" is true, the ViewModel or ListViewModel that owns the method will be reloaded after the call is complete, and only after that happens will the callback be called.")],-1),P=e("p",null,"Class with one observable member per method argument for binding method arguments to user input. Only generated for methods with arguments.",-1),V=e("p",null,"Default instance of the args class. Only generated for methods with arguments.",-1),A=e("p",null,"Function for invoking the method using the args class. The default instance of the args class will be used if none is provided. Only generated for methods with arguments.",-1),K=e("p",null,[t("Simple interface using browser "),e("code",null,"prompt()"),t(" input boxes to prompt the user for the required data for the method call. The call is then made with the data provided. Only generated for methods with arguments.")],-1),E={href:"https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL",target:"_blank",rel:"noopener noreferrer"},F=e("code",null,"src",-1),S=e("code",null,"image",-1),B=e("code",null,"video",-1),N=e("code",null,"this.args",-1);function z(I,U){const o=a("RouterLink"),i=a("router-link"),l=a("Prop"),r=a("ExternalLinkIcon");return d(),h("div",null,[m,e("p",null,[t("For each "),s(o,{to:"/modeling/model-components/methods.html"},{default:n(()=>[t("Custom Method")]),_:1}),t(" you define, a class will be created on the corresponding TypeScript ViewModel (instance methods) or ListViewModel (static methods) that contains the properties and functions for interaction with the method. This class is accessible through a static property named after the method. An instance of this class will also be created on each instance of its parent - this instance is in a property with the camel-cased name of the method.")]),b,e("nav",f,[e("ul",null,[e("li",null,[s(i,{to:"#base-members"},{default:n(()=>[t("Base Members")]),_:1})]),e("li",null,[s(i,{to:"#listresult-t-base-members"},{default:n(()=>[t("ListResult Base Members")]),_:1})]),e("li",null,[s(i,{to:"#method-specific-members"},{default:n(()=>[t("Method-specific Members")]),_:1})])])]),g,_,s(l,{def:"public result: KnockoutObservable ",lang:"ts"}),v,s(l,{def:"public rawResult: KnockoutObservable ",lang:"ts"}),w,s(l,{def:"public isLoading: KnockoutObservable ",lang:"ts"}),D,s(l,{def:"public message: KnockoutObservable ",lang:"ts"}),y,s(l,{def:"public wasSuccessful: KnockoutObservable ",lang:"ts"}),k,O,C,s(l,{def:"public page: KnockoutObservable ",lang:"ts"}),M,s(l,{def:"public pageSize: KnockoutObservable