@antelopejs/interface-data-api 0.5.0 → 0.6.1

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.
Files changed (139) hide show
  1. package/README.md +26 -22
  2. package/dist/antelope.test.js +1 -5
  3. package/dist/antelope.test.js.map +1 -1
  4. package/dist/components.d.ts +57 -37
  5. package/dist/components.js +36 -68
  6. package/dist/components.js.map +1 -1
  7. package/dist/index.d.ts +44 -60
  8. package/dist/index.js +22 -10
  9. package/dist/index.js.map +1 -1
  10. package/dist/internal/computed.js +12 -4
  11. package/dist/internal/computed.js.map +1 -1
  12. package/dist/internal/controller-types.d.ts +19 -4
  13. package/dist/internal/default-filter.js +11 -1
  14. package/dist/internal/default-filter.js.map +1 -1
  15. package/dist/internal/deferred-joined-stream.js +40 -27
  16. package/dist/internal/deferred-joined-stream.js.map +1 -1
  17. package/dist/internal/extractors.d.ts +25 -0
  18. package/dist/internal/extractors.js +31 -0
  19. package/dist/internal/extractors.js.map +1 -0
  20. package/dist/internal/field-types.d.ts +22 -6
  21. package/dist/internal/filters.d.ts +3 -1
  22. package/dist/internal/filters.js +9 -2
  23. package/dist/internal/filters.js.map +1 -1
  24. package/dist/internal/foreign.js +1 -1
  25. package/dist/internal/foreign.js.map +1 -1
  26. package/dist/internal/guards.d.ts +20 -1
  27. package/dist/internal/guards.js +42 -0
  28. package/dist/internal/guards.js.map +1 -1
  29. package/dist/internal/joined.js +1 -1
  30. package/dist/internal/joined.js.map +1 -1
  31. package/dist/internal/list.d.ts +2 -2
  32. package/dist/internal/list.js +10 -6
  33. package/dist/internal/list.js.map +1 -1
  34. package/dist/internal/meta-records.d.ts +2 -0
  35. package/dist/internal/meta-records.js +10 -5
  36. package/dist/internal/meta-records.js.map +1 -1
  37. package/dist/internal/properties.d.ts +2 -2
  38. package/dist/internal/properties.js +6 -4
  39. package/dist/internal/properties.js.map +1 -1
  40. package/dist/internal/prototypes.d.ts +14 -0
  41. package/dist/internal/prototypes.js +38 -0
  42. package/dist/internal/prototypes.js.map +1 -0
  43. package/dist/internal/read-properties.d.ts +3 -0
  44. package/dist/internal/read-properties.js +29 -0
  45. package/dist/internal/read-properties.js.map +1 -0
  46. package/dist/internal/tables.d.ts +12 -2
  47. package/dist/internal/tables.js +11 -0
  48. package/dist/internal/tables.js.map +1 -1
  49. package/dist/internal/validation.d.ts +4 -3
  50. package/dist/internal/validation.js +18 -6
  51. package/dist/internal/validation.js.map +1 -1
  52. package/dist/metadata.d.ts +13 -13
  53. package/dist/metadata.js +29 -31
  54. package/dist/metadata.js.map +1 -1
  55. package/dist/tests/components/access_control.test.js +11 -3
  56. package/dist/tests/components/access_control.test.js.map +1 -1
  57. package/dist/tests/components/computed.test.js +14 -3
  58. package/dist/tests/components/computed.test.js.map +1 -1
  59. package/dist/tests/components/cross_instance.test.js +2 -2
  60. package/dist/tests/components/cross_instance.test.js.map +1 -1
  61. package/dist/tests/components/deferred_joined.test.js +1 -1
  62. package/dist/tests/components/deferred_joined.test.js.map +1 -1
  63. package/dist/tests/components/extract_generic.test.d.ts +1 -0
  64. package/dist/tests/components/extract_generic.test.js +64 -0
  65. package/dist/tests/components/extract_generic.test.js.map +1 -0
  66. package/dist/tests/components/filter_comparisons.test.d.ts +1 -0
  67. package/dist/tests/components/filter_comparisons.test.js +34 -0
  68. package/dist/tests/components/filter_comparisons.test.js.map +1 -0
  69. package/dist/tests/components/filter_list_route.test.d.ts +1 -0
  70. package/dist/tests/components/filter_list_route.test.js +182 -0
  71. package/dist/tests/components/filter_list_route.test.js.map +1 -0
  72. package/dist/tests/components/filter_options.test.js +3 -3
  73. package/dist/tests/components/filter_options.test.js.map +1 -1
  74. package/dist/tests/components/foreign_computed.test.js +15 -4
  75. package/dist/tests/components/foreign_computed.test.js.map +1 -1
  76. package/dist/tests/components/foreign_joined.test.js +16 -4
  77. package/dist/tests/components/foreign_joined.test.js.map +1 -1
  78. package/dist/tests/components/joined.test.js +26 -8
  79. package/dist/tests/components/joined.test.js.map +1 -1
  80. package/dist/tests/components/listable.test.js +31 -12
  81. package/dist/tests/components/listable.test.js.map +1 -1
  82. package/dist/tests/components/localized_sort.test.js +5 -1
  83. package/dist/tests/components/localized_sort.test.js.map +1 -1
  84. package/dist/tests/components/mandatory.test.js +1 -1
  85. package/dist/tests/components/mandatory.test.js.map +1 -1
  86. package/dist/tests/components/modifier_key.test.js +12 -4
  87. package/dist/tests/components/modifier_key.test.js.map +1 -1
  88. package/dist/tests/components/parse_body.test.js +17 -16
  89. package/dist/tests/components/parse_body.test.js.map +1 -1
  90. package/dist/tests/components/parsed_value.test.js +2 -2
  91. package/dist/tests/components/parsed_value.test.js.map +1 -1
  92. package/dist/tests/components/partial_edit.test.js +8 -3
  93. package/dist/tests/components/partial_edit.test.js.map +1 -1
  94. package/dist/tests/components/per_action_access.test.js +11 -3
  95. package/dist/tests/components/per_action_access.test.js.map +1 -1
  96. package/dist/tests/components/prototype_named_filter.test.d.ts +1 -0
  97. package/dist/tests/components/prototype_named_filter.test.js +87 -0
  98. package/dist/tests/components/prototype_named_filter.test.js.map +1 -0
  99. package/dist/tests/components/validator.test.js +1 -1
  100. package/dist/tests/components/validator.test.js.map +1 -1
  101. package/dist/tests/components/written_row.test.d.ts +1 -0
  102. package/dist/tests/components/written_row.test.js +99 -0
  103. package/dist/tests/components/written_row.test.js.map +1 -0
  104. package/dist/tests/helpers/other-instance.js +8 -4
  105. package/dist/tests/helpers/other-instance.js.map +1 -1
  106. package/dist/tests/index/field_decorator_target.test.d.ts +1 -0
  107. package/dist/tests/index/field_decorator_target.test.js +20 -0
  108. package/dist/tests/index/field_decorator_target.test.js.map +1 -0
  109. package/dist/tests/index/inheritance.test.js +6 -2
  110. package/dist/tests/index/inheritance.test.js.map +1 -1
  111. package/dist/tests/index/metadata_names.test.d.ts +1 -0
  112. package/dist/tests/index/metadata_names.test.js +91 -0
  113. package/dist/tests/index/metadata_names.test.js.map +1 -0
  114. package/dist/tests/index/routes.test.js +14 -5
  115. package/dist/tests/index/routes.test.js.map +1 -1
  116. package/dist/tests/integration.test.js +24 -11
  117. package/dist/tests/integration.test.js.map +1 -1
  118. package/dist/tests/internal/controller-fields.test.d.ts +1 -0
  119. package/dist/tests/internal/controller-fields.test.js +25 -0
  120. package/dist/tests/internal/controller-fields.test.js.map +1 -0
  121. package/dist/tests/internal/http.d.ts +21 -9
  122. package/dist/tests/internal/http.js +24 -3
  123. package/dist/tests/internal/http.js.map +1 -1
  124. package/dist/tests/internal/shop-schema.d.ts +28 -20
  125. package/dist/tests/utils.d.ts +5 -7
  126. package/dist/tests/utils.js.map +1 -1
  127. package/docs/1.introduction.md +38 -35
  128. package/docs/10.joined.md +51 -40
  129. package/docs/11.computed.md +91 -47
  130. package/docs/2.data-controllers.md +50 -42
  131. package/docs/3.routes.md +150 -133
  132. package/docs/4.access-rights.md +48 -44
  133. package/docs/5.validators.md +54 -45
  134. package/docs/6.listable.md +53 -49
  135. package/docs/7.foreign-keys.md +55 -48
  136. package/docs/8.filters.md +64 -57
  137. package/docs/9.modifiers.md +29 -25
  138. package/package.json +17 -14
  139. package/skills/data-api-interface/SKILL.md +9 -3
