add-api-caller
Add GraphQL API caller methods (fetch, fetchAll, save, delete/expire) to an existing model pair in a layrz_models sub-library module.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Add GraphQL API caller methods (fetch, fetchAll, save, delete/expire) to an existing model pair in a layrz_models sub-library module.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Add GraphQL API caller methods (fetch, fetchAll, save, delete/expire) to an existing model pair in a layrz_models sub-library module, using the connector.query/connector.mutate API.
Add a freezed + unfreezed model pair to a layrz_models sub-library module. Use when creating new data models from backend struct definitions.
| name | add-api-caller |
| description | Add GraphQL API caller methods (fetch, fetchAll, save, delete/expire) to an existing model pair in a layrz_models sub-library module. |
| argument-hint | <module> <ModelName> |
Add fetch(), fetchAll(), delete()/expire() methods to a @freezed model and a save() method
to its @unfreezed input variant, following the established pattern from Locator/LocatorInput.
/add-api-caller <module> <ModelName>
Where <module> is the sub-library directory name (e.g. locator, map) and <ModelName> is the
PascalCase class name (e.g. Locator, Poi).
Use the EnterPlanMode tool immediately before doing any work. Present the full plan to the user
and wait for approval via ExitPlanMode before writing any files or running any commands.
Read lib/src/<module>/src/<model_snake_case>.dart (or whichever file contains the model).
@freezed ModelName and @unfreezed ModelNameInput exist.id (needed for single-entity operations).Ask the user for the following. Accept a backend schema or operation list as a single paste if available.
| Detail | Example |
|---|---|
| GraphQL query name for fetching | locators, pois |
| GraphQL add mutation name | addLocator, addPoi |
| GraphQL edit mutation name | editLocator, editPoi |
| GraphQL delete/expire mutation name | expireLocators, deletePois (or skip if none) |
| Destructive operation style | delete or expire |
| GraphQL input type name | LocatorInput!, PoiInput! |
| GraphQL fragment fields | Paste or describe; can also be derived from the @freezed model fields |
fetchAll query style | Full fragment, or lighter subset of fields (ask the user) |
| Fragment name (camelCase) | locatorFragment, poiFragment |
| GraphQL type name (PascalCase) | Locator, Poi |
If the fragment composes fragments from other models (e.g. RegisteredApp.registeredAppFragment),
ask the user which fragments to include.
@freezed modelIf const ModelName._(); is not already present, add it right after the opening { of the class body,
before the const factory constructor.
Then add the following after the fromJson factory, inside the class body:
fetch() — instance method/// [fetch] fetches a single [ModelName] from the server by its ID
Future<ModelName?> fetch({
/// [apiToken] is the API token to use for authentication
required String apiToken,
/// [uri] is the GraphQL endpoint to use
required Uri uri,
/// [onResponse] is an optional callback invoked with the status code
void Function(String statusCode)? onResponse,
}) async {
final connector = LayrzConnector(uri: uri, apiToken: apiToken);
try {
final response = await connector.perform(
query: fetchSingleQuery,
variables: {'apiToken': apiToken, 'id': id},
);
final data = response.data;
if (data == null) {
onResponse?.call(ApiStatus.internalError.toJson());
Log.error("layrz_models/ModelName/fetch(): No response from server");
return null;
}
final result = data['data']['queryName'];
if (result == null) {
onResponse?.call(ApiStatus.internalError.toJson());
Log.error("layrz_models/ModelName/fetch(): No result from server");
return null;
}
if (result['status'] != 'OK') {
onResponse?.call(result['status']);
return null;
}
if (result['result'] == null || (result['result'] as List).isEmpty) {
onResponse?.call('NOT_FOUND');
return null;
}
return ModelName.fromJson(Map<String, dynamic>.from(result['result'][0] as Map));
} catch (e, stack) {
Log.critical("layrz_models/ModelName/fetch(): General exception => $e\n$stack");
return null;
}
}
fetchAll() — static method/// [fetchAll] fetches all [ModelName] from the server
static Future<List<ModelName>> fetchAll({
required String apiToken,
required Uri uri,
void Function(String statusCode)? onResponse,
}) async {
final connector = LayrzConnector(uri: uri, apiToken: apiToken);
try {
final response = await connector.perform(
query: fetchAllGraphqlQuery,
variables: {'apiToken': apiToken},
);
final data = response.data;
if (data == null) {
onResponse?.call(ApiStatus.internalError.toJson());
Log.error("layrz_models/ModelName/fetchAll(): No response from server");
return [];
}
final result = data['data']['queryName'];
if (result == null) {
onResponse?.call(ApiStatus.internalError.toJson());
Log.error("layrz_models/ModelName/fetchAll(): No result from server");
return [];
}
if (result['status'] != 'OK') {
onResponse?.call(result['status']);
return [];
}
return (result['result'] as List<dynamic>?)
?.map((e) => ModelName.fromJson(Map<String, dynamic>.from(e as Map)))
.toList() ??
[];
} catch (e, stack) {
Log.critical("layrz_models/ModelName/fetchAll(): General exception => $e\n$stack");
return [];
}
}
delete()/expire() — instance method (skip if not applicable)/// [expire] expires this [ModelName]
Future<bool> expire({
required String apiToken,
required Uri uri,
void Function(String statusCode)? onResponse,
}) async {
final connector = LayrzConnector(uri: uri, apiToken: apiToken);
try {
final response = await connector.perform(
query: expireGraphqlMutation,
variables: {'apiToken': apiToken, 'ids': [id]},
);
final data = response.data;
if (data == null) {
onResponse?.call(ApiStatus.internalError.toJson());
Log.error("layrz_models/ModelName/expire(): No response from server");
return false;
}
final result = data['data']['expireModelNames'];
if (result == null) {
onResponse?.call(ApiStatus.internalError.toJson());
Log.error("layrz_models/ModelName/expire(): No result from server");
return false;
}
if (result['status'] != 'OK') {
onResponse?.call(result['status']);
return false;
}
return true;
} catch (e, stack) {
Log.critical("layrz_models/ModelName/expire(): General exception => $e\n$stack");
return false;
}
}
deleteMultiple()/expireMultiple() — static method (skip if not applicable)/// [expireMultiple] expires multiple [ModelName] by their IDs
static Future<bool> expireMultiple({
required String apiToken,
required Uri uri,
required List<String> ids,
void Function(String statusCode)? onResponse,
}) async {
final connector = LayrzConnector(uri: uri, apiToken: apiToken);
try {
final response = await connector.perform(
query: expireGraphqlMutation,
variables: {'apiToken': apiToken, 'ids': ids},
);
final data = response.data;
if (data == null) {
onResponse?.call(ApiStatus.internalError.toJson());
Log.error("layrz_models/ModelName/expireMultiple(): No response from server");
return false;
}
final result = data['data']['expireModelNames'];
if (result == null) {
onResponse?.call(ApiStatus.internalError.toJson());
Log.error("layrz_models/ModelName/expireMultiple(): No result from server");
return false;
}
if (result['status'] != 'OK') {
onResponse?.call(result['status']);
return false;
}
return true;
} catch (e, stack) {
Log.critical("layrz_models/ModelName/expireMultiple(): General exception => $e\n$stack");
return false;
}
}
/// [fetchSingleQuery] is the GraphQL query to fetch a single [ModelName] by its ID
static String get fetchSingleQuery =>
'${ModelName.graphqlFragment}'
r'''
query fetchModelNames($apiToken: String!, $id: ID) {
queryName(apiToken: $apiToken, id: $id) {
status
errors
result {
...modelNameFragment
}
}
}
''';
/// [fetchAllGraphqlQuery] is the GraphQL query to fetch all [ModelName]s
static String get fetchAllGraphqlQuery => r'''
query fetchModelNames($apiToken: String!) {
queryName(apiToken: $apiToken) {
status
errors
result {
// fields here — full fragment or lighter subset per user preference
}
}
}
''';
/// [graphqlFragment] is the GraphQL fragment for [ModelName]
static String get graphqlFragment => '''
// compose sub-fragments first, e.g.: \${OtherModel.otherFragment}
fragment modelNameFragment on GraphQLTypeName {
// all fields
}
''';
/// [expireGraphqlMutation] is the GraphQL mutation to expire one or more [ModelName]s
static String get expireGraphqlMutation => r'''
mutation expireModelName($apiToken: String!, $ids: [ID!]!) {
expireModelNames(apiToken: $apiToken, ids: $ids) {
status
errors
}
}
''';
save() to the @unfreezed input modelIf const ModelNameInput._(); is not already present, add it right after the opening { of the class body,
before the factory constructor.
Then add the following after the fromJson factory, inside the class body:
/// [save] creates or updates this [ModelName] on the server
/// Returns an [ApiResponse] with the saved [ModelName] on success, or errors on failure.
/// Returns `null` on a network/server error.
Future<ApiResponse<ModelName, Map<String, dynamic>>?> save({
required String apiToken,
required Uri uri,
void Function(String statusCode)? onResponse,
}) async {
final connector = LayrzConnector(uri: uri, apiToken: apiToken);
try {
final response = await connector.perform(
query: id == null ? addGraphqlMutation : editGraphqlMutation,
variables: {'apiToken': apiToken, 'data': toJson()},
);
final data = response.data;
if (data == null) {
onResponse?.call(ApiStatus.internalError.toJson());
Log.error("layrz_models/ModelNameInput/save(): No response from server");
return null;
}
final result = id == null ? data['data']['addModelName'] : data['data']['editModelName'];
if (result == null) {
onResponse?.call(ApiStatus.internalError.toJson());
Log.error("layrz_models/ModelNameInput/save(): No result from server");
return null;
}
if (result['status'] != 'OK') {
onResponse?.call(result['status']);
return ApiResponse(
status: ApiStatus.fromJson(result['status']),
errors: Map<String, dynamic>.from(result['errors'] ?? {}),
);
}
return ApiResponse(status: ApiStatus.ok, result: ModelName.fromJson(result['result']));
} catch (e, stack) {
Log.critical("layrz_models/ModelNameInput/save(): General exception => $e\n$stack");
return null;
}
}
/// [addGraphqlMutation] is the GraphQL mutation to add a [ModelName]
static String get addGraphqlMutation =>
'${ModelName.graphqlFragment}'
r'''
mutation addModelName($apiToken: String!, $data: ModelNameInput!) {
addModelName(data: $data, apiToken: $apiToken) {
status
errors
result {
...modelNameFragment
}
}
}
''';
/// [editGraphqlMutation] is the GraphQL mutation to edit a [ModelName]
static String get editGraphqlMutation =>
'${ModelName.graphqlFragment}'
r'''
mutation editModelName($apiToken: String!, $data: ModelNameInput!) {
editModelName(data: $data, apiToken: $apiToken) {
status
errors
result {
...modelNameFragment
}
}
}
''';
.dart fileEnsure lib/src/<module>/<module>.dart contains these imports (add if missing):
import 'package:layrz_models/src/utils/utils.dart';
import 'package:layrz_models/src/api/api.dart';
make freezed
List the modified files and whether the build succeeded.
.freezed.dart or .g.dart files.Locator/LocatorInput (lib/src/locator/) — do not invent new patterns./// [name] description comment.'${OtherModel.fragment}' r'''...'''fetchAll query body may differ from the fetchSingleQuery (lighter fields for list views) — always ask the user.id field or no destructive operation, skip those methods."layrz_models/ClassName/methodName(): message".