Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
# 7.0.0
- Breaking: The generated ViewModel stubs for abstract model types have been replaced by static objects with a static `.load(id)` method that returns a standard `ItemApiState` caller. They are no longer exposed as instantiable proxy objects that mutate themselves into the correct implementation type after `$load`ing from the server - this approach did not fully satisfy the TypeScript contract of the derived types at runtime and otherwise attempted (and failed) to provide a concrete instance of a type that should not actually be instantiable.
- API callers (`ItemApiState`, `ListApiState`) are now awaitable. `await caller` now resolves to `caller.result` after the current or previous operation is completed.
- `await vm.$load(1)` - performs a new load call and waits for completion
- `await vm.$load` - waits for completion of the pending load operation, or immediately resolves with the last result if no operation is pending.
- `useAppUpdateCheck` now also listens for Vite's `vite:preloadError` event, showing the update notification when dynamic imports fail due to stale chunks after a deployment.

# 6.6.0
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<c-loader-status :loaders="{ '': [vm.$load] }" no-initial-content>
<c-loader-status :loaders="{ '': [loader] }" no-initial-content>
{{ new Date().valueOf() }}

{{ vm.id }}
Expand All @@ -16,7 +16,8 @@

<script setup lang="ts">
import { AbstractClassViewModel } from "@/viewmodels.g";
import { computed } from "vue";

const vm = new AbstractClassViewModel({});
vm.$load(1);
const loader = AbstractClassViewModel.load(1);
const vm = computed(() => loader.result!);
</script>
4 changes: 2 additions & 2 deletions playground/Coalesce.Web.Vue3/src/viewmodels.g.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public override Task<string> BuildOutputAsync()
b.Line("import * as $metadata from './metadata.g'");
b.Line("import * as $models from './models.g'");
b.Line("import * as $apiClients from './api-clients.g'");
b.Line("import { ViewModel, ListViewModel, ViewModelCollection, ServiceViewModel, type DeepPartial, defineProps, createAbstractProxyViewModelType } from 'coalesce-vue/lib/viewmodel'");
b.Line("import { ViewModel, ListViewModel, ViewModelCollection, ServiceViewModel, type DeepPartial, defineProps, createAbstractLoader } from 'coalesce-vue/lib/viewmodel'");
b.Line();

foreach (var model in Model.CrudApiBackedClasses.OrderBy(e => e.ClientTypeName))
Expand Down Expand Up @@ -80,7 +80,14 @@ private void WriteViewModel(TypeScriptCodeBuilder b, ClassViewModel model)
if (model.Type.IsAbstract)
{
b.Line($"export type {viewModelName} = {string.Join(" | ", model.ClientDerivedTypes.Select(t => new VueType(t.Type).TsType(viewModel: true)))}");
b.Line($"export const {viewModelName} = createAbstractProxyViewModelType<{modelName}, {viewModelName}>({metadataName}, $apiClients.{name}ApiClient)");
if (model.ClientDataSources(Model).Any())
{
b.Line($"export const {viewModelName} = createAbstractLoader<{viewModelName}>($apiClients.{name}ApiClient, {{ DataSources: {modelName}.DataSources }})");
}
else
{
b.Line($"export const {viewModelName} = createAbstractLoader<{viewModelName}>($apiClients.{name}ApiClient)");
}
b.Line();
return;
}
Expand Down
17 changes: 4 additions & 13 deletions src/coalesce-vue-vuetify3/src/components/c-metadata-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import type {
BooleanValue,
CollectionValue,
ObjectValue,
ApiStateTypeWithArgs,
ListViewModel,
ServiceViewModel,
ModelCollectionValue,
Expand Down Expand Up @@ -74,18 +73,10 @@ TModel extends Model ?
}[keyof PropsOf<TModel>]