@@ -1,3 +1,4 @@
1
+ import { z } from "zod";
1
2
  import { type RequestContext } from "@antelopejs/interface-api";
2
3
  /**
3
4
  * Builds the context a route handler receives for a request that never
@@ -7,17 +8,28 @@ import { type RequestContext } from "@antelopejs/interface-api";
7
8
  * @returns A complete request context
8
9
  */
9
10
  export declare function createRequestContext(url?: string): RequestContext;
10
- /** What the `list` route answers: one page of rows and where it sits. */
11
- export interface ListResponse<Row> {
12
- results: Row[];
13
- total: number;
14
- offset: number;
15
- limit: number;
16
- }
17
11
  /**
18
- * Reads a response body as the JSON shape the caller expects.
12
+ * Schema of what the `list` route answers around a row schema: one page of
13
+ * rows and where it sits. Fields the row schema does not name pass through.
19
14
  *
15
+ * @param row Schema of one listed row
16
+ * @returns Schema of one page of rows
17
+ */
18
+ export declare function listResponseSchema<Row extends z.ZodType>(row: Row): z.ZodObject<{
19
+ results: z.ZodArray<Row>;
20
+ total: z.ZodNumber;
21
+ offset: z.ZodNumber;
22
+ limit: z.ZodNumber;
23
+ }, z.core.$loose>;
24
+ /** What the `new` route answers: the created row, with its key. */
25
+ export declare const createdRowSchema: z.ZodObject<{
26
+ _id: z.ZodString;
27
+ }, z.core.$loose>;
28
+ /**
29
+ * Reads a response body and parses it with the schema the caller expects.
30
+ *
31
+ * @param schema Shape the body must have
20
32
  * @param response The response whose body is read
21
33
  * @returns The parsed body
22
34
  */
23
- export declare function readJson<T>(response: Response): Promise<T>;
35
+ export declare function readJson<Schema extends z.ZodType>(schema: Schema, response: Response): Promise<z.infer<Schema>>;
@@ -1,10 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createdRowSchema = void 0;
3
4
  exports.createRequestContext = createRequestContext;
5
+ exports.listResponseSchema = listResponseSchema;
4
6
  exports.readJson = readJson;
5
7
  /** @internal @packageDocumentation */
6
8
  const node_http_1 = require("node:http");
7
9
  const node_net_1 = require("node:net");
10
+ const zod_1 = require("zod");
8
11
  const interface_api_1 = require("@antelopejs/interface-api");
9
12
  /**
10
13
  * Builds the context a route handler receives for a request that never
@@ -24,12 +27,30 @@ function createRequestContext(url = "http://localhost/") {
24
27
  };
25
28
  }
26
29
  /**
27
- * Reads a response body as the JSON shape the caller expects.
30
+ * Schema of what the `list` route answers around a row schema: one page of
31
+ * rows and where it sits. Fields the row schema does not name pass through.
28
32
  *
33
+ * @param row Schema of one listed row
34
+ * @returns Schema of one page of rows
35
+ */
36
+ function listResponseSchema(row) {
37
+ return zod_1.z.looseObject({
38
+ results: zod_1.z.array(row),
39
+ total: zod_1.z.number(),
40
+ offset: zod_1.z.number(),
41
+ limit: zod_1.z.number(),
42
+ });
43
+ }
44
+ /** What the `new` route answers: the created row, with its key. */
45
+ exports.createdRowSchema = zod_1.z.looseObject({ _id: zod_1.z.string() });
46
+ /**
47
+ * Reads a response body and parses it with the schema the caller expects.
48
+ *
49
+ * @param schema Shape the body must have
29
50
  * @param response The response whose body is read
30
51
  * @returns The parsed body
31
52
  */
