@takeshape/schema 11.41.0 → 11.44.0

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 (38) hide show
  1. package/dist/cjs/src/agents.js +87 -1
  2. package/dist/cjs/src/index.js +2 -0
  3. package/dist/cjs/src/relationships.js +86 -1
  4. package/dist/cjs/src/runtime-schema.js +81 -0
  5. package/dist/cjs/src/schemas/index.js +3 -7
  6. package/dist/cjs/src/schemas/project-schema/experimental.json +305 -0
  7. package/dist/cjs/src/schemas/project-schema/latest.json +1 -1
  8. package/dist/cjs/src/schemas/project-schema/v3.50.0.json +1 -1
  9. package/dist/cjs/src/service-dependencies.js +167 -0
  10. package/dist/cjs/src/util/patch-schema.js +33 -28
  11. package/dist/cjs/src/validate.js +40 -4
  12. package/dist/esm/src/agents.js +83 -0
  13. package/dist/esm/src/index.js +2 -0
  14. package/dist/esm/src/relationships.js +85 -1
  15. package/dist/esm/src/runtime-schema.js +73 -0
  16. package/dist/esm/src/schemas/index.js +0 -2
  17. package/dist/esm/src/schemas/project-schema/experimental.json +305 -0
  18. package/dist/esm/src/schemas/project-schema/latest.json +1 -1
  19. package/dist/esm/src/schemas/project-schema/v3.50.0.json +1 -1
  20. package/dist/esm/src/service-dependencies.js +159 -0
  21. package/dist/esm/src/util/patch-schema.js +33 -27
  22. package/dist/esm/src/validate.js +40 -4
  23. package/dist/types/src/agents.d.ts +4 -1
  24. package/dist/types/src/index.d.ts +2 -0
  25. package/dist/types/src/migration/types.d.ts +1 -3
  26. package/dist/types/src/project-schema/latest.d.ts +143 -4
  27. package/dist/types/src/project-schema/v3.48.0.d.ts +143 -4
  28. package/dist/types/src/project-schema/v3.49.0.d.ts +143 -4
  29. package/dist/types/src/project-schema/v3.50.0.d.ts +143 -4
  30. package/dist/types/src/relationships.d.ts +4 -0
  31. package/dist/types/src/runtime-schema.d.ts +5 -0
  32. package/dist/types/src/schemas/index.d.ts +0 -2
  33. package/dist/types/src/service-dependencies.d.ts +13 -0
  34. package/dist/types/src/types/types.d.ts +1 -0
  35. package/dist/types/src/util/patch-schema.d.ts +4 -4
  36. package/dist/types/src/validate.d.ts +8 -3
  37. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  38. package/package.json +6 -6
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.resolveSchemaShapeDependencies = exports.collectReferencedShapeNames = void 0;
7
+ const util_1 = require("@takeshape/util");
8
+ const get_js_1 = __importDefault(require("lodash/get.js"));
9
+ const set_js_1 = __importDefault(require("lodash/set.js"));
10
+ const isObject_js_1 = __importDefault(require("lodash/isObject.js"));
11
+ const pick_js_1 = __importDefault(require("lodash/pick.js"));
12
+ const types_1 = require("./types");
13
+ const refs_1 = require("./refs");
14
+ const interfaces_1 = require("./interfaces");
15
+ const schema_util_1 = require("./schema-util");
16
+ function getGraphQLServiceShapeMap(layers) {
17
+ return Object.assign({}, ...Object.values(layers).map(layer => layer.schema?.shapes ?? {}));
18
+ }
19
+ function schemaExtendsShape(context, shapeName, schema) {
20
+ return ((0, types_1.isExtendsSchema)(schema) &&
21
+ schema.extends.some((item) => (0, refs_1.getRefShapeName)(context, item) === shapeName));
22
+ }
23
+ function getMissingPropertyRefs(projectSchema, getNamespace) {
24
+ const propertyRefs = (0, refs_1.getAllPropertyRefs)(projectSchema);
25
+ return propertyRefs.filter(ref => {
26
+ const path = (0, refs_1.propertyRefItemToPath)(getNamespace, ref);
27
+ return !(0, get_js_1.default)(projectSchema, path);
28
+ });
29
+ }
30
+ function refToQuery(serviceSchemas, ref) {
31
+ const serviceSchema = serviceSchemas[ref.serviceId].schema;
32
+ if (serviceSchema) {
33
+ if (ref.shapeName === 'Query' || ref.shapeName === 'Mutation') {
34
+ const operation = ref.shapeName === 'Query' ? 'queries' : 'mutations';
35
+ return serviceSchema[operation][ref.propertyName];
36
+ }
37
+ }
38
+ }
39
+ function getShapeRefs(services, serviceSchemas, propertyRefs) {
40
+ const results = [];
41
+ for (const ref of propertyRefs) {
42
+ const query = refToQuery(serviceSchemas, ref);
43
+ const layerSchema = serviceSchemas[ref.serviceId]?.schema;
44
+ if (query && layerSchema) {
45
+ const layerWithService = {
46
+ ...layerSchema,
47
+ services: (0, pick_js_1.default)(services, [ref.serviceId]) // include own service so refs have isValidService: true
48
+ };
49
+ results.push(...(0, schema_util_1.getAllRefsInQuery)(layerWithService, [], query));
50
+ }
51
+ }
52
+ return results;
53
+ }
54
+ function interfacesToShapeNames(projectSchema, shapeRefArray) {
55
+ return shapeRefArray.map(shapeRef => (0, refs_1.refItemToNamespacedShapeName)((0, refs_1.refExpressionToRefItem)(projectSchema, shapeRef)));
56
+ }
57
+ /**
58
+ * Add all referenced shape names to the refSet meant to be used as a callback to visitSchemaProperties
59
+ */
60
+ function collectReferencedShapeNames(context, shapeNames) {
61
+ const addRef = (refItem) => {
62
+ shapeNames.add((0, refs_1.refItemToNamespacedShapeName)(refItem));
63
+ };
64
+ const collect = (schema) => {
65
+ const args = schema['@args'];
66
+ if (args && (0, isObject_js_1.default)(args) && (0, types_1.isObjectSchema)(args)) {
67
+ Object.values(args.properties).forEach(collect);
68
+ }
69
+ const argsRefItem = (0, schema_util_1.getArgsReference)(context, schema);
70
+ if (argsRefItem) {
71
+ addRef(argsRefItem);
72
+ }
73
+ const refItem = (0, refs_1.getRef)(context, schema);
74
+ if (refItem) {
75
+ addRef(refItem);
76
+ }
77
+ };
78
+ return collect;
79
+ }
80
+ exports.collectReferencedShapeNames = collectReferencedShapeNames;
81
+ /**
82
+ * Returns a schema with all of its dependencies resolved.
83
+ * SHOULD only return the minimum set of remote dependencies required to enable
84
+ * the local shape definitions.
85
+ */
86
+ function resolveSchemaShapeDependencies(projectSchema, serviceLayers) {
87
+ const newSchema = (0, util_1.deepClone)(projectSchema);
88
+ if ((0, types_1.isProjectSchemaWithServices)(projectSchema)) {
89
+ const newShapeMap = {};
90
+ const requiredShapeNames = new Set((0, schema_util_1.getAllNamespaceShapes)(projectSchema));
91
+ const getNamespace = (0, refs_1.createGetNamespace)(projectSchema);
92
+ const missingPropertyRefs = getMissingPropertyRefs(projectSchema, getNamespace);
93
+ if (requiredShapeNames.size || missingPropertyRefs.length) {
94
+ const remoteShapeMap = getGraphQLServiceShapeMap(serviceLayers);
95
+ // Add shape references from queries
96
+ (0, util_1.addAll)(requiredShapeNames, getShapeRefs(projectSchema.services, serviceLayers, missingPropertyRefs)
97
+ .filter(schema_util_1.isValidRefItem)
98
+ .map(refs_1.refItemToNamespacedShapeName));
99
+ // Add referenced queries and mutations
100
+ for (const ref of missingPropertyRefs) {
101
+ const query = refToQuery(serviceLayers, ref);
102
+ if (query) {
103
+ (0, set_js_1.default)(newSchema, (0, refs_1.propertyRefItemToPath)(getNamespace, ref), query);
104
+ }
105
+ }
106
+ // Gather all the remote dependencies that our local schema requires.
107
+ // If the required shape exists in the remote shape map, visit the
108
+ // properties on the remote shape's schema and ensure they are required
109
+ // as well.
110
+ const collectDependencies = (shapeNames) => {
111
+ for (const name of shapeNames) {
112
+ const localShape = newSchema.shapes[name];
113
+ const remoteShape = remoteShapeMap[name];
114
+ // Don't check for dependencies if the shape is already in our schema unless it extends a remote shape
115
+ // getNamespacedShapeNameList already collected those dependencies
116
+ if ((!localShape || (localShape && schemaExtendsShape(projectSchema, name, localShape.schema))) &&
117
+ remoteShape) {
118
+ (0, schema_util_1.visitSchemaProperties)(remoteShape.schema, [remoteShape.name, 'schema'], collectReferencedShapeNames({ services: newSchema.services }, shapeNames));
119
+ }
120
+ }
121
+ };
122
+ collectDependencies(requiredShapeNames);
123
+ // Collect interfaces that requiredShapeNames implement
124
+ const interfaceNames = new Set();
125
+ const requiredShapes = (0, util_1.mapSet)(requiredShapeNames, shapeName => newSchema.shapes[shapeName] ?? remoteShapeMap[shapeName]);
126
+ for (const shape of requiredShapes) {
127
+ if (shape?.interfaces) {
128
+ (0, util_1.addAll)(interfaceNames, interfacesToShapeNames(projectSchema, shape.interfaces));
129
+ }
130
+ }
131
+ // Collect implementations for interfaces in requiredShapeNames
132
+ const implementationNames = new Set();
133
+ const implementations = (0, interfaces_1.getImplementationShapeNameMap)({ ...newSchema.shapes, ...remoteShapeMap });
134
+ for (const shape of requiredShapes) {
135
+ if ((0, interfaces_1.isInterfaceShape)(shape) && implementations[shape.name]) {
136
+ (0, util_1.addAll)(implementationNames, implementations[shape.name]);
137
+ }
138
+ }
139
+ // Collect the dependencies for the interfaces and implementations
140
+ collectDependencies(interfaceNames);
141
+ collectDependencies(implementationNames);
142
+ (0, util_1.addAll)(requiredShapeNames, interfaceNames);
143
+ (0, util_1.addAll)(requiredShapeNames, implementationNames);
144
+ // Populate a ShapeMap with the remote shapes we've gathered
145
+ // A combined context which prefers remote shapes for use when merging/inlining
146
+ const mergingContext = { services: newSchema.services, shapes: { ...newSchema.shapes, ...remoteShapeMap } };
147
+ for (const name of requiredShapeNames) {
148
+ const localShape = newSchema.shapes[name];
149
+ const remoteShape = remoteShapeMap[name];
150
+ if (localShape && remoteShape && schemaExtendsShape(mergingContext, name, localShape.schema)) {
151
+ // Merge and inline combined shape when a local shape extends a remote shape
152
+ newShapeMap[name] = {
153
+ ...localShape,
154
+ schema: (0, refs_1.dereferenceObjectSchema)(mergingContext, newSchema.shapes[name].schema)
155
+ };
156
+ }
157
+ else if (localShape ?? remoteShape) {
158
+ newShapeMap[name] = localShape ?? remoteShape;
159
+ }
160
+ }
161
+ }
162
+ // Assign remote shapes to the new schema, mutating the new schema
163
+ Object.assign(newSchema.shapes, (0, interfaces_1.pruneUnusedInterfaces)(newSchema, newShapeMap));
164
+ }
165
+ return newSchema;
166
+ }
167
+ exports.resolveSchemaShapeDependencies = resolveSchemaShapeDependencies;
@@ -3,48 +3,53 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.patchSchema = exports.shallowMerge = void 0;
7
- const forIn_js_1 = __importDefault(require("lodash/forIn.js"));
6
+ exports.patchSchema = void 0;
8
7
  const omit_js_1 = __importDefault(require("lodash/omit.js"));
