Referensi API Tabel
Halaman ini memuat seluruh member public dari class-class tabel beserta signature dan nilai default sebenarnya. Gunakan sebagai referensi cepat ketika Anda perlu mencari API tertentu; halaman yang ditautkan dari setiap bagian menjelaskan kapan dan mengapa API tersebut digunakan.
Namespace
| Class | Tujuan |
|---|---|
PandaPanel\Tables\TableSchema | Deskripsi deklaratif sebuah tabel |
PandaPanel\Tables\TableQuery | Menerapkan state dari URL ke query |
PandaPanel\Tables\ArrayTableData | Menjalankan tabel yang sama terhadap record yang tidak berasal dari database |
PandaPanel\Tables\Group | Mendefinisikan satu cara mengelompokkan row |
PandaPanel\Tables\Tab | Satu tab filter di atas halaman list |
PandaPanel\Tables\Columns\Column | Base class seluruh kolom |
PandaPanel\Tables\Columns\EditableColumn | Base class seluruh kolom yang dapat ditulis |
PandaPanel\Tables\Columns\Concerns\HasRelationshipState | Aggregate dan pengurutan relation, tersedia pada setiap kolom |
PandaPanel\Tables\Filters\Filter | Base class seluruh filter |
PandaPanel\Tables\Filters\Constraints\Constraint | Satu kolom yang boleh digunakan query builder sebagai constraint |
PandaPanel\Tables\Summaries\Summarizer | Satu nilai ringkasan di bawah kolom |
PandaPanel\Tables\Enums\* | Closed set yang dikirim ke Vue |
TableSchema
Merupakan final class. Seluruh builder method mengembalikan self.
Konstruksi dan konten
public static function make(): self;
public function columns(array $columns): self; // array<array-key, Column>
public function filters(array $filters): self; // array<array-key, Filter>
public function groups(array $groups): self; // array<array-key, Group>
public function defaultGroup(string $name): self;2
3
4
5
columns() dan filters() melempar PandaPanel\Exceptions\PanelSchemaException ketika menemukan nama duplikat.
Pengurutan, pencarian, dan pagination
public function defaultSort(string $column, SortDirection $direction = SortDirection::Descending): self;
public function defaultSortOptionLabel(string $label): self; // null
public function perPageOptions(array $options): self; // [10, 25, 50, 100]
public function defaultPerPage(int $perPage): self; // 25
public function searchPlaceholder(string $placeholder): self; // 'Search...'
public function searchDebounce(int $milliseconds): self; // 300, clamped to >= 0
public function searchOnBlur(bool $onBlur = true): self; // false
public function splitSearchTerms(bool $split = true): self; // true2
3
4
5
6
7
8
Persistence dan deferred state
public function persistSearchInSession(bool $persist = true): self; // false
public function persistSortInSession(bool $persist = true): self; // false
public function persistFiltersInSession(bool $persist = true): self; // false
public function persistColumnsInSession(bool $persist = true): self; // false
public function deferFilters(bool $defer = true): self; // false
public function deferColumnManager(bool $defer = true): self; // false2
3
4
5
6
Filter bar dan column manager
public function filtersTrigger(string $label, ?string $icon = null): self; // 'Filters', null
public function filtersApplyLabel(string $label): self; // 'Apply filters'
public function filtersResetLabel(string $label): self; // 'Clear'
public function showFiltersResetAction(bool $show = true): self; // true
public function reorderableColumns(bool $reorderable = true): self; // false
public function columnManagerTrigger(string $label, ?string $icon = null): self; // 'Columns', null
public function columnManagerInModal(bool $inModal = true): self; // false
public function showColumnManagerReset(bool $show = true): self; // true2
3
4
5
6
7
8
Label reset pada column manager selalu Reset; hanya visibility action reset yang dapat dikonfigurasi.
Row, selection, dan action
public function selectable(bool $selectable = true): self; // false
public function reorderable(string $column): self; // also sets defaultSort($column, Ascending)
public function recordActions(array $actions): self;
public function recordActionsPosition(RecordActionsPosition $position): self; // AfterColumns
public function recordActionsLabel(string $label): self; // null
public function bulkActions(array $actions): self; // sets selectable(true) when non-empty
public function headerActions(array $actions): self;
public function toolbarActions(array $actions): self;
public function emptyStateActions(array $actions): self;
public function frozenActions(bool $frozen = true): self; // false2
3
4
5
6
7
8
9
10
Setiap setter action menolak nama duplikat dan action yang tidak memiliki perilaku apa pun.
Empty state
public function emptyState(string $heading, ?string $description = null, ?string $icon = null): self;
public function emptyStateComponent(string $component): self; // null2
Heading default adalah No records found.
API sisi baca
public function getColumns(): array; // list<Column>
public function getColumn(string $name): ?Column;
public function columnNames(): array; // list<string>
public function defaultVisibleColumnNames(): array; // list<string>
public function toggleableColumnNames(): array; // list<string>
public function getSortableColumns(): array; // list<Column>
public function getSearchColumns(): array; // list<string>, local only
public function getSearchRelations(): array; // list<string>, dotted
public function getIndividuallySearchableColumns(): array;
public function getIndividuallySearchableColumn(string $name): ?Column;
public function isSearchable(): bool;
public function getFilters(): array; // list<Filter>
public function getFilter(string $name): ?Filter;
public function defaultFilters(): array; // array<string, mixed>
public function persistsFiltersInSession(): bool;
public function defersFilters(): bool;
public function getGroups(): array; // list<Group>
public function getGroup(string $name): ?Group;
public function getDefaultGroup(): ?string;
public function getDefaultSortColumn(): ?string;
public function getDefaultSortDirection(): SortDirection;
public function getPerPageOptions(): array; // list<int>
public function getDefaultPerPage(): int; // clamped to the options
public function shouldSplitSearchTerms(): bool;
public function persistsSearchInSession(): bool;
public function persistsSortInSession(): bool;
public function persistsColumnsInSession(): bool;
public function hasReorderableColumns(): bool;
public function isSelectable(): bool;
public function isReorderable(): bool;
public function getReorderColumn(): ?string;
public function hasFrozenStart(): bool;
public function hasFrozenActions(): bool;
public function hasSummaries(): bool;
public function getRecordActions(): array; // list<Action>
public function getRecordAction(string $name): ?Action; // row actions and column actions
public function getBulkActions(): array;
public function getBulkAction(string $name): ?Action;
public function getHeaderActions(): array;
public function getToolbarActions(): array;
public function getEmptyStateActions(): array;
public function getTableAction(string $name): ?Action; // header, toolbar, empty state2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
Menghasilkan payload
public function applyColumnQueries(Builder $query): void;
public function summaries(Builder $query, array $records): array;
public function groupSummaries(Builder $query, array $records, Group $group): array;
public function toRow(Model $record, ?Group $group = null): array;
public function toArray(): array;2
3
4
5
toArray() memvalidasi default sort column dan melempar PanelSchemaException::unknownDefaultSort() ketika nama tersebut bukan kolom yang dideklarasikan.
TableQuery
Merupakan final readonly class.
public function __construct(
TableSchema $schema,
Request $request,
?string $namespace = null,
?string $sessionKey = null,
);
public function namespace(): ?string;
public function constrain(Builder $query): void;
public function paginate(Builder $query): LengthAwarePaginator;
public function paginateRelation(Relation $relation): LengthAwarePaginator;
public function activeGroup(): ?Group;
public function state(): array;2
3
4
5
6
7
8
9
10
11
12
13
constrain() menerapkan state dalam urutan berikut: column query, base-query filter, global table search, pencarian per kolom, filter, lalu sort. paginate() menjalankan constrain() dan kemudian melakukan pagination dengan withQueryString().
Query parameter
Jika namespace diberikan, state dibaca dari namespace tersebut, misalnya ?relations[tasks][page]=2.
| Parameter | Tipe | Divalidasi terhadap |
|---|---|---|
page | scalar | max(1, (int) $value) |
perPage | scalar | perPageOptions(), jika tidak cocok gunakan default |
search | scalar | di-trim dan dipotong maksimal 255 karakter |
columnSearch[{column}] | map | kolom dengan searchable(individually: true) |
sort | scalar | kolom dengan sortable() |
direction | scalar | SortDirection; nilai tidak dikenal menjadi asc |
group | scalar | group yang dideklarasikan; '' mematikan grouping |
filters | map, atau '' | sanitize() milik masing-masing filter |
columns[visible], columns[order] | list | nama kolom yang dideklarasikan |
state()
[
'search' => ?string,
'sort' => ?string,
'direction' => 'asc'|'desc',
'perPage' => int,
'filters' => array<string, mixed>, // only the ones that sanitized
'filterIndicators' => list<array{name: string, label: string}>,
'columnSearches' => array<string, string>,
'columns' => ['visible' => list<string>, 'order' => list<string>],
'group' => ?string,
]2
3
4
5
6
7
8
9
10
11
Column
Merupakan abstract class dengan constructor final. Setiap setter mengembalikan static.
public static function make(string $name): static; // throws on an empty name
abstract public function type(): ColumnType;
abstract public function toCell(Model $record): mixed;2
3
| Method | Signature | Default |
|---|---|---|
label | label(string $label): static | Str::headline($name) |
sortable | sortable(bool $sortable = true, ?string $column = null): static | false |
sortUsing | sortUsing(Closure $callback): static | tidak ada; sekaligus mengaktifkan sortable |
searchable | searchable(bool $searchable = true, ?array $columns = null, bool $individually = false): static | false |
visible | visible(bool $visible = true): static | true |
toggleable | toggleable(bool $toggleable = true): static | true |
alignment | alignment(Alignment|string $alignment): static | Alignment::Start |
headerAlignment | headerAlignment(Alignment|string $alignment): static | mengikuti alignment |
placeholder | placeholder(string $placeholder): static | null |
default | default(mixed $default): static | null |
tooltip | tooltip(Closure|string $tooltip): static | null |
headerTooltip | headerTooltip(string $tooltip): static | null |
wrapHeader | wrapHeader(bool $wrap = true): static | false |
width | width(string $width): static | null |
frozen | frozen(ColumnPin|bool $pin = true): static | null |
extraAttributes | extraAttributes(Closure|array $attributes): static | [] |
url | url(Closure $callback): static | null |
action | action(Action $action): static | null |
formatUsing | formatUsing(Closure $callback): static | null |
summarize | summarize(array $summarizers): static | [] |
API sisi baca:
public function getName(): string;
public function getLabel(): string;
public function isSortable(): bool;
public function isSearchable(): bool;
public function isIndividuallySearchable(): bool;
public function isVisible(): bool;
public function isToggleable(): bool;
public function hasCustomSort(): bool;
public function getSortColumn(): string;
public function getSearchColumns(): array; // list<string>
public function getFrozen(): ?ColumnPin;
public function getAction(): ?Action;
public function getSummarizers(): array; // list<Summarizer>
public function hasSummaries(): bool;
public function summaryColumn(): string;
public function resolveValue(Model $record): mixed;
public function applyCustomSort(Builder $query, SortDirection $direction): void;
public function toCellMeta(Model $record): ?array;
public function toArray(): array;2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Signature closure:
| Method | Closure |
|---|---|
sortUsing | fn (Builder $query, SortDirection $direction): void |
formatUsing | fn (mixed $value, Model $record): mixed |
tooltip | fn (Model $record): ?string |
url | fn (Model $record): ?string |
extraAttributes | fn (Model $record): array — hanya scalar, key on* ditolak |
Jenis kolom
| Class | type() | Method khusus |
|---|---|---|
TextColumn | text | limit(int $characters), wrap(bool $wrap = true) |
NumberColumn | number | decimals(int) (0), prefix(string), suffix(string) |
BadgeColumn | badge | colors(array), labels(array) |
BooleanColumn | boolean | labels(string $true, string $false) (Yes, No) |
DateColumn | date | format(string) (M j, Y), relative(bool $relative = true) |
DateTimeColumn | datetime | mewarisi DateColumn; format M j, Y H:i |
ImageColumn | image | circular(bool), size(int) (32), fallbackUsing(Closure) |
IconColumn | icon | icons(array), colors(array), iconUsing(Closure), boolean(string $trueIcon = 'check', string $falseIcon = 'x') |
ColorColumn | color | copyable(bool $copyable = true) |
CustomColumn | custom | component(string), state(Closure) |
Alignment default: End untuk NumberColumn; Center untuk BooleanColumn, IconColumn, ToggleColumn, CheckboxColumn; dan Start untuk jenis lainnya.
Bentuk sel lengkap tersedia di halaman kolom.
EditableColumn
Merupakan abstract class extends Column. Definisi hasil serialisasi membawa editable => true.
public function rules(array $rules): static; // []
public function disabledUsing(Closure $callback): static; // fn (Model $record): bool
public function mutateUsing(Closure $callback): static; // fn (mixed $value, Model $record): mixed
public function updateUsing(Closure $callback): static; // fn (mixed $value, Model $record): void
public function writeTo(string $attribute): static;
public function getWriteAttribute(): string;
public function isDisabledFor(Model $record): bool;
public function validationRules(): array; // implied rules, then rules()
public function write(Model $record, mixed $value): void;2
3
4
5
6
7
8
9
10
| Class | type() | Method khusus | Aturan implisit |
|---|---|---|---|
ToggleColumn | toggle | — | boolean |
CheckboxColumn | checkbox | — | boolean |
TextInputColumn | text_input | numeric(), maxLength(int) | string atau numeric, ditambah max: |
SelectColumn | select | options(array) | Rule::in(array_keys($options)) |
HasRelationshipState
Concern ini tersedia pada setiap kolom.
public function counts(string $relation): static;
public function exists(string $relation): static;
public function sum(string $relation, string $column): static;
public function avg(string $relation, string $column): static;
public function min(string $relation, string $column): static;
public function max(string $relation, string $column): static;
public function sortableByRelation(string $relation, string $column): static;
public function getAggregateRelation(): ?string;
public function getSortRelation(): ?string;
public function aggregateAttribute(): ?string;
public function summaryUsesAggregate(): bool;
public function applyQuery(Builder $query): void;
public function applyRelationshipSort(Builder $query, SortDirection $direction): void;2
3
4
5
6
7
8
9
10
11
12
13
14
Nama atribut yang dihasilkan mengikuti aturan Eloquent: {relation}_count, {relation}_exists, dan {relation}_{aggregate}_{column}.
Filter
Merupakan abstract class dengan constructor final.
public static function make(string $name): static;
abstract public function type(): FilterType;
abstract public function sanitize(mixed $value): mixed; // null rejects
abstract protected function constrain(Builder $query, mixed $value): void;
public function label(string $label): static; // Str::headline($name)
public function column(string $column): static; // the filter name
public function query(Closure $callback): static; // fn (Builder, mixed): void
public function modifyBaseQueryUsing(Closure $callback): static; // fn (Builder, mixed): void
public function default(mixed $value): static;
public function getName(): string;
public function getLabel(): string;
public function getColumn(): string;
public function hasDefault(): bool;
public function getDefault(): mixed;
public function indicator(mixed $value): ?string;
public function toArray(): array;
final public function apply(Builder $query, mixed $value): void;
final public function applyBaseQuery(Builder $query, mixed $value): void;
protected function describe(mixed $value): string;
protected function extraArray(): array;2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| Class | type() | Method khusus |
|---|---|---|
SelectFilter | select | options(array), placeholder(string) |
BooleanFilter | boolean | labels(string $true, string $false), nullable(bool $nullable = true) |
DateFilter | date | — |
TernaryFilter | ternary | labels(string $true, string $false, ?string $blank = null), nullable(), queries(Closure $true, Closure $false); konstanta TRUE, FALSE |
TrashedFilter | select | konstanta WITHOUT, WITH, ONLY |
FormFilter | form | form(Closure), schema(): FormSchema |
QueryBuilderFilter | query_builder | constraints(array), maxRules(int) (10), constraint(string): ?Constraint |
Constraint
Merupakan abstract class dengan constructor final.
public static function make(string $name): static;
abstract public function operators(): array; // list<ConstraintOperator>
abstract public function inputType(): string; // 'text' | 'number' | 'date' | 'none'
public function label(string $label): static;
public function column(string $column): static;
public function getName(): string;
public function getLabel(): string;
public function getColumn(): string;
public function supports(ConstraintOperator $operator): bool;
public function accepts(ConstraintOperator $operator, mixed $value): bool;
public function apply(Builder $query, ConstraintOperator $operator, mixed $value): void;
public function toArray(): array;2
3
4
5
6
7
8
9
10
11
12
13
| Class | inputType() | Operator |
|---|---|---|
TextConstraint | text | contains, does not contain, starts with, ends with, equal, not equal, is filled, is blank |
NumberConstraint | number | equal, not equal, >, >=, <, <=, is filled, is blank |
DateConstraint | date | equal, >, >=, <, <=, is filled, is blank |
BooleanConstraint | none | is true, is false, is filled, is blank |
NumberConstraint::accepts() mensyaratkan is_numeric(). DateConstraint::accepts() mensyaratkan string yang dapat dibaca strtotime(). Implementasi base menerima scalar non-empty untuk operator yang membutuhkan value.
Summarizer
Merupakan abstract class dengan constructor final.
public static function make(string $name = ''): static;
abstract public function aggregate(): ?string;
abstract protected function reduce(array $values): mixed;
public function label(string $label): static;
public function formatUsing(Closure $callback): static; // fn (mixed $value): string
public function perPage(bool $perPage = true): static; // false
public function isPerPage(): bool;
public function getName(): string; // lowercased class basename when unnamed
public function getLabel(): string; // Str::headline(getName())
public function summarize(QueryBuilder $query, string $column): mixed;
public function summarizeRecords(array $records, string $column): mixed;
public function format(mixed $value): string; // '—' for null
public function toArray(mixed $value): array; // name, label, value, raw, perPage2
3
4
5
6
7
8
9
10
11
12
13
14
| Class | aggregate() | Hasil |
|---|---|---|
Sum | sum | float|int |
Average | avg | ?float |
Count | count | int |
Range | null | {min, max}, dihitung melalui min() dan max() miliknya sendiri |
Group
public function __construct(string $name);
public static function make(string $name): self;
public function label(string $label): self; // Str::headline($name)
public function column(string $column): self; // the group name
public function direction(SortDirection $direction): self; // Ascending
public function titleUsing(Closure $callback): self; // fn (Model): ?string
public function descriptionUsing(Closure $callback): self; // fn (Model): ?string
public function getName(): string;
public function getLabel(): string;
public function getColumn(): string;
public function keyFor(Model $record): string;
public function titleFor(Model $record): string; // 'Ungrouped' for an empty key
public function descriptionFor(Model $record): ?string;
public function applySort(Builder $query): void;
public function toArray(): array; // name, label2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Tab
Tab dideklarasikan pada method tabs() milik halaman ListRecords dan di-key berdasarkan nilai yang digunakan URL.
public static function make(string $key, ?string $label = null): self;
public readonly string $key;
public function query(Closure $callback): self; // fn (Builder): Builder
public function badge(string|int|Closure|null $badge): self;
public function icon(?string $icon): self; // an icon registry key
public function getLabel(): string;
public function apply(Builder $query): Builder;
public function resolveBadge(): string|int|null;
public function toArray(bool $active): array; // key, label, icon, badge, active2
3
4
5
6
7
8
9
10
ArrayTableData
Merupakan final readonly class.
public function __construct(
TableSchema $schema,
Collection $records,
Request $request,
?string $namespace = null,
);
public static function make(
TableSchema $schema,
iterable $records,
Request $request,
?string $namespace = null,
): self;
public function paginate(): LengthAwarePaginator;
public function rows(LengthAwarePaginator $records): array;
public function pagination(LengthAwarePaginator $records): array;
public function state(): array;
public function sortableColumns(): array; // list<Column>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ArrayTableData membaca page, search, sort, dan direction. Filter, pencarian per kolom, grouping, column manager, perPage, serta session persistence tidak diterapkan.
Enum
| Enum | Case |
|---|---|
ColumnType | text, number, badge, boolean, date, datetime, image, icon, color, custom, toggle, checkbox, text_input, select |
FilterType | select, boolean, date, ternary, form, query_builder |
Alignment | start, center, end, justify; fromRequest() memetakan left/right |
BadgeColor | neutral, success, warning, danger, info; fromValue() fallback ke Neutral |
ColumnPin | start, end |
SortDirection | asc, desc; fromRequest() fallback ke Ascending; opposite() |
RecordActionsPosition | after_columns, before_columns, after_cells |
RelationshipAggregate | count, exists, sum, avg, min, max; attributeFor(), apply() |
ConstraintOperator | contains, does_not_contain, starts_with, ends_with, equal_to, not_equal_to, greater_than, greater_than_or_equal, less_than, less_than_or_equal, is_filled, is_blank, is_true, is_false; label(), needsValue() |
Exception
Seluruh exception di bagian ini adalah PandaPanel\Exceptions\PanelSchemaException, dan dilempar pada setter sehingga stack trace menunjuk ke deklarasi yang menyebabkan masalah.
| Factory | Dilempar ketika |
|---|---|
duplicateColumns(array $names) | dua kolom memiliki nama yang sama |
duplicateFilters(array $names) | dua filter memiliki nama yang sama |
duplicateActions(string $set, array $names) | dua action dalam satu set memiliki nama yang sama |
inertAction(string $name) | action tidak memiliki handler, URL, form, atau modal |
emptyName(string $what) | Column::make('') |
unknownDefaultSort(string $column, array $available) | toArray() menemukan default sort column tidak dideklarasikan |
Endpoint
Setiap panel memiliki satu set endpoint. Nama route mengikuti pola panel.{panel id}.actions.*.
| Route | Method | Payload |
|---|---|---|
actions.record | POST | resource, action, record, parent? |
actions.bulk | POST | resource, action, records[] (1–500), parent? |
actions.table | POST | resource, action, parent? |
actions.cell | POST | resource, record, column, value, parent? |
actions.reorder | POST | resource, records[] (1–500), parent? |
actions.form | GET | resource, action, scope, record? — mengambil form milik action |
actions.submit | POST | resource, action, scope, record?, records[]?, ditambah field milik form |
Kedua route form berada pada path actions/form; scope adalah salah satu dari record, table, bulk, atau infolist.
ResourcePage::actionEndpoints() mengirim URL yang sudah di-resolve ke Vue, sehingga frontend tidak perlu melakukan hardcode URL panel.
Bentuk data hasil serialisasi
TableSchema::toArray()
Key yang dikirim: columns, filters, groups, defaultGroup, columnManager, filterBehaviour, searchable, searchPlaceholder, searchDebounce, searchOnBlur, individualSearchColumns, selectable, reorderable, frozen, perPageOptions, defaultPerPage, defaultSort, bulkActions, recordActions, headerActions, toolbarActions, dan emptyState.
TableSchema::toRow()
[
'key' => int|string,
'group' => ['key' => string, 'title' => string, 'description' => ?string] | null,
'cells' => array<string, mixed>,
'cellMeta' => array<string, array{tooltip?, url?, attributes?, action?}>,
'actions' => list<array<string, mixed>>,
]2
3
4
5
6
7
Pagination
['page' => int, 'perPage' => int, 'total' => int, 'lastPage' => int, 'from' => int, 'to' => int]Summary
// TableSchema::summaries()
['total' => [['name' => 'sum', 'label' => 'Sum', 'value' => '1,240', 'raw' => 1240, 'perPage' => false]]]
// TableSchema::groupSummaries(), keyed by group key first
['open' => ['total' => [...]]]2
3
4
5
Helper untuk testing
PandaPanel\Testing\TestsTables memeriksa apa yang ditampilkan tabel melalui schema dan query yang sebenarnya.
public static function for(string $resource): self;
public function filter(array $values): self;
public function search(string $term): self;
public function sort(string $column, string $direction = 'asc'): self;
public function page(int $page): self;
public function records(): array; // list<Model>
public function keys(): array; // list<int|string>
public function row(Model $record): array;
public function schema(): TableSchema;
public function assertCanSeeRecord(Model $record): self;
public function assertCanNotSeeRecord(Model $record): self;
public function assertCanSeeRecords(array $records): self;
public function assertCount(int $count): self;
public function assertRecordsInOrder(array $records): self;
public function assertCellEquals(Model $record, string $column, mixed $expected): self;
public function assertColumnExists(string $column): self;2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Seluruh method selain schema() bersifat immutable: filter(), search(), sort(), dan page() mengembalikan clone.