32
- async function readJson(response) {
33
- return JSON.parse(await response.text());
53
+ async function readJson(schema, response) {
54
+ return schema.parse(JSON.parse(await response.text()));
34
55
  }
35
56
  //# sourceMappingURL=http.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"http.js","sourceRoot":"","sources":["../../../src/tests/internal/http.ts"],"names":[],"mappings":";;AAYA,oDAWC;AAgBD,4BAEC;AAzCD,sCAAsC;AACtC,yCAA4D;AAC5D,uCAAkC;AAClC,6DAA4E;AAE5E;;;;;;GAMG;AACH,SAAgB,oBAAoB,CAClC,GAAG,GAAG,mBAAmB;IAEzB,MAAM,UAAU,GAAG,IAAI,2BAAe,CAAC,IAAI,iBAAM,EAAE,CAAC,CAAC;IACrD,OAAO;QACL,UAAU;QACV,WAAW,EAAE,IAAI,0BAAc,CAAC,UAAU,CAAC;QAC3C,GAAG,EAAE,IAAI,GAAG,CAAC,GAAG,CAAC;QACjB,eAAe,EAAE,EAAE;QACnB,QAAQ,EAAE,IAAI,0BAAU,EAAE;KAC3B,CAAC;AACJ,CAAC;AAUD;;;;;GAKG;AACI,KAAK,UAAU,QAAQ,CAAI,QAAkB;IAClD,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;AAC3C,CAAC"}
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../../../src/tests/internal/http.ts"],"names":[],"mappings":";;;AAaA,oDAWC;AASD,gDAOC;AAYD,4BAKC;AAzDD,sCAAsC;AACtC,yCAA4D;AAC5D,uCAAkC;AAClC,6BAAwB;AACxB,6DAA4E;AAE5E;;;;;;GAMG;AACH,SAAgB,oBAAoB,CAClC,GAAG,GAAG,mBAAmB;IAEzB,MAAM,UAAU,GAAG,IAAI,2BAAe,CAAC,IAAI,iBAAM,EAAE,CAAC,CAAC;IACrD,OAAO;QACL,UAAU;QACV,WAAW,EAAE,IAAI,0BAAc,CAAC,UAAU,CAAC;QAC3C,GAAG,EAAE,IAAI,GAAG,CAAC,GAAG,CAAC;QACjB,eAAe,EAAE,EAAE;QACnB,QAAQ,EAAE,IAAI,0BAAU,EAAE;KAC3B,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,kBAAkB,CAAwB,GAAQ;IAChE,OAAO,OAAC,CAAC,WAAW,CAAC;QACnB,OAAO,EAAE,OAAC,CAAC,KAAK,CAAC,GAAG,CAAC;QACrB,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE;QACjB,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE;QAClB,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE;KAClB,CAAC,CAAC;AACL,CAAC;AAED,mEAAmE;AACtD,QAAA,gBAAgB,GAAG,OAAC,CAAC,WAAW,CAAC,EAAE,GAAG,EAAE,OAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAEnE;;;;;;GAMG;AACI,KAAK,UAAU,QAAQ,CAC5B,MAAc,EACd,QAAkB;IAElB,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AACzD,CAAC"}
@@ -63,18 +63,20 @@ declare const CustomerModel_base: {
63
63
  readonly table: import("@antelopejs/interface-database").Table<Customer>;
64
64
  readonly database: import("@antelopejs/interface-database").SchemaInstance<any>;
65
65
  get(id: string): PromiseLike<Customer | undefined>;
66
- getBy(index: keyof Customer, ...keys: any[]): PromiseLike<Customer[]>;
66
+ getBy(index: keyof Customer, ...keys: import("@antelopejs/interface-database-decorators").IndexKey[]): PromiseLike<Customer[]>;
67
67
  getAll(): PromiseLike<Customer[]>;
68
68
  insert(obj: import("@antelopejs/interface-database-decorators").DeepPartial<Customer> | import("@antelopejs/interface-database-decorators").DeepPartial<Customer>[], options?: import("@antelopejs/interface-database-decorators").ValidateOptions): Promise<string[]>;
69
69
  update(id: string, obj: import("@antelopejs/interface-database-decorators").DeepPartial<Customer>, options?: import("@antelopejs/interface-database-decorators").ValidateOptions): Promise<number>;
70
- update(obj: import("@antelopejs/interface-database-decorators").DeepPartial<Customer>, options?: import("@antelopejs/interface-database-decorators").ValidateOptions): Promise<number>;
70
+ update(obj: import("@antelopejs/interface-database-decorators").DeepPartial<Customer> & {
71
+ _id: string;
72
+ }, options?: import("@antelopejs/interface-database-decorators").ValidateOptions): Promise<number>;
71
73
  delete(id: string): Promise<number>;
72
74
  };
73
- readonly schemaName: string;
75
+ readonly schemaName: string | undefined;
74
76
  readonly tableClass: import("@antelopejs/interface-database-decorators").Constructible<Customer>;
75
- fromPlainData(obj: any): Customer;
77
+ fromPlainData(obj: Customer | import("@antelopejs/interface-database-decorators").PlainData<Customer>): Customer;
76
78
  fromDatabase(obj: any): Customer | undefined;
77
- toDatabase(obj: any): Record<string, any>;
79
+ toDatabase(obj: Customer | import("@antelopejs/interface-database-decorators").PlainData<Customer>): import("@antelopejs/interface-database-decorators").StoredRow<Customer>;
78
80
  validate(obj: unknown, options?: import("@antelopejs/interface-database-decorators").ValidateInputOptions): import("@antelopejs/interface-database-decorators").ValidationResult;
79
81
  };