9
- const schemaUpdateMergedKeys = ['queries', 'mutations', 'shapes', 'workflows', 'forms', 'services'];
8
+ const get_js_1 = __importDefault(require("lodash/get.js"));
9
+ const set_js_1 = __importDefault(require("lodash/fp/set.js"));
10
+ const unset_js_1 = __importDefault(require("lodash/unset.js"));
11
+ /**
12
+ * Array of lodash.get paths to keys that are merged when applying a schema update.
13
+ */
14
+ const schemaUpdateMergedKeys = [
15
+ 'queries',
16
+ 'mutations',
17
+ 'shapes',
18
+ 'workflows',
19
+ 'forms',
20
+ 'services',
21
+ 'ai-experimental.agents'
22
+ ];
10
23
  function shallowMerge(original, update) {
11
24
  const result = { ...original };
12
- if (update) {
13
- (0, forIn_js_1.default)(update, (obj, key) => {
14
- // Null is a sentinel value to indicate delete
15
- if (obj === null) {
16
- // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
17
- delete result[key];
18
- }
19
- else {
20
- result[key] = obj;
21
- }
22
- });
25
+ for (const [key, obj] of Object.entries(update)) {
26
+ // Null is a sentinel value to indicate delete
27
+ if (obj === null) {
28
+ (0, unset_js_1.default)(result, key);
29
+ }
30
+ else {
31
+ result[key] = obj;
32
+ }
23
33
  }
24
34
  return result;
25
35
  }
26
- exports.shallowMerge = shallowMerge;
27
36
  /**
28
- * Apply a schema update to a schema.
29
- * Resulting schema is not assumed to be valid and should be validated if appropriate.
37
+ * Apply a schema update to a schema. This can operate on a schema with frozen properties,
38
+ * such as one produced by immer. Resulting schema is not assumed to be valid and should
39
+ * be validated if appropriate.
30
40
  */
31
- function patchSchema(projectSchema, update) {
41
+ function patchSchema(projectSchema, schemaUpdate) {
32
42
  // Patch old schema with update
33
- const updatedSchema = {
43
+ let updatedSchema = {
34
44
  ...projectSchema,
35
- ...(0, omit_js_1.default)(update, [...schemaUpdateMergedKeys, 'ai-experimental'])
45
+ ...(0, omit_js_1.default)(schemaUpdate, schemaUpdateMergedKeys)
36
46
  };
37
47
  for (const key of schemaUpdateMergedKeys) {
38
- if (update[key]) {
39
- updatedSchema[key] = shallowMerge(projectSchema[key], update[key]);
48
+ const update = (0, get_js_1.default)(schemaUpdate, key);
49
+ if (update) {
50
+ updatedSchema = (0, set_js_1.default)(key, shallowMerge((0, get_js_1.default)(projectSchema, key) ?? {}, update), updatedSchema);
40
51
  }
41
52
  }
42
- if (update['ai-experimental']?.agents) {
43
- const ai = {
44
- agents: shallowMerge(projectSchema['ai-experimental']?.agents, update['ai-experimental'].agents)
45
- };
46
- updatedSchema['ai-experimental'] = ai;
47
- }
48
53
  return updatedSchema;
49
54
  }
50
55
  exports.patchSchema = patchSchema;
@@ -141,6 +141,9 @@ function enumerateBasicResolvers(resolver, path) {
141
141
  visit(resolver, path);
142
142
  return results;
143
143
  }
144
+ const isValidAgentMutation = (projectSchema, mutationName) => {
145
+ return Boolean(projectSchema['ai-experimental']?.agents?.[mutationName]);
146
+ };
144
147
  const validateAIToolConfig = (projectSchema, getNamespace, tool, basePath) => {
145
148
  const toolRef = (0, ai_tools_1.getToolRef)(tool);
146
149
  const path = typeof tool === 'string' ? basePath : basePath.concat('ref');
@@ -164,6 +167,9 @@ const validateAIToolConfig = (projectSchema, getNamespace, tool, basePath) => {
164
167
  };
165
168
  }
166
169
  const property = (0, get_js_1.default)(projectSchema, propertyPath);
170
+ if (propertyPath[0] === 'mutations' && isValidAgentMutation(projectSchema, propertyPath[1])) {
171
+ return;
172
+ }
167
173
  if (!property) {
168
174
  return {
169
175
  type: 'notFound',
@@ -487,8 +493,8 @@ function validateShapeLoaders(context, projectSchema, shape) {
487
493
  }
488
494
  }
489
495
  }
490
- if (shape.cache?.triggers) {
491
- const min = context.entitlements?.minScheduleTriggerInterval ?? util_1.DEFAULT_MIN_SCHEDULE_TRIGGER_INTERVAL;
496
+ if (shape.cache?.triggers && !context.ignoreEntitlements) {
497
+ const min = context.entitlements.minScheduleTriggerInterval ?? util_1.DEFAULT_MIN_SCHEDULE_TRIGGER_INTERVAL;
492
498
  for (let i = 0; i < shape.cache.triggers.length; i++) {
493
499
  const trigger = shape.cache.triggers[i];
494
500
  if (trigger.type === 'schedule' && trigger.interval < min) {
@@ -786,14 +792,16 @@ function validateInterfaceImplementations(projectSchema) {
786
792
  function validateAgents(projectSchema) {
787
793
  const getNamespace = (0, refs_1.createGetNamespace)(projectSchema);
788
794
  const errors = [];
795
+ const guards = projectSchema['ai-experimental']?.guards;
789
796
  const agents = projectSchema['ai-experimental']?.agents;
790
797
  if (!agents) {
791
798
  return errors;
792
799
  }
793
800
  for (const [agentName, agent] of Object.entries(agents)) {
794
801
  const stateNames = new Set();
802
+ const agentPath = ['ai-experimental', 'agents', agentName];
795
803
  for (const [stateId, state] of Object.entries(agent.states)) {
796
- const statePath = ['ai-experimental', 'agents', agentName, 'states', stateId];
804
+ const statePath = [...agentPath, 'states', stateId];
797
805
  if (stateNames.has(state.name)) {
798
806
  errors.push({
799
807
  path: [...statePath, 'name'],
@@ -819,6 +827,17 @@ function validateAgents(projectSchema) {
819
827
  });
820
828
  }
821
829
  }
830
+ if (agent.guards) {
831
+ const invalidGuards = agent.guards.filter(({ guardId }) => !guards?.[guardId]);
832
+ if (invalidGuards.length) {
833
+ const guardPath = [...agentPath, 'guards'];
834
+ errors.push(...invalidGuards.map(({ guardId }, index) => ({
835
+ path: [...guardPath, index, 'guardId'],
836
+ type: 'notFound',
837
+ message: `Invalid guardId "${guardId}"`
838
+ })));
839
+ }
840
+ }
822
841
  }
823
842
  return errors;
824
843
  }
@@ -906,6 +925,22 @@ function validateStructure(schemaVersion, context, schema, ref) {
906
925
  }
907
926
  return { valid: true, schema: schema, errors: undefined };
908
927
  }
928
+ function validateGuards(context, schema) {
929
+ const errors = [];
930
+ if (context.ignoreEntitlements) {
931
+ return errors;
932
+ }
933
+ const numOfGuards = schema['ai-experimental']?.guards ? Object.keys(schema['ai-experimental']?.guards).length : 0;
934
+ const entitledToGuards = context.entitlements.guards ?? 0;
935
+ if (numOfGuards > entitledToGuards) {
936
+ errors.push({
937
+ type: 'entitlement',
938
+ message: `Number of guards exceeds your entitled limit of ${entitledToGuards}`,
939
+ path: ['ai-experimental', 'guards']
940
+ });
941
+ }
942
+ return errors;
943
+ }
909
944
  function formatValidationResult(context, errors, schema) {
910
945
  const { suppressErrorPaths } = context;
911
946
  if (suppressErrorPaths) {
@@ -932,7 +967,8 @@ function validateSyntax(context, schema) {
932
967
  .concat(validateIndexedShapes(context, schema))
933
968
  .concat(validateInterfaces(schema))
934
969
  .concat(validateInterfaceImplementations(schema))
935
- .concat(validateAgents(schema));
970
+ .concat(validateAgents(schema))
971
+ .concat(validateGuards(context, schema));
936
972
  return formatValidationResult(context, errors, schema);
937
973
  }
938
974
  async function validateReferences(context, schema) {
@@ -1,4 +1,6 @@
1
1
  import upperFirst from 'lodash/upperFirst.js';
2
+ import uniq from 'lodash/uniq.js';
3
+ import uniqBy from 'lodash/uniqBy.js';
2
4
  export const END_AGENT_EXECUTION = 'endAgentExecution';
3
5
  export const BUILT_IN_CHAT_ARGS = [
4
6
  {
@@ -47,3 +49,84 @@ export const getAgentEndStates = (agent, includeSuspend = false) => {
47
49
  export const getInspectAgentSessionQueryName = (agentName) => {
48
50
  return `inspect${upperFirst(agentName)}`;
49
51
  };
52
+ export const removeBuiltInArgs = (args) => {
53
+ return args.filter(arg => !BUILT_IN_CHAT_ARG_NAMES.includes(arg.argName));
54
+ };
55
+ export const createArgs = (agent) => {
56
+ let apiArguments = uniqBy(agent.api.arguments ?? [], arg => arg.argName);
57
+ if (agent.api.type === 'chat') {
58
+ apiArguments = removeBuiltInArgs(apiArguments).concat(BUILT_IN_CHAT_ARGS);
59
+ }
60
+ return apiArguments.length > 0
61
+ ? {
62
+ type: 'object',
63
+ properties: apiArguments.reduce((acc, argument) => {
64
+ acc[argument.argName] = {
65
+ type: argument.argType === 'sessionId' ? 'string' : argument.argType
66
+ };
67
+ return acc;
68
+ }, {}),
69
+ required: apiArguments.filter(arg => arg.required).map(arg => arg.argName)
70
+ }
71
+ : undefined;
72
+ };
73
+ export function addAiQueries(projectSchema) {
74
+ const agents = projectSchema['ai-experimental']?.agents;
75
+ if (!agents) {
76
+ return projectSchema;
77
+ }
78
+ const newSchema = {
79
+ ...projectSchema,
80
+ queries: { ...projectSchema.queries },
81
+ mutations: { ...projectSchema.mutations }
82
+ };
83
+ for (const [agentName, agent] of Object.entries(agents)) {
84
+ // Get valid return types based on states that could possibly be the end state
85
+ // TODO In the future the agent will have a return type defined and we will instead validate
86
+ // that all the return states will return the correct shape.
87
+ const returnStates = [...getAgentEndStates(agent, true)];
88
+ const returnTypes = uniq(returnStates.map(stateId => {
89
+ const { execution } = agent.states[stateId];
90
+ if (execution.type === 'chat') {
91
+ return 'TSChatResponse';
92
+ }
93
+ if (execution.type === 'generate') {
94
+ return execution.outputShape ? execution.outputShape : 'string';
95
+ }
96
+ return 'JSON';
97
+ }));
98
+ const shape = returnTypes.length === 0 ? 'string' : returnTypes.length === 1 ? returnTypes[0] : 'JSON';
99
+ if (newSchema.mutations[agentName] !== undefined) {
100
+ throw new Error(`Schema already has a mutation with the name ${agentName}`);
101
+ }
102
+ newSchema.mutations[agentName] = {
103
+ description: agent.description,
104
+ args: createArgs(agent),
105
+ shape,
106
+ resolver: {
107
+ name: 'ai:runAgent',
108
+ agentName
109
+ }
110
+ };
111
+ if (shape === 'TSChatResponse') {
112
+ newSchema.queries[getInspectAgentSessionQueryName(agentName)] ||= {
113
+ description: `Inspect a session for the ${agentName} agent`,
114
+ args: {
115
+ type: 'object',
116
+ properties: {
117
+ sessionId: {
118
+ type: 'string'
119
+ }
120
+ },
121
+ required: ['sessionId']
122
+ },
123
+ shape: 'TSAgentSession',
124
+ resolver: {
125
+ name: 'ai:inspectAgentSession',
126
+ agentName
127
+ }
128
+ };
129
+ }
130
+ }
131
+ return newSchema;
132
+ }
@@ -42,3 +42,5 @@ export * from "./util/merge.js";
42
42
  export * from "./util/patch-schema.js";
43
43
  export * from "./util/shapes.js";
44
44
  export * from "./constants.js";
45
+ export * from "./runtime-schema.js";
46
+ export * from "./service-dependencies.js";
@@ -1,6 +1,8 @@
1
1
  import { isDefined } from "@takeshape/util";
2
2
  import find from "lodash/find.js";
3
- import { isPropertySchemaWithRelationship, isModelShape } from "./types/index.js";
3
+ import camelCase from "lodash/camelCase.js";
4
+ import uniq from "lodash/uniq.js";
5
+ import { isPropertySchemaWithRelationship, isModelShape, isObjectSchema } from "./types/index.js";
4
6
  import { getRefShapeName, followRef } from "./refs.js";
5
7
  import { isUnionSchema } from "./unions.js";
6
8
  import { builtInShapes } from "./builtin-schema.js";
@@ -220,3 +222,85 @@ export function hasUnequalRelationships(relationships) {
220
222
  return nextPropertySchema ? !isEqualRelationship(propertySchema, nextPropertySchema) : false;
221
223
  }));
222
224
  }
225
+ function getRelatedShapeIds(relationships) {
226
+ return uniq(relationships.map(rel => (rel.hasBackreference ? rel.shapeId : undefined)).filter(isDefined));
227
+ }
228
+ function getShapes(projectSchema, shapeIds) {
229
+ return shapeIds.map(shapeId => getShapeById(projectSchema, shapeId)).filter(isDefined);
230
+ }
231
+ /**
232
+ * Adds backreference fields to the schema.
233
+ */
234
+ export function addRelatedFields(projectSchema, allRelationships) {
235
+ for (const [shapeId, shapeRelationships] of Object.entries(allRelationships)) {
236
+ const shape = getShapeById(projectSchema, shapeId);
237
+ if (shape && isObjectSchema(shape.schema)) {
238
+ const relatedShapeIds = getRelatedShapeIds(shapeRelationships);
239
+ const relatedShapeNames = getShapes(projectSchema, relatedShapeIds).map(shape => shape.name);
240
+ if (relatedShapeNames.length) {
241
+ let shapeName;
242
+ if (relatedShapeNames.length === 1) {
243
+ // If only one back reference exists, _references is a regular shape
244
+ shapeName = relatedShapeNames[0];
245
+ }
246
+ else {
247
+ // If many back references exist, _references will be a union of referring shapes
248
+ shapeName = `${shape.name}Reference`;
249
+ projectSchema.shapes[shapeName] = {
250
+ id: shapeName,
251
+ name: shapeName,
252
+ title: shapeName,
253
+ schema: {
254
+ oneOf: relatedShapeNames.map(name => ({ "@ref": `local:${name}` }))
255
+ }
256
+ };
257
+ }
258
+ // _references, a list of all backreferences across all fields on this shape.
259
+ // It lists all the items that have references to the current item.
260
+ // It is for convenience.
261
+ if (shapeRelationships.some(rel => rel.hasBackreference)) {
262
+ shape.schema.properties._references = {
263
+ "@args": `TSListArgs<local:${shapeName}>`,
264
+ "@ref": `PaginatedList<local:${shapeName}>`,
265
+ "@resolver": {
266
+ name: "shapedb:list",
267
+ service: "shapedb",
268
+ args: {
269
+ ops: [
270
+ { path: "$", mapping: "$args" },
271
+ { path: `baseWhere._references.eq`, mapping: "$source._id" },
272
+ { path: `baseWhere._shapeId.in`, value: relatedShapeIds }
273
+ ]
274
+ }
275
+ }
276
+ };
277
+ }
278
+ }
279
+ // Create a schema for the backreference on the referred-to shape
280
+ for (const relationship of shapeRelationships) {
281
+ const relatedShape = getShapeById(projectSchema, relationship.shapeId);
282
+ if (relatedShape && relationship.hasBackreference) {
283
+ const { relatedName } = relationship;
284
+ const relatedFieldName = relatedName ? relatedName : `${camelCase(relatedShape.name)}Set`;
285
+ const filterField = relatedName ? relationship.path.concat("_id").join(".") : "_references";
286
+ shape.schema.properties[relatedFieldName] = {
287
+ "@args": `TSListArgs<local:${relatedShape.name}>`,
288
+ "@ref": `PaginatedList<local:${relatedShape.name}>`,
289
+ "@resolver": {
290
+ name: "shapedb:list",
291
+ service: "shapedb",
292
+ args: {
293
+ ops: [
294
+ { path: "$", mapping: "$args" },
295
+ { path: `baseWhere.${filterField}.eq`, mapping: "$source._id" },
296
+ { path: `baseWhere._shapeId.eq`, value: relatedShape.id }
297
+ ]
298
+ }
299
+ }
300
+ };
301
+ }
302
+ }
303
+ }
304
+ }
305
+ return projectSchema;
306
+ }
@@ -0,0 +1,73 @@
1
+ import { SchemaBuildError } from "@takeshape/errors";
2
+ import compose from "lodash/fp/compose.js";
3
+ import { deepClone } from "@takeshape/util";
4
+ import set from "lodash/set.js";
5
+ import isError from "lodash/isError.js";
6
+ import { isModelShape } from "./types/index.js";
7
+ import { applyDefaultsToSchema, createShape } from "./schema-util.js";
8
+ import { addRelatedFields, findExistingRelationships } from "./relationships.js";
9
+ import { flattenTemplates } from "./flatten-templates.js";
10
+ import { addAiQueries } from "./agents.js";
11
+ import { resolveSchemaShapeDependencies } from "./service-dependencies.js";
12
+ export function applyLegacyCompatibilityTweaks(projectSchema) {
13
+ const newSchema = deepClone(projectSchema);
14
+ let hasSearchableShapes = false;
15
+ for (const [shapeName, shape] of Object.entries(newSchema.shapes)) {
16
+ if (isModelShape(shape)) {
17
+ hasSearchableShapes = true;
18
+ // Magic _contentTypeId field used for V1 compatibility
19
+ set(shape, ["schema", "properties", "_contentTypeId"], { type: "string" });
20
+ set(shape, ["schema", "properties", "_contentTypeName"], { type: "string" });
21
+ if (shape.model?.type !== "single") {
22
+ newSchema.queries[`search${shapeName}Index`] = {
23
+ shape: `SearchResults<${shapeName}>`,
24
+ resolver: {
25
+ name: "takeshape:search",
26
+ service: "takeshape",
27
+ shapeName
28
+ },
29
+ args: `TSSearchArgs<${shapeName}>`
30
+ };
31
+ }
32
+ }
33
+ if (shapeName === "Asset") {
34
+ // Magic s3Key field to provide V1 compatibility with old projects until the field is
35
+ // formally removed and users are notified
36
+ set(shape, ["schema", "properties", "s3Key"], {
37
+ title: "s3 key",
38
+ type: "string",
39
+ "@mapping": "shapedb:Asset.Hk6FQuz5",
40
+ "@deprecationReason": "Use path instead"
41
+ });
42
+ }
43
+ }
44
+ if (hasSearchableShapes) {
45
+ // A placeholder shape for the TSSearchable interface
46
+ newSchema.shapes.TSSearchable = createShape("TSSearchable", { type: "object", properties: {} });
47
+ newSchema.queries.search = {
48
+ shape: "SearchResults<TSSearchable>",
49
+ resolver: {
50
+ name: "takeshape:search",
51
+ service: "takeshape"
52
+ },
53
+ args: "TSSearchArgs<TSSearchable>"
54
+ };
55
+ }
56
+ addRelatedFields(newSchema, findExistingRelationships(projectSchema, projectSchema.shapes));
57
+ return newSchema;
58
+ }
59
+ export const applyDefaultsAndFlatten = compose(flattenTemplates, applyLegacyCompatibilityTweaks, applyDefaultsToSchema, addAiQueries);
60
+ export function buildRuntimeSchema(projectSchema, serviceLayers, log) {
61
+ try {
62
+ const projectSchemaWithDependencies = resolveSchemaShapeDependencies(projectSchema, serviceLayers);
63
+ return applyDefaultsAndFlatten(projectSchemaWithDependencies);
64
+ }
65
+ catch (err) {
66
+ const error = new SchemaBuildError("An error occurred while building the schema", {
67
+ cause: isError(err) ? err : undefined,
68
+ schema: projectSchema
69
+ });
70
+ log("build runtime error", error);
71
+ throw error;
72
+ }
73
+ }
@@ -2,8 +2,6 @@
2
2
  export const CURRENT_SCHEMA_VERSION = '3.50.0';
3
3
  export { default as anyProjectSchema } from './project-schema.json';
4
4
  export { default as latestSchemaJson } from './project-schema/v3.50.0.json';
5
- export { default as authSchemaJson } from './auth-schemas.json';
6
- export { default as experimentalSchemaJson } from './project-schema/experimental.json';
7
5
  import experimentalSchemaJson from './project-schema/experimental.json';
8
6
  import metaSchemaV1_0_0 from './project-schema/meta-schema-v1.0.0.json';
9
7
  import projectSchemaV1_0_0 from './project-schema/v1.0.0.json';