feat(schema): enhance API configuration schemas with new properties and validations
All checks were successful
continuous-integration/drone/push Build is passing

- Updated collection.json to include upload defaults, audit logging, query limits, and more.
- Enhanced collectionNavigation.json with viewHint configurations.
- Added project-wide upload defaults and hook configurations in config.json.
- Expanded field.json to support new field types, validations, and properties.
- Improved fieldMeta.json with additional widget configurations and properties.
- Updated hooks.json to include new bulk operation hooks and audit logging.
- Enhanced imageFilter.json with additional image processing options.
- Added timeout properties to job.json for better execution control.
- Refined permissions.json to allow more granular control over HTTP method permissions and added filter and field visibility options.
This commit is contained in:
2026-03-30 12:28:50 +00:00
parent afa7c9b238
commit 025a7ccca4
10 changed files with 1504 additions and 128 deletions

770
index.d.ts vendored
View File

@@ -6,6 +6,286 @@ declare global {
[key: string]: any
}
/**
* Represents a `boolean` value or an object with an `eval` JS expression.
* Used for field-level `readonly` and `hidden` configuration.
*/
export type BoolOrEval = boolean | { eval: string }
/** Structured field validator as defined in Go CollectionField.Validator */
export interface FieldValidator {
/** Array of allowed values */
in?: any[]
/** Whether the field is required */
required?: boolean
/** Whether zero-values are allowed when required is true */
allowZero?: boolean
/** JS eval expression for custom validation */
eval?: string
}
/** Search configuration for a collection */
export interface SearchConfig {
/** Search config name (used in query parameter qName) */
name: string
/** Search mode: text (MongoDB text index), regex, eval (JS), filter, ngram */
mode: "text" | "regex" | "eval" | "filter" | "ngram"
/** Fields to search in (for text/regex mode) */
fields?: string[]
/** Meta information */
meta?: { [key: string]: any }
/** JS eval expression (for eval mode) */
eval?: string
/** MongoDB filter template (for filter mode) */
filter?: { [key: string]: any }
}
/** Query limits configuration for a collection */
export interface QueryLimitsConfig {
/** Default limit for GET queries (if client doesn't specify) */
defaultLimit?: number
/** Maximum allowed limit for GET queries */
maxLimit?: number
}
/** Bulk operation optimization configuration */
export interface BulkOptimizeConfig {
/** Optimize bulk create operations */
create?: boolean
/** Optimize bulk update operations */
update?: boolean
/** Optimize bulk delete operations */
delete?: boolean
}
/** Bulk configuration for a collection */
export interface BulkConfig {
/** Optimization settings for bulk operations */
optimize?: BulkOptimizeConfig
}
/** Collection index definition */
export interface CollectionIndex {
/** Index name */
name?: string
/** Index key fields (prefix with - for descending) */
key: string[]
/** Whether the index enforces uniqueness */
unique?: boolean
/** Whether to drop duplicate entries */
dropDups?: boolean
/** Build index in background */
background?: boolean
/** Only index documents that contain the indexed field */
sparse?: boolean
/** Default language for text indexes */
defaultLanguage?: string
/** Field that contains the language override */
languageOverride?: string
}
/** Audit configuration for a collection */
export interface AuditCollectionConfig {
/** Whether audit logging is enabled for this collection */
enabled?: boolean
/** List of actions to audit: create, update, delete, bulkCreate, bulkUpdate, bulkDelete, get */
actions?: string[]
}
/** Image filter parameters — used in both CollectionConfig.imageFilter and ImagePackage.filter() */
export interface ImageFilterParams {
width?: number
height?: number
fit?: boolean
fill?: boolean
resampling?:
| "nearestNeighbor"
| "hermite"
| "linear"
| "catmullRom"
| "lanczos"
| "box"
| "mitchellNetravili"
| "bSpline"
| "gaussian"
| "bartlett"
| "hann"
| "hamming"
| "blackman"
| "welch"
| "cosine"
anchor?:
| "center"
| "topLeft"
| "top"
| "topRight"
| "left"
| "right"
| "bottomLeft"
| "bottom"
| "bottomRight"
brightness?: number
saturation?: number
contrast?: number
gamma?: number
blur?: number
sharpen?: number
invert?: boolean
grayscale?: boolean
quality?: number
lossless?: boolean
skipLargerDimension?: boolean
skipLargerFilesize?: boolean
outputType?: "jpg" | "jpeg" | "png" | "webp"
}
/** Collection config as returned by context.collection() JSON-serialized from Go CollectionConfig */
export interface CollectionInfo {
name: string
defaultLanguage?: string
permissions?: { [role: string]: PermissionSet }
projections?: { [name: string]: ProjectionConfig }
meta?: { [key: string]: any }
fields?: CollectionFieldInfo[]
/** Named image filter presets for file fields */
imageFilter?: { [name: string]: ImageFilterParams[] }
/** Collection index definitions */
indexes?: CollectionIndex[]
/** Audit configuration */
audit?: AuditCollectionConfig
/** Bulk operation configuration */
bulk?: BulkConfig
/** Query limits for GET requests */
queryLimits?: QueryLimitsConfig
/** Fields that are read-only at collection level */
readonlyFields?: string[]
/** Fields that are hidden at collection level */
hiddenFields?: string[]
/** Search configurations */
search?: SearchConfig[]
/** Reject unknown fields not defined in the fields array */
strictFields?: boolean
[key: string]: any
}
export type MethodPermission = boolean | { allow: boolean; bulk?: boolean }
export type SimpleMethodPermission = boolean | { allow: boolean }
export interface PermissionSet {
methods?: {
get?: SimpleMethodPermission
/** `true` = allow single POST, `{allow: true, bulk: true}` = allow single + bulk POST */
post?: MethodPermission
/** `true` = allow single PUT, `{allow: true, bulk: true}` = allow single + bulk PUT */
put?: MethodPermission
/** `true` = allow single DELETE, `{allow: true, bulk: true}` = allow single + bulk DELETE */
delete?: MethodPermission
}
/** MongoDB filter applied to all queries for this permission set */
filter?: { [key: string]: any }
/** List of projection names this permission set is allowed to use */
validProjections?: string[]
/** Fields that are read-only for this permission set */
readonlyFields?: string[]
/** Fields that are hidden for this permission set */
hiddenFields?: string[]
[key: string]: any
}
export interface ProjectionConfig {
select?: { [field: string]: 0 | 1 }
[key: string]: any
}
export interface CollectionFieldInfo {
name: string
type: string
subFields?: CollectionFieldInfo[]
index?: string[]
validator?: FieldValidator
meta?: { [key: string]: any }
/** Field-level readonly — boolean or JS eval expression */
readonly?: BoolOrEval
/** Field-level hidden — boolean or JS eval expression */
hidden?: BoolOrEval
/** Reject unknown sub-fields not defined in subFields */
strictFields?: boolean
}
/** API info as returned by context.api() JSON-serialized from Go API struct */
export interface ApiInfo {
isOnline: boolean
lastReload: string
namespace: string
meta?: { [key: string]: any }
assets?: { name: string; path: string }[]
collections?: CollectionInfo[]
jobs?: JobConfig[]
errors?: { collection?: string; context?: string; error: any }[]
[key: string]: any
}
/** Project info as returned by context.project() JSON-serialized from Go Project struct */
export interface ProjectInfo {
name: string
description?: string
namespace?: string
users?: string[]
configFile?: string
api?: ApiInfo
yourPermissions?: { [key: string]: any }
[key: string]: any
}
/** Represents a single audit log entry as received in audit.return hooks */
export interface AuditLogEntry {
id?: string
insertTime?: string
updateTime?: string
/** ID of the user who performed the action */
userId?: string
/** Username of the user who performed the action */
username?: string
/** Role of the user (0=admin, 1=user) */
userRole?: number
/** Project namespace */
projectNamespace?: string
/** Project display name */
projectName?: string
/** Collection name */
collection?: string
/** Action performed: create, bulkCreate, update, delete, bulkUpdate, bulkDelete, get, login, reload, shutdown */
action?: string
/** Source information (type, collection, method, step, file, line) */
source?: {
type?: string
collection?: string
method?: string
step?: string
file?: string
line?: number
}
/** Document ID affected */
documentId?: string
/** Full document snapshot at time of action */
snapshot?: { [key: string]: any }
/** Changed fields (for updates) */
changes?: { [key: string]: any }
/** Filter used (for queries/deletes) */
filter?: { [key: string]: any }
/** Number of affected documents (e.g. deleteMany) */
count?: number
/** Client IP address */
ip?: string
/** Authentication method used (e.g. "jwt", "adminToken") */
authMethod?: string
/** Label of the admin token used (if applicable) */
tokenLabel?: string
/** Prefix of the admin token used (if applicable) */
tokenPrefix?: string
[key: string]: any
}
export interface DbReadOptions {
filter?: {
[key: string]: any
@@ -24,13 +304,16 @@ declare global {
interface GetHookGetOnlyData {
/**
* true if only one document was requested via /COLLECTION/ID
* true if only one document was requested via /COLLECTION/ID.
* In audit.return hooks this is always true (entries are processed individually).
*/
one?: boolean
/**
* get list of documents (only valid after stage "read" in "get" hook)
* Get list of documents.
* - In get.return hooks: array of collection documents
* - In audit.return hooks: array with a single AuditLogEntry
*/
results(): CollectionDocument[]
results(): CollectionDocument[] | AuditLogEntry[]
}
interface GetHookData {
@@ -68,9 +351,30 @@ declare global {
interface PostHookData {
/**
* post data only valid in "post" and "put" hooks
* post data only valid in "post" and "put" hooks.
* For bulk hooks (bulkCreate), this is an array of documents.
*/
data?: CollectionDocument
data?: CollectionDocument | CollectionDocument[]
/**
* Number of created documents — only set in post.bulkReturn hook
*/
created?: number
/**
* Array of created document IDs — only set in post.bulkReturn hook
*/
ids?: string[]
/**
* Number of modified documents — only set in put.bulkReturn hook
*/
modified?: number
/**
* Number of deleted documents — only set in delete.bulkReturn hook
*/
deleted?: number
}
interface JobConfig {
@@ -93,6 +397,11 @@ declare global {
* jobs program file
*/
file?: string
/**
* execution timeout in seconds
*/
timeout?: number
}
interface JobData {
@@ -113,22 +422,58 @@ declare global {
* get current project object
*
*/
project(): {
[key: string]: any
}
project(): ProjectInfo
/**
* get server config object
* get server config object (sensitive fields like jwtSecret, adminTokens, apiKeys are excluded)
*
*/
server(): {
api: {
port: number
secureCookies?: boolean
accessTokenLifetime?: string
refreshTokenLifetime?: string
}
security: {
allowAbsolutePaths: boolean
allowUpperPaths: boolean
}
llm?: {
providers?: {
name: string
type: string
baseURL: string
defaultModel: string
models?: string[]
maxTokensPerRequest?: number
}[]
}
audit?: {
enabled: boolean
defaultTTL: string
defaultLimit?: number
maxLimit?: number
}
ratelimit?: {
enabled?: boolean
loginInitialDelay?: string
loginMaxDelay?: string
loginResetAfter?: string
}
hook?: {
cache: {
maxEntries?: number
defaultTTL?: string
}
ratelimit: {
backoffInitialDelay?: string
backoffMaxDelay?: string
backoffResetAfter?: string
windowSize?: string
windowMaxHits?: number
}
}
}
}
@@ -160,14 +505,25 @@ declare global {
/**
* update a document in a collection
*
* Supports field-level operators in data:
* - `{ "$inc": value }` — increment by value
* - `{ "$mul": value }` — multiply by value
* - `{ "$unset": true }` — remove the field
* - `{ "$set": value }` — explicit set (same as plain value)
*
* Operators are converted to native MongoDB operators and executed
* atomically in a single DB call.
*
* @param colName collection name
* @param id id of entry
* @param data new/changed data
* @param data new/changed data (may contain operators)
* @param options optional: `{ raw: true }` to pass data as raw MongoDB update document
*/
update(
colName: string,
id: string,
data: CollectionDocument
data: CollectionDocument,
options?: { raw?: boolean }
): CollectionDocument
/**
@@ -188,6 +544,26 @@ declare global {
colName: string,
options?: DbReadOptions
): { message: "ok"; removed: number }
/**
* update multiple documents matching a filter
*
* Use `changes` for simple field updates (supports operators like $inc, $mul).
* Use `update` for raw MongoDB update documents.
*
* @param colName collection name
* @param options options with filter and changes/update
*/
updateMany(
colName: string,
options: {
filter?: { [key: string]: any }
/** Field updates — may contain operators like { "$inc": 1 } */
changes?: { [key: string]: any }
/** Raw MongoDB update document (e.g. { "$inc": { stock: -1 } }) */
update?: { [key: string]: any }
}
): { message: "ok"; modified: number }
}
interface SmtpPackage {
@@ -305,7 +681,7 @@ declare global {
interface HttpPackage {
/**
* http request
* http request — reads the entire response body into memory
*
* @param url url for request
* @param options request options
@@ -316,7 +692,7 @@ declare global {
method?: string
headers?: { [key: string]: string }
body?: string
// timeout in seconds
/** timeout in seconds (default 10) */
timeout?: number
}
): {
@@ -330,6 +706,35 @@ declare global {
json(): any
}
}
/**
* streaming http request — response body is NOT read into memory.
* Use body.read() to read line by line (ideal for SSE / newline-delimited
* protocols like OpenAI streaming API).
*
* @param url url for request
* @param options request options
*/
fetchStream(
url: string,
options?: {
method?: string
headers?: { [key: string]: string }
body?: string
/** timeout in seconds (default 0 = no timeout, hook timeout controls duration) */
timeout?: number
}
): {
status: number
statusText: string
headers: { [key: string]: string }
body: {
/** returns the next line as string, or null at EOF */
read(): string | null
/** explicitly closes the response body (auto-closed at EOF) */
close(): void
}
}
}
interface DebugPackage {
@@ -355,12 +760,46 @@ declare global {
interface ResponsePackage {
/**
* set response header
* set response header (must be called before writeHeader/write)
*
* @param name header name
* @param value value
*/
header(name: string, value: any): void
/**
* set the HTTP status code — must be called before write().
* If not called, the first write() will implicitly send status 200.
*
* @param status HTTP status code
*/
writeHeader(status: number): void
/**
* write data directly to the HTTP response body.
* Use for streaming responses (e.g. SSE to a chat widget).
*
* @param data string or byte data to write
*/
write(data: string | any): void
/**
* flush buffered data to the client immediately.
* Essential for streaming / SSE to push chunks without waiting
* for the full response to complete.
*/
flush(): void
/**
* write a Server-Sent Event and flush immediately.
* Formats the output as SSE: "event: {event}\ndata: {data}\n\n"
* If called with one argument, only "data: {data}\n\n" is written.
*
* @param event SSE event name (optional if using single-argument form)
* @param data SSE data payload
*/
writeSSE(event: string, data: string): void
writeSSE(data: string): void
}
interface UserPackage {
@@ -464,41 +903,7 @@ declare global {
filter(
sourceFile: string,
targetFile: string,
filters: {
width?: number
height?: number
fit?: boolean
fill?: boolean
resampling?: "nearestNeighbor"
| "hermite"
| "linear"
| "catmullRom"
| "lanczos"
| "box"
| "mitchellNetravili"
| "bSpline"
| "gaussian"
| "bartlett"
| "hann"
| "hamming"
| "blackman"
| "welch"
| "cosine"
anchor?: "center" | "topLeft" | "top" | "topRight" | "left" | "right" | "bottomLeft" | "bottom" | "bottomRight"
brightness?: number
saturation?: number
contrast?: number
gamma?: number
blur?: number
sharpen?: number
invert?: boolean
grayscale?: boolean
quality?: number
lossless?: boolean
skipLargerDimensions?: boolean
skipLargerFilesize?: boolean
outputType?: "jpg" | "jpeg" | "png" | "webp"
}[]
filters: ImageFilterParams[]
): void
}
@@ -509,10 +914,13 @@ declare global {
* @param data object or array (map with string keys as nodes (-KEY will be attribute in parent node))
* @param options options
*/
create(data: {[key: string]: any}, options?: {
rootElement?: string
includeHeader?: boolean
}): string
create(
data: { [key: string]: any },
options?: {
rootElement?: string
includeHeader?: boolean
}
): string
/**
* parse xml string to json
@@ -689,45 +1097,224 @@ declare global {
}
interface ExecPackage {
command(cmd: string, options?: {
args?: string[]
stdin?: string
dir?: string
env?: string[]
timeout?: number
returnObject?: boolean
combineOutput?: boolean
}): string
command(
cmd: string,
options?: {
args?: string[]
stdin?: string
dir?: string
env?: string[]
timeout?: number
returnObject?: boolean
combineOutput?: boolean
}
): string
command<T extends {
args?: string[]
stdin?: string
dir?: string
env?: string[]
timeout?: number
returnObject?: boolean
combineOutput?: boolean
}>(cmd: string, options: T):
T['returnObject'] extends true
? T['combineOutput'] extends true
? { output: string; exitCode: number }
: { stdout: string; stderr: string; exitCode: number }
: string
command<
T extends {
args?: string[]
stdin?: string
dir?: string
env?: string[]
timeout?: number
returnObject?: boolean
combineOutput?: boolean
},
>(
cmd: string,
options: T
): T["returnObject"] extends true
? T["combineOutput"] extends true
? { output: string; exitCode: number }
: { stdout: string; stderr: string; exitCode: number }
: string
}
interface Base64Package {
encode(data: string|Int8Array): string
encode(data: string | Int8Array): string
decode(data: string): string
decode(data: string, options: {returnBytes: true}): Int8Array
decode(data: string, options: {returnBytes: false}): string
decode(data: string, options: {returnBytes?: boolean}): string | Int8Array
decode(data: string, options: { returnBytes: true }): Int8Array
decode(data: string, options: { returnBytes: false }): string
decode(
data: string,
options: { returnBytes?: boolean }
): string | Int8Array
}
/**
* Project-scoped in-memory cache.
*
* Entries survive across requests but are cleared on project reload.
* Configured via project config `hook.cache` or server config `hook.cache`.
*/
interface CachePackage {
/**
* Store a value in the cache.
*
* @param key Cache key
* @param value Any JSON-serializable value
* @param ttlMs Optional time-to-live in milliseconds (overrides defaultTTL)
*/
set(key: string, value: any, ttlMs?: number): void
/**
* Retrieve a value from the cache.
*
* @param key Cache key
* @returns The cached value, or `null` if not found / expired.
*/
get(key: string): any | null
/**
* Check whether a key exists (and is not expired) in the cache.
*
* @param key Cache key
*/
has(key: string): boolean
/**
* Delete a single key from the cache.
*
* @param key Cache key
* @returns `true` if the key existed, `false` otherwise.
*/
delete(key: string): boolean
/**
* Remove all entries from the cache.
*/
clear(): void
/**
* Return the number of entries currently in the cache.
*/
count(): number
}
/**
* Project-scoped rate limiter with two strategies:
* - **Backoff**: Exponential back-off per key (e.g. login brute-force protection).
* - **Window**: Sliding-window counter per key (e.g. API call quotas).
*
* Configured via project config `hook.ratelimit` or server config `hook.ratelimit`.
*/
interface RatelimitPackage {
/**
* Check whether a key is currently blocked by the exponential back-off strategy.
*
* @param key Rate-limit key (e.g. IP address or user ID)
* @returns `{ blocked, retryAfterMs }` — if blocked, retryAfterMs indicates how long to wait.
*/
backoffCheck(key: string): { blocked: boolean; retryAfterMs: number }
/**
* Record a failure for the given key, increasing its back-off delay.
*
* @param key Rate-limit key
*/
backoffRecord(key: string): void
/**
* Reset the back-off state for a key (e.g. after a successful login).
*
* @param key Rate-limit key
*/
backoffReset(key: string): void
/**
* Check the sliding-window strategy for a key.
*
* @param key Rate-limit key
* @returns `{ allowed, retryAfterMs }` — if not allowed, retryAfterMs indicates how long to wait.
*/
windowCheck(key: string): { allowed: boolean; retryAfterMs: number }
/**
* Reset the sliding-window counter for a key.
*
* @param key Rate-limit key
*/
windowReset(key: string): void
/**
* Return the current hit count in the sliding window for a key.
*
* @param key Rate-limit key
*/
windowCount(key: string): number
}
/**
* Options for channel.subscribe().
*/
interface ChannelSubscribeOptions {
/** Maximum number of buffered messages per subscriber (default: 64). */
bufferSize?: number
/**
* Behaviour when the buffer is full.
* - "drop-oldest" (default): discard the oldest message
* - "drop-newest": discard the incoming message
*/
onFull?: "drop-oldest" | "drop-newest"
/** Time-to-live per message in milliseconds. Expired messages are skipped on receive. */
messageTTL?: number
/** Replay the last N messages from the channel's ring-buffer to the new subscriber. */
lastN?: number
/** Replay only messages younger than maxAge milliseconds. */
maxAge?: number
}
/**
* A subscription to a real-time channel.
* Returned by channel.subscribe().
*/
interface ChannelSubscription {
/**
* Blocks until a message arrives, the client disconnects, or the
* subscription/channel is closed.
*
* @returns The message data, or `null` when the client disconnects.
* @throws `{error: string, code: "channel_closed"}` when the
* subscription or channel is closed (e.g. project reload).
*/
receive(): any
/** Manually close the subscription (idempotent). */
close(): void
}
/**
* Real-time pub/sub channel package.
*
* Channels are in-memory, project-scoped, and named freely (not bound
* to collections). Every subscriber receives an independent deep-copy
* of each message.
*/
interface ChannelPackage {
/**
* Subscribe to a named channel. The calling hook will block until
* the client disconnects or the subscription is closed.
* The execution timeout is automatically disabled for hooks that subscribe.
*
* @param name Channel name (arbitrary string).
* @param options Optional subscribe options.
*/
subscribe(
name: string,
options?: ChannelSubscribeOptions
): ChannelSubscription
/**
* Publish data to all current subscribers of a named channel.
* This is a fire-and-forget operation; if no one is subscribed, the
* message is buffered in the channel's ring-buffer for future replay.
*
* @param name Channel name.
* @param data Arbitrary data (will be deep-copied per subscriber).
*/
send(name: string, data: any): void
}
export interface HookContext
extends GetHookData,
GetHookGetOnlyData,
PostHookData,
JobData {
extends GetHookData, GetHookGetOnlyData, PostHookData, JobData {
request(): {
method: string
remoteAddr: string
@@ -745,17 +1332,11 @@ declare global {
bodyBytes(): string
}
api(): {
[key: string]: any
}
api(): ApiInfo
project(): {
[key: string]: any
}
project(): ProjectInfo
collection(): {
[key: string]: any
}
collection(): CollectionInfo
config: ConfigPackage
db: DbPackage
@@ -777,6 +1358,9 @@ declare global {
json: JsonPackage
exec: ExecPackage
base64: Base64Package
channel: ChannelPackage
cache: CachePackage
ratelimit: RatelimitPackage
}
export interface HookException {