80
82
  export declare class CustomerModel extends CustomerModel_base {
@@ -84,18 +86,20 @@ declare const ProductModel_base: {
84
86
  readonly table: import("@antelopejs/interface-database").Table<Product>;
85
87
  readonly database: import("@antelopejs/interface-database").SchemaInstance<any>;
86
88
  get(id: string): PromiseLike<Product | undefined>;
87
- getBy(index: keyof Product, ...keys: any[]): PromiseLike<Product[]>;
89
+ getBy(index: keyof Product, ...keys: import("@antelopejs/interface-database-decorators").IndexKey[]): PromiseLike<Product[]>;
88
90
  getAll(): PromiseLike<Product[]>;
89
91
  insert(obj: import("@antelopejs/interface-database-decorators").DeepPartial<Product> | import("@antelopejs/interface-database-decorators").DeepPartial<Product>[], options?: import("@antelopejs/interface-database-decorators").ValidateOptions): Promise<string[]>;
90
92
  update(id: string, obj: import("@antelopejs/interface-database-decorators").DeepPartial<Product>, options?: import("@antelopejs/interface-database-decorators").ValidateOptions): Promise<number>;
91
- update(obj: import("@antelopejs/interface-database-decorators").DeepPartial<Product>, options?: import("@antelopejs/interface-database-decorators").ValidateOptions): Promise<number>;
93
+ update(obj: import("@antelopejs/interface-database-decorators").DeepPartial<Product> & {
94
+ _id: string;
95
+ }, options?: import("@antelopejs/interface-database-decorators").ValidateOptions): Promise<number>;
92
96
  delete(id: string): Promise<number>;
93
97
  };
94
- readonly schemaName: string;
98
+ readonly schemaName: string | undefined;
95
99
  readonly tableClass: import("@antelopejs/interface-database-decorators").Constructible<Product>;
96
- fromPlainData(obj: any): Product;
100
+ fromPlainData(obj: Product | import("@antelopejs/interface-database-decorators").PlainData<Product>): Product;
97
101
  fromDatabase(obj: any): Product | undefined;
98
- toDatabase(obj: any): Record<string, any>;
102
+ toDatabase(obj: Product | import("@antelopejs/interface-database-decorators").PlainData<Product>): import("@antelopejs/interface-database-decorators").StoredRow<Product>;
99
103
  validate(obj: unknown, options?: import("@antelopejs/interface-database-decorators").ValidateInputOptions): import("@antelopejs/interface-database-decorators").ValidationResult;
100
104
  };
101
105
  export declare class ProductModel extends ProductModel_base {
@@ -105,18 +109,20 @@ declare const OrderModel_base: {
105
109
  readonly table: import("@antelopejs/interface-database").Table<Order>;
106
110
  readonly database: import("@antelopejs/interface-database").SchemaInstance<any>;
107
111
  get(id: string): PromiseLike<Order | undefined>;
108
- getBy(index: keyof Order, ...keys: any[]): PromiseLike<Order[]>;
112
+ getBy(index: keyof Order, ...keys: import("@antelopejs/interface-database-decorators").IndexKey[]): PromiseLike<Order[]>;
109
113
  getAll(): PromiseLike<Order[]>;
110
114
  insert(obj: import("@antelopejs/interface-database-decorators").DeepPartial<Order> | import("@antelopejs/interface-database-decorators").DeepPartial<Order>[], options?: import("@antelopejs/interface-database-decorators").ValidateOptions): Promise<string[]>;
111
115
  update(id: string, obj: import("@antelopejs/interface-database-decorators").DeepPartial<Order>, options?: import("@antelopejs/interface-database-decorators").ValidateOptions): Promise<number>;
112
- update(obj: import("@antelopejs/interface-database-decorators").DeepPartial<Order>, options?: import("@antelopejs/interface-database-decorators").ValidateOptions): Promise<number>;
116
+ update(obj: import("@antelopejs/interface-database-decorators").DeepPartial<Order> & {
117
+ _id: string;
118
+ }, options?: import("@antelopejs/interface-database-decorators").ValidateOptions): Promise<number>;
113
119
  delete(id: string): Promise<number>;
114
120
  };
115
- readonly schemaName: string;
121
+ readonly schemaName: string | undefined;
116
122
  readonly tableClass: import("@antelopejs/interface-database-decorators").Constructible<Order>;
117
- fromPlainData(obj: any): Order;
123
+ fromPlainData(obj: Order | import("@antelopejs/interface-database-decorators").PlainData<Order>): Order;
118
124
  fromDatabase(obj: any): Order | undefined;
119
- toDatabase(obj: any): Record<string, any>;
125
+ toDatabase(obj: Order | import("@antelopejs/interface-database-decorators").PlainData<Order>): import("@antelopejs/interface-database-decorators").StoredRow<Order>;
120
126
  validate(obj: unknown, options?: import("@antelopejs/interface-database-decorators").ValidateInputOptions): import("@antelopejs/interface-database-decorators").ValidationResult;
121
127
  };
122
128
  export declare class OrderModel extends OrderModel_base {
@@ -126,18 +132,20 @@ declare const OrderItemModel_base: {
126
132
  readonly table: import("@antelopejs/interface-database").Table<OrderItem>;
127
133
  readonly database: import("@antelopejs/interface-database").SchemaInstance<any>;
128
134
  get(id: string): PromiseLike<OrderItem | undefined>;
129
- getBy(index: keyof OrderItem, ...keys: any[]): PromiseLike<OrderItem[]>;
135
+ getBy(index: keyof OrderItem, ...keys: import("@antelopejs/interface-database-decorators").IndexKey[]): PromiseLike<OrderItem[]>;
130
136
  getAll(): PromiseLike<OrderItem[]>;
131
137
  insert(obj: import("@antelopejs/interface-database-decorators").DeepPartial<OrderItem> | import("@antelopejs/interface-database-decorators").DeepPartial<OrderItem>[], options?: import("@antelopejs/interface-database-decorators").ValidateOptions): Promise<string[]>;
132
138
  update(id: string, obj: import("@antelopejs/interface-database-decorators").DeepPartial<OrderItem>, options?: import("@antelopejs/interface-database-decorators").ValidateOptions): Promise<number>;
133
- update(obj: import("@antelopejs/interface-database-decorators").DeepPartial<OrderItem>, options?: import("@antelopejs/interface-database-decorators").ValidateOptions): Promise<number>;
139
+ update(obj: import("@antelopejs/interface-database-decorators").DeepPartial<OrderItem> & {
140
+ _id: string;
141
+ }, options?: import("@antelopejs/interface-database-decorators").ValidateOptions): Promise<number>;
134
142
  delete(id: string): Promise<number>;
135
143
  };
136
- readonly schemaName: string;
144
+ readonly schemaName: string | undefined;
137
145
  readonly tableClass: import("@antelopejs/interface-database-decorators").Constructible<OrderItem>;
138
- fromPlainData(obj: any): OrderItem;
146
+ fromPlainData(obj: OrderItem | import("@antelopejs/interface-database-decorators").PlainData<OrderItem>): OrderItem;
139
147
  fromDatabase(obj: any): OrderItem | undefined;
140
- toDatabase(obj: any): Record<string, any>;
148
+ toDatabase(obj: OrderItem | import("@antelopejs/interface-database-decorators").PlainData<OrderItem>): import("@antelopejs/interface-database-decorators").StoredRow<OrderItem>;
141
149
  validate(obj: unknown, options?: import("@antelopejs/interface-database-decorators").ValidateInputOptions): import("@antelopejs/interface-database-decorators").ValidationResult;
142
150
  };