// Handle binding of `:model` to an API caller (which binds values to the caller's args object):
: TModel extends ApiStateTypeWithArgs<
// eslint-disable-next-line @typescript-eslint/no-unused-vars
infer TMethod extends Method,
any,
infer TArgsObj,
any
> ?
// HACK: Pulling types off of TArgsObj is a concession we make
// due to ApiStateTypeWithArgs's constituent types not actually capturing
// the type of their metadata. At some point this could be made better if
// we were able to pull metadata off of `TMethod. In other words, what we'd
// really like to do here is do the same thing we do for props on a model.
: TModel extends AnyArgCaller<any, infer TArgsObj> ?
// HACK: Pulling types off of TArgsObj is suboptimal.
// Can we instead extract from `& { $metadata: infer TMethod extends Method }`,
// like we do for props on a model?
| {
[K in keyof TArgsObj]: TArgsObj[K] extends ((
ValueKind extends ModelValue ? Model<ModelType> :
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,6 @@ describe("CInput", () => {
//@ts-expect-error bad prop value
() => <CInput model={model} for="color" variant="bad-variant" />;


// ******
// Enum filter
// ******
Expand Down
40 changes: 19 additions & 21 deletions src/coalesce-vue-vuetify3/src/components/input/c-input.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ type _ValueType<
TModel extends Model | DataSource | AnyArgCaller | undefined,
TFor extends ForSpec<TModel>,
> =
TModel extends ApiStateTypeWithArgs<any, any, infer TArgsObj, any>
TModel extends AnyArgCaller<any, infer TArgsObj>
? TFor extends keyof TArgsObj
? TArgsObj[TFor]
: any
Expand Down Expand Up @@ -69,26 +69,25 @@ type _ValueType<
type _MetadataType<
TModel extends Model | DataSource | AnyArgCaller | undefined,
TFor extends ForSpec<TModel>,
> =
TModel extends ApiStateTypeWithArgs<infer TMethod, any, any, any>
? TMethod extends Method
? TFor extends keyof TMethod["params"]
? TMethod["params"][TFor]
: never
: never
: TFor extends Value
? TFor
: TFor extends string
? TFor extends keyof EnumTypeLookup
? // Wrap in synthetic EnumValue so stage 2's guard is uniform.
// MetadataToModelType resolves this via typeDef.name → EnumTypeLookup.
EnumValue & { readonly typeDef: { readonly name: TFor } }
: TModel extends Model
? TFor extends PropNames<TModel["$metadata"]>
? TModel["$metadata"]["props"][TFor]
: never
> = TModel extends AnyArgCaller & {
$metadata?: infer TMethod extends Method;
}
? TFor extends keyof TMethod["params"]
? TMethod["params"][TFor]
: never
: TFor extends Value
? TFor
: TFor extends string
? TFor extends keyof EnumTypeLookup
? // Wrap in synthetic EnumValue so stage 2's guard is uniform.
// MetadataToModelType resolves this via typeDef.name → EnumTypeLookup.
EnumValue & { readonly typeDef: { readonly name: TFor } }
: TModel extends Model
? TFor extends PropNames<TModel["$metadata"]>
? TModel["$metadata"]["props"][TFor]
: never
: never;
: never
: never;

type SelectSlotItemType<
TModel extends Model | DataSource | AnyArgCaller | undefined,
Expand Down Expand Up @@ -211,7 +210,6 @@ import {
type AnyArgCaller,
mapValueToModel,
parseValue,
ApiStateTypeWithArgs,
ModelTypeLookup,
PropNames,
Value,
Expand Down
3 changes: 1 addition & 2 deletions src/coalesce-vue-vuetify3/src/components/input/c-select.vue
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ type _SelectedModelType<
| ModelValue
| ForeignKeyProperty
? ValueOrFkToModelType<TFor>
: TModel extends ApiStateTypeWithArgs<any, any, infer TArgsObj, any>
: TModel extends AnyArgCaller<any, infer TArgsObj>
? TFor extends keyof TArgsObj
? TMultiple extends true
? TArgsObj[TFor] extends Array<any> | null | undefined
Expand Down Expand Up @@ -364,7 +364,6 @@ import {
ModelTypeLookup,
PrimaryKeyProperty,
PropNames,
ApiStateTypeWithArgs,
ModelCollectionValue,
modelDisplay,
MetadataToModelType,
Expand Down
Loading
Loading