@atscript/core 0.1.27 → 0.1.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +427 -134
- package/dist/index.d.ts +7 -12
- package/dist/index.mjs +427 -134
- package/package.json +9 -3
- package/scripts/setup-skills.js +77 -0
- package/skills/atscript-core/.gitkeep +0 -0
- package/skills/atscript-core/SKILL.md +32 -0
- package/skills/atscript-core/annotations.md +157 -0
- package/skills/atscript-core/core.md +125 -0
- package/skills/atscript-core/plugins.md +206 -0
- package/skills/atscript-core/primitives.md +156 -0
package/dist/index.mjs
CHANGED
|
@@ -409,60 +409,13 @@ else obj[key] = value;
|
|
|
409
409
|
var SemanticPrimitiveNode = class SemanticPrimitiveNode extends SemanticNode {
|
|
410
410
|
applyAnnotations() {
|
|
411
411
|
this.annotations = [];
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
name: "expect.maxLength",
|
|
420
|
-
token: dummyToken,
|
|
421
|
-
args: [num(this.config.expect.maxLength)]
|
|
422
|
-
});
|
|
423
|
-
}
|
|
424
|
-
if (this.type === "string") {
|
|
425
|
-
if (this.config.expect?.required === true) this.annotations.push({
|
|
426
|
-
name: "meta.required",
|
|
427
|
-
token: dummyToken,
|
|
428
|
-
args: []
|
|
429
|
-
});
|
|
430
|
-
if (this.config.expect?.pattern !== undefined) {
|
|
431
|
-
const patterns = Array.isArray(this.config.expect.pattern) ? this.config.expect.pattern : [this.config.expect.pattern];
|
|
432
|
-
for (const p of patterns) {
|
|
433
|
-
const args = typeof p === "string" ? [text$1(p)] : [text$1(p.source), text$1(p.flags)];
|
|
434
|
-
if (this.config.expect.message) args[2] = text$1(this.config.expect.message);
|
|
435
|
-
this.annotations.push({
|
|
436
|
-
name: "expect.pattern",
|
|
437
|
-
token: dummyToken,
|
|
438
|
-
args
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
if (this.type === "number") {
|
|
444
|
-
if (typeof this.config.expect?.min === "number") this.annotations.push({
|
|
445
|
-
name: "expect.min",
|
|
446
|
-
token: dummyToken,
|
|
447
|
-
args: [num(this.config.expect.min)]
|
|
448
|
-
});
|
|
449
|
-
if (typeof this.config.expect?.max === "number") this.annotations.push({
|
|
450
|
-
name: "expect.max",
|
|
451
|
-
token: dummyToken,
|
|
452
|
-
args: [num(this.config.expect.max)]
|
|
453
|
-
});
|
|
454
|
-
if (this.config.expect?.int === true) this.annotations.push({
|
|
455
|
-
name: "expect.int",
|
|
456
|
-
token: dummyToken,
|
|
457
|
-
args: []
|
|
458
|
-
});
|
|
459
|
-
}
|
|
460
|
-
if (this.type === "boolean") {
|
|
461
|
-
if (this.config.expect?.required === true) this.annotations.push({
|
|
462
|
-
name: "meta.required",
|
|
463
|
-
token: dummyToken,
|
|
464
|
-
args: []
|
|
465
|
-
});
|
|
412
|
+
for (const [name, value] of Object.entries(this.config.annotations || {})) {
|
|
413
|
+
const spec = this.annotationTree ? resolveAnnotation(name, this.annotationTree) : undefined;
|
|
414
|
+
const isMultiple = spec?.config.multiple ?? false;
|
|
415
|
+
if (Array.isArray(value)) {
|
|
416
|
+
if (isMultiple) for (const item of value) this.annotations.push(toAnnotationTokens(name, item, spec));
|
|
417
|
+
else if (value.length > 0) this.annotations.push(toAnnotationTokens(name, value[0], spec));
|
|
418
|
+
} else this.annotations.push(toAnnotationTokens(name, value, spec));
|
|
466
419
|
}
|
|
467
420
|
}
|
|
468
421
|
get key() {
|
|
@@ -491,8 +444,8 @@ var SemanticPrimitiveNode = class SemanticPrimitiveNode extends SemanticNode {
|
|
|
491
444
|
s += this.renderChildren();
|
|
492
445
|
return indent + s.split("\n").join(`\n${indent}`);
|
|
493
446
|
}
|
|
494
|
-
constructor(_id, config, parentKey = "") {
|
|
495
|
-
super("primitive"), _define_property$11(this, "_id", void 0), _define_property$11(this, "config", void 0), _define_property$11(this, "parentKey", void 0), _define_property$11(this, "type", void 0), _define_property$11(this, "props", void 0), _define_property$11(this, "tags", void 0), this._id = _id, this.config = config, this.parentKey = parentKey;
|
|
447
|
+
constructor(_id, config, parentKey = "", annotationTree) {
|
|
448
|
+
super("primitive"), _define_property$11(this, "_id", void 0), _define_property$11(this, "config", void 0), _define_property$11(this, "parentKey", void 0), _define_property$11(this, "annotationTree", void 0), _define_property$11(this, "type", void 0), _define_property$11(this, "props", void 0), _define_property$11(this, "tags", void 0), this._id = _id, this.config = config, this.parentKey = parentKey, this.annotationTree = annotationTree;
|
|
496
449
|
this.props = new Map();
|
|
497
450
|
this.tags = new Set([_id, ...config?.tags || []]);
|
|
498
451
|
for (const [ext, def] of Object.entries(config.extensions || {})) {
|
|
@@ -501,11 +454,11 @@ var SemanticPrimitiveNode = class SemanticPrimitiveNode extends SemanticNode {
|
|
|
501
454
|
documentation: def.documentation ?? config.documentation,
|
|
502
455
|
extensions: def.extensions,
|
|
503
456
|
tags: Array.from(new Set([...def.tags || [], ...Array.from(this.tags)])),
|
|
504
|
-
|
|
505
|
-
...config.
|
|
506
|
-
...def.
|
|
457
|
+
annotations: {
|
|
458
|
+
...config.annotations,
|
|
459
|
+
...def.annotations
|
|
507
460
|
}
|
|
508
|
-
}, this.key);
|
|
461
|
+
}, this.key, this.annotationTree);
|
|
509
462
|
this.props.set(ext, node);
|
|
510
463
|
}
|
|
511
464
|
if (typeof config.type === "object") this.type = config.type.kind === "final" ? config.type.value : config.type.kind;
|
|
@@ -513,6 +466,36 @@ else this.type = config.type;
|
|
|
513
466
|
this.applyAnnotations();
|
|
514
467
|
}
|
|
515
468
|
};
|
|
469
|
+
function toAnnotationTokens(name, value, spec) {
|
|
470
|
+
if (typeof value === "boolean") return {
|
|
471
|
+
name,
|
|
472
|
+
token: dummyToken,
|
|
473
|
+
args: []
|
|
474
|
+
};
|
|
475
|
+
if (typeof value === "string") return {
|
|
476
|
+
name,
|
|
477
|
+
token: dummyToken,
|
|
478
|
+
args: [text$1(value)]
|
|
479
|
+
};
|
|
480
|
+
if (typeof value === "number") return {
|
|
481
|
+
name,
|
|
482
|
+
token: dummyToken,
|
|
483
|
+
args: [num(value)]
|
|
484
|
+
};
|
|
485
|
+
const argNames = spec?.arguments.map((a) => a.name) ?? Object.keys(value);
|
|
486
|
+
const raw = argNames.map((argName) => {
|
|
487
|
+
const v = value[argName];
|
|
488
|
+
if (v === undefined) return undefined;
|
|
489
|
+
return typeof v === "number" ? num(v) : text$1(String(v));
|
|
490
|
+
});
|
|
491
|
+
while (raw.length > 0 && raw[raw.length - 1] === undefined) raw.pop();
|
|
492
|
+
const args = raw.map((t) => t ?? text$1(""));
|
|
493
|
+
return {
|
|
494
|
+
name,
|
|
495
|
+
token: dummyToken,
|
|
496
|
+
args
|
|
497
|
+
};
|
|
498
|
+
}
|
|
516
499
|
const dummyToken = new Token({
|
|
517
500
|
getRange: () => ({
|
|
518
501
|
end: {
|
|
@@ -3024,6 +3007,162 @@ const zeroRange = {
|
|
|
3024
3007
|
}
|
|
3025
3008
|
};
|
|
3026
3009
|
|
|
3010
|
+
//#endregion
|
|
3011
|
+
//#region packages/core/src/defaults/db-annotations.ts
|
|
3012
|
+
const dbAnnotations = {
|
|
3013
|
+
table: new AnnotationSpec({
|
|
3014
|
+
description: "Marks an interface as a database-persisted entity (table in SQL, collection in MongoDB). If the name argument is omitted, the adapter derives the table name from the interface name.\n\n**Example:**\n```atscript\n@db.table \"users\"\nexport interface User { ... }\n```\n",
|
|
3015
|
+
nodeType: ["interface"],
|
|
3016
|
+
argument: {
|
|
3017
|
+
optional: true,
|
|
3018
|
+
name: "name",
|
|
3019
|
+
type: "string",
|
|
3020
|
+
description: "Table/collection name. If omitted, derived from interface name."
|
|
3021
|
+
}
|
|
3022
|
+
}),
|
|
3023
|
+
schema: new AnnotationSpec({
|
|
3024
|
+
description: "Assigns the entity to a database schema/namespace.\n\n**Example:**\n```atscript\n@db.table \"users\"\n@db.schema \"auth\"\nexport interface User { ... }\n```\n",
|
|
3025
|
+
nodeType: ["interface"],
|
|
3026
|
+
argument: {
|
|
3027
|
+
name: "name",
|
|
3028
|
+
type: "string",
|
|
3029
|
+
description: "Schema/namespace name."
|
|
3030
|
+
}
|
|
3031
|
+
}),
|
|
3032
|
+
index: {
|
|
3033
|
+
plain: new AnnotationSpec({
|
|
3034
|
+
description: "Standard (non-unique) index for query performance. Fields sharing the same index name form a composite index.\n\n**Example:**\n```atscript\n@db.index.plain \"idx_timeline\", \"desc\"\ncreatedAt: number.timestamp\n```\n",
|
|
3035
|
+
nodeType: ["prop"],
|
|
3036
|
+
multiple: true,
|
|
3037
|
+
mergeStrategy: "append",
|
|
3038
|
+
argument: [{
|
|
3039
|
+
optional: true,
|
|
3040
|
+
name: "name",
|
|
3041
|
+
type: "string",
|
|
3042
|
+
description: "Index name / composite group name."
|
|
3043
|
+
}, {
|
|
3044
|
+
optional: true,
|
|
3045
|
+
name: "sort",
|
|
3046
|
+
type: "string",
|
|
3047
|
+
values: ["asc", "desc"],
|
|
3048
|
+
description: "Sort direction. Defaults to \"asc\"."
|
|
3049
|
+
}]
|
|
3050
|
+
}),
|
|
3051
|
+
unique: new AnnotationSpec({
|
|
3052
|
+
description: "Unique index — ensures no two rows/documents have the same value(s). Fields sharing the same index name form a composite unique constraint.\n\n**Example:**\n```atscript\n@db.index.unique \"tenant_email\"\nemail: string.email\n```\n",
|
|
3053
|
+
nodeType: ["prop"],
|
|
3054
|
+
multiple: true,
|
|
3055
|
+
mergeStrategy: "append",
|
|
3056
|
+
argument: {
|
|
3057
|
+
optional: true,
|
|
3058
|
+
name: "name",
|
|
3059
|
+
type: "string",
|
|
3060
|
+
description: "Index name / composite group name."
|
|
3061
|
+
}
|
|
3062
|
+
}),
|
|
3063
|
+
fulltext: new AnnotationSpec({
|
|
3064
|
+
description: "Full-text search index. Fields sharing the same index name form a composite full-text index.\n\n**Example:**\n```atscript\n@db.index.fulltext \"ft_content\"\ntitle: string\n```\n",
|
|
3065
|
+
nodeType: ["prop"],
|
|
3066
|
+
multiple: true,
|
|
3067
|
+
mergeStrategy: "append",
|
|
3068
|
+
argument: {
|
|
3069
|
+
optional: true,
|
|
3070
|
+
name: "name",
|
|
3071
|
+
type: "string",
|
|
3072
|
+
description: "Index name / composite group name."
|
|
3073
|
+
}
|
|
3074
|
+
})
|
|
3075
|
+
},
|
|
3076
|
+
column: new AnnotationSpec({
|
|
3077
|
+
description: "Overrides the physical column/document-field name in the database.\n\n**Example:**\n```atscript\n@db.column \"first_name\"\nfirstName: string\n```\n",
|
|
3078
|
+
nodeType: ["prop"],
|
|
3079
|
+
argument: {
|
|
3080
|
+
name: "name",
|
|
3081
|
+
type: "string",
|
|
3082
|
+
description: "The actual column/field name in the DB."
|
|
3083
|
+
}
|
|
3084
|
+
}),
|
|
3085
|
+
default: {
|
|
3086
|
+
value: new AnnotationSpec({
|
|
3087
|
+
description: "Sets a static DB-level default value (used in DDL DEFAULT clause). For string fields the value is used as-is; for other types it is parsed as JSON.\n\n**Example:**\n```atscript\n@db.default.value \"active\"\nstatus: string\n```\n",
|
|
3088
|
+
nodeType: ["prop"],
|
|
3089
|
+
argument: {
|
|
3090
|
+
name: "value",
|
|
3091
|
+
type: "string",
|
|
3092
|
+
description: "Static default value. Strings used as-is; other types parsed via JSON.parse()."
|
|
3093
|
+
}
|
|
3094
|
+
}),
|
|
3095
|
+
fn: new AnnotationSpec({
|
|
3096
|
+
description: "Sets a DB-level generated default. The function name is portable — each adapter maps it to the appropriate DB mechanism.\n\n**Example:**\n```atscript\n@db.default.fn \"increment\"\nid: number\n```\n",
|
|
3097
|
+
nodeType: ["prop"],
|
|
3098
|
+
argument: {
|
|
3099
|
+
name: "fn",
|
|
3100
|
+
type: "string",
|
|
3101
|
+
values: [
|
|
3102
|
+
"increment",
|
|
3103
|
+
"uuid",
|
|
3104
|
+
"now"
|
|
3105
|
+
],
|
|
3106
|
+
description: "Generation function name: \"increment\", \"uuid\", or \"now\"."
|
|
3107
|
+
},
|
|
3108
|
+
validate(token, args, doc) {
|
|
3109
|
+
const errors = [];
|
|
3110
|
+
if (!args[0]) return errors;
|
|
3111
|
+
const fnName = args[0].text;
|
|
3112
|
+
const validFns = [
|
|
3113
|
+
"increment",
|
|
3114
|
+
"uuid",
|
|
3115
|
+
"now"
|
|
3116
|
+
];
|
|
3117
|
+
if (!validFns.includes(fnName)) {
|
|
3118
|
+
errors.push({
|
|
3119
|
+
message: `Unknown @db.default.fn "${fnName}" — expected "increment", "uuid", or "now"`,
|
|
3120
|
+
severity: 1,
|
|
3121
|
+
range: args[0].range
|
|
3122
|
+
});
|
|
3123
|
+
return errors;
|
|
3124
|
+
}
|
|
3125
|
+
const field = token.parentNode;
|
|
3126
|
+
const definition$1 = field.getDefinition();
|
|
3127
|
+
if (!definition$1 || !isRef(definition$1)) return errors;
|
|
3128
|
+
const unwound = doc.unwindType(definition$1.id, definition$1.chain);
|
|
3129
|
+
if (!unwound || !isPrimitive(unwound.def)) return errors;
|
|
3130
|
+
const baseType = unwound.def.config.type;
|
|
3131
|
+
if (fnName === "increment" && baseType !== "number") errors.push({
|
|
3132
|
+
message: `@db.default.fn "increment" is not compatible with type "${baseType}" — requires number`,
|
|
3133
|
+
severity: 1,
|
|
3134
|
+
range: token.range
|
|
3135
|
+
});
|
|
3136
|
+
else if (fnName === "uuid" && baseType !== "string") errors.push({
|
|
3137
|
+
message: `@db.default.fn "uuid" is not compatible with type "${baseType}" — requires string`,
|
|
3138
|
+
severity: 1,
|
|
3139
|
+
range: token.range
|
|
3140
|
+
});
|
|
3141
|
+
else if (fnName === "now" && !["number", "string"].includes(baseType)) errors.push({
|
|
3142
|
+
message: `@db.default.fn "now" is not compatible with type "${baseType}" — requires number or string`,
|
|
3143
|
+
severity: 1,
|
|
3144
|
+
range: token.range
|
|
3145
|
+
});
|
|
3146
|
+
return errors;
|
|
3147
|
+
}
|
|
3148
|
+
})
|
|
3149
|
+
},
|
|
3150
|
+
ignore: new AnnotationSpec({
|
|
3151
|
+
description: "Excludes a field from the database schema. The field exists in the Atscript type but has no column in the DB.\n\n**Example:**\n```atscript\n@db.ignore\ndisplayName: string\n```\n",
|
|
3152
|
+
nodeType: ["prop"],
|
|
3153
|
+
validate(token, args, doc) {
|
|
3154
|
+
const errors = [];
|
|
3155
|
+
const field = token.parentNode;
|
|
3156
|
+
if (field.countAnnotations("meta.id") > 0) errors.push({
|
|
3157
|
+
message: `@db.ignore cannot coexist with @meta.id — a field cannot be both a primary key and excluded from the database`,
|
|
3158
|
+
severity: 1,
|
|
3159
|
+
range: token.range
|
|
3160
|
+
});
|
|
3161
|
+
return errors;
|
|
3162
|
+
}
|
|
3163
|
+
})
|
|
3164
|
+
};
|
|
3165
|
+
|
|
3027
3166
|
//#endregion
|
|
3028
3167
|
//#region packages/core/src/defaults/emit-annotations.ts
|
|
3029
3168
|
const emitAnnotations = { jsonSchema: new AnnotationSpec({
|
|
@@ -3200,7 +3339,35 @@ const expectAnnotations = {
|
|
|
3200
3339
|
}
|
|
3201
3340
|
return [];
|
|
3202
3341
|
}
|
|
3203
|
-
})
|
|
3342
|
+
}),
|
|
3343
|
+
array: { key: new AnnotationSpec({
|
|
3344
|
+
description: "Marks a **key field** inside an array. This annotation is used to identify unique fields within an array that can be used as **lookup keys**.\n\n\n\n**Example:**\n```atscript\nexport interface User {\n id: string\n profiles: {\n @expect.array.key\n profileId: string\n name: string\n }[]\n}\n```\n",
|
|
3345
|
+
nodeType: ["prop", "type"],
|
|
3346
|
+
multiple: false,
|
|
3347
|
+
validate(token, args, doc) {
|
|
3348
|
+
const field = token.parentNode;
|
|
3349
|
+
const errors = [];
|
|
3350
|
+
const isOptional = !!field.token("optional");
|
|
3351
|
+
if (isOptional) errors.push({
|
|
3352
|
+
message: `@expect.array.key can't be optional`,
|
|
3353
|
+
severity: 1,
|
|
3354
|
+
range: field.token("identifier").range
|
|
3355
|
+
});
|
|
3356
|
+
const definition$1 = field.getDefinition();
|
|
3357
|
+
if (!definition$1) return errors;
|
|
3358
|
+
let wrongType = false;
|
|
3359
|
+
if (isRef(definition$1)) {
|
|
3360
|
+
const def = doc.unwindType(definition$1.id, definition$1.chain)?.def;
|
|
3361
|
+
if (isPrimitive(def) && !["string", "number"].includes(def.config.type)) wrongType = true;
|
|
3362
|
+
} else wrongType = true;
|
|
3363
|
+
if (wrongType) errors.push({
|
|
3364
|
+
message: `@expect.array.key must be of type string or number`,
|
|
3365
|
+
severity: 1,
|
|
3366
|
+
range: token.range
|
|
3367
|
+
});
|
|
3368
|
+
return errors;
|
|
3369
|
+
}
|
|
3370
|
+
}) }
|
|
3204
3371
|
};
|
|
3205
3372
|
|
|
3206
3373
|
//#endregion
|
|
@@ -3215,13 +3382,8 @@ const metaAnnotations = {
|
|
|
3215
3382
|
}
|
|
3216
3383
|
}),
|
|
3217
3384
|
id: new AnnotationSpec({
|
|
3218
|
-
description: "Marks a field as a unique identifier across multiple domains (DB, API, UI, logs).\n\n**Example:**```atscript@meta.
|
|
3219
|
-
|
|
3220
|
-
optional: true,
|
|
3221
|
-
name: "name",
|
|
3222
|
-
type: "string",
|
|
3223
|
-
description: "Custom identifier name (defaults to property name if omitted)."
|
|
3224
|
-
}
|
|
3385
|
+
description: "Marks a field as a unique identifier across multiple domains (DB, API, UI, logs). Multiple fields can be annotated with `@meta.id` to form a composite primary key.\n\n**Example:**```atscript@meta.idid: string```",
|
|
3386
|
+
nodeType: ["prop"]
|
|
3225
3387
|
}),
|
|
3226
3388
|
description: new AnnotationSpec({
|
|
3227
3389
|
description: "Provides a **detailed description** of a field or entity, often used in documentation.\n\n**Example:**```atscript@meta.description \"Stores the user email address\"email: string```",
|
|
@@ -3240,15 +3402,6 @@ const metaAnnotations = {
|
|
|
3240
3402
|
description: "A line of documentation text. Multiple annotations can be used to form a full Markdown document."
|
|
3241
3403
|
}
|
|
3242
3404
|
}),
|
|
3243
|
-
placeholder: new AnnotationSpec({
|
|
3244
|
-
description: "Defines a **default placeholder value** for UI input fields.\n\n**Example:**```atscript@meta.placeholder \"Enter your name\"name: string```",
|
|
3245
|
-
nodeType: ["prop", "type"],
|
|
3246
|
-
argument: {
|
|
3247
|
-
name: "text",
|
|
3248
|
-
type: "string",
|
|
3249
|
-
description: "The placeholder text to display in UI forms."
|
|
3250
|
-
}
|
|
3251
|
-
}),
|
|
3252
3405
|
sensitive: new AnnotationSpec({
|
|
3253
3406
|
description: "Marks a field as **sensitive** (e.g., passwords, API keys), ensuring it is hidden in logs and UI.\n\n**Example:**```atscript@meta.sensitivepassword: string```",
|
|
3254
3407
|
nodeType: ["prop", "type"],
|
|
@@ -3286,34 +3439,6 @@ const metaAnnotations = {
|
|
|
3286
3439
|
type: "string",
|
|
3287
3440
|
description: "The example value. Strings are used as-is; other types are parsed as JSON."
|
|
3288
3441
|
}
|
|
3289
|
-
}),
|
|
3290
|
-
isKey: new AnnotationSpec({
|
|
3291
|
-
description: "Marks a **key field** inside an array. This annotation is used to identify unique fields within an array that can be used as **lookup keys**.\n\n\n\n**Example:**\n```atscript\nexport interface User {\n id: string\n profiles: {\n @meta.isKey\n profileId: string\n name: string\n }[]\n}\n```\n",
|
|
3292
|
-
nodeType: ["prop", "type"],
|
|
3293
|
-
multiple: false,
|
|
3294
|
-
validate(token, args, doc) {
|
|
3295
|
-
const field = token.parentNode;
|
|
3296
|
-
const errors = [];
|
|
3297
|
-
const isOptional = !!field.token("optional");
|
|
3298
|
-
if (isOptional) errors.push({
|
|
3299
|
-
message: `@meta.isKey can't be optional`,
|
|
3300
|
-
severity: 1,
|
|
3301
|
-
range: field.token("identifier").range
|
|
3302
|
-
});
|
|
3303
|
-
const definition$1 = field.getDefinition();
|
|
3304
|
-
if (!definition$1) return errors;
|
|
3305
|
-
let wrongType = false;
|
|
3306
|
-
if (isRef(definition$1)) {
|
|
3307
|
-
const def = doc.unwindType(definition$1.id, definition$1.chain)?.def;
|
|
3308
|
-
if (isPrimitive(def) && !["string", "number"].includes(def.config.type)) wrongType = true;
|
|
3309
|
-
} else wrongType = true;
|
|
3310
|
-
if (wrongType) errors.push({
|
|
3311
|
-
message: `@meta.isKey must be of type string or number`,
|
|
3312
|
-
severity: 1,
|
|
3313
|
-
range: token.range
|
|
3314
|
-
});
|
|
3315
|
-
return errors;
|
|
3316
|
-
}
|
|
3317
3442
|
})
|
|
3318
3443
|
};
|
|
3319
3444
|
|
|
@@ -3321,11 +3446,11 @@ const metaAnnotations = {
|
|
|
3321
3446
|
//#region packages/core/src/defaults/primitives.ts
|
|
3322
3447
|
const positive = {
|
|
3323
3448
|
documentation: "Number that greater than or equal to zero.",
|
|
3324
|
-
|
|
3449
|
+
annotations: { "expect.min": 0 }
|
|
3325
3450
|
};
|
|
3326
3451
|
const negative = {
|
|
3327
3452
|
documentation: "Number that less than or equal to zero.",
|
|
3328
|
-
|
|
3453
|
+
annotations: { "expect.max": 0 }
|
|
3329
3454
|
};
|
|
3330
3455
|
const positiveOrNegative = {
|
|
3331
3456
|
positive,
|
|
@@ -3339,47 +3464,60 @@ const primitives = {
|
|
|
3339
3464
|
extensions: {
|
|
3340
3465
|
email: {
|
|
3341
3466
|
documentation: "Represents an email address.",
|
|
3342
|
-
expect: {
|
|
3343
|
-
pattern:
|
|
3467
|
+
annotations: { "expect.pattern": {
|
|
3468
|
+
pattern: "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$",
|
|
3344
3469
|
message: "Invalid email format."
|
|
3345
|
-
}
|
|
3470
|
+
} }
|
|
3346
3471
|
},
|
|
3347
3472
|
phone: {
|
|
3348
3473
|
documentation: "Represents an phone number.",
|
|
3349
|
-
expect: {
|
|
3350
|
-
pattern:
|
|
3474
|
+
annotations: { "expect.pattern": {
|
|
3475
|
+
pattern: "^\\+?[0-9\\s-]{10,15}$",
|
|
3351
3476
|
message: "Invalid phone number format."
|
|
3352
|
-
}
|
|
3477
|
+
} }
|
|
3353
3478
|
},
|
|
3354
3479
|
date: {
|
|
3355
3480
|
documentation: "Represents a date string.",
|
|
3356
|
-
expect:
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3481
|
+
annotations: { "expect.pattern": [
|
|
3482
|
+
{
|
|
3483
|
+
pattern: "^\\d{4}-\\d{2}-\\d{2}$",
|
|
3484
|
+
message: "Invalid date format."
|
|
3485
|
+
},
|
|
3486
|
+
{
|
|
3487
|
+
pattern: "^\\d{2}/\\d{2}/\\d{4}$",
|
|
3488
|
+
message: "Invalid date format."
|
|
3489
|
+
},
|
|
3490
|
+
{
|
|
3491
|
+
pattern: "^\\d{2}-\\d{2}-\\d{4}$",
|
|
3492
|
+
message: "Invalid date format."
|
|
3493
|
+
},
|
|
3494
|
+
{
|
|
3495
|
+
pattern: "^\\d{1,2} [A-Za-z]+ \\d{4}$",
|
|
3496
|
+
message: "Invalid date format."
|
|
3497
|
+
}
|
|
3498
|
+
] }
|
|
3365
3499
|
},
|
|
3366
3500
|
isoDate: {
|
|
3367
3501
|
documentation: "Represents a date string in ISO format.",
|
|
3368
|
-
expect: {
|
|
3369
|
-
pattern:
|
|
3502
|
+
annotations: { "expect.pattern": [{
|
|
3503
|
+
pattern: "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?Z$",
|
|
3370
3504
|
message: "Invalid ISO date format."
|
|
3371
|
-
}
|
|
3505
|
+
}, {
|
|
3506
|
+
pattern: "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?([+-]\\d{2}:\\d{2})$",
|
|
3507
|
+
message: "Invalid ISO date format."
|
|
3508
|
+
}] }
|
|
3372
3509
|
},
|
|
3373
3510
|
uuid: {
|
|
3374
3511
|
documentation: "Represents a UUID.",
|
|
3375
|
-
expect: {
|
|
3376
|
-
pattern:
|
|
3512
|
+
annotations: { "expect.pattern": {
|
|
3513
|
+
pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
|
|
3514
|
+
flags: "i",
|
|
3377
3515
|
message: "Invalid UUID format."
|
|
3378
|
-
}
|
|
3516
|
+
} }
|
|
3379
3517
|
},
|
|
3380
3518
|
required: {
|
|
3381
3519
|
documentation: "Non-empty string that contains at least one non-whitespace character.",
|
|
3382
|
-
|
|
3520
|
+
annotations: { "meta.required": true }
|
|
3383
3521
|
}
|
|
3384
3522
|
}
|
|
3385
3523
|
},
|
|
@@ -3399,11 +3537,22 @@ const primitives = {
|
|
|
3399
3537
|
int: {
|
|
3400
3538
|
extensions: positiveOrNegative,
|
|
3401
3539
|
documentation: "Represents an integer number.",
|
|
3402
|
-
|
|
3540
|
+
annotations: { "expect.int": true }
|
|
3403
3541
|
},
|
|
3404
3542
|
timestamp: {
|
|
3405
3543
|
documentation: "Represents a timestamp.",
|
|
3406
|
-
|
|
3544
|
+
annotations: { "expect.int": true },
|
|
3545
|
+
extensions: {
|
|
3546
|
+
created: {
|
|
3547
|
+
documentation: "Timestamp auto-set on creation. Auto-applies @db.default.fn \"now\".",
|
|
3548
|
+
tags: ["created"],
|
|
3549
|
+
annotations: { "db.default.fn": "now" }
|
|
3550
|
+
},
|
|
3551
|
+
updated: {
|
|
3552
|
+
documentation: "Timestamp auto-updated on every write. DB adapters read the \"updated\" tag.",
|
|
3553
|
+
tags: ["updated"]
|
|
3554
|
+
}
|
|
3555
|
+
}
|
|
3407
3556
|
}
|
|
3408
3557
|
}
|
|
3409
3558
|
},
|
|
@@ -3413,7 +3562,7 @@ const primitives = {
|
|
|
3413
3562
|
extensions: {
|
|
3414
3563
|
required: {
|
|
3415
3564
|
documentation: "Boolean that must be true. Useful for checkboxes like \"accept terms\".",
|
|
3416
|
-
|
|
3565
|
+
annotations: { "meta.required": true }
|
|
3417
3566
|
},
|
|
3418
3567
|
true: { documentation: "Represents a true value." },
|
|
3419
3568
|
false: { documentation: "Represents a false value." }
|
|
@@ -3437,6 +3586,148 @@ const primitives = {
|
|
|
3437
3586
|
}
|
|
3438
3587
|
};
|
|
3439
3588
|
|
|
3589
|
+
//#endregion
|
|
3590
|
+
//#region packages/core/src/defaults/ui-annotations.ts
|
|
3591
|
+
const uiAnnotations = {
|
|
3592
|
+
placeholder: new AnnotationSpec({
|
|
3593
|
+
description: "Defines **placeholder text** for UI input fields.\n\n**Example:**\n```atscript\n@ui.placeholder \"Enter your name\"\nname: string\n```\n",
|
|
3594
|
+
nodeType: ["prop", "type"],
|
|
3595
|
+
argument: {
|
|
3596
|
+
name: "text",
|
|
3597
|
+
type: "string",
|
|
3598
|
+
description: "The placeholder text to display in UI input fields."
|
|
3599
|
+
}
|
|
3600
|
+
}),
|
|
3601
|
+
component: new AnnotationSpec({
|
|
3602
|
+
description: "Hints which **UI component** to use when rendering this field.\n\n**Example:**\n```atscript\n@ui.component \"select\"\ncountry: string\n```\n",
|
|
3603
|
+
nodeType: ["prop", "type"],
|
|
3604
|
+
argument: {
|
|
3605
|
+
name: "name",
|
|
3606
|
+
type: "string",
|
|
3607
|
+
description: "The component name or type to use for rendering."
|
|
3608
|
+
}
|
|
3609
|
+
}),
|
|
3610
|
+
hidden: new AnnotationSpec({
|
|
3611
|
+
description: "Hides this field or entity from **UI forms and tables** entirely.\n\n**Example:**\n```atscript\n@ui.hidden\ninternalId: string\n```\n",
|
|
3612
|
+
nodeType: [
|
|
3613
|
+
"prop",
|
|
3614
|
+
"type",
|
|
3615
|
+
"interface"
|
|
3616
|
+
]
|
|
3617
|
+
}),
|
|
3618
|
+
group: new AnnotationSpec({
|
|
3619
|
+
description: "Groups fields into **form sections**. Fields sharing the same group name are rendered together.\n\n**Example:**\n```atscript\n@ui.group \"personal\"\nfirstName: string\n\n@ui.group \"personal\"\nlastName: string\n\n@ui.group \"contact\"\nemail: string.email\n```\n",
|
|
3620
|
+
nodeType: ["prop"],
|
|
3621
|
+
argument: {
|
|
3622
|
+
name: "name",
|
|
3623
|
+
type: "string",
|
|
3624
|
+
description: "The section/group name to place this field in."
|
|
3625
|
+
}
|
|
3626
|
+
}),
|
|
3627
|
+
order: new AnnotationSpec({
|
|
3628
|
+
description: "Sets the **display order** of a field in auto-generated forms. Lower numbers appear first.\n\n**Example:**\n```atscript\n@ui.order 1\nname: string\n\n@ui.order 2\nemail: string.email\n```\n",
|
|
3629
|
+
nodeType: ["prop"],
|
|
3630
|
+
argument: {
|
|
3631
|
+
name: "order",
|
|
3632
|
+
type: "number",
|
|
3633
|
+
description: "Display order number. Lower values appear first."
|
|
3634
|
+
}
|
|
3635
|
+
}),
|
|
3636
|
+
width: new AnnotationSpec({
|
|
3637
|
+
description: "Provides a **layout width hint** for the field in auto-generated forms.\n\n**Example:**\n```atscript\n@ui.width \"half\"\nfirstName: string\n```\n",
|
|
3638
|
+
nodeType: ["prop", "type"],
|
|
3639
|
+
argument: {
|
|
3640
|
+
name: "width",
|
|
3641
|
+
type: "string",
|
|
3642
|
+
description: "Layout width hint (e.g., \"half\", \"full\", \"third\", \"quarter\")."
|
|
3643
|
+
}
|
|
3644
|
+
}),
|
|
3645
|
+
icon: new AnnotationSpec({
|
|
3646
|
+
description: "Provides an **icon hint** for the field or entity.\n\n**Example:**\n```atscript\n@ui.icon \"mail\"\nemail: string.email\n```\n",
|
|
3647
|
+
nodeType: [
|
|
3648
|
+
"prop",
|
|
3649
|
+
"type",
|
|
3650
|
+
"interface"
|
|
3651
|
+
],
|
|
3652
|
+
argument: {
|
|
3653
|
+
name: "name",
|
|
3654
|
+
type: "string",
|
|
3655
|
+
description: "Icon name or identifier."
|
|
3656
|
+
}
|
|
3657
|
+
}),
|
|
3658
|
+
hint: new AnnotationSpec({
|
|
3659
|
+
description: "Provides **help text or tooltip** displayed near the field in UI forms.\n\n**Example:**\n```atscript\n@ui.hint \"Must be a valid business email\"\nemail: string.email\n```\n",
|
|
3660
|
+
nodeType: ["prop", "type"],
|
|
3661
|
+
argument: {
|
|
3662
|
+
name: "text",
|
|
3663
|
+
type: "string",
|
|
3664
|
+
description: "Help text or tooltip content."
|
|
3665
|
+
}
|
|
3666
|
+
}),
|
|
3667
|
+
disabled: new AnnotationSpec({
|
|
3668
|
+
description: "Marks a field as **disabled** (rendered but non-interactive) in UI forms.\n\n**Example:**\n```atscript\n@ui.disabled\nreferralCode: string\n```\n",
|
|
3669
|
+
nodeType: ["prop", "type"]
|
|
3670
|
+
}),
|
|
3671
|
+
type: new AnnotationSpec({
|
|
3672
|
+
description: "Specifies the **input type** for the field in UI forms. Maps to HTML input types or framework-specific equivalents.\n\n**Example:**\n```atscript\n@ui.type \"textarea\"\nbio: string\n\n@ui.type \"password\"\nsecret: string\n```\n",
|
|
3673
|
+
nodeType: ["prop", "type"],
|
|
3674
|
+
argument: {
|
|
3675
|
+
name: "type",
|
|
3676
|
+
type: "string",
|
|
3677
|
+
description: "Input type (e.g., \"text\", \"textarea\", \"password\", \"number\", \"date\", \"color\", \"range\")."
|
|
3678
|
+
}
|
|
3679
|
+
}),
|
|
3680
|
+
attr: new AnnotationSpec({
|
|
3681
|
+
description: "Passes an arbitrary **HTML/component attribute** to the rendered field. Multiple `@ui.attr` annotations can be used on the same field.\n\n**Example:**\n```atscript\n@ui.attr \"rows\", \"5\"\n@ui.attr \"autocomplete\", \"off\"\nbio: string\n```\n",
|
|
3682
|
+
nodeType: [
|
|
3683
|
+
"prop",
|
|
3684
|
+
"type",
|
|
3685
|
+
"interface"
|
|
3686
|
+
],
|
|
3687
|
+
multiple: true,
|
|
3688
|
+
mergeStrategy: "append",
|
|
3689
|
+
argument: [{
|
|
3690
|
+
name: "key",
|
|
3691
|
+
type: "string",
|
|
3692
|
+
description: "Attribute name."
|
|
3693
|
+
}, {
|
|
3694
|
+
name: "value",
|
|
3695
|
+
type: "string",
|
|
3696
|
+
description: "Attribute value."
|
|
3697
|
+
}]
|
|
3698
|
+
}),
|
|
3699
|
+
class: new AnnotationSpec({
|
|
3700
|
+
description: "Adds **CSS class names** to the rendered field or entity. Multiple `@ui.class` annotations are appended.\n\n**Example:**\n```atscript\n@ui.class \"text-bold\"\n@ui.class \"mt-4\"\ntitle: string\n```\n",
|
|
3701
|
+
nodeType: [
|
|
3702
|
+
"prop",
|
|
3703
|
+
"type",
|
|
3704
|
+
"interface"
|
|
3705
|
+
],
|
|
3706
|
+
multiple: true,
|
|
3707
|
+
mergeStrategy: "append",
|
|
3708
|
+
argument: {
|
|
3709
|
+
name: "names",
|
|
3710
|
+
type: "string",
|
|
3711
|
+
description: "One or more CSS class names (space-separated)."
|
|
3712
|
+
}
|
|
3713
|
+
}),
|
|
3714
|
+
style: new AnnotationSpec({
|
|
3715
|
+
description: "Adds **inline CSS styles** to the rendered field or entity. Multiple `@ui.style` annotations are appended.\n\n**Example:**\n```atscript\n@ui.style \"color: red\"\n@ui.style \"font-weight: bold\"\nwarning: string\n```\n",
|
|
3716
|
+
nodeType: [
|
|
3717
|
+
"prop",
|
|
3718
|
+
"type",
|
|
3719
|
+
"interface"
|
|
3720
|
+
],
|
|
3721
|
+
multiple: true,
|
|
3722
|
+
mergeStrategy: "append",
|
|
3723
|
+
argument: {
|
|
3724
|
+
name: "css",
|
|
3725
|
+
type: "string",
|
|
3726
|
+
description: "CSS style declarations (semicolon-separated)."
|
|
3727
|
+
}
|
|
3728
|
+
})
|
|
3729
|
+
};
|
|
3730
|
+
|
|
3440
3731
|
//#endregion
|
|
3441
3732
|
//#region packages/core/src/default-atscript-config.ts
|
|
3442
3733
|
function getDefaultAtscriptConfig() {
|
|
@@ -3445,7 +3736,9 @@ function getDefaultAtscriptConfig() {
|
|
|
3445
3736
|
annotations: {
|
|
3446
3737
|
meta: { ...metaAnnotations },
|
|
3447
3738
|
expect: { ...expectAnnotations },
|
|
3448
|
-
emit: { ...emitAnnotations }
|
|
3739
|
+
emit: { ...emitAnnotations },
|
|
3740
|
+
db: { ...dbAnnotations },
|
|
3741
|
+
ui: { ...uiAnnotations }
|
|
3449
3742
|
}
|
|
3450
3743
|
};
|
|
3451
3744
|
return defaulTAtscriptConfig;
|
|
@@ -3473,7 +3766,7 @@ var PluginManager = class {
|
|
|
3473
3766
|
this._docConfig = {};
|
|
3474
3767
|
if (raw?.primitives) {
|
|
3475
3768
|
this._docConfig.primitives = this._docConfig.primitives || new Map();
|
|
3476
|
-
for (const [key, value] of Object.entries(raw.primitives)) this._docConfig.primitives.set(key, new SemanticPrimitiveNode(key, value));
|
|
3769
|
+
for (const [key, value] of Object.entries(raw.primitives)) this._docConfig.primitives.set(key, new SemanticPrimitiveNode(key, value, "", raw.annotations));
|
|
3477
3770
|
}
|
|
3478
3771
|
if (raw?.annotations) {
|
|
3479
3772
|
this._docConfig.annotations = this._docConfig.annotations || {};
|