143
151
  export declare class OrderItemModel extends OrderItemModel_base {
@@ -1,4 +1,4 @@
1
- import type { FieldValue, FieldValues } from "../metadata";
1
+ import type { FieldValue } from "../metadata";
2
2
  /**
3
3
  * Returns the name of the test function that called it.
4
4
  *
@@ -13,12 +13,10 @@ export declare function getRequest(functionName: string, queryParams?: Record<st
13
13
  export declare function listRequest(functionName: string, queryParams?: Record<string, string>): Promise<Response>;
14
14
  export declare function editRequest(functionName: string, payload: FieldValue, queryParams?: Record<string, string>): Promise<Response>;
15
15
  export declare function deleteRequest(functionName: string, queryParams?: Record<string, string>): Promise<Response>;
16
- export declare function validateObject<T>(object: T, expectedObject: Partial<T>, fieldsToCheck: (keyof T)[]): Promise<void>;
16
+ export declare function validateObject<T, E>(object: T, expectedObject: Partial<E>, fieldsToCheck: (keyof T & keyof E)[]): Promise<void>;
17
17
  export declare function getSchemaInstance(schemaName: string): import("@antelopejs/interface-database").SchemaInstance<any>;
18
18
  export declare function validateObjectList<T extends {
19
19
  _id: string;
20
- }>(objectList: T[], expectedObjectList: Partial<T>[], fieldsToCheck: (keyof T)[]): Promise<void>;
21
- /** What the `new` route answers: the created row, with its key. */
22
- export interface CreatedRow extends FieldValues {
23
- _id: string;
24
- }
20
+ }, E extends {
21
+ _id?: unknown;
22
+ }>(objectList: T[], expectedObjectList: Partial<E>[], fieldsToCheck: (keyof T & keyof E)[]): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/tests/utils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/tests/utils.ts"],"names":[],"mappings":"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"}
@@ -1,21 +1,27 @@
1
- # Expose database records through an API
1
+ ---
2
+ title: Expose database records through an API
3
+ description: Build a first data controller that serves CRUD routes for a users table, then check which fields it reads and writes.
4
+ mode: tutorial
5
+ ---
2
6
 
3
7
  ## Overview
4
8
 
5
- Use `@antelopejs/interface-data-api` to expose a table through named CRUD routes and an explicit field policy. A data controller connects the table model to HTTP handlers; its metadata decides which fields clients may read, write, list, and filter.
9
+ In this tutorial, we expose a `users` table through five CRUD routes with `@antelopejs/interface-data-api`. A data controller connects the table model to HTTP handlers. Its metadata decides which fields clients can read, write, list and filter.
6
10
 
7
11
  ## Build one resource first
8
12
 
9
- 1. **Prepare the database.** Define a table and register its schema with database-decorators. Your AntelopeJS module needs a database provider and an API provider.
10
- 2. **Choose the routes.** Start with the [route set](./3.routes.md) your clients need, rather than exposing every write operation by default.
11
- 3. **Set the field policy.** Add [access rights](./4.access-rights.md), [validators](./5.validators.md), and [list fields](./6.listable.md). Field permissions are not user authentication or row-level authorization.
12
- 4. **Check one read and one rejected write.** Confirm the response excludes private fields and that invalid input cannot reach storage. Then add [relationships](./7.foreign-keys.md) and [filters](./8.filters.md).
13
+ We build one resource in four steps.
13
14
 
14
- ## Quick Start
15
+ 1. **Prepare the database.** Define a table and register its schema with database-decorators. The AntelopeJS project needs a database module and an API module.
16
+ 2. **Choose the routes.** Start with the [route set](./3.routes.md) your clients need, instead of exposing every write route by default.
17
+ 3. **Set the field policy.** Add [access rights](./4.access-rights.md), [validators](./5.validators.md) and [list fields](./6.listable.md). Field permissions do not authenticate users and do not authorize access to rows.
18
+ 4. **Check one read and one rejected write.** Confirm that the response leaves out private fields and that invalid input never reaches storage. Then add [relationships](./7.foreign-keys.md) and [filters](./8.filters.md).
15
19
 
16
- The following example declares all five CRUD routes for a `users` table. It assumes a configured provider and an existing `my-database` instance. Import the table declaration before calling `await RegisterSchema("myapp")` in the declaring module's initialization; await that setup before serving requests.
20
+ ## Quick start
17
21
 
18
- This is a field-policy example, not a production account system: write-only prevents a password from appearing in responses, but does not hash it or authenticate requests. Add an appropriate [database modifier](./9.modifiers.md) and your authentication policy before accepting real credentials.
22
+ The example declares all five CRUD routes for a `users` table. It assumes a configured database module and an existing `my-database` instance. Import the table declaration, then `await RegisterSchema("myapp")` when the declaring module initializes. Wait for that setup to finish before the module serves requests.
23
+
24
+ This example sets a field policy. It is not a production account system. `AccessMode.WriteOnly` keeps the password out of responses, but it does not hash the password or authenticate requests. Add a [database modifier](./9.modifiers.md) and your authentication policy before you accept real credentials.
19
25
 
20
26
  ```ts docs-check
21
27
  import { Controller } from "@antelopejs/interface-api";
@@ -40,7 +46,6 @@ import {
40
46
  Table,
41
47
  } from "@antelopejs/interface-database-decorators";
42
48
 
43
- // Define the database table
44
49
  @RegisterTable("users", "myapp")
45
50
  class User extends Table {
46
51
  @Field("string")
@@ -57,10 +62,8 @@ class User extends Table {
57
62
  declare password: string;
58
63
  }
59
64
 
60
- // Create the database model
61
65
  class UserModel extends BasicDataModel(User, "users") {}
62
66
 
63
- // Create the data controller
64
67
  @RegisterDataController()
65
68
  class UserAPI extends DataController(
66
69
  User,
@@ -91,38 +94,38 @@ class UserAPI extends DataController(
91
94
  }
92
95
  ```
93
96
 
94
- This generates the following REST endpoints:
97
+ The controller serves these REST endpoints.
95
98
 
96
- | Method | Endpoint | Description |
97
- | ------ | ----------------------- | -------------------------- |
98
- | GET | `/users/get?id=<id>` | Retrieve a single user |
99
- | GET | `/users/list?limit=10` | List users with pagination |
100
- | POST | `/users/new` | Create a new user |
101
- | PUT | `/users/edit?id=<id>` | Update an existing user |
102
- | DELETE | `/users/delete?id=<id>` | Delete a user |
99
+ | Method | Endpoint | Description |
100
+ | ------ | ----------------------- | -------------------- |
101
+ | GET | `/users/get?id=<id>` | Read one user |
102
+ | GET | `/users/list?limit=10` | List users, one page |
103
+ | POST | `/users/new` | Create a user |
104
+ | PUT | `/users/edit?id=<id>` | Update a user |
105
+ | DELETE | `/users/delete?id=<id>` | Delete a user |
103
106
 
104
107
  ### Check the field policy
105
108
 
106
- Use disposable data and your configured API base URL. Create a user with `name`, `email`, and a dummy `password`; the response is an array of inserted IDs. Use the first ID for the following checks:
109
+ Use disposable data and the base URL of your API module. First, create a user with `POST /users/new` and a body that holds `name`, `email` and a dummy `password`. The response is the created user, with its `_id` and without `password`. Use that `_id` in the next checks.
107
110
 
108
- 1. `GET /users/get?id=<id>` returns the readable fields, never `password`.
109
- 2. `GET /users/list?limit=10` includes `_id`, `name`, and `email` in `results`.
110
- 3. `PUT /users/edit?id=<id>` with `{ "name": "Updated" }` preserves the omitted email. With `{ "email": "" }`, it returns `400` because email is mandatory on edit.
111
+ 1. `GET /users/get?id=<id>` returns the readable fields. You see `_id`, `email` and `name`, and never `password`.
112
+ 2. `GET /users/list?limit=10` returns `_id`, `name` and `email` for each entry of `results`.
113
+ 3. `PUT /users/edit?id=<id>` with `{ "name": "Updated" }` keeps the email you did not send. With `{ "email": "" }`, the route answers `400`, because `email` is mandatory on `edit`.
111
114
 
112
- `@Mandatory` does not check email syntax or password strength. Add [validators](./5.validators.md) before accepting untrusted input.
115
+ `@Mandatory` does not check the email syntax or the password strength. Add [validators](./5.validators.md) before you accept untrusted input.
113
116
 
114
117
  ## Core guides
115
118
 
116
- - [Data Controllers](./2.data-controllers.md) - Create and configure data controllers
117
- - [Routes](./3.routes.md) - Default routes, custom routes, and route options
118
- - [Access Rights](./4.access-rights.md) - Control field read/write permissions
119
- - [Validators](./5.validators.md) - Field-level validation and mandatory fields
120
- - [Listable Fields](./6.listable.md) - Pagination, pluck modes, and sorting
119
+ - [Data controllers](./2.data-controllers.md). Create and configure data controllers.
120
+ - [Routes](./3.routes.md). Default routes, custom routes and route options.
121
+ - [Access rights](./4.access-rights.md). Which fields each action reads and writes.
122
+ - [Validators](./5.validators.md). Field validation and mandatory fields.
123
+ - [Listable fields](./6.listable.md). Pagination, pluck modes and sorting.
121
124
 
122
125
  ## Relationships and advanced queries
123
126
 
124
- - [Foreign Keys](./7.foreign-keys.md) - Establish relationships between tables
125
- - [Filters](./8.filters.md) - Add filtering capabilities to list endpoints
126
- - [Modifiers](./9.modifiers.md) - Automatic data transformation with database-decorators
127
- - [Joined Fields](./10.joined.md) - Flatten a remote field onto rows for native sort/filter
128
- - [Computed Fields](./11.computed.md) - In-database computed values, sortable and pageable natively
127
+ - [Foreign keys](./7.foreign-keys.md). Relations between tables.
128
+ - [Filters](./8.filters.md). Filters on list endpoints.
129
+ - [Modifiers](./9.modifiers.md). Data transformation with database-decorators modifiers.
130
+ - [Joined fields](./10.joined.md). A remote field flattened onto each row, sortable and filterable in the database.
131
+ - [Computed fields](./11.computed.md). Values computed in the database, sortable and pageable there.
package/docs/10.joined.md CHANGED
@@ -1,18 +1,22 @@
1
- # Joined Fields
1
+ ---
2
+ title: Joined fields
3
+ description: Flatten one field of another table onto each row with @Joined, so list requests sort and filter on it in the database.
4
+ mode: reference
5
+ ---
2
6
 
3
7
  ## Overview
4
8
 
5
- The `@Joined` decorator imports a single scalar field from another table and flattens it onto each row at query time. Unlike `@Foreign`, which embeds the full referenced record as a nested object in the response, `@Joined` copies one remote field to the top level of the row before sorting and filtering are applied. This makes the joined field natively sortable, filterable, and searchable in the database.
9
+ The `@Joined` decorator copies one scalar field from another table onto each row at query time. `@Foreign` nests the whole referenced record under the field. `@Joined` puts one remote field at the top level of the row, before the list query sorts and filters. The database can therefore sort, filter, and search on the joined field.
6
10
 
7
- Joined fields are read-only: the value is set by a lookup at query time and is never persisted on the controller's own table. Declaring a field as joined automatically sets its access mode to read-only and forces non-indexed sorting.
11
+ Joined fields are meant to be read-only. A lookup sets the value at query time, and the controller's own table never stores it. `@Joined` sets the field's access mode to read-only when it runs. An `@Access` written above it runs later and replaces that mode, so do not combine the two. `@Joined` also makes the field's sort non-indexed, whatever the decorator order.
8
12
 
9
- ## Basic Usage
13
+ ## Basic usage
10
14
 
11
- Apply `@Joined` to a field that should be populated from a remote table. The local row must carry a foreign key (`localKey`) that matches the remote table's `remoteIndex`.
15
+ Apply `@Joined` to a field that takes its value from a remote table. The local row must hold a key (`localKey`) that matches the remote table's `remoteIndex`.
12
16
 
13
- Use [foreign keys](./7.foreign-keys.md) when the response needs a nested record instead. The snippets below assume the controller imports and model from the [quick start](./1.introduction.md#quick-start), with `myapp` registered before requests are served.
17
+ Use [foreign keys](./7.foreign-keys.md) when the response needs a nested record instead. The snippets below assume the controller imports and the model from the [quick start](./1.introduction.md#quick-start), with `myapp` registered before the module serves requests.
14
18
 
15
- ```typescript
19
+ ```typescript fragment
16
20
  import { Joined } from "@antelopejs/interface-data-api/metadata";
17
21
  import {
18
22
  Field,
@@ -84,25 +88,27 @@ A GET request to `/orders/get?id=order-123` returns the joined value at the top
84
88
  }
85
89
  ```
86
90
 
87
- ## Decorator Parameters
91
+ ## Decorator parameters
88
92
 
89
- ```typescript
93
+ ```typescript fragment
90
94
  @Joined({ table, localKey, remoteField, remoteIndex?, schema? })
91
95
  ```
92
96
 
93
97
  | Option | Type | Required | Default | Description |
94
98
  | ------------- | -------------------------- | -------- | ----------------- | -------------------------------------------------------------------------------------------------- |
95
- | `table` | `Class<Table>` or `string` | Yes | - | The remote table class or registered table name string |
96
- | `localKey` | `string` | Yes | - | The field on the local row holding the value matched against the remote table |
97
- | `remoteField` | `string` | Yes | - | The remote field whose value is flattened onto the row |
99
+ | `table` | `Class<Table>` or `string` | Yes | None | The remote table class or registered table name string |
100
+ | `localKey` | `string` | Yes | None | The field on the local row holding the value matched against the remote table |
101
+ | `remoteField` | `string` | Yes | None | The remote field whose value is flattened onto the row |
98
102
  | `remoteIndex` | `string` | No | `"_id"` | The index on the remote table to match `localKey` against |
99
103
  | `schema` | `string` | No | Controller schema | Name of the schema the remote table is registered in, when it differs from the controller's schema |
100
104
 
101
- ## Sorting and Filtering
105
+ When a list mode includes a joined field, the mode also selects the field's `localKey`.
102
106
 
103
- Because the join is applied to the underlying query before sorting and filtering, joined fields behave like native columns. Combine `@Joined` with `@Sortable` or `@Filter` to order or filter by the imported value.
107
+ ## Sorting and filtering
104
108
 
105
- ```typescript
109
+ The list query applies the join before it sorts and filters, so a joined field behaves like a stored column. Combine `@Joined` with `@Sortable` or `@Filter` to sort or filter on the joined value.
110
+
111
+ ```typescript fragment
106
112
  @Sortable()
107
113
  @Filter()
108
114
  @Joined({
@@ -113,30 +119,35 @@ Because the join is applied to the underlying query before sorting and filtering
113
119
  declare userName: string;
114
120
  ```
115
121
 
116
- Joined fields are always sorted without a database index (`@Sortable` is forced to non-indexed for them), since the value comes from a runtime lookup rather than a stored column.
122
+ A joined field always sorts without a database index, because its value comes from a lookup at query time and not from a stored column. `@Sortable` on a joined field is always non-indexed.
117
123
 
118
- ## Deferred Resolution on Lists
124
+ ## Deferred resolution on lists
119
125
 
120
- The default list route handles this scheduling for you. Continue with [cross-schema joins](#cross-schema-joins) unless you are building a custom route with `Query.List`.
126
+ The default list route schedules the lookups itself. Skip to [cross-schema joins](#cross-schema-joins) unless you are building a custom route with `Query.List`.
121
127
 
122
- ![Joined fields used by filters are resolved before counting; sort fields before paging; display-only fields after paging in the default list route.](./assets/joined-list.svg){width="480"}
128
+ ![Joined fields used by filters are resolved before counting; sort fields before paging; display-only fields after paging in the default list route.](./assets/joined-list.svg "illustration"){width="480"}
123
129
 
124
130
  ### Advanced: composing `Query.List`
125
131
 
126
- `Query.List` only materializes the joined groups that the request's sort or filters reference before the count. The remaining display-only groups are deferred: the returned stream resolves them itself as soon as an operation can observe them, so destructuring `[query, total]` alone always yields rows with every `@Joined` field resolved.
132
+ `Query.List` resolves the joined fields that the request's filters read before it counts the rows. It resolves the joined fields that the sort reads after the count and before the sort. It defers the remaining display-only joined fields. The returned stream resolves them as soon as an operation can read rows, so destructuring `[query, total]` alone always gives rows with every `@Joined` field resolved.
127
133
 
128
- - Paging the stream (`slice`, `nth`) appends the lookups after the page boundary, so they only run on the returned rows.
129
- - A bare `count()` — and any field aggregate (`sum`, `min`, `distinct(field)`, ...) on a non-joined field — skips them entirely (left-join lookups never change the row count nor other fields).
130
- - `changes()` attaches to the raw stream, since change feeds cannot carry the join lookups.
131
- - Any other operation (`filter`, `orderBy`, `map`, ...) materializes them first, since it may read the joined values.
134
+ - Paging the stream (`slice`, `nth`) adds the lookups after the page boundary, so they run only on the returned rows.
135
+ - A bare `count()` skips the lookups, and so does a field aggregate such as `sum`, `min`, or `distinct(field)` on a field that is not joined. A left-join lookup never changes the row count or the other fields.
136
+ - `changes()` attaches to the raw stream, because change feeds cannot carry the join lookups.
137
+ - Any other operation, such as `filter`, `orderBy`, or `map`, resolves the lookups first, because it may read the joined values.
132
138
 
133
- The page-only benefit therefore holds as long as paging is the first thing the caller does. An operation applied to the whole stream before paging — a filter, a sort, or the `lookup` that `Query.Foreign` stages for a `@Foreign` field — materializes the joined groups over the whole matched set instead. The lookups still stay out of the count pipeline, but a caller that wants them strictly on the page must pass `{ exposeDeferredJoined: true }` to `Query.List` and resolve them itself after the slice: the raw stream is then returned unchanged and the third tuple element names the joined fields left to resolve with `Query.Joined`. This is what the default list route does, which also lets it limit the page lookups to the plucked fields.
139
+ The lookups therefore run on the page only when paging is the first operation the caller applies. An operation on the whole stream before paging resolves the joined fields over the whole matched set. Such operations include a filter, a sort, and the `lookup` that `Query.Foreign` adds for a `@Foreign` field. The lookups still stay out of the count. A caller that wants them on the page only must pass `{ exposeDeferredJoined: true }` to `Query.List` and resolve them after the slice. `Query.List` then returns the raw stream unchanged, and the third tuple element names the joined fields to resolve with `Query.Joined`. The default list route works this way, which also lets it limit the page lookups to the plucked fields.
134
140
 
135
141
  ### Reading a page: `Query.ListPage`
136
142
 
137
- A custom list route over `Query.List` reads its page with `Query.ListPage` rather than chaining the steps itself. It slices the stream, then resolves on the page only, in this order: the deferred joined groups, the `@Computed` fields the filters and the sort did not already merge, the `@Foreign` relations (last, since a computed expression may read a relation's key), and the pluck.
143
+ A custom list route over `Query.List` reads its page with `Query.ListPage` instead of chaining the steps itself. `Query.ListPage` slices the stream, then resolves these on the page only, in this order:
144
+
145
+ 1. The deferred joined fields.
146
+ 2. The `@Computed` fields that the filters and the sort did not already merge.
147
+ 3. The `@Foreign` relations. They come last because a computed expression may read a relation's key.
148
+ 4. The pluck.
138
149
 
139
- ```typescript
150
+ ```typescript fragment
140
151
  const [query, total, deferredJoined] = Query.List(
141
152
  this,
142
153
  meta,
@@ -158,11 +169,11 @@ const rows = await Query.ListPage(model.database, meta, query, {
158
169
  });
159
170
  ```
160
171
 
161
- ## Cross-Schema Joins
172
+ ## Cross-schema joins
162
173
 
163
- By default, the remote table is resolved in the same schema as the controller's table. Use the `schema` option to join against a table registered in a different schema; the join is resolved through the remote table's class, against the default instance of the schema `RegisterSchema` bound it to. The remote class must be registered with `@RegisterTable` before the field is declared; a string table name with no registered class in that schema throws at declaration.
174
+ By default, the Data API looks for the remote table in the schema of the controller's table. The `schema` option joins a table registered in a different schema. The join goes through the remote table's class, against the default instance of the schema that `RegisterSchema` bound it to. Register the remote class with `@RegisterTable` before you declare the field. A string table name with no registered class in that schema throws at declaration.
164
175
 
165
- ```typescript
176
+ ```typescript fragment
166
177
  @Joined({
167
178
  table: "users",
168
179
  localKey: "userId",
@@ -172,17 +183,17 @@ By default, the remote table is resolved in the same schema as the controller's
172
183
  declare userName: string;
173
184
  ```
174
185
 
175
- When a table class is passed, its schema is inferred from its `@RegisterTable` metadata. If both the class metadata and the explicit `schema` option specify a schema and they disagree, an error is thrown.
186
+ When you pass a table class, the Data API reads its schema from its `@RegisterTable` metadata. If the class metadata and the `schema` option name different schemas, the declaration throws.
176
187
 
177
- ## Joined vs. Foreign
188
+ ## `@Joined` compared with `@Foreign`
178
189
 
179
- | Aspect | `@Joined` | `@Foreign` |
180
- | ------------------- | -------------------------------------------- | --------------------------------------------------------------------- |
181
- | Result shape | A single scalar value flattened onto the row | The full referenced record (or plucked subset) nested under the field |
182
- | Access | Read-only (enforced) | Configurable via `@Access` |
183
- | Sort / filter | Native (join applied before sort/filter) | Not natively sortable/filterable on remote fields |
184
- | Multiple references | One remote field per decorator | Single or array (`multi`) references |
190
+ | Aspect | `@Joined` | `@Foreign` |
191
+ | ------------------- | ----------------------------------------------- | ------------------------------------------------------------------------- |
192
+ | Result shape | A single scalar value flattened onto the row | The full referenced record, or its plucked fields, nested under the field |
193
+ | Access | Read-only, unless a later `@Access` replaces it | Set with `@Access` |
194
+ | Sort and filter | In the database, join applied before both | No sort or filter on remote fields |
195
+ | Multiple references | One remote field per decorator | Single or array (`multi`) references |
185
196
 
186
- ## Next Steps
197
+ ## Next steps
187
198
 
188
- See the [computed fields documentation](./11.computed.md) to generalize this merge mechanism to arbitrary in-database expressions, or the [modifiers documentation](./9.modifiers.md) to learn how automatic data transformation integrates with the Data API.
199
+ To merge any in-database expression onto rows, see [Computed fields](./11.computed.md). To see how modifiers transform data in the Data API, see [Modifiers](./9.modifiers.md).